본문 바로가기

유니티/그래픽스

[Unity] 셰이더 코드 (3) - SubShader 작성 방법

반응형

지난 포스팅에서는 ShaderLab의 구조와 Properties에 대해 작성하였다.

이제 본격적인 SubShader 코드 작성 방법에 대해 포스팅하고자 한다.


SubShader와 Tags, Pass 그리고 HLSLPROGRAM

서브 셰이더는 호환되는 하드웨어의 정보와 타깃 렌더 파이프라인의 정보가 들어가 있고, Tag와 Pass라는 정보도 들어가 있다.

그렇다면 이러한 것들은 어떻게 코드로 나타내고 또 어떤 의미를 지니고 있을까.

 

시작하기 전에 전체 코드를 다시 복기할 필요가 있다.

 

Shader "Examples/Code"
{
    Properties
    {
        _BaseColor("Base Color", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags
        {
            "Render Type" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
            "Queue" = "Geometry"
        }
        Pass
        {
            Tags
            {
                "LightMode" = "UniversalForward"
            }

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

            struct appdata
            {
                float4 positionOS : POSITION;
            };

            struct v2f
            {
                float4 positionCS : SV_POSITION;
            };

            float4 _BaseColor;

            v2f vert(appdata v)
            {
                v2f o;
                o.positionCS = TransformObjectToHClip(v.positionOS);
                return o;
            }

            float4 frag(v2f i) : SV_TARGET
            {
                return _BaseColor;
            }
            ENDHLSL
        }
    }
}

 

서브셰이더가 시작되며 제일 먼저 작성된 것은 Tags이다.

 

Tags

태그는 엔진에서 렌더링 하는 방식과 시점들을 지정하는 역할을 한다.

일반적으로 키-밸류 페어 형태를 지니고 있으며 Pass 내부에도 작성을 할 수 있다.

 

Tags
{
    "Render Type" = "Opaque"
    "RenderPipeline" = "UniversalPipeline"
    "Queue" = "Geometry"
}

 

이 예제에서는 셰이더 타입과 타깃 파이프라인 그리고 렌더링 순서(큐)를 지정해주고 있다.

 

Pass

패스는 실질적으로 렌더링 되는 완전한 한 사이클을 우리가 코드로 작성해 주는 부분이다.

여러 개의 패스를 가질 수 있으며, 위에서 아래로 순차적으로 실행된다.

위의 태그가 패스 내부에 작성될 수 있는데, 이는 Pass의 목적으로 명시적으로 나타내준다.

 

화면에 나타나는 셰이더 로직을 직접적으로 조작하는 구간이기 때문에 우리는 이 Pass를 다루는 것에 초점을 맞춰야 한다.

 

Pass
{
    Tags
    {
        ...
    }

    HLSLPROGRAM
    ...
    ENDHLSL
}

 

HLSLPROGRAM / ENDHLSL

어떤 셰이딩 언어가 사용될지 나타내며, HLSL의 경우 HLSLPROGRAM으로 열며 ENDHLSL로 끝마친다.

 

pragma와 include

#pragma와 #include 모두 프리프로세서 지시문이다. (c언어의 경우 전처리 단계에 실행)

유니티 셰이더에서는 이러한 지시문들을 사용해 컴파일 단계에 셰이더에게 명령을 내릴 수 있다. (아무 데나 작성할 수 있지만 보통 처음에 작성)

 

#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

 

예를 들어, #pragma vertex vert의 경우 vert라는 이름의 함수를 버텍스 셰이더(정점)로 컴파일하겠다는 의미가 된다.

마찬가지로 #fragment frag는 frag라는 이름의 함수를 프래그먼트 셰이더(색상)로 컴파일하겠다는 뜻이다.

 

참고로 이 두 개의 프리프로세서 지시문은 필수이다. (당연히 버텍스와 프래그먼트를 뺄 수는 없기 때문이다)

 

#include는 C나 C++에서 사용하듯이 외부 셰이더 라이브러리를 포함시키는 기능이다.

 

이 예제는 URP를 기준으로 진행하고 있기 때문에 아래의 경로를 지정해줘야 한다.

"Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

 

빌트인 파이프라인의 경우 "UnityCG.cginc"를 추가해 주면 된다.

 

(struct와 Semantics, 함수에 대한 내용은 이은 포스팅에서 계속됩니다)

반응형