Unity/Rendering&Shader

[SHADERLAB] URP Shader 1편

김성인 2023. 11. 8. 23:00

[SHADER] URP Shader 1편 

 

함께 회사에 다니는 친구를 위하여 기록합니다.


 URP 전에 흔하게 사용되던 레거시 셰이더의 경우, 유니티 홈페이지에서 따로 받을 수 있고, URP 프로젝트에서 셰이더를 생성하더라도 레거시 셰이더(CGPROGRAM)으로 생성이 됩니다.  그렇기 때문에 URP 셰이더를 처음 시작할 경우 어렵게 느낄 수 있습니다.

 

 이와 같은 이유로 직접 Shader Library에서 Unlit.shader 파일을 열어 시작하는 방법과, 유니티 URP 매뉴얼에 샘플로 제공하는 기본형 셰이더로 시작할 수도 있습니다.

 

 우선 Shader Library의 경우, 경로가 조금씩 다를 수 있지만 보통 프로젝트 폴더 아래, "Library\PackageCache\com.unity.render-pipelines.core@14.0.8\ShaderLibrary"에 존재합니다.

위 주소 중 14.0.8은 Core RP Library 버전을 뜻합니다.

 

 윈도우 파일 탐색기에서 Unlit.shader로 검색하면 쉽게 찾을 수 있습니다.

 

 그리고 유니티의 URP 메뉴얼에서 샘플 코드를 열람할 수 있습니다.  이 글에서 그 코드를 가지고 Unity Shader에 대하여 설명을 진행하겠습니다.


 유니티의 렌더링 파이프라인은 크게 두 가지로 나눌 수 있는데, 기존의 내장 렌더링 파이프라인 built-in render pipeline(BRP)와 개발자가 프로그래밍 할 수 있는 렌더링 파이프라인인 srciptable render pipeline(SRP)가 있다.  여기서 SRP의 경우 다시 URP와 HDRP로 나눌 수 있습니다.

 

 URP는 BRP와 달리 개발자가 직접 필요 없는 부분을 배제하고, 최적화 등을 직접 진행할 수 있기 때문에 렌더링 성능이 좋아지고, 확장성이 더 좋아졌습니다.


https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@17.0/manual/writing-shaders-urp-basic-unlit-structure.html : URP 17.0.1 메뉴얼의 URP unlit basic shader 코드를 열람 할 수 있습니다.

 

 직접 코드를 보며 각 부분이 무엇이고, 어떤 역할을 하는지 이야기하겠습니다.

 

 먼저 샘플 코드 전체를 봅시다.  하단의 코드는 메뉴얼에서 제공하는 샘플 코드에서 주석을 지우고, 열을 정리하였습니다.

Shader "Example/URPUnlitShaderBasic"
{

    Properties
    { }

        SubShader
    {
        Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

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

            struct Attributes
            {
                float4 positionOS   : POSITION;
            };

            struct Varyings
            {
                float4 positionHCS  : SV_POSITION;
            };

            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);

                return OUT;
            }

            half4 frag() : SV_Target
            {
                half4 customColor = half4(0.5, 0, 0, 1);

                return customColor;
            }
        ENDHLSL
        }
    }
}

Shader "Example/URPUnlitShaderBasic"  { ... }


 유니티는 ShaderLab이라는 셰이더 언어로 작성됩니다.  그리고 위의 코드는 유니티에서 셰이더의 경로와 이름을 보여주는 코드입니다.  이런 형식이라면 뒤도 보지 않고 유니티에서 사용하는 셰이더라고 생각하면 좋을 것 같습니다.


Properties  { ... }


 속성(프로퍼티) : 개발자가 의도적으로 셰이더에 접근할 수 있는 변수들을 메테리얼의 인스팩터에서 노출시키는데 사용됩니다.  특정 키워드를 이용하여 메테리얼의 인스팩터에서 보이지 않도록 할 수 있으며, 스크립트로도 제어할 수 있습니다.


SubShader  { ... }


 SubShader : 실질적으로 최종 픽셀의 색을 정하는 버텍스 셰이더와 프레그먼트 셰이더가 작동하기 전, 셰이더 모델과 GPU 기능을 타겟팅 합니다. 

 

 예를 들어서 #pragma target 4.0 이라는 지시어를 통하여 셰이더 모델을 지정합니다.  자세한 정보는 유니티 메뉴얼 https://docs.unity3d.com/Manual/SL-ShaderCompileTargets.html 에 있습니다.


Tags { "RenderType" = "Opaque" "RenderPipeline" = "UniversalPipeline" }


Tags : 렌더파이프라인, 큐, 렌더 타입 등 여러가지를 설정 할 수 있는 부분입니다.  자세한 정보는 유니티 메뉴얼 https://docs.unity3d.com/2023.3/Documentation/Manual/SL-SubShaderTags.html 에 있습니다.


