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 해준다.

 

+ Recent posts