DirectX 11 3d

230531 Frustum Culling, 평면의 방정식

슬뷔 2023. 6. 4. 03:16

1. 랜더링 최적화 (이전에 안 한 것..) Frustum Culling

컬링 작업이 랜더링 파이프라인에서 레스터라이저에 의해서 발생한다. 

NDC 좌표계에 안 들어오는 정점들을 버린다.

그런데 NDC 좌표계 안 들어왔다고 해서 무조건 다 버리는 것은 아니다.

(정점이 위와 같이 찍혀 삼각형을 이루고 NDC 좌표계가 사각형이라면, 둘이 겹치는 곳을 색칠하였다.)

정점 3개 중 전부 다 NDC 좌표와 겹치는지 아닌지 검사를 해서 일부라도 팻치가 들어오는지 레스터라이저에서 판단을 해야한다. 3개 다 들어오지 않는 경우, 다 버리면 된다.

 

정점 3개가 다 NDC 좌표계에 들어왔더라도 반시계로 들어왔다면, cull_back 모드를 사용하고 있다는 전제하에 뒷면이라고 생각하고 컬링을 하면 된다.

 

Frustum Culling은 랜더파이프라인에서 작동하는게 아니라(GPU) 우리가 직접 CPU로 자체적으로 컬링하는 전처리 작업이다.

object를 감싸는 어떤 bounding box, 혹은 sphere를 만들어서 이 object자체가 frustum내에 들어가는지 아닌지 CPU Stage에서 검사를 해서 view frustum에서 걸러지는 object들은 애초에 GPU로 보내지 않는 것이다.

GPU로 들어가는 카메라와 카메라가 보여지는 영역에 대한 정보는 CPU에서 알고있기 때문이다.

 

view frustum 밖에 있는 것을 CPU에서 culling 처리를 하지 않고, 파이프라인으로 보낸다고 해서 그려지는건 아니다.

버텍스 쉐이더에서 투영 변환 수행 후에 clip space가 생기게 되는데, 이 clip space에서 view frustum 밖에 있는것은 모두 클리핑해주긴 한다.

그러나 GPU성능을 극대화 하기 위해서는 CPU도 어느정도는 써줘야 한다.

 

View Frustum culling기법은 기본적으로 Object를 감싸는bounding volume을 구성하여 View frustum 절두체가 해당 bounding volume과 교차하는지 매 프레임마다 검사하여 교차한다면 GPU로 보내고, 아니라면 CPU에서 GPU로 보내지 않는다.

 

절두체 도형을 이용해서 카메라 시야를 도형으로 표현한다. (내 시야 범위를 도식화 한다)

뷰스페이스라고 생각하고, 카메라를 원점이라고 한다면 카메라가 보는 시야를 절두체 도형으로 표현할 수 있다.

물체가 절두체 도형과 충돌을 했다면 안에 잡히고 있는 것이고, 충돌하지 않았다면 물체는 절두체 안에 있지 않았다는 의미이다.

 

* 3차원에서 평면의 방정식 (내적 개념이 많이 들어가 있다)

참고 : https://blog.naver.com/bswbsw0131/222998330355

 

공간에서 벡터를 이용해 평면의 방정식 나타내기

좌표평면은 하나의 평면(ex. xy평면) 만을 나타내고, 그 평면에서 영역을 정해 구분한다. 좌표공간에서는 ...

blog.naver.com

 

참고 : https://blog.naver.com/netuee/222390503917

 

평면의 방정식, 절두체 컬링

노션에 작성한 내용을 블로그에 옮겼습니다 노션 : https://www.notion.so/201713053-7-4f2dd60342b7406bac...

blog.naver.com

참고 : https://velog.io/@ounols/%EA%B2%8C%EC%9E%84-%EC%88%98%ED%95%99-%EC%A0%88%EB%91%90%EC%B2%B4-%EC%BB%AC%EB%A7%81

 

[게임 수학] 절두체 컬링의 원리

🧐 해당 파트는 게임 개발 환경을 구성하는 컴퓨터 그래픽스(Computer Graphics)를 이해하기 위한 기초 수학의 간단한 개념에 대해 설명하고 있습니다!혹여나 이해가 잘 안되거나 잘못된 정보를 발

velog.io

