ftftgop3 2024. 1. 20. 15:52

기하 셰이더 

실행 시점 : 정점 셰이더와 픽셀 셰이더 단계 사이에서 동작

입력 값 : 온전한 기본 도형을 입력

역할 : 기하 구조를 새로 생성 및 파괴

출력 : 다른 종류의 기본 도형을 기하 셰이더가 출력 

출력 하는 기본 도형들은 정점 목록으로 정의 되고, 해당 정점목록은 동차 절단 공간에 존재

 

 

기하 셰이더 프로그래밍

기하 셰이더의 일반적인 틀

[maxvertexcount(4)]
void GS(point VertexOut gin[1], 
        uint primID : SV_PrimitiveID, 
        inout TriangleStream<GeoOut> triStream)
{
	//기하 셰이더 함수 본문


}

[Maxvertexcount(N)] : 한 실행에서 출력할 정점들의 최대 개수, 최대 정점 개수 특성 지정

point VertexOut gin[1] : 입력 기본 도형 점일 때 사용,

점, 띠, 삼각형, 인접성 정보를 가신 선, 인접성을 가진 목록 및 띠 중에 골라서 입력 기본 도형을 정한다

 

출력 매개 변수 

 inout : 출력 매개 변수로 기본 도형 정점을 스트림 형식으로 출력

스트림 형식들은 템플릿 형태로 구분, 점, 라인, 삼각형 목록

Pointstream<OutputVertexType>, Linestream<OutputVertexType> , triangleStream <OutputVertexType>

 

선과 삼각형의 경우 출력 기본 도형은 항상 띠 인데 RestartStrip() 메서드를 사용하면 선 목록이나 삼각형 목록을 표현

void StreamOutputObject< OutputVertexType>::ReStartStrip();

예시)

//최대 4개의 정점 출력
//입력 기본 도형은 선, 출력 기본 도형은 삼각형 띠
[maxvertexcount(4)]
void GS(Line VertexOut gin[2], 
        uint primID : SV_PrimitiveID, 
        inout TriangleStream<GeoOut> triStream)
{
	//기하 셰이더 함수 본문
}
//최대 32개의 정점 출력
//입력 기본 도형은 삼각형, 출력 기본 도형은 삼각형 띠
[maxvertexcount(32)]
void GS(triangle VertexOut gin[3], 
        uint primID : SV_PrimitiveID, 
        inout TriangleStream<GeoOut> triStream)
{
	//기하 셰이더 함수 본문
}
//최대 4개의 정점 출력
//입력 기본 도형은 점, 출력 기본 도형은 삼각형 띠
[maxvertexcount(4)]
void GS(point VertexOut gin[1], 
        uint primID : SV_PrimitiveID, 
        inout TriangleStream<GeoOut> triStream)
{
	//기하 셰이더 함수 본문
}

 

Append 메서드, RestartStrip 메서드 용법  예시

입력 된 삼각형을 같은 크기의 작은 삼각형 네개로 세분( subdivision) 네개의 삼각형 출력

struct VertexOut
{
    float3 PosL : POSITION;
    float3 NormalL : NORMAL;
    float2 Tex   : TEXCOORD;
};

struct GeoOut
{
    float4 PosH    : SV_POSITION;
    float3 PosW    : POSITION;
    float3 NormalW : NORMAL;
    float2 Tex     : TEXCOORD;
    float  FogLerp : FOG;
};

