오늘은 빌보드에 대해 복습하고 예제 코드를 분석해보자

빌보드는 오브젝트가 항상 플레이어를 바라보도록 하는 것이다. 이전에 빌보드는 구현할 때 여러 오브젝트를 생성 할때

정점 4개를 두고 VS단계에서 고쳐주고 늘리면서 사용하게 했다. 이 방법보다 더 나은 방법이 있는데 바로 Geometry Shader를 사용하는 방법이다.

https://learn.microsoft.com/ko-kr/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline

 

Pipeline 중간 아래쪽에 보면 Geometry Shader가 있다. 이 단계의 역할은 정점을 인위적으로 늘리고 줄일 수 있는 정점 

분리기이다. 이전에 4개를 복사하던 것에서 1개를 두고 GS단계에서 고쳐주고 늘려주면 되는 것이다. 

코드를 살펴보기 전에 예제를 먼저 실행해보자.

 

기존에 나무가 추가되어 있는 화면에서 나무가 추가되어있는 모습을 볼 수 있다. 이 나무 하나하나가 모두 빌보드인 것이다. 사각형에 2D를 그려서 이 사각혀잉 플레이어를 바라보게 만들어 준 것이다.

코드를 살펴보자

우선 정점을 만드는 부분을 살펴보자면 TreePointSprite라는 위치와 크기만을 가진 구조체로 값을 받아주고 있으며 

struct TreePointSprite
{
	XMFLOAT3 pos;
	XMFLOAT2 size;
};

 

이를 랜덤하게 배치하지만 언덕위에만 배치할 수 있도록 x y z 값을 설정해준다. 이 정점을 설정해준 개수 만큼 만들어주고 버퍼를 통해 쉐이더에 전달해준다. 정점하나당 나무 하나가 만들어 지는 것이다. 이 정점 하나를 GS단계에서 동적으로 여러 정점으로 

void TreeBillboardDemo::BuildTreeSpritesBuffer()
{
	Vertex::TreePointSprite v[TreeCount];

	for (UINT i = 0; i < TreeCount; ++i)
	{
		float x = MathHelper::RandF(-35.0f, 35.0f);
		float z = MathHelper::RandF(-35.0f, 35.0f);
		float y = GetHillHeight(x, z);

		// Move tree slightly above land height.
		y += 10.0f;

		v[i].pos = XMFLOAT3(x, y, z);
		v[i].size = XMFLOAT2(24.0f, 24.0f);
	}

	D3D11_BUFFER_DESC vbd;
	vbd.Usage = D3D11_USAGE_IMMUTABLE;
	vbd.ByteWidth = sizeof(Vertex::TreePointSprite) * TreeCount;
	vbd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
	vbd.CPUAccessFlags = 0;
	vbd.MiscFlags = 0;
	D3D11_SUBRESOURCE_DATA vinitData;
	vinitData.pSysMem = v;
	HR(_device->CreateBuffer(&vbd, &vinitData, _treeSpritesVB.GetAddressOf()));
}

 

 

코드를 보면 기존의 VS단계에서 PS단계로 바로 넘어가는 것이 아니라 GS단계를 이제 거치는 것을 볼 수 있다. GS단계에서 정점을 4개로 불려주고 이 값을 PS단계로 전달하게 된다.

struct GeoOut
{
	float4 PosH    : SV_POSITION;
    float3 PosW    : POSITION;
    float3 NormalW : NORMAL;
    float2 Tex     : TEXCOORD;
    uint   PrimID  : SV_PrimitiveID;
};

 // 최대 정점4개
[maxvertexcount(4)]
//정점 하나가 들어옴	도형의 넘버링	입력받은 값을 연산하고 저장해줄 곳
void GS(point VertexOut gin[1],  uint primID : SV_PrimitiveID, inout TriangleStream<GeoOut> triStream)
{	
	//
	// 플레이어를 바라보게 
	//

	float3 up = float3(0.0f, 1.0f, 0.0f);
	float3 look = gEyePosW - gin[0].CenterW;
	look.y = 0.0f; // y-axis aligned, so project to xz-plane
	look = normalize(look);
	float3 right = cross(up, look);

	//
	// 4개 정점 만들기
	//
	float halfWidth  = 0.5f*gin[0].SizeW.x;
	float halfHeight = 0.5f*gin[0].SizeW.y;
	
	float4 v[4];
	v[0] = float4(gin[0].CenterW + halfWidth*right - halfHeight*up, 1.0f);
	v[1] = float4(gin[0].CenterW + halfWidth*right + halfHeight*up, 1.0f);
	v[2] = float4(gin[0].CenterW - halfWidth*right - halfHeight*up, 1.0f);
	v[3] = float4(gin[0].CenterW - halfWidth*right + halfHeight*up, 1.0f);

	//
	// 만든 정점을 결과값 저장하는 곳에 밀어넣기
	//
	GeoOut gout;
	[unroll]
	for(int i = 0; i < 4; ++i)
	{
		gout.PosH     = mul(v[i], gViewProj);
		gout.PosW     = v[i].xyz;
		gout.NormalW  = look;
		gout.Tex      = gTexC[i];
		gout.PrimID   = primID;
		
		triStream.Append(gout);
	}
}

 

굳이 정점을 이렇게 연산해서 넘겨주는 이유는 4개를 넘겨주는 것보다는 1개를 가지고 연산하는 것이 비용을 아껴줄 수 있기 때문이다.

+ Recent posts