그림자는 어떤 방향으로 빛을 보내는지 짐작 가능하고 장면의 사실감을 높이다.
평면 그림자를 구현 하려면 물체가 평면에 드리우는 그림자를 계산하고 기하구조 형태로 만들면 된다
위에 처럼 계산 후 투명한 삼각형으로 만들면 이중 혼합이하는 렌더링 결함이 생긴다
평행광 그림자
평행광 방향 : L, 정점 : P
반직선과 그림자 평면의 교점 : s
거쳐 가는 광선에 해당하는 반직선 $r(t) = p+ tL$ 로 정의
점점 P를 교점 s 에 투영 하는걸 그림자 투영이라고 하고
물체의 정점 마다 빈직선을 쏘아서 그림자 투영 점들을 모아서 그림자의 기하구조를 생성
행렬 곱셈 에 의해 $S_w = n \cdot L$ 된다 원근 나누기 시 s의 성분들이 $n\cdot L $ 로 나누어 진다
그림자 렌더링 할떄는 그림자 행렬을 세계 행렬과 결합 해서 적용
세계 변환 후 아직 원근 나누기가 일어나는 않는 시점에서는 기하구조가 제대로 그림자 평면에 투영 된 상태가 아니다
$S_w = n \cdot L < 0$ 이면 w 성분이 음수가 되므로 문제 발생 한다
w가 음수 일 경우 해당 점은 시야 공간 바깥에 존재하므로 절단 시킨다
그래서 L 대신 무한히 멀리 있는 광원을 향해 $L = -L $ 사용
$r(t) = p+ tL$ 과 $r(t) = p+ t(-L)$ 3차원 반직선은 동일해서 평면의 교점도 동일
( 교차 매개변수 $t_s$ 는 다르다)
그림자가 유효한 상황에서는 항상 $S_w = n \cdot (-L) > 0$ 이므로 w 성분이 음수가 되는 문제가 발생하지 않는다
점광 그림자
L : 점광의 위치, 정점 : P,
반직선과 그림자 평면의 교점 : s
거쳐 가는 광선에 해당하는 반직선 $r(t) = p+ t(P-L)$ 로 정의
행렬의 마지막 성분이 0이 아닌 값으로 점광 그림자의 w 성분은 다르다
평행광와 점광 그림자에 L은 글자만 같을 뿐 의미가 전혀 다르다
점광 그림자에서의 L은 점광원의 위치,
평행광 그림자에서는 L은 무한히 멀이있는 광원을 향한 방향( 평행광선의 반대 방향)
범용 그림자 행렬
동차 좌표를 이용하면 점광, 평행광 모두 사용하는 범용 그림자 행렬 생성
다음과 같은 관례를 사용
1. $L_w = 0$ L은 무한히 멀리 있는 광원을 향한 방향( 평행광선의 나아가는 반대 반향)
2. $L_w = 1$ L은 점광원 위치
$L_w = 0$ 경우 S가 $S_{dir}$ 같고, $L_w = 1$ 경우 S가 $S_{point}$ 변경
범용 그림자 행렬을 생성 해주는 예제 코드
//둘째 매개변수를 0으로 넣으면 평행광 그림자 행렬 리턴
//1로 넣으면 점광 그림자 행렬 리턴
inline XMMATRIX XM_CALLCONV XMMatrixShadow
(
FXMVECTOR ShadowPlane,
FXMVECTOR LightPosition
)
스텐실 버퍼를 이용한 이중 혼합 방지
물체의 기하구조를 평면에 평평하게 투영 시 둘이상의 투영된 삼각형의 겹쳐서 렌더링 되는 현상(이중 혼합)
삼각형들이 겹치는 영역에는 더 어둡게 표현
이중 혼합 방지 방법
1. 그림자를 렌더링 할 시텐슬 버퍼 픽셀 0으로 설정,
2. 스텐실 버퍼 항목 0인 픽셀만 판정에 성공 시 스텐실 1값 설정
이미 렌더링 영역( 스텐실 버퍼에 값 1로 표시된 영역)에는 픽셀 들이 겹쳐 그려지지 않으며, 따라서 이중 혼합 방지
평면 그림자 구현 코드
//그림자 재질 설정, 50% 투명한 검은색
auto shadowMat = std::make_unique<Material>();
shadowMat->Name = "shadowMat";
shadowMat->MatCBIndex = 4;
shadowMat->DiffuseSrvHeapIndex = 3;
shadowMat->DiffuseAlbedo = XMFLOAT4(0.0f, 0.0f, 0.0f, 0.5f);
shadowMat->FresnelR0 = XMFLOAT3(0.001f, 0.001f, 0.001f);
shadowMat->Roughness = 0.0f;
void StencilApp::BuildPSOs()
{
//이중 혼합 방지 위해 PSO 깊이 스텐길 상태 설정
//투명도 혼합 적용 후 그림자 렌더링
D3D12_DEPTH_STENCIL_DESC shadowDSS;
shadowDSS.DepthEnable = true;
shadowDSS.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
shadowDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
shadowDSS.StencilEnable = true;
shadowDSS.StencilReadMask = 0xff;
shadowDSS.StencilWriteMask = 0xff;
shadowDSS.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
shadowDSS.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
shadowDSS.FrontFace.StencilPassOp = D3D12_STENCIL_OP_INCR; //1 증가
shadowDSS.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL; // 0인지 판단
}
void StencilApp::Draw(const GameTimer& gt)
{
//그림자 렌더링
mCommandList->OMSetStencilRef(0);
mCommandList->SetPipelineState(mPSOs["shadow"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Shadow]);
}
void StencilApp::OnKeyboardInput(const GameTimer& gt)
{
// Update shadow world matrix.
XMVECTOR shadowPlane = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); // xz plane
XMVECTOR toMainLight = -XMLoadFloat3(&mMainPassCB.Lights[0].Direction);
XMMATRIX S = XMMatrixShadow(shadowPlane, toMainLight);
//y축 0.001f 이동 후 렌더링
XMMATRIX shadowOffsetY = XMMatrixTranslation(0.0f, 0.001f, 0.0f);
XMStoreFloat4x4(&mShadowedSkullRitem->World, skullWorld * S * shadowOffsetY);
}
그림자를 y축 : 0.001f 이동 시켜서 렌더링
평면 과 그림자 z 경쟁 문제 때문에 y 축을 증가 후 렌더링 한다
깊이 버퍼의 정밀도 한계 때문에 바닥 메시 픽셀와 그림자 픽셀들이 자리를 다투면서 깜빡거리는 현상이 발생
참고 및 내용 인용
프랭크 D 루나 지음, 류광 옮김
한빛 미디어
Directx 12를 이용한 3D 게임 프로그래밍 입문(2017)
'Directx12 3D 프로그래밍 입문 > 11.스텐실 적용' 카테고리의 다른 글
스텐실 적용 - 연습 문제 8~12 (0) | 2023.12.24 |
---|---|
11.6 요약 및 연습문제 (1) | 2023.12.06 |
11.4 평면거울 구현 (1) | 2023.11.14 |
11. 1 ~3 깊이 스텐실 버퍼 자료, (0) | 2023.11.07 |