void Subdivide(VectexOut inverts[3], out VertexOut outVerts[6])
{
	//       v1
	//       *
	//      / \
	//     /   \
	//  m0*-----*m1
	//   / \   / \
	//  /   \ /   \
	// *-----*-----*
	// v0    m2     v2

	VertexOut m[3];

	//각 변의 중점 계산
	m[0].PosL = 0.5f * (inverts[0].PosL + inVerts[1].PosL);
	m[1].PosL = 0.5f * (inverts[1].PosL + inVerts[2].PosL);
	m[2].PosL = 0.5f * (inverts[2].PosL + inVerts[0].PosL);
	//단위 벡터로 변경
	m[0].PosL = normalize(m[0].PosL);
	m[1].PosL = normalize(m[1].PosL);
	m[2].PosL = normalize(m[2].PosL);
	//노말 지정
	m[0].NormalL = m[0].PosL;
	m[1].NormalL = m[1].PosL;
	m[2].NormalL = m[2].PosL;

	//텍스처 좌표 보간
	m[0].Tex = 0.5f * (inverts[0].Tex + inVerts[1].Tex);
	m[1].Tex = 0.5f * (inverts[1].Tex + inVerts[2].Tex);
	m[2].Tex = 0.5f * (inverts[2].Tex + inVerts[0].Tex);

	OutVerts[0] = inVerts[0];
	OutVerts[1] = m[0];
	OutVerts[2] = m[2];
	OutVerts[3] = m[1];
	OutVerts[4] = inVerts[2];
	OutVerts[5] = inVerts[1];	
}
void OutputSubdivision(VertexOut v[6],
	inout TriangleStream<GeoOut> triStream)
{
	Geoout gout[6];
	[unroll]
	for (int i = 0; i < 6; ++i)
	{
		//세계 공간으로 변환
		gout[i].PosW = mul(float4(v[i].PosL, 1.0f), gWorld).xyz;
		gout[i].NormalW = mul(v[i].NormlL, (float3x3) gWorldInTranspose);

		//동차 절단 공간으로 변환
		gout[i].PosH = mul((float4(v[i].PosL, 1.0f), gWorldViewProj);
		gout[i].Tex = v[i].Tex;
	}

	//       v1
	//       *
	//      / \
	//     /   \
	//  m0*-----*m1
	//   / \   / \
	//  /   \ /   \
	// *-----*-----*
	// v0    m2     v2
	//세분된 삼각형들을 두개의 띠로 그린다
    //띠 1: 아래쪽 삼각형 세 개
    //띠 2: 위쪽 삼각형 하나
	[unroll]
	for (int j = 0; j < 5; ++J)
	{
		triStream.Append(gout[j]);
	}
	triStream.RestartStrip();
	triStream.Append(gout[1]);
	triStream.Append(gout[5]);
	triStream.Append(gout[3]);
}
[maxvertexcount(8)]
void GS(triangle VertexOut gin[3], inout TriangleStream<GeoOut>)
{
	VertexOut v[6];
	Subdivide(gin, v);
	OutputSubdivision(v, triStream);
}

triStream 에는 총 8개의 정점이 만들어 지고 동작 

GS 함수에서 입력 -> 분할 -> 출력을 거친다

 

기하 셰이더 컴파일 방법 및 PSO 포함 시키는 방법

void TreeBillboardsApp::BuildShadersAndInputLayouts()
{
	//...
	mShaders["treeSpriteGS"] = d3dUtil::CompileShader
	(L"Shaders\\TreeSprite.hlsl", nullptr, "GS", "gs_5_0");
	//...
}

void TreeBillboardsApp::BuildPSOs()
{
//...
D3D12_GRAPHICS_PIPELINE_STATE_DESC treeSpritePsoDesc = opaquePsoDesc;
treeSpritePsoDesc.GS =
	{
		reinterpret_cast<BYTE*>(mShaders["treeSpriteGS"]->GetBufferPointer()),
		mShaders["treeSpriteGS"]->GetBufferSize()
	};
//...
}

 참고

기하 셰이더에서 아무것도 출력 하지 않으면 파괴, 하나의 기본도형을 완성 하기 충분한 개수의 정점을 출력 하면 

미완성된 기본 도형을 폐기

 

참고 및 내용 인용
프랭크 D 루나 지음, 류광 옮김 
한빛 미디어
Directx 12를 이용한 3D 게임 프로그래밍 입문(2017)