절두체 컬링이란
절두체 컬링이란 절두체 안에 들어온 물체만 그리겠다는 뜻이다.
현재 색이 들어간 물체들은 모두 DrawCall을 호출하고 있다. PixelShader단계 까지 가지만 절두체 밖에 있는 물체들은 그려지지 않는다. '즉 모든 오브젝트가 DrawCall을 호출하기 때문에 절두체 안에 들어와 있는 녀석들만 DrawCall을 호출하게 하여 성능 향상을 도모 하겠다는 것'이다.
이렇게 절두체 밖에 있는 오브젝트들은 DrawCall을 아예 호출하지 못하도록 하는 것이다.
구현 방법
평면의 방정식과 NDC 공간의 개념만 알고 있으면 어렵지 않다.
우선 NDC상의 절두체를 선언해준다. 기억이 나나? 좌상단은 (-1, 1), 우하단은 (1, -1) 가운데가 (0, 0)이다.
이렇게 점 8개를 선언해둔다.
이후 해당점들에 대해 투영 행렬의 역행렬, 뷰 행렬의 역행렬을 곱해서 8개의 점을 월드 공간으로 이동시킨다.
왜 월드 공간으로 이동시키나? 모든 오브젝트들의 가장 낮은 레벨의 공통된 공간이 월드이기 때문이다. (계산하기 편하다는 장점도 있다)
월드 공간 까지 점 8개를 이동 시켰다면 어떤 물체(A)의 좌표 (x, y, z, 1)이 절두체 안에 있는지 밖에 있는지 확인해야한다.
이때 '평면의 방정식'이 사용된다.
평면의 방정식은 https://cjbworld.tistory.com/77 이전글에 설명이 있으니 참고하도록 하자.
즉 NavMesh의 높이를 구할 때 필요 했던 것 처럼 평면의 방정식을 통해 d = 거리 를 구하여 평면으로 부터 떨어진 거리가 양수라면 평면이 향하는 '법선 벡터' 방향으로 어느 위치에 물체가 있다는 뜻이다. 이 말은 절두체 밖에 있다는 것이다.
즉 평면의 방정식으로 구한 거리 d가 양수라면 절두체 밖에 음수라면 절두체 안에 있다는 것이다.
초록색 물체와 한 평면으로 부터 구한 평면의 방정식을 통해 거리 d를 구해주고 남은 5개의 평면에 대해서도 평면의 방정식을 통해 거리 d 를 다 구해준다.
이 값이 모두 음수라면 절두체안에 있는 것이고 하나라도 양수가 나온다면 절두체 밖에 있는 것이라 할 수 있다.
구현
m_vPoints[0] = _float3(-1.f, 1.f, 0.f);
m_vPoints[1] = _float3(1.f, 1.f, 0.f);
m_vPoints[2] = _float3(1.f, -1.f, 0.f);
m_vPoints[3] = _float3(-1.f, -1.f, 0.f);
m_vPoints[4] = _float3(-1.f, 1.f, 1.f);
m_vPoints[5] = _float3(1.f, 1.f, 1.f);
m_vPoints[6] = _float3(1.f, -1.f, 1.f);
m_vPoints[7] = _float3(-1.f, -1.f, 1.f);
이런식으로 NDC상의 점 8개를 선언해준다.
주의할 부분이 있다. NavMesh를 만들 때 처럼 모든 점을 시계 방향이나 반시계 방향으로 셋팅해주어야한다.
이에 대한 이유는 평면의 법선 벡터를 구할 때 XMPlaneFromPoints 함수를 사용하는데 들어가는 인자 순서에 따라 법선 벡터의 방향이 뒤집힐 수 있기 때문이다.
이런 점 8개에 투영 행렬의 역행렬, 뷰 행렬의 역행렬을 곱해준다.
for (_int i = 0; i < 8; ++i)
{
// View까지 이동
// Coord함수의 경우 역행렬을 곱하면 w곱하기를 수행해준다. Shader의 mul함수와 다르다.
XMStoreFloat3(&m_vPointsInWorld[i], XMVector3TransformCoord(XMLoadFloat3(&m_vPoints[i]), ProjInv));
// 월드까지 이동
XMStoreFloat3(&m_vPointsInWorld[i], XMVector3TransformCoord(XMLoadFloat3(&m_vPointsInWorld[i]), ViewInv));
}
위의 루프를 돌고나면 m_vPointsworld는 이제 월드 상의 점 8개를 나타내게 된다.
한가지 짚고갈 부분이 있다. XMVectorTransformCoord함수를 보자.
XMVector3TransformCoord(XMLoadFloat3(&m_vPoints[i]), ProjInv));
이렇게 투영의 행렬의 역행렬을 NDC상의 좌표에 XMVector3TransformCoord를 통해 곱했는데 NDC상의 좌표는 w나누기가 이루어진 좌표이다.
정상적으로 하려면 w를 곱한뒤 투영행렬의 역행렬을 곱해야 할거같지만 XMVector3TransformCoord가 친절히도 W를 곱해주고 투영행렬의 역행렬을 곱해준다.
그냥 투영행렬을 곱할 때도 마찬가지이다. W나누기를 수행하고 곱해준다.
이 사실을 알고 사용하는게 좋다.
쨋든 이렇게 변환을 수행하고 특정 좌표 vWorldPos를 매개 변수로 받는 함수를 만들어주도록 하자.
_bool CFrustum::IsInWorldSpace(_fvector vWorldPos, _float fRange)
{
for (_int i = 0; i < 6; ++i)
{
// a b c d
// x y z w(1)
// ax + by + cz + d = ? => 벡터의 dot 연산과 같다.
// - w 값 신용할 수 없다면 XMPlaneDot 를 쓴다.
if (fRange < XMVectorGetX(XMPlaneDotCoord(XMLoadFloat4(&m_vPlanesInWorld[i]), vWorldPos)))
return false;
}
return true;
}
m_vPlanesInWorld는 XMPlaneFromPoints함수를 통해 평면의 법선 벡터 (n)의 계수 a, b, c를 이미 담고 있다.
이 평면의 법선 벡터와 월드상의 어떤 쩜 vWorldPos를 평면의 방정식을 통해 구하는 과정은 내적과 동일 하기 때문에 XMPlaneDotCoord를 통해 거리 d를 계산 해준다.
여기서 이 거리 d가 양수라면 절두체 밖에, 음수라면 절두체 안에 있다고 할 수 있다.
'컴퓨터 그래픽스 > DirectX' 카테고리의 다른 글
NavMesh 만들기 (2) | 2024.12.07 |
---|---|
[Animation] 애니매이션을 만들어보기 (정리글) (1) | 2024.12.06 |
[Animation] 스키닝 애니매이션이란? (Skinning Animation) (1) | 2024.11.10 |
View Spcae Matrix 만들기 (0) | 2024.07.31 |
[DX] 렌더링 파이프 라인 (2) | 2023.11.30 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!