Geometry Shader 도 Tessellation Shader와 마찬가지로
렌더링 파이프라인의 스테이지중 하나이다.
또! 리마인드하자. 중요하니까.
https://lucid-boundary.tistory.com/115
01. Shader 란? - 다니엘 릿 쉐이더 프로젝트
Shader를 알기 위해서는.. 기초적인 그래픽스 지식과 수학이 필요하다.우선, 필요한 지식들을 먼저 이해해보자. RendererRenderering은 일종의 데이터 처리를 아래 처럼, 순차적으로 하는 단계라고 한다
lucid-boundary.tistory.com
1. Raw Vertex Data
2. Vertex Shader
3. Tessellation Shader
4. Geometry Shader
5. Rasterization
6. Fragment Shader
7. Per-Fragment Operations
이번에도 Geometry는 Vertex와 Fragment 사이에 위치한다.
Geometry 함수는 point, triangle mesh 오리지널 값 (primitive)을 입력으로 받고 새로운 오리지널 값을 생성할 수 있다.
새로운 값을 오리지널로 생성하는 것이라 성능에 영향을 줄 수 있다.
Geometry Shader는 Tessellation Shader이나 Compute Shader랑 같이 사용하면 효과적이다.
Geometry를 이용해 normal 벡터를 표현하기 (Spike 효과)

Shader "LucidBoundary/Geometry_Spike"
{
Properties
{
_DebugColor("Debug Color", Color) = (0, 0, 0, 1)
_WireThickness("Wire Thickness", Range(0, 0.1)) = 0.01
_WireLength("Wire Length", Range(0, 1)) = 0.2
}
SubShader
{
Tags
{
"RenderType"="Opaque"
"Queue" = "Geometry"
"RenderPipeline" = "UniversalPipeline"
}
Pass
{
Cull Off
Tags
{
"LightMode" = "UniversalForward"
}
HLSLPROGRAM
#pragma require geometry
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
struct appdata
{
float4 positionOS : POSITION;
float3 normalOS: NORMAL;
float4 tangentOS: TANGENT;
};
struct v2g
{
float4 positionWS : SV_POSITION;
float3 normalWS : NORMAL;
float4 tangentWS : TANGENT;
};
struct g2f
{
float4 postionsCS : SV_POSITION;
};
CBUFFER_START(UnityPerMaterial)
float4 _DebugColor;
float _WireThickness;
float _WireLength;
CBUFFER_END
v2g vert (appdata v)
{
v2g o;
o.positionWS = mul(unity_ObjectToWorld, v.positionOS);
o.normalWS = TransformObjectToWorldNormal(v.normalOS);
o.tangentWS = mul(unity_ObjectToWorld, v.tangentOS);
// o.tangentWS = v.tangentOS;
return o;
}
g2f geomToClip(float3 positionOS, float3 offsetOS)
{
g2f o;
o.postionsCS = mul(UNITY_MATRIX_VP, float4(positionOS + offsetOS, 1.0f));
return o;
}
[maxvertexcount(8)]
void geom(point v2g i[1], inout TriangleStream<g2f> triStream)
{
float3 normal = normalize(i[0].normalWS);
float4 tangent = normalize(i[0].tangentWS);
float3 bitangent = normalize(cross(normal, tangent.xyz) * tangent.w);
float3 xOffset = tangent * _WireThickness * 0.5f;
float3 yOffset = normal * _WireLength;
float3 zOffset = bitangent * _WireThickness * 0.5f;
float3 offsets[8] =
{
-xOffset,
xOffset,
-xOffset + yOffset,
xOffset + yOffset,
-zOffset,
zOffset,
-zOffset + yOffset,
zOffset + yOffset
};
float4 pos = i[0].positionWS;
triStream.Append(geomToClip(pos, offsets[0]));
triStream.Append(geomToClip(pos, offsets[1]));
triStream.Append(geomToClip(pos, offsets[2]));
triStream.Append(geomToClip(pos, offsets[3]));
triStream.RestartStrip();
triStream.Append(geomToClip(pos, offsets[4]));
triStream.Append(geomToClip(pos, offsets[5]));
triStream.Append(geomToClip(pos, offsets[6]));
triStream.Append(geomToClip(pos, offsets[7]));
triStream.RestartStrip();
}
float4 frag(g2f i) : SV_Target
{
return _DebugColor;
}
ENDHLSL
}
}
}
Properties
Properties
{
_DebugColor("Debug Color", Color) = (0, 0, 0, 1)
_WireThickness("Wire Thickness", Range(0, 0.1)) = 0.01
_WireLength("Wire Length", Range(0, 1)) = 0.2
}
Properties는 컬러, 표시할 선의 굵기, 길이를 갖고 있다.
Procompiler
#pragma require geometry
#pragma vertex vert
#pragma geometry geom
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
프리 컴파일러에 추가된건
geometry 이다.
geometry shader를 사용할 수 있게 해준다.
Struct
struct appdata
{
float4 positionOS : POSITION;
float3 normalOS: NORMAL;
float4 tangentOS: TANGENT;
};
struct v2g
{
float4 positionWS : SV_POSITION;
float3 normalWS : NORMAL;
float4 tangentWS : TANGENT;
};
struct g2f
{
float4 postionsCS : SV_POSITION;
};
총 3개의 구조체를 사용한다.
먼저 입력이 되는 appdata는 오브젝트의 position, normal, tangent 값을 갖고온다.
Vertex To Geometry (v2g) 는 appdata의 값을 geometry 함수에 넘겨주기 전에 값을 저장한다.
월드 스페이스로 변환된 position, normal, tangent 값을 갖는다.
Geometry To Fragment (g2f)는 새로 생성된 geometry 값을 fragment 함수에 넘겨주기 전에 값을 저장한다.
포지션 값만 갖고 색상을 입히면 되므로 position 하나만 갖는다.
Vert 함수
v2g vert (appdata v)
{
v2g o;
o.positionWS = mul(unity_ObjectToWorld, v.positionOS);
o.normalWS = TransformObjectToWorldNormal(v.normalOS);
o.tangentWS = mul(unity_ObjectToWorld, v.tangentOS);
// o.tangentWS = v.tangentOS;
return o;
}
Vert 함수는 Geometry로 값을 보내주기 위한 v2g 구조체를 완성하기 위한 함수이다.
worldspace로 변환을 해준다.
geom 함수
[maxvertexcount(8)]
void geom(point v2g i[1], inout TriangleStream<g2f> triStream)
{
float3 normal = normalize(i[0].normalWS);
float4 tangent = normalize(i[0].tangentWS);
float3 bitangent = normalize(cross(normal, tangent.xyz) * tangent.w);
float3 xOffset = tangent * _WireThickness * 0.5f;
float3 yOffset = normal * _WireLength;
float3 zOffset = bitangent * _WireThickness * 0.5f;
float3 offsets[8] =
{
-xOffset,
xOffset,
-xOffset + yOffset,
xOffset + yOffset,
-zOffset,
zOffset,
-zOffset + yOffset,
zOffset + yOffset
};
float4 pos = i[0].positionWS;
triStream.Append(geomToClip(pos, offsets[0]));
triStream.Append(geomToClip(pos, offsets[1]));
triStream.Append(geomToClip(pos, offsets[2]));
triStream.Append(geomToClip(pos, offsets[3]));
triStream.RestartStrip();
triStream.Append(geomToClip(pos, offsets[4]));
triStream.Append(geomToClip(pos, offsets[5]));
triStream.Append(geomToClip(pos, offsets[6]));
triStream.Append(geomToClip(pos, offsets[7]));
triStream.RestartStrip();
}
(1) maxvertexcount
해당 어트리뷰트는 한번에 생성할 수 있는 vertex의 갯수를 제한하는 기능이다.


