(DirectX : Basic) 26. Frustum Culling

Posted by : at

Category : DirectX



지금까지는 Mesh를 만들기만 하면 무조건 그리고 있다.
지금부터 frustum culling을 통해 그릴놈 안그릴놈 구분해주는 기능이라 생각하면 되겠다.


일단 3차원 평면의 식을 아래와 같이 정의해보자.

ax + by + cz + d = 0

위 3차원 평면의 Normal Vector(평면에 수직인 벡터)는 N(a, b, c)가 되며
d의 경우 원점에서 평면까지의 거리가 된다.(이건 수학적인 부분이기에 받아들일 것)
증명을 해보자면 한 평면위의 두 점 A(x1, y1, z1), B(x2, y2, z2)과 Normal Vector N을 내적시 0이 되면 N(a, b, c)가 Normal Vector임이 증명이 된다.
AB = (x2 - x1, y2 - y1, z2 - z1), N(a, b, c)를 내적해보자.

AB * N = a(x2 - x1) + b(y2 - y1) + c(z2 - z1)
AB * N = (ax2+by2+cz2) - (ax1+by1+cz1)
// ax2+by2+cz2+d = 0
// ax1+by1+cz1+d = 0
// 이기에
// ax2+by2+cz2 = -d
// ax1+by1+cz1 = -d
AB * N = 0      // 증명 끝!

갑자기 이건 왜 한걸까??
특정 평면을 기준으로 평면보다 멀리 혹은 가까이 있는지를 판별하기 위해서이다.

뭔소린가?? 좀 더 자세히 설명해 보자면

    \
     A1(x, y, z)
      \
     / \
  d /   \
   /     \
 O        \
           \

OA1 * N (벡터 OA1과 Normal Vector N의 내적)은 d(평면과 O과의 거리)가 된다
그럼 평면위의 점이 아니라 좀 더 가까운 점 A2의 내적은?

        \
  A2(x, y z)
          \
           \
            \
             \
     O        \
               \

ab + by + cz + d < 0이 되게 된다.
만약 평면보다 멀다면 ab + by + cz + d > 0


이제 카메라뷰의 각 평면(6개)의 Normal Vector를 구한 후 내적해서 0보다 큰지 작은지 구하면 된다.

그러면 그려야 할지 말지 결정을 위해 Projection -> View -> World로 변환이 필요하다
Why? 실제로 Projection까지 가야 카메라에 나오는 부분, 안나오는 부분이 분별이 가능하기에

#pragma once

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

	PLANE_END
};

class Frustum
{
public:
	void FinalUpdate();

    // 그려할 평면 내부에 있는지 확인
	bool ContainsSphere(const Vec3& pos, float radius);

private:
	array<Vec4, PLANE_END> _planes;
};
#include "pch.h"
#include "Frustum.h"
#include "Camera.h"

void Frustum::FinalUpdate()
{
	// Projection -> View -> World 을 해야하기에
	// View Inverse Matrix, Projection Inverse Matrix가 필요하다
	Matrix matViewInv = Camera::S_MatView.Invert();
	Matrix matProjectionInv = Camera::S_MatProjection.Invert();
	Matrix matInv = matProjectionInv * matViewInv;

	vector<Vec3> worldPos =
	{
		::XMVector3TransformCoord(Vec3(-1.f, 1.f, 0.f), matInv),		// 0
		::XMVector3TransformCoord(Vec3(1.f, 1.f, 0.f), matInv),			// 1
		::XMVector3TransformCoord(Vec3(1.f, -1.f, 0.f), matInv),		// 2
		::XMVector3TransformCoord(Vec3(-1.f, -1.f, 0.f), matInv),		// 3
		::XMVector3TransformCoord(Vec3(-1.f, 1.f, 1.f), matInv),		// 4
		::XMVector3TransformCoord(Vec3(1.f, 1.f, 1.f), matInv),			// 5
		::XMVector3TransformCoord(Vec3(1.f, -1.f, 1.f), matInv),		// 6
		::XMVector3TransformCoord(Vec3(-1.f, -1.f, 1.f), matInv)		// 7

		/*
		   (4)------------(5)
		     /           /
            /		    /
		(0)------------ (1)
		   |           |  /(6)
		   |           | /
		   |           |/
	    (3)------------(2)

		*/
	};

    // 평면을 생성 : 점 세개를 넘긴다
	_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]);
}

bool Frustum::ContainsSphere(const Vec3& pos, float radius)
{
	for (const Vec4& plane : _planes)
	{
		// n = (a, b, c)
		Vec3 normal = Vec3(plane.x, plane.y, plane.z);

		// ax + by + cz + d > radius
		if (normal.Dot(pos) + plane.w > radius)
			return false;
	}

	return true;
}
void Camera::Render()
{
	shared_ptr<Scene> scene = GET_SINGLE(SceneManager)->GetActiveScene();

	// TODO : Layer 구분
	const vector<shared_ptr<GameObject>>& gameObjects = scene->GetGameObjects();

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

        // Frustum을 적용하지 말아야 할 object도 존재한다(skybox)
		if (gameObject->GetCheckFrustum())
		{
            // 랜더링 할지 말지 결정
			if (_frustum.ContainsSphere(
				gameObject->GetTransform()->GetWorldPosition(),
				gameObject->GetTransform()->GetBoundingSphereRadius()) == false)
			{
				continue;
			}
		}

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

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links