참고 : https://velog.io/@ounols/%EA%B2%8C%EC%9E%84-%EC%88%98%ED%95%99-%ED%8F%89%EB%A9%B4%EC%9D%98-%EB%B0%A9%EC%A0%95%EC%8B%9D

 

[게임 수학] 평면의 방정식

🧐 해당 파트는 게임 개발 환경을 구성하는 컴퓨터 그래픽스(Computer Graphics)를 이해하기 위한 기초 수학의 간단한 개념에 대해 설명하고 있습니다!혹여나 이해가 잘 안되거나 잘못된 정보를 발

velog.io

 

절두체 컬링에 왜 평면의 방정식이 필요할까 ? (정확히 잘 모르겠음 찾아보기)

임의의 점이 있고, 평면에 수직한 법선벡터(a, b,c)가 있을 때, 임의의 점이 평면 위에 있는지 테스트

법선벡터랑 임의의 점을 내적시켜서 d가 일치하면 평면 위에 있는 점이라는게 된다.

 

절두체를 구성하는 시야로 평면을 만들었을 때, 총 6개의 면이 있고 6개의 면을 평면의 방정식으로 만들어서 법선벡터를 전부 바깥을 향하게 만든다.

6개 전부 테스트를 했을 때, 전부 다 d보다 작다라고 나와야 비로소 그 점이 절두체 안에 있다고 판정이 된다.

수학적인 개념말고 코딩에서는 하나의 평면을 정의하려면 a b c d 만 있으면 된다.

 

ax+by+cz+d=0

 

a b c 는 만드려고 하는 평면에 수직한 법선벡터 (x y z)

d는 이 평면에 임의의 점을 내적했을 때 비교판정을 하기 위한 원점에서 평면까지의 최단거리 (w)

 

즉, vec4가 평면 하나이다.

 

2. Frustum (컴포넌트는 아니고 절두체라는 개념을 생성)

절두체를 카메라에 붙여서, 카메라가 시야범위정보를 넘기면 절두체가 이를 이용해서 6개의 평면으로 절두체가 하나 생성이 된다.

카메라 컴포넌트에도 Frustum 을 갖게 한다.

 

카메라가 내가 바라보는 방향으로 이동하고 실시간으로 움직이기 때문에, Frustum 을 매 프레임마다 만들어내야한다.

현재 바라보는 시점으로 평면 6개를 만들어내야한다.

 

Camera의 finaltick에서 모든 연산이 끝나고 마지막에 m_Frustum.finaltick을 하게 한다.

Frustum 이 카메라에 접근해서 카메라 정보를 읽어들여서 카메라의 시야정보를 Frustum 으로 만들어낸다.

 

6개 평면을 만들어서 테스트를 해야된다..

임의의 한 점과 평면에 수직하는 법선벡터(노말벡터)를 내적시켜서 d값과 일치해야 평면 위에 있는 점이고,

d값보다 작거나 크면 평면을 기준으로 공간을 나누어 어디에 있는지 따질 수 있다.

절두체를 구성하고 있는 면을 평면의 방정식으로 만들어서 하나의 점이 이 안에 들어있는지 따질 수 있는 판정조건을 만들려면, 카메라가 보고있는 시야범위를 6개의 평면으로 만드는 작업을 해야하는데 이걸 할 수가 없다..

 

그래서 생각을 역발상해서 쉬운?방법으로 간다..!

 

월드상의 물체를 좌표변환 할 때, 월드에 있는 카메라를 원점 기준으로 해서 뷰스페이스로 또 새로운 좌표계가 만들어진다.

그리고 투영시켜 NDC 공간으로 간다.

 

어떤 물체가 카메라 시야에 안 들어왔다면 좌표 변환을 통해서 뷰스페이스에 왔는데 NDC 공간 투영을 했는데 밖에 있었다는 것이다. -1 ~ 1 범위에 안 들어온다,

니어에서 파에 있던게 압축돼서 0 ~ 1 사이로 들어온다.

최종 투영공간은 x y 는 -1 ~ 1 (길이 2) z로는 0 ~ 1 (길이1) 인 NDC 공간이 나온다.

박스가 투영공간인데 역으로 생각해서 뷰스페이스로 가면 뒷면의 모서리점이 far 끝으로 가고 앞면의 모서리점은 near로 가게 된다. 