(2) 입력 파라미터
Func Name (PrimitiveType [num_elements], inout StreamOutputObject)
Primitive Type 이란 도형을 이루는데 필요한 기본적인 타입을 말한다.
Point, Line, Triangle, lineadj, triangleadj 등이 있다.
Point [1] 이므로, 한 지점에서 동작하는걸 말한다.
inout 한정자는 본래 C# 에서는 사용하지 않지만, HLSL에서는 가능하다.
스트림 객체를 지정하는데, 본 함수에서는 출력 용도로 사용된다.
triStream 이라는 변수를 입력으로 받으면서, 해당 함수는 리턴값이 없는데
triStream에 출력물을 넣고 끝나는 함수이기 때문이다.
스트림은 리스트 형식으로 GPU에 전달 되어서 처리 되는 방식인 것으로 보인다.
(3) Offset 계산하기
여기서 쓰이는 Offset은 일종의 Scale 값이라 생각하자
한 점의 x, y, z 축의 Thickness, Length 만큼 좌표들이 계산된다.
계산된 offset과 좌표값은 geomToClip 함수로 넘겨준다.
geomToClip 함수
g2f geomToClip(float3 positionOS, float3 offsetOS)
{
g2f o;
o.postionsCS = mul(UNITY_MATRIX_VP, float4(positionOS + offsetOS, 1.0f));
return o;
}
클립 스페이스로 변환하는 함수이다.
앞서 계산된 좌표와 offset을 더한 position matrix를 View and Projection Matrix에 넣어서 Clip Space 상의 좌표를 구한다.
frag 함수
float4 frag(g2f i) : SV_Target
{
return _DebugColor;
}
보고자 하는 색상은 이미 정해져있으므로, 해당 position 값에 컬러만 넣어주면 끝난다.

마치며
Geometry Shader는 잔디나 풀숲 처럼 랜덤하게 값을 줘가며 Shader로 구현할 수 있다.
더 심화된건 해당 프로젝트가 모두 끝나면 만들어보겠다.
'Game Dev > Unity Shader' 카테고리의 다른 글
| Toon Shader 만들기 - HLSL 유니티 Shader (0) | 2025.10.29 |
|---|---|
| 22. Compute Shader - 다니엘 릿 쉐이더 프로젝트 (0) | 2025.10.27 |
| 20. Tessellation Shader - 다니엘 릿 쉐이더 프로젝트 (0) | 2025.10.26 |
| 19. Image Effects, Post-Processing Effects - 다니엘 릿 쉐이더 프로젝트 (0) | 2025.10.24 |
| 18. Shadow Casting - 다니엘 릿 쉐이더 프로젝트 (0) | 2025.10.21 |