서의 공간

제10장 스텐실 적용 본문

Graphics API/DirectX 11 - Luna

제10장 스텐실 적용

홍서의 2020. 12. 23. 02:47

목표

1. ID3D11DepthStancilState 인터페이스를 이용해서 깊이·스텐실 버퍼의 설정들을 제어하는 방법을 파악한다.

2. 거울을 구현할 때 스텐실 버퍼를 이용해서 거울 면 바깥에 반사상이 그려지지 않도록 만드는 방법을 배운다.

3. 이중 혼합이 무엇이고 스텐실 버퍼를 이용해서 이중 혼합을 방지하려면 어떻게 해야 하는지 살펴본다.

4. 깊이 복잡도를 이해하고, 장면의 깊이 복잡도를 측정하는 두 가지 방법을 알아본다.

 

스텐실: [미술용어] 투명 필름지 등에 도안을 그리고 모양대로 오려낸 후, 종이나 벽, 가구등 원하는 곳에 필름을 붙인 후 오려낸 자리에 물감을 두드려 발라서 도안과 같은 그림을 만드는 형식의 용법이다. 판화와 흡사하나 좌우반전이 없다.

 

10.1 깊이·스텐실 버퍼의 자료 형식과 버퍼 지우기

10.2 스텐실 판정

스텐실 버퍼를 이용하면 후면 버퍼의 특정 영역으로의 렌더링을 방지할 수 있다. 주어진 한 픽셀을 후면 버퍼에 기록할 것인지 아닌지를 스텐실 버퍼에 근거해서 판정하는 것을 스텐실 판정(stencil test)이라고 부른다. 스텐을 판정을 의사코드로 표현하면 다음과 같다. 가운데 ><=는 부등호 연산자의 일반화다. 어떠한 부등호 연산자가 놓일 수 있다.

if(StencilRef & StencilReadMask ><= Value & StencilReadMask)
	픽셀을 허용
else
	픽셀을 기각

스텐실 적용이 활성화된 경우 스텐실 판정은 픽셀이 래스터화되는 과정에서(즉 출력 병합기 단계 도중에) 일어난다. 스텐실 판정은 두 피연산자를 응용 프로그램에서 지정한 비교 연산자(><=)로 비교하는 것으로, 두 피연산자는 다음과 같다.

  1. 연산자 좌변의 피연산자는 응용 프로그램이 정의한 스텐실 기준 값(stencil reference value; StencilRef)과 응용 프로그램이 정의한 마스킹 값(masking value; StencilReadMask)을 비트별 논리곱(bitwise AND)로 결합한 것이다.
  2. 우변의 피연산자는 스텐실 버퍼에 이미 있던 해당 픽셀의 값(Value)과 응용 프로그램이 정의한 마스킹 값(StencilReadMask)를 비트별 논리곱으로 결합한 것이다.

좌변과 우변의 StencilReadMask는 같은 값이다. 

비교 연산자로 사용할 수 있는 함수의 종류가 D3D11_COMPARISON_FUNC 열거형으로 정의되어 있다.

typedef enum D3D11_COMPARISION_FUNC
{
	D3D11_COMPARISON_NEVER = 1,
    D3D11_COMPARISON_LESS = 2,
    D3D11_COMPARISON_EQUAL = 3,
    D3D11_COMPARISON_LESS_EQUAL = 4,
    D3D11_COMPARISON_GREATER = 5,
    D3D11_COMPARISON_NOT_EQUAL = 6,
    D3D11_COMPARISON_GREATER_EQUAL = 7,
    D3D11_COMPARISON_ALWAYS = 8,
} D3D11_COMPARISON_FUNC;

순서대로

  1. D3D11_COMPARISON_NEVER: 항상 거짓을 반환
  2. D3D11_COMPARISON_LESS: < 연산자
  3. D3D11_COMPARISON_EQUAL: == 연산자
  4. D3D11_COMPARISON_LESS_EQUAL: <= 연산자
  5. D3D11_COMPARISON_GREATER: > 연산자
  6. D3D11_COMPARISON_NOT_EQUAL: != 연산자
  7. D3D11_COMPARISON_GREATER_EQUAL: >= 연산자
  8. D3D11_COMPARISON_ALWAYS: 항상 참을 반환

10.3 깊이·스텐실 상태 집합

ID3D11DepthStencilState 인터페이스를 생성하려면 우선 D3D11_DEPTH_STENCIL_DESC 구조체를 채워야 한다. 그리고 ID3D11Device::CreateDepthStencilState 메서드를 호출하여 객체를 만든다. 그 다음 ID3D11DeviceContext::OMSetDepthStencilState 메서드를 호출하여 출력 병합기 단계에 바인딩한다. 

HLSL에서 DepthStencilState 구조체를 정의하여 설정하는 것도 가능하다.

10.4 평면거울의 구현

