Frustum culling

ㅋㅋ·2022년 7월 25일
0

DirectX12강의

목록 보기
26/39

카메라의 절두체 영역 밖 물체들을 그릴 필요가 없기 때문에 CPU 단계에서 필터링하는 최적화 기술


평면

평면의 방정식

ax + by + cz + d = 0

normalize(a,b,c) = 노멀 벡터

평면 위의 점 A(x,y,z),B(x,y,z)A(x,y,z), B(x', y',z')

AB벡터=(xx,yy,zz)AB 벡터 = (x' - x, y' - y, z' - z)

=>

AB 벡터와 normal 벡터 내적 시 0이 나와야 함

a(xx)+b(yy)+c(zz)a(x' - x) + b(y' - y) + c(z' - z)

=(ax+by+cz)(ax+by+cz)= (ax'+by'+cz') - (ax+by+cz)

(ax'+by'+cz'), (ax+by+cz) => 평면 위이 점이기 때문에 -d를 만족

따라서 (d)(d)=0(-d) - (-d) = 0

d -> 원점에서 평면까지의 거리

(normal 벡터와 평면위의 점으로 가는 벡터와 내적 시 해당 점으로 향하는 벡터의 크기가 나옴)


특정 좌표가 ax + by + cz + d = 0을 만족하지 않을 시

해당 평면보다 앞이나 뒤의 점이라는 것을 알 수 있음

절두체가 가지는 6개의 평면을 가지고 물체들이 절두체 내부에 있는지 판단 가능

=>

물체들이 좌표를 가지고 있긴 하지만 실제 부피가 어떨지는 알 수 없음

평면에 일부가 걸쳐 있는 물체가 표현되지 않게 됨

=>

물체를 대략적으로 구체처럼 생겼다고 가정하고 r을 이용 (추후 좀더 세밀한 추정 필요)


평면을 구하기 위해서는 최소 3개의 점을 필요로 함

투영 공간에서 만들어질 절두체의 좌표를 역변환 행렬을 사용하여 월드 공간 좌표로 변환


Frustum 클래스

enum PLANE_TYPE : uint8
{
	PLANE_FRONT,
	PLANE_BACK,
	PLANE_UP,
	PLANE_DOWN,
	PLANE_LEFT,
	PLANE_RIGHT,

	PLANE_END
};

class Frustum
{
public:
	void Frustum::FinalUpdate();
	bool ContainsSphere(const Vec3& pos, float radius);
    
private:
	array<Vec4, PLANE_END> _planes;
};

FinalUpdate 함수가 불릴 시 절두체의 평면들이 어떻게 구성될지 계산

평면들을 가지고 특정 좌표가 절두체 내부에 있는지 판단하는 ContainsSphere 함수를 정의


void Frustum::FinalUpdate()
{
	...
    
	Matrix matInv = matProjectionInv * matViewInv;

	vector<Vec3> worldPos =
	{
		::XMVector3TransformCoord(Vec3(-1.f, 1.f, 0.f), matInv),
		::XMVector3TransformCoord(Vec3(1.f, 1.f, 0.f), matInv),
		::XMVector3TransformCoord(Vec3(1.f, -1.f, 0.f), matInv),
		::XMVector3TransformCoord(Vec3(-1.f, -1.f, 0.f), matInv),
		::XMVector3TransformCoord(Vec3(-1.f, 1.f, 1.f), matInv),
		::XMVector3TransformCoord(Vec3(1.f, 1.f, 1.f), matInv),
		::XMVector3TransformCoord(Vec3(1.f, -1.f, 1.f), matInv),
		::XMVector3TransformCoord(Vec3(-1.f, -1.f, 1.f), matInv)
	};

	_planes[PLANE_FRONT] = ::XMPlaneFromPoints(worldPos[0], worldPos[1], worldPos[2]);
	_planes[PLANE_BACK] = ::XMPlaneFromPoints(worldPos[4], worldPos[7], worldPos[5]);
	_planes[PLANE_UP] = ::XMPlaneFromPoints(worldPos[4], worldPos[5], worldPos[1]);
	_planes[PLANE_DOWN] = ::XMPlaneFromPoints(worldPos[7], worldPos[3], worldPos[6]);
	_planes[PLANE_LEFT] = ::XMPlaneFromPoints(worldPos[4], worldPos[0], worldPos[7]);
	_planes[PLANE_RIGHT] = ::XMPlaneFromPoints(worldPos[5], worldPos[6], worldPos[1]);
}

view space에서 wolrd space로 역변환 하는 행렬을 matInv에 저장

XMVector3TransformCoord 함수를 통하여 view space에서 절두체의 좌표들을 world로 변환

XMPlaneFromPoints 함수로 좌표 3개로 평면을 계산


bool Frustum::ContainsSphere(const Vec3& pos, float radius)
{
	for (const Vec4& plane : _planes)
	{
		...

		if (normal.Dot(pos) + plane.w > radius)
			return false;
	}

	return true;
}

ax + by + cz + d > radius 일 경우 평면 밖에 있기 때문에 false 반환


DX12에서 BoundingFrustum struct를 지원


camera 클래스 수정


void Camera::FinalUpdate()
{
	...

	_frustum.FinalUpdate();
}

void Camera::Render()
{
	...

	for (auto& gameObject : gameObjects)
	{
		if (gameObject->GetMeshRenderer() == nullptr)
			continue;

		if (gameObject->GetCheckFrustum())
		{
			if (_frustum.ContainsSphere(
				gameObject->GetTransform()->GetWorldPosition(),
				gameObject->GetTransform()->GetBoundingSphereRadius()) == false)
			{
				continue;
			}
		}

		gameObject->GetMeshRenderer()->Render();
	}
}

카메라가 frustum을 들고 프레임이 끝날 때마다 절두체의 평면을 계산

그리고 게임 오브젝트들을 렌더링할 때 체크하여

절두체의 내부를 만족하는 경우만 렌더

skybox의 경우 절두체 포함 여부와 상관 없이 렌더 되야 하기 때문에

GetCheckFrustum 시 false를 반환할 수 있도록 구현

0개의 댓글