3 분 소요

기하 쉐이더(Geometry Shader,GS)

Image

기하 쉐이더는 VertexShaer와 PixelShader 사이에 존재하는 하나의 스테이지

  • 입력 받는 것 : Primitive(도형)
  • 출력 하는 것 : 수정된 Primitive
  • 해당 과정을 통해, 동적인 수정 or 일부를 제거하는 쉐이딩 단계이다

기하 정보와 관련된
VS ~ GS 를 별도로
Geometry Pipeline이라 퉁치기도 함

요점은 대략적인 Mesh를 받은 후

  • 고퀄리티 Mesh를 GPU에서 만듦(CPU -> GPU를 가볍게)
  • SubDivision 같은 기술을 이용하여 실시간 메시를 변형 가능

IA에서 이미 삼각형이 정점 3개가 필요하다는 사실을 알고 있기에
GS에 들어오는 것은 여러 정점 데이터가 한번에 들어올 수 있음
(Primitive)

구체적으로 하는 일? 사례?

  • 추가적인 생성
    Vertex 하나를 ‘사각형’으로 만들어줄 수 있음
    (추가적인 폴리곤 생성으로 더 부드럽거나 독특한 도형으로 변형)
  • LOD (Level of Detail)
    카메라와의 거리를 통해 삼각형을 늘리거나 줄임

유의할 점들

  • 동적인 Primitive 수정이 가능하지만
    종종 병목 현상의 원인이 되기도 함
    (최신 그래픽스의 경우, Compute Shader, Mesh Shader 등을 고려하는 편)

  • 한번의 GS 호출에서 출력하는 Vertex에 제한 존재
    (D3D11 : 약 1024)

  • 쉐이더를 GSSetShader로 등록 시,
    한번의 Draw 콜에서 ‘계속’ 쉐이더로 남아있기에
    다른 물체들이 GS를 사용하지 않는다면
    GSSetShader(nullptr,0,0) 같은 방식으로
    GS쉐이더의 사용을 해제해야 함

Render()
{
  context->GSSetConstantBuffers(0, 1, m_constantBuffer.GetAddressOf());
  context->GSSetShader(m_geometryShader.Get(), 0, 0);

  // draw Call...

  context->GSSetShader(nullptr, 0, 0);
}

이전 세부 단계는 나중에 다룰 예정
(Hull,Domain, Tessellator)

예제 - GS를 이용하여 정점을 삼각형으로 변환

Image

// Geometry-Shader Object
// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-geometry-shader

// Stream-Output Object
// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-so-type

cbuffer BillboardPointsConstantData : register(b0)
{
    float3 eyeWorld;
    float width;
    Matrix model; // For vertex shader
    Matrix view; // For vertex shader
    Matrix proj; // For vertex shader
};

struct GeometryShaderInput
{
    float4 pos : SV_POSITION;
};

struct PixelShaderInput
{
    float4 pos : SV_POSITION; // not POSITION
    uint primID : SV_PrimitiveID;
};

//TODO: PointStream -> TriangleStream
[maxvertexcount(100)] // 최대 출력 Vertex 갯수
void main(point GeometryShaderInput input[1], uint primID : SV_PrimitiveID,
                              inout PointStream<PixelShaderInput> outputStream)
{
    PixelShaderInput output;
    
    output.pos = input[0].pos;
    
    for (int i = 0; i < 100; i ++)
    {
        output.pos = input[0].pos + float4(0.0, 0.003, 0.0, 0.0) * float(i);
        output.pos = mul(output.pos, view);
        output.pos = mul(output.pos, proj);
        output.primID = primID;

        outputStream.Append(output);
    }
}
  • VS -> GS -> PS 이기에
    VS에서 입력을 받고
    GS에서는 PS의 결과물로 return 한다
    (inout)

  • 예제 목표는 저 PointStream을 Triangle로 바꾸는 것

  • point GeometryShaderInput input[1]
  • point : 입력 Primitive를 지정 (Point : 점 1개, line : 선 1개 등)
  • GeometryShaderInput : 우리가 정의한 입력 구조체 타입
  • [1] : 도형을 구성하는 정점 개수(Point라서 점 1개)

  • SV_PrimitiveID?
    현재 쉐이더에서 처리 중인 도형의 고유 ID
    (삼각형의 단위를 표현)
    (PS 의 Output이며, ps에서 사용 가능)
    (ex : 3번째 삼각형마다 색 변경 등)
    (저 primID는 GPU가 넣어주는 값이기에 VS에서 생각할 필요 없음)
  • [maxvertexcount(100)]
    최대 출력 Vertex (아까 말했듯 32비트 환경에선 1024로 제한됨)
  • 어차피 POINTLIST로 IA에서 설정하였고
    정점 5개를 받아 각각 물체를 만들어주는 것이 목표