다음은 거울을 포함한 두개골 장면을 그리는 대략의 단계들이다.

  1. 바닥과 벽, 두개골을 보통의 방법으로 렌더링한다. 거울은 아직 그리지 않는다. 이 단계에서는 스텐실 버퍼를 갱신할 필요가 없음을 주목할 것.
  2. 스텐실 버퍼를 0으로 지운다. 그림 10.1은 이 지점에서의 후면 버퍼와 스텐실 버퍼를 나타낸 것이다.
  3. 거울을 스텐실 버퍼에만 렌더링 한다. 후면 버퍼에 색상 성분들이 기록되지 않게 하려면 깊이·스텐실 상태의 RenderTargetWriteMask를 0으로 설정하면 된다. 즉,
    D3D11_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask = 0;
    그리고 깊이 버퍼 쓰기를 비활성화하려면 깊이·스텐실 상태의 해당 항목을 다음과 같이 설정하면 된다.
    D3D11_DEPTH_STENCIL_DESC::DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
    거울을 스텐실 버퍼에 렌더링할 때에는 스텐실 판정이 항상 성공하도록(D3D11_COMPARISON_ALWAYS) 설정한다. 또한 판정 성공 시(지금 경우 항상) 스텐실 버퍼 항목이 1(StencilRef)로 대체되도록(D3D11_STENCIL_OP_REPLACE) 설정한다. 깊이 판정이 실패하는 경우의 행동 방식은 D3D11_STENCIL_OP_KEEP으로 설정한다. 이렇게 하면 깊이 판정이 실패해도(이를테면 두개골이 거울의 일부를 가리는 경우에 그럴 수 있다) 스텐실 버퍼는 변하지 않는다. 거울만 스텐실 버퍼에 렌더링하는 것이므로 스텐실 버퍼의 항목들 중 거울의 가시적인 부분(반사상이 나타날 수 있는 영역)의 픽셀에 해당하는 항목들은 모두 1이고, 그 외의 항목들은 모두 0이 된다. 그림 10.2에 갱신된 스텐실 버퍼가 나와 있다. 간단히 말하면, 이 단계에 의해 스텐실 버퍼는 거울의 가시적 픽셀들이 표시된 상태가 된다.
  4. 이제 반사된 두개골을 후면 버퍼와 스텐실 버퍼에 렌더링한다. 이때 픽셀이 스텐실 판정을 통과한 경우에만 후면 버퍼에 기록되게 해야 한다. 이번에는 스텐실 버퍼 항목이 1인 경우에면 판정을 통과하게 한다. 이를 위해 StencilRef를 1로 설정하고 스텐실 판정 연산자는 D3D11_COMPARISON_EQUAL로 설정한다. 이렇게 하면 반사된 두개골은 스텐실 항목이 1인 영역에만 그려진다. 스텐실 버퍼의 그러한 영역은 단계 3에서 렌더링한 거울의 가시적 부분에 해당한다. 결과적으로 반사된 두개골은 거울의 가시적부분에만 렌더링된다.
  5. 마지막으로, 거울 자체를 후면 버퍼에 통상적인 방법으로 렌더링 하되, 반사된 두개골(거울 평면 뒤에 있다)이 가려지지 않도록 하기 위해 투명도 혼합을 적용한다. 거울을 (반)투명하게 렌더링 하지 않는다면 거울의 깊이가 반사된 두개골의 깊이보다 작으므로 거울이 반사상을 그대로 가려버린다. 분산광 재질의 알파 성분을 0.5로 설정해서 거울이 50% 투명하게 나타나게 한다. 

반사된 두개골의 픽셀들이 후면 버퍼에 기록되어 있다고 할 때, 혼합에 의해 거울(원본) 색상의 50%와 두개골(대상) 색상의 50%가 보이게 된다.

[그림 10.1] 바닥과 벽, 두개골을 후면 버퍼에 그린 후 스텐실 버퍼를 0(밝은 회색으로 표시되었음)으로 지운다. 스텐실 버퍼에 그려진 검은 외곽선은 후면 버퍼 픽셀들과 스텐실 버퍼 픽셀들의 관계를 보여 주기 위한 것일 뿐, 실제로 스텐실 버퍼에 기록된 자료를 나타낸 것은 아니다.
[그림 10.2] 거울을 스텐실 버퍼에 렌더링해서, 거울의 가시적 부분에 해당하는 영역을 스텐실 버퍼에 표시해 둔다. 그림에서 검은색 영역이 거울의 가시적 부분, 즉 스텐실 항목이 1로 설정된 부분이다. 가시적 부분에 해당하는 스텐실 버퍼 항목들 중에서 상자에 가려진 부분은 깊이 판정에 실패해서(상자가 거울의 그 부분보다 앞에 있으므로) 1로 설정되지 않았음을 주목하라.

반사된 삼각형의 정점들이 감긴 순서를 반대로 설정하기 위해 래스터화 상태를 다음과 같이 변경한다.

// 주의: 반시계방향을 전면 삼각형으로 간주해서 후면 삼각형들을 선별한다.
// 후면 선별을 비활성화하는 것이 아님을 주의할 것. 후면 선별을 하지 않는다면
// D3D11_DEPTH_STENCIL_DESC의 BackFace 속성에 신경을 써야 한다.
D3D11_RASTERIZER_DESC cullClockwiseDesc;
ZeroMemory(&cullClockwiseDesc, sizeof(D3D11_RASTERIZER_DESC));
cullClockwiseDesc.FillMode = D3D11_FILL_SOLID;
cullClockwiseDesc.CullMode = D3D11_CULL_BACK;
cullClockwiseDesc.FrontCounterClockwise = true;
cullClockwiseDesc.DepthClipEnable = true;

ID3D11RasterizerState* CullClockwiseRS;
HR(device->CreateRasterizerState(
	&cullClockwiseDesc, &CullClockwiseRS));

10.5 평면 그림자의 구현

'Graphics API > DirectX 11 - Luna' 카테고리의 다른 글

제12장 계산 셰이더  (0) 2020.12.23
제11장 기하 셰이더  (0) 2020.12.23
제9장 혼합  (0) 2020.12.23
제8장 텍스처 적용  (0) 2020.12.22
DirectX 11 그래픽스  (0) 2020.12.07
Comments