Frustum Culling
- 기존엔 파이프라인 과정에서 래스터라이저가 물체가 그려질 범위 내에 존재하는지를 확인하여 렌더를 하지 않는 방식이었다.
- 하지만 이러한 방식은 그려지지 않을 물체들 또한 각종 자원을 사용하고 이는 GPU의 작업을 요구하며 파이프라인을 거치기 때문에 비효율적인 방법이다.
- 이를 해결하기 위해 CPU 단계에서 그려지지 않을 물체를 판별해준 뒤 GPU에게 넘겨주지 않는 방식으로 Culling을 해주며 이는 많은 성능향상을 이루어준다.
- Frustum Culling을 위한 방식은 다음과 같다.
- 만약 M이라는 점이 평면 위에 있다면 ax+by+cz+d = 0 이 될 것이고,
- ax+by+cz+d < 0 이라면 점이 평면보다 앞에, 즉 절두체 끝 범위보다 가까이 존재한다는 뜻이 되며
- ax+by+cz+d > 0 이라면 점이 평면보다 멀리 있다는 뜻이 된다.
- 절두체는 총 6면이 있기 때문에 한 정점이 ax+by+cz+d < 0를 총 6번 만족하게 된다면 절두체 내에 있다는 뜻이 될 것이고, 그렇지 않다면 외부에 있다는 뜻이 되기 때문에 이를 컬링 해준다.
- 절두체의 Projection -> World는 Projection 상태의 절두체를 생성해준 뒤 ProjectionInv와 ViewInv 행렬을 곱해주면 된다.
- 절두체를 생성할 때 각 면에서의 Normal Vector 방향을 고려해주어 CW나 CCW를 맞춰서 생성해주어야 한다.
<Frustum.h>
더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
#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;
};
|
cs |
- Projection Frustum을 생성하기 위한 PLANE_TYPE
- Projection Frustum을 생성해주는 함수인 FinalUpdate()
- 물체가 Frustum 내에 존재하는지 판별하기 위한 ContainSphere
- 절두체의 면을 관리하기 위한 _planes가 존재하는 Frustum class이다.
<Frustum.cpp>
더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
#include "pch.h"
#include "Frustum.h"
#include "Camera.h"
void Frustum::FinalUpdate()
{
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),
::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)
};
//절두체의 6면
_planes[PLANE_FRONT] = ::XMPlaneFromPoints(worldPos[0], worldPos[1], worldPos[2]); // CW
_planes[PLANE_BACK] = ::XMPlaneFromPoints(worldPos[4], worldPos[7], worldPos[5]); // CCW
_planes[PLANE_UP] = ::XMPlaneFromPoints(worldPos[4], worldPos[5], worldPos[1]); // CW
_planes[PLANE_DOWN] = ::XMPlaneFromPoints(worldPos[7], worldPos[3], worldPos[6]); // CCW
_planes[PLANE_LEFT] = ::XMPlaneFromPoints(worldPos[4], worldPos[0], worldPos[7]); // CW
_planes[PLANE_RIGHT] = ::XMPlaneFromPoints(worldPos[5], worldPos[6], worldPos[1]); // CCW
}
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;
}
|
cs |
- FinalUpdate() = Projection 좌표계에서의 절두체를 생성한 다음 World로 넘겨준 뒤 각 면을 생성해주는 함수
Projection -> World 변환을 하기 위한 행렬인 matInv를 구해주며
Projection 좌표계의 범위인 (-1 ~ 1)를 통해절두체의 범위를 지정해준 뒤 matInv를 곱해줌으로써 World 좌표계에서의 절두체로 변경시킨다.
이후 정점 세가지를 통해 면을 생성해주는데, 이때 앞면은 CW 뒷면은 CCW로 생성하는 등 생성 순서를 맞추어 Normal Vector의 방향을 맞춰준다. - ContainSphere() = 절두체 내에 물체가 존재하는지 판별해주는 함수.
절두체의 각 면을 확인하며 Normal Vector를 구해주고, ax+by+cz+d < r 을 만족하는지를 확인해준다.
이때 d가 아닌 r을 사용하는 이유는, 점이 아닌 물체를 판별하기 위한 복잡한 과정을 대신하기 위해서이다.
<Camera.cpp>
더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
#include "pch.h"
#include "Camera.h"
#include "Transform.h"
#include "Scene.h"
#include "SceneManager.h"
#include "GameObject.h"
#include "MeshRenderer.h"
#include "Engine.h"
Matrix Camera::S_MatView;
Matrix Camera::S_MatProjection;
Camera::Camera() : Component(COMPONENT_TYPE::CAMERA)
{
}
Camera::~Camera()
{
}
void Camera::FinalUpdate()
{
_matView = GetTransform()->GetLocalToWorldMatrix().Invert();
float width = static_cast<float>(GEngine->GetWindow().width);
float height = static_cast<float>(GEngine->GetWindow().height);
if (_type == PROJECTION_TYPE::PERSPECTIVE)
_matProjection = ::XMMatrixPerspectiveFovLH(_fov, width / height, _near, _far);
else
_matProjection = ::XMMatrixOrthographicLH(width * _scale, height * _scale, _near, _far);
S_MatView = _matView;
S_MatProjection = _matProjection;
_frustum.FinalUpdate();
}
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;
if (gameObject->GetCheckFrustum())
{
if (_frustum.ContainsSphere(
gameObject->GetTransform()->GetWorldPosition(),
gameObject->GetTransform()->GetBoundingSphereRadius()) == false)
{
continue;
}
}
gameObject->GetMeshRenderer()->Render();
}
}
|
cs |
- FInalUpdate() 시에 _frustum의 FinalUpdate또한 실행해주는 모습이 보이며
- Render()를 할 때에 GetCheckFrustum()을 통해 물체의 Frustum Culling 여부를 판별하고 (skybox등을 제외하기 위함), 만약 필요한 물체라면 ContainSphere을 통해 절두체 내에 존재하는지를 판별하여 존재하지 않는다면 Render를 Skip 해준다.
'Grahpics' 카테고리의 다른 글
[Graphics] DirectX12 - 다중 렌더 타겟 (Multiple Render Target) (0) | 2022.02.12 |
---|---|
[Graphics] DirectX12 - 직교 투영 (Orthographic) (0) | 2022.02.11 |
[Graphics] DirectX12 - SkyBox (0) | 2022.02.08 |
[Graphics] DirectX12 - Normal Mapping (0) | 2022.02.08 |
[Graphics] DirectX12 - Lighting (0) | 2022.02.07 |