예제 수정 코드

[maxvertexcount(100)] // 최대 출력 Vertex 갯수
void main(point GeometryShaderInput input[1], uint primID : SV_PrimitiveID,
                              inout TriangleStream<PixelShaderInput> outputStream)
{
    
    float hw = 0.5 * width;
    
    PixelShaderInput o1;
    
    o1.pos = input[0].pos + float4(-hw, -hw, 0.0, 0.0);
    o1.pos = mul(o1.pos, view);
    o1.pos = mul(o1.pos, proj);
    o1.primID = primID;
    outputStream.Append(o1);

    o1.pos = input[0].pos + float4(-hw, hw, 0.0, 0.0);
    o1.pos = mul(o1.pos, view);
    o1.pos = mul(o1.pos, proj);
    outputStream.Append(o1);

    o1.pos = input[0].pos + float4(hw, hw, 0.0, 0.0);
    o1.pos = mul(o1.pos, view);
    o1.pos = mul(o1.pos, proj);
    outputStream.Append(o1);

    outputStream.RestartStrip();

    o1.pos = input[0].pos + float4(-hw, -hw, 0.0, 0.0);
    o1.pos = mul(o1.pos, view);
    o1.pos = mul(o1.pos, proj);
    outputStream.Append(o1);

    o1.pos = input[0].pos + float4(hw, hw, 0.0, 0.0);
    o1.pos = mul(o1.pos, view);
    o1.pos = mul(o1.pos, proj);
    outputStream.Append(o1);

    o1.pos = input[0].pos + float4(hw, -hw, 0.0, 0.0);
    o1.pos = mul(o1.pos, view);
    o1.pos = mul(o1.pos, proj);
    outputStream.Append(o1);
}

  • 삼각형으로 출력할 때는
    output에 맞게 각 요소에
    수정
    • 시계 방향으로 위치를 조작
    • 이후 outputStream에 Append
    • 삼각형을 그린 후, RestartStrip을 통하여 끊어주기
  • 내부적으로 그릴때 TriangleStrip을 사용하기에
    2가지 방식으로 구현이 가능하다
    • POINTLIST 처럼 각각의 독립적인 3각형 2개를 합침 - 현재 방식
    • Strip을 이용하여 정점 4개만을 이용하여 그리는 방식
만약 4개로 그린다면?
// 좌 하
o1.pos = input[0].pos + float4(-hw, -hw, 0.0, 0.0);
o1.pos = mul(o1.pos, view);
o1.pos = mul(o1.pos, proj);
o1.primID = primID;
outputStream.Append(o1);

// 좌 상
o1.pos = input[0].pos + float4(-hw, hw, 0.0, 0.0);
o1.pos = mul(o1.pos, view);
o1.pos = mul(o1.pos, proj);
outputStream.Append(o1);

// 우 하
o1.pos = input[0].pos + float4(hw, -hw, 0.0, 0.0);
o1.pos = mul(o1.pos, view);
o1.pos = mul(o1.pos, proj);
outputStream.Append(o1);

// 우 상
o1.pos = input[0].pos + float4(hw, hw, 0.0, 0.0);
o1.pos = mul(o1.pos, view);
o1.pos = mul(o1.pos, proj);
outputStream.Append(o1);
    

결과

Image

  • 첫 예제라 비교적 가벼운 내용이었다

댓글남기기