오늘은 스텐실을 구현해둔 클래스를 살펴보자
우선 스텐실이라고 했을 때 Depth Stencil을 먼저 떠올리게 된다. Depth Stencil에서 Depth는 깊이를 표현하는 것으로 물체의 깊이에 따라서 그려줄지 말지를 결정하는 것이고 Stencil은 픽셀 단이로 정보를 저장하며 렌더링 할 영역을 제어할 수 있는데 말 그대로 그려줄 수 있는 영역을 정해주게 된다. 특정 조건에 따라 픽셀의 렌더링을 허용하거나 차단할 수 있으며, 복잡한 이미지 효과나 마스킹, 포털 등의 특수 효과 구현에 사용한다.
이 두 정보는 텍스처를 통해 같이 전달해주게 되는데 텍스처desc에서 옵션 값중에 Format으로 값의 크기를 정해주는데 보통 다음과 같이 설정해준다.
desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
이렇게 해주면 깊이표현에 24비트 사용하고 스텐실에 8비트를 사용한다는 것이다. 24비트면 3바이트이다. float가 4바이트를 사용하는 것을 생각해보면 오차가 생길 수 있지만 깊이는 0~1까지 사용할 것이고 이 안에서 우리가 float를 활용하여 사용하기 때문에 24비트만 사용하는 것을 볼 수 있다.
스텐실은 0~ 255의 값을 사용할 수 있다.
이렇게 세팅해준 값을 우리가 활용해서 Depth Stencil로 한번에 제어해주면 되는 것이다.
DX에서는 ClearDepthStencilView를 통해 값을 Clear해주고 값을 세팅해주면 된다.
_deviceContext->ClearDepthStencilView(_depthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
이제 스텐실을 어떻게 사용하고 이 것으로 무엇을 할 수 있는지 살펴보기 위해 우선 예제코드를 실행해보자.
스텐실 관점에서 보자면 거울에 비친 모습이 해골을 하나 더 그린다고 생각하자면 어느 관점에서는 절반만 보이고 어느시점에서는 아예 안보일 때도 있다. 거울 영역에서만 잘라서 보여주게 되는 것이다. 이때 스텐실을 사용하게 된다.
이때 거울을 먼저 그리고 그 거울에 해당하는 부분에 스텐실을 뚫어주고 이 부분에서만 값을 갱신하게 해준다. 그리고 반사된 해골을 그려줄 때 스텐실이 1인 부분만 그려주게 해서 안보이는 나머지부분은 잘라주게 된다.
그림자에서도 그림자를 그리는 과정에서 같은 곳에 두번 연속해서 그림자를 그려준다고 하면 점점 진하게 보일텐데 그렇게 안되게 하려고 한번만 그려주게 하는 곳에 스텐실을 사용할 수 있다.
예제 코드에서 어떤식으로 스텐실을 사용하는 지 살펴보기 전에 RenderState부분에서 DepthStencil을 사용하기 위한 변수를 보자.
static ComPtr<ID3D11DepthStencilState> MarkMirrorDSS;
static ComPtr<ID3D11DepthStencilState> DrawReflectionDSS;
static ComPtr<ID3D11DepthStencilState> NoDoubleBlendDSS;
이렇게 값을 선언해주고 이 변수를 활용해서 값을 묘사해주고 넘겨주면 된다.
//
// MarkMirrorDSS
//
D3D11_DEPTH_STENCIL_DESC mirrorDesc;
mirrorDesc.DepthEnable = true;
mirrorDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
mirrorDesc.DepthFunc = D3D11_COMPARISON_LESS; //DEPTH
mirrorDesc.StencilEnable = true; //STENCIL
mirrorDesc.StencilReadMask = 0xff; //255: 전부 1로
mirrorDesc.StencilWriteMask = 0xff; //255: 전부 1로
mirrorDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; //각 상황에 맞는 규칙
mirrorDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
mirrorDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE;
mirrorDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; //스텐실 무조건 통과
// We are not rendering backfacing polygons, so these settings do not matter.
mirrorDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
mirrorDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
mirrorDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_REPLACE;
mirrorDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
HR(device->CreateDepthStencilState(&mirrorDesc, &MarkMirrorDSS));
이렇게 만들어주고 렌더링 파이프라인에 연결 시켜준다음 우리가 사용하겠다라고 선언해줘야한다.
이때 예제코드를 보면 우선 거울부분에 스텐실 버퍼에서만 그려주는 부분에 OMSetDepthStencilState 함수로 이를 설정해주고 있다.
//
// 스텐실 버퍼에 거울 그려주기
//
activeTech->GetDesc(&techDesc);
for (uint32 p = 0; p < techDesc.Passes; ++p)
{
ID3DX11EffectPass* pass = activeTech->GetPassByIndex(p);
_deviceContext->IASetVertexBuffers(0, 1, _roomVB.GetAddressOf(), &stride, &offset);
// Set per object constants.
XMMATRIX world = ::XMLoadFloat4x4(&_roomWorld);
XMMATRIX worldInvTranspose = MathHelper::InverseTranspose(world);
XMMATRIX worldViewProj = world * view * proj;
Effects::BasicFX->SetWorld(world);
Effects::BasicFX->SetWorldInvTranspose(worldInvTranspose);
Effects::BasicFX->SetWorldViewProj(worldViewProj);
Effects::BasicFX->SetTexTransform(XMMatrixIdentity());
// 렌더타켓에 그려주지 않는다.
_deviceContext->OMSetBlendState(RenderStates::NoRenderTargetWritesBS.Get(), blendFactor, 0xffffffff);
//스텐실 사용- 스텐실 구멍 뚫어주기 - 1로 바꿔줌
_deviceContext->OMSetDepthStencilState(RenderStates::MarkMirrorDSS.Get(), 1);
pass->Apply(0, _deviceContext.Get());
_deviceContext->Draw(6, 24);
// 파이프라인 동작 방식 리셋
_deviceContext->OMSetDepthStencilState(0, 0);
_deviceContext->OMSetBlendState(0, blendFactor, 0xffffffff);
}
이렇게 해주면 거울부분에 스텐실 값이 모두 1이 된다. 이 다음에 스텐실에 반사된 해골을 그려주면 된다. 이때
RenderStates::DrawReflectionDSS.Get() 이 것을 설정해주면 스텐실 값이 1이랑 같을 때만 통과시켜주는 것으로 1인 부분만 그려주게 된다.
//
// 반사된 해골 그려주기
//
activeSkullTech->GetDesc(&techDesc);
for (uint32 p = 0; p < techDesc.Passes; ++p)
{
ComPtr<ID3DX11EffectPass> pass = activeSkullTech->GetPassByIndex(p);
_deviceContext->IASetVertexBuffers(0, 1, _skullVB.GetAddressOf(), &stride, &offset);
_deviceContext->IASetIndexBuffer(_skullIB.Get(), DXGI_FORMAT_R32_UINT, 0);
XMVECTOR mirrorPlane = ::XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
XMMATRIX R = ::XMMatrixReflect(mirrorPlane);
XMMATRIX world = ::XMLoadFloat4x4(&_skullWorld) * R;
XMMATRIX worldInvTranspose = MathHelper::InverseTranspose(world);
XMMATRIX worldViewProj = world * view * proj;
Effects::BasicFX->SetWorld(world);
Effects::BasicFX->SetWorldInvTranspose(worldInvTranspose);
Effects::BasicFX->SetWorldViewProj(worldViewProj);
Effects::BasicFX->SetMaterial(_skullMat);
// Cache the old light directions, and reflect the light directions.
XMFLOAT3 oldLightDirections[3];
for (int i = 0; i < 3; ++i)
{
oldLightDirections[i] = _dirLights[i].Direction;
XMVECTOR lightDir = XMLoadFloat3(&_dirLights[i].Direction);
XMVECTOR reflectedLightDir = XMVector3TransformNormal(lightDir, R);
XMStoreFloat3(&_dirLights[i].Direction, reflectedLightDir);
}
Effects::BasicFX->SetDirLights(_dirLights);
// Cull clockwise triangles for reflection.
_deviceContext->RSSetState(RenderStates::CullClockwiseRS.Get());
// Only draw reflection into visible mirror pixels as marked by the stencil buffer.
_deviceContext->OMSetDepthStencilState(RenderStates::DrawReflectionDSS.Get(), 1);
pass->Apply(0, _deviceContext.Get());
_deviceContext->DrawIndexed(_skullIndexCount, 0, 0);
// Restore default states.
_deviceContext->RSSetState(0);
_deviceContext->OMSetDepthStencilState(0, 0);
// Restore light directions.
for (int i = 0; i < 3; ++i)
{
_dirLights[i].Direction = oldLightDirections[i];
}
Effects::BasicFX->SetDirLights(_dirLights);
}
그 다음 최종적으로 거울을 그리고 해골이랑 섞기 위해 TransparentBS를 사용해준다.
//
// 거울 그려주기 - 해골이랑 잘 섞일 수 있도록 블렌딩 조정
//
activeTech->GetDesc(&techDesc);
for (uint32 p = 0; p < techDesc.Passes; ++p)
{
ID3DX11EffectPass* pass = activeTech->GetPassByIndex(p);
_deviceContext->IASetVertexBuffers(0, 1, _roomVB.GetAddressOf(), &stride, &offset);
// Set per object constants.
XMMATRIX world = ::XMLoadFloat4x4(&_roomWorld);
XMMATRIX worldInvTranspose = MathHelper::InverseTranspose(world);
XMMATRIX worldViewProj = world * view * proj;
Effects::BasicFX->SetWorld(world);
Effects::BasicFX->SetWorldInvTranspose(worldInvTranspose);
Effects::BasicFX->SetWorldViewProj(worldViewProj);
Effects::BasicFX->SetTexTransform(XMMatrixIdentity());
Effects::BasicFX->SetMaterial(_mirrorMat);
Effects::BasicFX->SetDiffuseMap(_mirrorDiffuseMapSRV.Get());
// Mirror
_deviceContext->OMSetBlendState(RenderStates::TransparentBS.Get(), blendFactor, 0xffffffff);
pass->Apply(0, _deviceContext.Get());
_deviceContext->Draw(6, 24);
}
그리고 그림자를 그려줄 때 스텐실이 활용된 부분을 보면 NoDoubleBlendDSS를 통해 2번 그려지는 것을 방지한다. 이때 한번 그려졌다면 스텐실 값을 증가시키고 이렇게 증가시키면 전의 값과 같지 않기 때문에 2번 그려지는 것을 방지 할 수 있는 것이다.
_deviceContext->OMSetDepthStencilState(RenderStates::NoDoubleBlendDSS.Get(), 0);
'게임공부 > Directx11(물방울책)' 카테고리의 다른 글
[Directx11][C++][물방울]8. Compute Shader (0) | 2024.10.22 |
---|---|
[Directx11][C++][물방울]7. Geometry Shader (1) | 2024.10.22 |
[Directx11][C++][물방울]5. 블렌딩 (1) | 2024.10.19 |
[Directx11][C++][물방울]4. 텍스쳐 (1) | 2024.10.18 |
[Directx11][C++][물방울]3. 조명 (3) | 2024.10.16 |