Game Dev/Unity Shader

02.기본적인 Shader 코드 양식 - 다니엘 릿 쉐이더 프로젝트

Septentrions 2025. 10. 7. 15:14

 

Shader 코드 (HLSL, OpenGL)도 결국은 다른 프로그래밍 언어와 같다.
규격화된 사용 방법이 존재하며, 구조를 먼저 이해하면 내부 코드도 쉽게 이해가 갈 것이다.
방식은 아무래도 OpenGL부터 이어져온 오래된 방식이다 보니 절차지향식 C에 가깝다.

 

01. Shader의 시작 부분

셰이더의 시작부분
Shader "CustomFolder/HelloShader"
}
 // Shader 내부
}

 

Shader의 시작 부분이다.

"CustomFolder" 라는 Path를 지정하고, HelloShader를 선택하여 해당 Shader를 적용한다.

 


02. Shader의 내부 구조

셰이더 내부 구조
Shader "CustomFolder/HelloShader"
{
    Tags {...}
    properties{...}

    SubShader
    {
    	Tags {...}
    	Pass
        {
        	Tags {...}
            ...
        }
        
        Pass {...}
        ...
    }
    
    SubShader
    {...}
}

 

*감싸는 구조를 "부분 (Wrapper)"라고 표현 하겠다.

 

Shader의 내부구조는 다음과 같다.

Tags 라는 이름으로, 해당 Shader의 설정 부분

- Tags에는 RenderType, RenderPipeline, Queue 등으로 어떻게 렌더링 시키고, 어떤 환경에서 동작 시킬지 설정하는 부분이다.

Tags
{
	"RenderType"= "QUEUE"
    "QUEUE" = "Geometry" // Render 순서
    "RenderPipeline" = "UniversalPipeline"

}

 

 

Properties 라는 이름으로, 해당 Shader의 사용할 변수 설정 부분

- 해당 Shader의 입력값들을 설정하는 부분이다. Color, 2DSampler, float 등 변수들을 등록하는 부분이다.

 

    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }

 

 

SubShader 라는 이름으로, 여러 환경에 따라 Shader를 다양하게 설정해야 할 필요가 있다.

이를 설정하는 부분이다.

 

SubShader에는 여러가지 Shader Effect 들을 구현할 수 있다.

이를 Pass 키워드를 통해, 순차적으로 적용 할 수 있게 한다.

 

복잡해보이지만, 거의 모든 Shader들이 해당 방식을 따라 구현된다.
HLSL 코드의 기본적인 구조는 이해했다고 보면 된다.

 

 


https://lucid-boundary.tistory.com/115

 

1. Shader 란? - 다니엘 릿 쉐이더 프로젝트

Shader를 알기 위해서는.. 기초적인 그래픽스 지식과 수학이 필요하다.우선, 필요한 지식들을 먼저 이해해보자. RendererRenderering은 일종의 데이터 처리를 아래 처럼, 순차적으로 하는 단계라고 한

lucid-boundary.tistory.com

참고 자료 : RenderProcess 

 

03. 구조체 (Struct)를 이용한 데이터 흐름 관리

우리가 사용할 Shader의 입력 값은 Vertext 정보, Color 정보, 오브젝트의 위치값등 Mesh에 대한 정보들은 굉장히 다양하다.

이 중에서 필요한 것만 Struct화 시켜서, Shader의 입력 값으로 활용한다.

 

struct appdata // struct 이름은 원하는 대로.
{
	float4 positionOS : POSITION
}

 

 

 

우선, Shader Programming에서 지원하는 Shader 함수들을 선언한다.

#pragma를 이용하여 다음 처럼 ShaderFunction을 가져온다.

Pass
{
	HLSLPROGRAM
    
    #pragma vertex vert
    #pragma fragment frag
    
    ENDHLSL
}

 

 

이후에는 v2f (vertext-to-fragment) 구조체를 이용하여 Vertext Shader와 Fragment Shader를 처리할 수 있다.

struct v2f
{
	float4 positionCS : SV_POSITION;
};

 

 

여기서 중요하게 짚고 넘어가야 할 것이 있다.

RenderProcess를 보면, Vertext -> Fragment 프로세스 사이에는 픽셀화 단계가 존재한다.

기본적으로 오브젝트의 렌더링은 우리의 눈에 보이는 화면 (2D 공간)에 렌더링하는 것이다.

Vertext Shader를 이용하여, 오브젝트의 Vertex 관련 처리를 한 뒤에 View Space으로 변환해주는 작업이 필요하다. 

 

v2f vert (appdata v)
{
	v2f o;
    // o.positionCS = UnityObjectToClipPos(v.positionOS) built-in
    o.positionCS = TransformObjectToHClip(v.positionOS)
    return o;
}

 

이후에는 Fragment Shader를 구현해주면 된다.

 

float4 frag(v2f i) : SV_TARGET

{

	return _BaseColor;

}

04. Shader를 이용한 최적화 진행

유니티에는 SRP Batcher와 GPU Instancing 기능이 존재한다.

여러 오브젝트가 같은 셰이더를 사용하고 있다면, 여러 오브젝트를 계속 렌더링을 하는 것보다

이미 동일한 셰이더를 이용하여 한번에 렌더링 하는 게 더 쉬울 것이다.

 

SRP Batcher와 GPU Instancing은 동시에 사용 할 수는 없다.

성능은 GPU Instnacing가 훨씬 좋지만, 대신에 복잡한 메쉬, 매트리얼을 사용할 때는 SRP Batch가 좀 더 효율적이다.

 

SRP Batcher를 사용 하기 위해서는 CBuffer를 정의해야 한다.

CBUFFER_START(UnityPerMaterial) ... CBUFFER_End

 

(GPU Instancing의 경우는 )

 

   UNITY_INSTANCING_BUFFER_START(Props)
        // put more per-instance properties here
    UNITY_INSTANCING_BUFFER_END(Props)

 

 


05. 마치며

지금까지 아주 기본적인 Shader 구조에 대해 알아보았다.

다들 알겠지만, 거의 대부분의 멋있는 효과들은 Shader Graph만 이용해도 할 수 있다.

하지만, 아는만큼 보인다고. Shader Programming을 잘 알면, 논문 구현이나 Graph로는 한계가 있는 이펙트마저 자력으로 만들 수 있을 것이다.