Pass { ... }


 본격적으로 셰이더가 계산되는 부분이라고 생각하면 편할 것 같습니다. 

 

 위 샘플 코드에서는 나오지 않았지만, Pass의 이름과, Tags에 라이트 모드를 설정할 수 있는데, 이것들은 예를 들어 "Name "Forward" Tags {"LightMode" = "UniversalForward"}"로 작성됩니다.  

 

 라이트 모드의 경우 유니티에서 해당 패스를 사용하는 방법을 설명하기도 하며, 랜더 피처에서 타겟팅 할 수 있습니다.

 

 URP용 SimpleLit.shader를 살펴 보면, 

  Name "ForwardLit" "LightMode" = "UniversalForward" : 포워드 렌더링 경로에서 오브젝트들을 렌더링하는데 사용됩니다.

  Name "ShadowCaster" "LightMode" = "ShadowCaster" : 그림자를 그리는데 사용됩니다.

  Name "GBuffer" "LightMode" = "UniversalGBuffer" : 디퍼드 랜더링의 G 버퍼 생성에 사용됩니다.

  Name "DepthOnly" "LightMode" = "DepthOnly" : 뎁스 버퍼 생성에 사용됩니다.

  Name "Meta" "LightMode" = "Meta" : 라이트맵 베이킹에 사용됩니다.

 

  Cull : 면을 추려내는 지시문입니다.

 

  ZTest : 뎁스 버퍼와 비교하여 렌더링 유무를 결정합니다.

  ZWrite : 뎁스 버퍼를 사용하는 유무를 결정합니다.

 

  Offset : 뎁스버퍼에서 실제 값보다 뒤나 앞에 오브젝트를 그릴 수 있도록 보정해줄 때 사용됩니다.

  관련 유니티 메뉴얼 : https://docs.unity3d.com/kr/2022.1/Manual/SL-Offset.html

 

  블랜드 모드 정의 관련 유니티 메뉴얼 : https://docs.unity3d.com/2023.3/Documentation/Manual/SL-Blend.html

 


 하나의 SubShader 안에 여러 개의 Pass를 만들 수 있습니다.  기본적으로 각 Pass는 오브젝트의 색, 그림자 등을 Pass 별로 나누어서 렌더링 하게 됩니다.  그리고 이런 일련의 과정들을 프레임 디버거나 렌더독으로 확인할 수 있습니다.


HLSLPROGRAM


 URP 전의 레거시 셰이더의 경우 CGPROGRAM을 사용하였고, #include "UnityCG.cginc"를 통하여 셰이더 제작에 도움을 주었습니다.

 URP는 HLSLPROGRAM을 사용하며, #include "~Core.hlsl"를 통하여 셰이더 제작에 도움을 주고 있습니다.

 

 ENDHLSL로 HLSLPROGRAM의 끝을 알립니다.


#pragma vertex vert

#pragma fragment frag


 사용할 버텍스 셰이더(함수)와 프래그먼트 셰이더(함수)를 설정합니다.

 

 #pragma vertex vert : vert라는 이름의 버텍스 셰이더(함수)를 사용할 것이라는 뜻입니다.


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


Core.hlsl를 포함할 것이라는 뜻입니다.  이 hlsl파일 안에는 유니티에서 제공하는 기본적인 설정이나, 도우미 함수들이 존재하고, 또한 많은 다른 hlsl파일들을 포함할 수 있도록 구조가 짜져 있습니다.


struct Attributes { ... }


 버텍스 셰이더에 정점 데이터를 입력하는 구조체입니다.


struct Varyings { ... }


 버텍스 셰이더의 출력과 프래그먼트 셰이더의 입력을 담당하는 구조체입니다.


 Attributes와, Varyings는 1:1 매칭이 아닙니다.

 

 예를 들어서 아래의 코드를 확인해 보면, 입력은 2개, 출력은 4개임을 볼 수 있습니다.

struct Attributes 
{
    float4 positionOS   : POSITION;
    float2 uv           : TEXCOORD0;
}

struct Varyings
{
    float2 uv           : TEXCOORD0;
    float4 positionCS   : SV_POSITION;
    float3 positionWS   : TEXCOORD1;
    half4 normalWS      : TEXCOORD2;
}

 몇 가지 시멘틱스(POSITION, TEXCOORD0 등 대문자로 표기된 것 들...)는 이것이 무엇인지 의미를 전달해 주는 것입니다.  TEXCOORD를 제외하고 몇 가지 정해진 이름이 있습니다.

 

 POSITION : 버텍스의 위치

 COLOR : 버텍스 칼라

 NORMAL : 버텍스의 노말

 TANGENT : 버텍스 탄젠트

 

 TEXCOORD의 경우 0부터 7까지 존재하는데, 사실 이것은 DX9 Shader Model 2.0에서는 8개까지, DX 3.0은 10개, OpenGL ES 3.0, 메탈은 16개, DX10 Shader Model 4.0은 32개까지 사용할 수 있지만, 보통의 노말 한 디바이스를 모두 지원하기 위하여 최대 8개 사용을 권장합니다.


            Varyings vert(Attributes IN)
            {
                Varyings OUT;
                OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
                
                return OUT;
            }

 버텍스 셰이더입니다.

 

구조체 Attributes를 매개변수로 작동하게 됩니다.

 

 OUT.positionHCS = TransformObjectToHClip(IN.positionOS.xyz); : 각 버텍스의 위치를 정합니다.  

 

 TransformObjectToHClip : 렌더파이프라인에서 버텍스의 위치는 우리가 보는 3D 공간에서 몇 개의 단계를 거쳐야 합니다.  로컬(모델) 스페이스 > 월드 스페이스 > 뷰(카메라) 스페이스 > 클립 스페이스 > 스크린(모니터) 스페이스, 이런 공간변환을 맡고 있는 함수입니다.

 이 함수의 경우 SpaceTransform.hlsl에 들어있습니다.


            half4 frag() : SV_Target
            {
                half4 customColor = half4(0.5, 0, 0, 1);
                return customColor;
            }

 프래그먼트 셰이더입니다.

 

 입력받는 값이 없기 때문에 frag()로 매개변수 부분이 비어 있습니다.  

 

 단순하게 색만 출력합니다.


 이다음부터는 부분별로 더욱 상세히 이야기할 것이며, 우선 여기서 한번 끊고, 다음 장에서 이어서 진행하겠습니다.