8장
스텐실 버퍼 : 특수한 효과를 위한 오프 스크린 버퍼,후면 버퍼 및 깊이 버퍼와 동일한 해상도를 가지므로 스텐실 버퍼 내의 ij번째 픽셀은 후면 버퍼나 깊이 버퍼의 ij번째 픽셀과 대응된다. 이름이 의미하는 것처럼 스텐실 버퍼는 후면 버퍼의 일정 부분이 렌더링되는 것을 막는 스텐실 효과에 이용된다.
예를 들어, 거울을 표현하고자 한다면 거울 평면에 특정 물체만을 반사하도록 하면되며, 거울에 반사되는 부분에 대해서만 드로잉을 수행하면 된다. 이 때 거울에 있지 않은 다른 부분이 렌더링되는 것을 막을 수 있는 기술이 바로 스텐실 버퍼이다.
스텐실 버퍼는 D3D의 아주 작은 부분이며 단순한 인터페이스에 의해 제어된다.하지만 블레딩과 마찬가지로 이 간단한 인터페이스로 유연함과 많은 강력한 기능들이 제공된다.
8.1 스텐실 버퍼 이용하기
스텐실 버퍼를 이용하기 위해서는 먼저 D3D를 초기화하는 시점에 스텐실 버퍼를 요청해야 하며,이용할 때 이를 활성화시켜야 한다. 스텐실 버퍼를 활성화하려면 D3DRS_STENCILENABLE 렌더 상태를 true로 지정하고, 비활성화하려면 false로 지정한다. 스텐실 버퍼를 활성화했다가 다시 비활성화하는 방법은 다음과 같다.
Device->SetRenderState(D3DRS_STENCILENABLE, true);
...// 스텐실 관련 작업을 수행한다.
Device->SetRenderState(D3DRS_STENCILENABLE, false);
스텐실 버퍼를 디폴트 값으로 되돌리기 위해서는 IDirect3DDevice9::Clear 메소드를 이용한다. 이 메서드는 후면 버퍼와 깊이 버퍼를 소거하는 데 이용하던 것과 동일한 것이다.
Device->Clear(0,0,D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER | D3DCLEAR_STENCIL,0xff000000,1.0f, 0 );
함수는 타겟 및 깊이 버퍼와 함께 세번째 인자로 D3DCLEAR_STENCIL을 추가하였다. 여섯번째 인자는 스텐실 버퍼를 소거하는 데 이용될 값을 지정하는 것으로,여기에서는 0을 이용하였다.
8.1.1 스텐실 버퍼 요청하기
스텐실 버퍼는 깊이 버퍼를 만들 때 함께 만들 수 있으며, 깊이 버퍼의 포맷을 지정 할 때 스텐실 버퍼의 포맷도 함께 지정할 수 있다. 실제로 스텐실 버퍼와 깊이 버퍼는 동일한 오프 스크린 표면 버퍼를 공유하며, 각 픽셀 내의 메모리 세그먼트만 해당 버퍼로 이용될 뿐이다.
D3DFMT_D24S8 - 32비트 깊이/스텐실 버퍼를 만들어 깊이 버퍼에는 픽셀 당 24비트를, 스텐실 버퍼에는 픽셀 당 8비트를 할당한다.
스텐실 버퍼를 할당하지 않을수도있고,스텐실 지원은 그래픽 카드에 따라서도 차이가 있을 수 있다.
8.1.2 스텐실 테스트
버퍼의 일부 영역이 렌더되는 것을 막는 데 스텐실 버퍼를 이용할 수 있다. 여기에서 특정 픽셀의 렌더링을 막을 것인지의 결정은 다음과 같은 표현식으로 나타낼 수 있는 스텐실 테스트에 의해 내려진다.
(참조 & 매스크) 비교 연산자 (값 & 매스크)
스텐실이 활성화되어 있다는 가정 하에 모든 픽셀에 대해 스텐실 테스트가 수행되고 이때 두 개의 피연산자를 이용.
LHS = (참조 & 매스크) - 애플리케이션이 정의한 스텐실 참조 값과 애플리케이션이 정의한 매스크값의 AND 연산으로 얻어진다.
RHS = (값 & 매스크) - 현재 테스트하려는 픽셀의 스텐실 버퍼와 애플리케이션이 정의한 매스크 값의 AND 연산으로 얻어진다.
이제 '비교 연산자'에 이정된 방법으로 LHS와 RHS를 비교하는 스텐실 테스트가 수행되며,표현식은 결국 BOOL값으로 평가된다(true면 후면 버퍼의 픽셀 출력,false면 출력 막음). 후면 버퍼에 픽셀이 쓰여지지 않으면 깊이 버퍼에도 또한 쓰여지지 않는다.
8.1.3 스텐실 테스트 제어하기
D3D는 유연선제공을 위해 스텐실 테스트에 이용되는 변수들을 우리가 제어할수있게함(스텐실 참조 값,매스크 값,비교 연산자를 우리가 지정가능)-> 우리가 직접 스텐실 값을 지정하지는 않지만 스텐실 버퍼에 쓰여질 값에 대한 어느 정도 수준의 제어가 가능하다.(스텐실 버퍼를 소거하는 일을 포함하여)
8.1.3.1 스텐실 참조 값
스텐실 참조값(DEFAULT = 0),D3DRS_STENCILREF 렌더 상태를 이용해 값을 바꿀 수 있다. 예를 들어, 다음의 코드는 스텐실 참조 값을 1로 바꾼다.
Device->SetRenderState(D3DRS_STENCILREF,0x1);
8.1.3.2 스텐실 매스크
스텐실 매스크 값은 참조와 값 변수 양쪽의 비트를 매스크(감추기) 하는 데 이용된다. 디폴트 매스크는 0xffffffff이며 이는 어떤 비트도 매스크하지 않는다는 의미를 가진다. D3DRS_STENCILMASK 렌더 상태를 이용하면 이 매스크 상태를 변경할 수 있는데, 예를 들어 다음의 예는 상위 16비트를 매스크한다.
Device->SetRenderState(D3DRS_STENCILMASK,0x0000ffff);
8.1.3.3 스텐실 값
스텐실값 : 스텐실 테스팅을 수행하고 있는 현재 픽셀의 스텐실 버퍼값. 현재 ij번째 픽셀을 테스트하고 있다면 이 값은 스텐실 버퍼의 ij번째 항목이 될 것이다. 각각의 스텐실 값을 우리가 직접 지정하는 것은 불가능하지만 스텐실 버퍼를 소거할 수는 있다. 또한 부가적으로 스텐실 렌더 상태를 이용하면 스텐실 버퍼로 쓰여질 것에 대한 제어가 가능하다.
8.1.3.4 비교 연산자
D3DRS_STENCILFUNC 렌더 상태를 이용하면 비교 연산자를 지정할 수 있으며, 이 비교 연산자로는 D3DCMPFUNC 열거형의 멤버 중 하나를 사용한다.
typedef enum D3DCMPFUNC {
D3DCMP NEVER = 1 , - 스텐실 테스트가 항상 실패
D3DCMP LESS = 2 , -LHS < RHS 일 경우 테스트 성공
D3DCMP EQUAL = 3, -LHS=RHS일 경우 테스트 성공
D3DCMP LESSEQUAL = 4, - LHS <= RHS 일경우 테스트 성공
D3DCMP GREATER = 5, -LHS > RHS 일경우 테스트 성공
D3DCMP NOTEQUAL = 6, -LHS != RHS 일경우 테스트 성공
D3DCMP GREATEREQUAL = 7 , -LHS >= RHS 일경우 테스트 성공
D3DCMP ALWAYS = 8, - 테스트 항상 성공
D3DCMP FORCE DWORD = Ox7fffffff
} D3DCMPFUNC;
8.1.4 스텐실 버퍼 갱신하기
특정한 픽셀이 후면 버퍼에 쓰여질지의 여부를 결정하는 것 이의에도 다음과 같은 세 가지의 가능한 상황에 따라 스텐실 버퍼 항목이 갱신되는 방법을 정의할 수 있다.
*IJ번째 픽셀에서 깊이 테스트가 실패.D3DRS_STENCILZFAIL 렌더 상태를 지정하여 이러한 상황이 발생했을 때 IJ번째 항목을 갱신하는 방법을 정의할 수 있다.
Device->SetRenderState(D3DRS_STENCILZFAIL, StencilOperation);
*IJ번째 픽셀에서 깊이 테스트와 스텐실 테스트가 성공. D3DRS_STENCILPASS 렌더 상태를 지정하여 이러한 상황이 발생했을때 IJ번째 항목을 갱신하는 방법을 정의할 수 있다.
Device->SetRenderState(D3DRS_STENCILPASS,StencilOperation);
여기에서 StencilOperation 값으로 다음 상수 중 하나를 넣는다.
D3DSTENCILOP_KEEP - 스텐실 버퍼 항목을 변경하지 않도록 지정(즉, 현재값을 그대로 유지)
D3DSTENCILOP_ZERO - 스텐실 버퍼 항목을 0으로 지정
D3DSTENCILOP_REPLACE - 스텐실 버퍼 항목을 스텐실 참조 값으로 대체하도록 지정
D3DSTENCILOP_INCRSAT - 스텐실 버퍼 항목을 증가,만약 증가된 값이 허용 최대 값을 넘을 경우,최대치로 고정
D3DSTENCILOP_DECRSAT - 스텐실 버퍼 항목을 감소,감소된 값이 0보다 작을 경우 0으로 고정
D3DSTENCILOP_INVERT - 스텐실 버퍼 항목을 반전시키도록 지정
D3DSTENCILOP_INCR - 스텐실 버퍼 항목을 증가,만약 증가된값이 허용 최대 값을 넘을 경우 0으로 돌려진다.
D3DSTENCILOP_DECR - 스텐실 버퍼 항목을 감소시키도록 지정. 감소된 값이 0보다 작을경우 최대값으로 돌려진다.
8.1.5 스텐실 쓰기 매스크
쓰기 매스크 : 스텐실 버퍼에 쓰여지는 모든 값을 매스크하는 매스크,디폴트 값은 0xffffffff이며, D3DRS_STENCILWRITEMASK 렌더 상태를 이용하면 쓰기 매스크를 지정가능 다음 코드는 상위 16비트를 매스크
Device->SetRenderState(D3DRS_STENCILWRITEMASK,0x0000ffff);
8.2 예제 애플리 케이션 : 거울
단순하 구현을 위해서 평평한 표면에 거울을 구현하는 것으로 내용을 제한
프로그래밍으로 거울을 구현하기 위해서는 두 가지 문제를 해결해야함. 먼저, 올바르게 반사를 그려내기 위해 임의의 평면에 물체가 반사되는 방법을 파악해야 한다.( 벡터 기하학을 통해 해결)두번째로, 거울 영역에만 반사를 그려야 한다. 즉, 표면을 거울로 '표시'하는 방법이 필요하며,거울로 표시된 부분에만 반사된 물체를 그리도록 하는 방법이 필요하다.(스텐실 버퍼를 통해 해결)
8.2.1 반사를 위한 수학
임의의 평면에 포인트 V의 반사포인트 V'를 계산하는 방법
D3DX 라이브러리는 R과 같은 임의의 평면에 대한 반사 행렬을 만들어내는 함수를 제공한다.
D3DMATRIX D3DXMatrixReflect(
D3DMATRIX pOut, // 결과 반사 행렬
CONST D3DXPLANE *pPlane // 반사할 평면
);
여기에서는 반사 변환에 대해 살펴보고 있으므로 반사 변환의 세 가지 특수한 경우를 확인해보기로 하자. 세 가지 특수한 경우란 표준 좌표 평면인 YZ평면, XZ 평면,XY평면을 말하는 것으로 다음과 같은 세 가지의 행렬을 통해 나타낼 수 있다.
YZ 평면 반대쪽의 포인트를 반사하기 위해서는 간단히 X성분의 반대를 취하면 된다. 비슷하게 XZ 평면 반대쪽의 포인트를 반사하기 위해서는 Y-성분의 반대를 취한다. 마지막으로 XY 평면 반대쪽의 포인트를 반사하기 위해서는 Z성분의 반대를 취한다.
이들 반사는 표준 좌표 평면의 대칭을 확인함으로서 바로 확인이 가능하다.
8.2.2 거울 구현의 개관
거울을 구현할 때 중요한 요점 한 가지는 거울의 앞에서만 물체가 반사된다는 것이다. 하지만 물체가 거울 앞에 있는지를 공간적으로 확인한다면 너무 복잡하다.따라서 항상 물체를 반사시키고 어떤 표면이든지 이를 렌더링하는 방법을 선택. 하지만 이 방법은 물체의 반사가 거울이 아닌 다른 표면에도 렌더링됨. 스텐실 버퍼는 후면 버퍼 내의 특정 영역이 렌더링되는 것을 막아줄 수 있으므로 이 문제의 해결책으로 적합하다. 즉 스텐실 버퍼를 이용해 반사된 주전자가 거울 이외의 다른 표면에 렌더링되는 것을 막을 수 있다.
바닥과 벽,거울, 주전자를 포함하는 전체 장면을 보통 때와 마찬가지로 렌더링.아직 주전자의 반사는 포함하지 않는다. 이 단계에서는 아직 스텐실 버퍼를 수정하지 않는다.
스텐실 버퍼를 0값으로 소거
3.거울을 구성하는 기본형을 스텐실 버퍼에만 렌더링한다. 스텐실 테스트가 항상 성공하도록 하고 테스트가 성공하면 스텐실 버퍼 항목을 1로 대체하도록 지정한다. 거울만을 렌더링하는 것이므로 거울에 해당하는 픽셀들만 1값을 가지며, 나머지 모든 영역은 0값으로 남는다. 밑 그림에 스텐실버퍼는 버퍼 내에서 거울에 해당하는 픽셀들을 표시한 것과 같다.
8.2.3 코드와 설명
void RenderMirror()
{
Device->SetRenderState(D3DRS STENCILENABLE, true);
Device- >SetRenderState(D3DRS STENCILFUNC, D3DCMP ALWAYS) ;
Device- >SetRenderState(D3DRS STENCILREF, Oxl) ;
Device->SetRenderState(D3DRS STENCILMASK, Oxffffffff) ;
Device- >SetRenderState(D3DRS STENCILWRITEMASK, Oxffffffff) ;
Device - >SetRenderState(D3DRS STENCILZFAIL, D3DSTENCILOP KEEP);
Device->SetRenderState(D3DRS STENCILFAIL, D3DSTENCILOP KEEP);
Device- >SetRenderState(D3DRS STENCILPASS , D3DSTENCILOP REPLACE) ;
}
스텐실 비교연산자(STENCILFUNC)를 D3DCMP_ALWAYS로 지정하여 스텐실 테스트가 항상 성공하도록 지정
깊이테스트(STENCILZFAIL)가 실패하면 D3DSTENCILOP_KEEP을 지정하여 스텐실 버퍼 항목을 갱신하지 말 것을 지시.즉 현재의 값을 그대로 유지한다. 깊이 테스트가 실패한다면 그것은 픽셀이 가려졌음을 의미, 가려진 픽셀에 반사된 물체를 렌더링할 필요는 없다.
스텐실 테스트(STENCILFAIL)가 실패한 경우에는 D3DSTENCILOP_KEEP을 지정. 실제로는 앞서 D3DCMP_ALWAYS를 지정하여 테스트가 실패하는 일은 없으므로 이 부분은 불필요하지만 조금뒤에 비교 연산자를 바꿀 것이므로 먼저 스텐실 실패 렌더 상태를 지정
깊이,스텐실테스트가 모두 성공한 경우(STENCILPASS)에는 D3DSTENCILOP_REPLACE를 지정하여 스텐실 버퍼 항목을 스텐실 참조 값인 0x1로 대체하도록 했다.
8.2.3.2 파트 2
이어지는 코드 블록에서는 스텐실 버퍼에 거울을 렌더링. D3DRS_ZWRITEENABLE을 false로 지정하면 깊이 버퍼로 쓰여지는 것을 막음. 또한, 원본 블렌드 인수를 D3DBLEND_ZERO로, 목적지 블렌드 인수를 D3DBLEN_ONE로 지정하고 블렌딩을 이용하면 후면 버퍼가 갱신되는 것을 막을 수 있다.이들 블렌드 인수를 블렌딩 방정식에 넣으면 후면 버퍼가 바뀌지 않게 됨.
8.2.3.3 파트 3
이제 스텐실 버퍼 내의 가시 픽셀 중 거울에 해당하는 픽셀들을 0x1 값을 가지며 거울로 렌더링될 부분을 표시한다. 다음은 반사된 주전자 렌더링을 위한 준비 단계이다. 우리는 거울에 해당하는 부분에만 반사를 렌더링할 것이며 이 픽셀들은 이미 스텐실 버퍼에 표시되어 있으므로 과정을 그리 어렵지 않다.
먼저 다음과 같이 렌더상태를 지정한다.
Device- >SetRenderState(D3DRS STENCILFUNC, D3DCMP EQUAL);
Device - >SetRenderState(D3DRS STENCILPASS , D3DSTENCILOP KEEP) ;
새로운 비교 연산자를 지정하고 다음과 같이 스텐실 테스트를 구성한다.
(ref & mask) == (value & mask)
(0x1 & 0xffffffff) == (value & 0xffffffff)
(0x1) == (value & 0xffffffff)
이제 value = 0x1인 경우에만 스텐실 테스트를 통과할 수 있다. 스텐실 버퍼 내에서 0x1 값을 가진 영역은 거울에 해당하는 영역이므로 이 영역을 렌더링하는 동안에만 테스트가 성공한다.따라서, 반사된 주전자는 거울에 해당하는 영역에만 렌더링된다.
D3DRS_STENCILPASS 렌더 상태를 D3DSTENCIL_KEEP로 지정하여 테스트가 성공하면 스텐실 버퍼 내의 값을 유지하도록 했다.따라서 다음 렌더링 단계에서는 스텐실 버퍼 내의 값은 변경되지 않는다. 스텐실 버퍼는 거울에 해당하는 픽셀들을 표시하는 데만 이용된다.
8.2.3.4 파트 4
RenderMirror 함수의 다음 파트는 장면 내 반산의 위치를 잡는 행렬을 계산하는 것이다.
//반사의 위치를 잡는다.
D3DXMATRIX W,T,R;
D3DXPLANE plane(0.0f, 0.0f, 1.0f,0.0f); xy 평면
D3DXMatrixReflect(&R,&plane);
D3DXMatrixTranslation(&T,
TeapotPosition.x,
TeapotPosition.y,
TeapotPosition.z,);
W = T * R;
먼저 반사되지 않은 주전자 위치로 이동했음을 확인하자.위치가 정해지면 xy평면으로 반사를 수행한다. 이와 같은 변환의 순서는 행렬을 곱하는 순서에 따라 정해진다.
8.2.3.5 파트 5
이제 반사된 주전자를 렌더링할 준비가 거의 완료되었다. 하지만 지금 시점에서 이를 렌더링한다면 주전자는 나타나지 않을 것이다. 왜냐하면 반사된 주전자의 깊이는 거울보다 크며, 따라서 거울의 기본형이 기술적으로는 반사된 주전자를 가리기 때문이다. 이 문제를 해결하기 위해서는 깊이 버퍼를 소거해야한다.
Device->Clear(0,0,D3DCLEAR_ZBUFFER,0,1.0f,0);
만약 단순히 깊이 버퍼를 소거한다면 반사된 주전자는 거울 전면에 그려지기 때문에 우리가 원하던 결과와는 차이가 있다. 우리가 원하는 결과를 얻기 위해서는 깊이 버퍼를 소거함과 동시에 반사된 주전자를 거울과 블렌드 주어야한다.이제는 거울 안에 주전자가 있는 것처럼 보이게 된다. 반사된 주전자와 거울을 블렌드하는 데는 다음과 같은 블렌딩 방정식이 이용된다.
원본 픽셀은 반사된 주전자이며 목적지 픽셀은 거울이므로 이들이 어떻게 블렌드되는지는 방정식을 통해 알 수 있다.
Device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_DESTCOLOR);
Device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ZERO);
이제 반사된 주전자를 그릴 준비가 되었다.
Device->SetTransform(D3DTS_WORLD,&W);
Device->SetMaterial(&TeapotMtrl);
Device->SetTexture(0,0);
Device->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
Teapot->DrawSubset(0);
8.2.3.4 섹션에서 설명한 대로 W는 반사된 주전자를 장면 내의 적절한 위치로 이동시킨다. 또한, 후면 추려내기 모드로 변경했음을 확인하자. 이것은 물체가 반사될 때 물체의 전면과 후면이 뒤바뀌기 때문인데 반면에 두르기 순서는 바뀌지 않는다.따라서 새로운 전면은 D3D가 후며능로 인식하는 두르기 순서를 가지며, 새로운 후면은 D3D가 전면으로 인식하는 두르기 순서를 가진다. 따라서 이를 보정하기 위해 후면 추려내기 조건을 변경해야 한다.
마지막으로 블렌딩과 스텐실을 비활성화하였으며, 후면 추려내기 모드를 원래대로 복구하였다.
Device->SetRenderState(D3DRS_ALPHABLENDENABLE,false);
Device->SetRenderState(D3DRS_STENCILENABLE,false);
Device->SetRenderState(D3DRS_CULLMODE,D3DCULL_CCW);
} // RenderMirror() 함수 종료
8.5 예제 애플리케이션 : 평면 그림자.
그림자는 장면의 어느 곳에서 빛이 비추고 있는지를 인식할 수 있도록 해주며 궁극적으로는 장면의 사실감을 높여준다.
평면 그림자를 구현하기 위해서는 먼저 물체가 평면에 만들어내는 그림자를 찾고 렌더링이 가능하도록 이를 기하학적으로 구성해야한다. 이 과정은 약간의 3D 수학을 통해 간단히 해결할 수 있으며,이어 그림자를 묘사하는 다각형을 50% 투명도를 가진 검은 재잴을 이용해 렌더링하면 된다. 이와 같은 그림자 렌더링 방식에는 더블 블렌딩이라 불리는 약간의 부작용이 따름을 기억하자.
8.3.1 평행 그림자.
위 그림은 평행한 광원에 의해 물체에 발생하는 그림자를 보여주고있다. L 방향을 가지는 평행 광원에서 버텍스 p로 통하는 광선은 r(t) = p + tL로 얻을 수 있고, 광선 r(t)와 평면 n . p +d =0을 교차하면 s를 얻을 수 있다. 또한 광선 r(t)를 물체의 각 버텍스에서 평면으로 발사해 얻은 교차점의 집합으로 그림자의 기하정보를 정의할 수 있다. 교차점 s는 광선/평면 교차 테스트를 통해 간단히 얻을 수 있다.
n . (p + tL) +d = 0 -> r(t)를 평면 방정식 n.p+d=0에 넣는다.
n . p + t(n . L) = -d;
t = (-d-n.p) / ( n . L )
결과는:
s = p + (-d-n.p) / ( n . L ) * L
8.3.2 점 조명 그림자
위 그림은 포인트 L위치에 존재하는 점 조명 광원에 의해 발생하는 그림자를 보여주고 있다. 점 조명에서 버텍스 p로 발사되는 광선은 r(t) = p + t(p-L)로 얻을 수 있으며,광선 r(t)와 평면 n.p+d=0과의 교차점으로 s를 얻을 수 있다.
광선 r(t)를 물체의 각 버텍스에서 평면으로 발사해 얻은 교차점의 집합으로 그림자의 기하정보를 얻을 수 있으며, 8.3.1 섹션에서 이용된 것과 같은 테크닉(평면/광선/교차)으로 s를 풀어낼 수 있다/
-NOTE-
점 조명과 평행 광원에서 L은 각기 다른 목적을 가진다. 점 조명에서 L은 조명의 위치를 정의하지만, 평행 조명엣서 L은 평행 광선이 나아가는 방향을 정의한다.
8.3.3 그림자 행렬
평행 조명을 보여주는 그림자는 결국 지정된 광선 방향으로 평면 n.p +d = 0에 물체를 평행 투영한 것이다. 비슷하게 점 조명을 보여주는 그림자는 광원의 관점에서 물체를 평면 n . p + d =0에 원근 투영한 것이다
버텍스 p에서 평면 으로의 투영 s는 하나의 행렬로 표현될 수 있다.또한, 동일한 행렬로 직각 투영과 원근 투영을 동시에 표현하는 것도 가능하다.
그림자가 만들어질 평면의 공통 평면 방정식의 계수로 4D 벡터를 이용하고,평행 조명의 방향이나 점 조명의 위치를 표현하는 4D 벡터로 L을 이용하자. w는 다음과 같이 이용된다
평면의 법서니 정규화되었다고 가정하고 두 4D 벡터의 내적을 각성분의 곱이라고하면
다음 그림자 행렬을 이용해 버텍스 p에서 투영 s로의 변환을 나타낼 수 있다.
D3DX 라이브러리는 그림자 행렬을 만들어내는 다음의 함수를 제공한다. 결과로 얻어진 행렬은 w = 0일 경우 평행 조명에서, w = 1일 경우 점 조명에서 주어진 평면으로 그림자를 투영한다.
D3DXMATRIX D3DXMatrixShadow(
D3DMATRIX pOut,
CONST D3DXVECTOR4 pLight, // L
CONST D3DXPLANE pPlane //그림자를 만들 평면
);
8.3.4 더블 블렌딩을 막기 위한 스텐실 버퍼 이용
물체의 기하정보를 평면에 납작하게 만들어 그림자를 표현하면 두 개 이상의 펴진 삼각형이 겹치는 현상이 발생할 수 있다. 반투명(블렌딩을 이용해)한 그림자를 렌더링하면 이 겹쳐진 영역들이 여러 차례 블렌드되어 더욱 어둡게 나타난다.
이 문제 해결의 열쇠가 되는 것이 바로 스텐실 버퍼이다. 즉,처음으로 렌더링되는 픽셀만을 받아들이도록 스텐실 테스트를 구성하여 그림자 픽셀을 후면 버퍼에 렌더링하는 동안 대응되는 스텐실 버퍼 항목에 표시를 남기는 것이다. 이제 이미 렌더링 된(스텐실 버퍼에 표시된) 영역에 픽셀을 쓰려고 하면 스텐실 테스트가 실패하므로 하나의 픽셀에 두 번 이상 블렌드가 적용되는 것을 막을 수 있다.
8.3.5 코드와 설명
void RenderShadow()
Device- >SetRenderState(D3DRS STENCILENABLE, true) ;
Device- >SetRenderState(D3DRS STENCILFUNC, D3DCMP EQUAL) ;
Device- >SetRenderState(D3DRS STENCILREF, OxO) ;
Device- >SetRenderState(D3DRS STENCILMASK, Oxffffffff);
Device- >SetRenderState(D3DRS STENCILWRITEMASK, Oxffffffff) ;
Device->SetRenderState(D3DRS STENCILZFAIL, D3DSTENCILOP KEEP ) ;
Device- >SetRenderState(D3DRS STENCILFAIL, D3DSTENCILOP KEEP);
Device- >SetRenderState(D3DRS STENCILPASS , D3DSTENCILOP INCR) ;
일단 스텐실 렌더 상태(STENCILFUNC)를 지정하였따. 스텐실 비교 함수를 D3DCMP_EQUAL로 지정하고 D3DRS_STENCILREF 렌더 상태를 0x0으로 지정하여, 스텐실 버퍼 내의 대응되는 항목이 0x0일 경우에만 후면 버퍼에 그림자를 렌더링하도록 했다.
스텐실 버퍼는 0(0x0)으로 소거되어 있으므로 처음으로 특정 픽셀을 쓸 때는 항상 테스트가 성공하지만 D3DRS_STENCILPASS를 D3DSTENCILOP_INCR로 지정하였으므로 이미 쓰여진 픽셀을 쓰려고 할 때는 테스트가 실패한다. 한 번 쓰여진 스텐실 항목의 픽셀은 0x1이 되므로 이를 다시 쓸 수 없다. 즉,픽셀의 덮어쓰기를 막는 방법으로 더블 블렌딩 현상을 제거한 것이다.
// 주전자를 그림자로 압축하기 위한 변환을 계산한다.
D3DXVECTOR4 lightDirection(0 . 707f, -0 . 707f, 0.707f, O. Of) ;
D3DXPLANE groundPlane(O.Of, - 1.0f, O.Of, O. Of) ;
D3DXMATRIX S;
D3DXMatrixShadow(&S , &lightDirection, &groundPlane) ;
D3DXMATRIX T;
D3DXMatrixTranslation(&T, TeapotPosition .x, TeapotPosition . y,
TeapotPosition .z ) ;
D3DXMATRIX W ~ T * S;
Device- >SetTransform(D3DTS WORLD , &W) ;
다음은 그림자 변환을 계산하고 장면 내의 적절한 위치로 그림자를 이동한다.
마지막으로 50% 투명도의 검은 재질을 지정하고 깊이 테스팅을 비활성화한 다음, 그림자를 렌더링하였으며 깊이 버퍼를 다시 활성화하고 알파 블렌딩과 스텐실 테스팅을 비활성화하는 정리 단계를 수행하였다. z-쟁탈을 막기 위해 깊이 버퍼를 비활성화했는데, z-쟁탈은 두 개의 다른 표면이 기핑 버퍼 내의 동일한 깊이 값을 가질 때 발생하는 현상으로, 깊이 버퍼가 어떤 표면이 앞에 나설지를 결정하지 못하여 눈에 거슬리는 깜박임 현상으로 나타나는 것(그림자와 바닥은 동일한 평면에 위치하므로 대부분 z-쟁탈이 일어난다.)바닥을 먼저 렌더링하고 깊이 테스팅을 끈 상태로 그림자를 렌더링하면 원래 의도했던 대로 바닥 위에 그림자가 그려지도록 할 수 있다.
8.4 요약
*스텐실 버퍼와 깊이 버퍼는 동일한 표면을 공유하므로 함께 만들어진다. D3DFORMAT 형을 이용해 깊이/스텐실 표면의 포맷을 지정할 수 있다.
*특정한 픽셀이 래스터라이즈되는 것으 막는 데 스텐실을 이용할 수 있다, 스텐실은 여러 가지 분야에 응용될 수 있으며특히 이 단원에서 살펴본 것처럼 거울이나 그림자를 구현하는 데 유용하다.
스텐실 연산이나 스텐실 버퍼가 갱신되는 방법을 지정하는 데는 D3DRS_STENCIL 계열의 렌더 상태가 이용된다.
*스텐실 버퍼를 이용할 수 있는 다른 응용 분야에는 다음과 같은 것들이 있다.
-그림자 볼륨
-분해 효과,희미해짐 효과
-깊이 복장성의 시각화
-외곽선과 실루엣
-구조적으로 채워진 기하정보
-동일 면 기하물체에 의한 z-쟁탈 해결
To his surprise, the game soon started paying off. Aiden found himself winning more frequently, and the excitement of seeing his small bets multiply quickly took over. As he continued to play, he hit a royal flush—a rare and thrilling win in https://1winind.in/ poker that dramatically increased his earnings. What started as a simple distraction turned into a memorable night of celebration. Aiden's quiet evening had transformed into an unexpected jackpot win, proving that sometimes, the best surprises come when you least expect them.