즉 박스의 8개의 점이 뷰스페이스상의 near 와 far 의 점이 될 것이다. (아래 그림 파란색)

뷰스페이스상의 8개 점을 다시 월드행렬로 보내면, 월드 공간상에서 카메라가 쳐다보고 있던 8개의 점이 나오게 된다.

 

어차피 필요로 하는건 월드공간상의 8개의 점이다!!

월드상에서 카메라가 쳐다보는 시야범위로 평면을 만들어내야한다.

why? 물체들은 월드상에서 transform 해서 월드기준으로 자신의 좌표를 갖고 있기 때문이다.

랜더링을 하기 위해 변환과정을 거쳐서 가는 것이지

실제 충돌 테스트나 모든 것들은 월드상에서 진행을 한다.

물체들은 월드상에 배치가 되어있으니까

 

class CCamera;

enum FACE_TYPE
{
    FT_NEAR,
    FT_FAR,
    FT_LEFT,
    FT_RIGHT,
    FT_TOP,
    FT_BOT,
    FT_END,
};

class CFrustum :
    public CEntity
{
private:
    CCamera*    m_pOwner; // 카메라의 정보를 가져와야하기 때문에
    Vec4        m_arrFace[FT_END];
    Vec3        m_arrProj[8];

public:
    void finaltick();

public:
    CLONE(CFrustum);
    CFrustum(CCamera* _pOwner);
    ~CFrustum();
};

Vec3        m_arrProj[8]; => 투영행렬 기준으로 최대 범위를 좌표로 정할 것이다.

 

CFrustum::CFrustum(CCamera* _pOwner)
	: m_pOwner(_pOwner)
	, m_arrFace{}
{
	  투영공간 좌표
      
	      4 ------ 5
	      |        |  Far
	    / |        |
           /  7 ------ 6	
	  /      /
	 0 -- 1     /
	 |    |    / Near
	 3 -- 2
     
	m_arrProj[0] = Vec3(-1.f, 1.f, 0.f);
	m_arrProj[1] = Vec3(1.f, 1.f, 0.f);
	m_arrProj[2] = Vec3(1.f, -1.f, 0.f);
	m_arrProj[3] = Vec3(-1.f, -1.f, 0.f);

	m_arrProj[4] = Vec3(-1.f, 1.f, 1.f);
	m_arrProj[5] = Vec3(1.f, 1.f, 1.f);
	m_arrProj[6] = Vec3(1.f, -1.f, 1.f);
	m_arrProj[7] = Vec3(-1.f, -1.f, 1.f);
}

투영좌표 기준으로 되돌아가야하니까 수정되거나 바뀌면 안된다. (항상 고정값)

월드좌표를 알아내기 위해서 역행렬을 곱한다.

투영행렬 역행렬 * 투영행렬 하면 뷰스페이스 공간의 좌표가 나오고, 

뷰스페이스 역행렬 * 뷰스페이스 하면 월드 공간의 좌표가 나온다..!

void CFrustum::finaltick()
{
	Vec3 vWorldPos[8] = {}; // 월드 포즈 8개 가져올거 준비

	Matrix matInv = m_pOwner->GetProjMatInv() * m_pOwner->GetViewMatInv();

	for (int i = 0; i < 8; ++i)
	{
		vWorldPos[i] = XMVector3TransformCoord(m_arrProj[i], matInv);		
	}
		
        // XMPlaneFromPoints = 점들로 평면을 만들어주는 함수
	m_arrFace[FT_NEAR]	= XMPlaneFromPoints(vWorldPos[0], vWorldPos[1], vWorldPos[2]);
	m_arrFace[FT_FAR]	= XMPlaneFromPoints(vWorldPos[7], vWorldPos[6], vWorldPos[5]);
	m_arrFace[FT_LEFT]      = XMPlaneFromPoints(vWorldPos[4], vWorldPos[0], vWorldPos[7]);
	m_arrFace[FT_RIGHT]     = XMPlaneFromPoints(vWorldPos[1], vWorldPos[5], vWorldPos[6]);
	m_arrFace[FT_TOP]	= XMPlaneFromPoints(vWorldPos[4], vWorldPos[5], vWorldPos[1]);
	m_arrFace[FT_BOT]	= XMPlaneFromPoints(vWorldPos[2], vWorldPos[6], vWorldPos[7]); 
}