https://youtu.be/QLrBoXCu6_Q?t=1233

데칼 레이어란

데칼 레이어는 데칼을 사용할 때 지형에는 적용하면서 캐릭터를 예외 처리 하고 싶을 때 사용합니다.

Unity 2023 URP 15에서 도입되었으며 Decal 렌더피쳐에서 옵션을 활성화 할 수 있습니다.

이번 포스팅은 데칼 레이어 원리와 데칼 레이어를 지원하는 쉐이더 구현 방법을 소개합니다.

 

URP 15 이전 데칼 레이어 구현 방법

간단히 스텐실 테스트를 통해 데칼이 원하지 않은 오브젝트에 덮히지 않게 할 수 있습니다.

특정 스텐실 넘버에 Replace 옵션으로 정보를 기록하고 데칼 마테리얼에서 NotEqual 옵션으로 스텐실 넘버가 겹치지 않은 부분만 렌더링하는 원리입니다.

좌 : Before 우 : After

쉐이더에 스텐실 제어하는 코드는 아래와 같습니다.

 Properties
    { 
        _BaseColor("Color", Color) = (1, 1, 1, 1)
        _BaseMap("Texture", 2D) = "white" {}

        [Header(Stencil Test)]
        [Space(10)]
        [IntRange] _Reference("Ref", Range(0,255)) = 0
        [Enum(UnityEngine.Rendering.CompareFunction)] _Comp("Comp", float) = 8
        [Enum(UnityEngine.Rendering.StencilOp)] _Pass("Pass", float) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _fail("fail", float) = 0
        [Enum(UnityEngine.Rendering.StencilOp)] _Zfail("Zfail", float) = 0
    }
    
     Pass
        {
            Name  "ScreenSpaceDecal"
            Tags {"LightMode" = "SRPDefaultUnlit"}

            Cull back
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha 

             Stencil
            {
                Ref [_Reference] 
                Comp [_Comp] 
                Pass [_Pass] 
                fail [_fail] 
                Zfail [_Zfail]
            }

 

 

URP 15 데칼 레이어 원리 분석

URP 15의 데칼 레이어는 스텐실 방식이 아닌 별도의 레이어 버퍼를 추가로 사용하여 데칼 쉐이더 내부에서 필터링하는 방식입니다. Decal 렌더피쳐에서 데칼 레이어 옵션을 활성화하면 아래와 같이 _CameraRenderingLayerTexture 버퍼가 추가로 생성되는 것을 확인 할 수 있습니다.

 

렌더링레이어 버퍼는 UniversalRenderer에서 렌더패스 호출하고 있으며 데칼레이어 옵션이 활성화되면 Forward 렌더링 환경에서 OpaqueForwardPass 대신 RenderingLayer 기능이 추가된 OpaqueForwardPass가 렌더패스 호출됩니다.

UniversalRenderer.cs / UniversalRenderer(생성자)
UniversalRenderer.cs / Setup

RenderingLayersPass 렌더패스에서는 렌더링 레이어 쉐이더 키워드를 활성화 시켜줍니다.

 

LitForward.hlsl를 보면 이전 버전과 다르게 쉐이더 결과를 return하는게 아닌 out 방식으로 변경되었고 렌더링 레이어 분기가 활성화되어 있으면 메쉬 레이어 정보를 추가로 내보냅니다.

 

GetMeshRenderingLayer() 함수는 Renderer 컴포넌트의 Rendering Layer Mask 정보 값이 들어옵니다.

이 정보 값을 SV_Target1로 내보내면 _CameraRenderingLayerTexture 버퍼에 정보가 그려지게 됩니다.

 

Opaque렌더링 이후에 렌더링되는 쉐이더에서 _CameraRenderingLayersTexture를 샘플링하여 렌더링레이어 정보를 사용할 수 있습니다. 데칼 쉐이더에서는 해당 버퍼를 샘플링하는 방식으로 필터링하는 방식으로 데칼 레이어가 구현되어 있습니다.

이 기술은 데칼뿐만 아니라 레이어 필터링이 필요한 기능을 구현할 때 응용 가능할 것으로 보입니다.
(예를 들어 포스트프로세스에서 특정 오브젝트를 제외하고 효과를 입히기.)

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

 

 

 

렌더링 레이어 사용 예제 

RenderingLayer Write지원하는 쉐이더 샘플

Shader "CatDarkGame/Unlit_WriteRenderingLayer"
{
    Properties
    { 
        [MainTexture] _BaseMap("Texture", 2D) = "white" {}
        [MainColor] _BaseColor("Color", Color) = (1, 1, 1, 1)
    }

    SubShader
    {
         Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalPipeline"
            "UniversalMaterialType" = "Lit"
            "IgnoreProjector" = "True"
        }

        Pass
        {
            Name  "URPUnlit"
            Tags {"LightMode" = "UniversalForward"}

            ZWrite on
            Cull back

            HLSLPROGRAM
            #pragma target 4.5
            #pragma exclude_renderers gles d3d9
            
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonLighting.hlsl"
            #include_with_pragmas "Packages/com.unity.render-pipelines.universal/ShaderLibrary/RenderingLayers.hlsl"


            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                float4 _BaseColor;
            CBUFFER_END

            TEXTURE2D(_BaseMap);    SAMPLER(sampler_BaseMap);


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

            struct Varyings
            {
                float4 positionCS    : SV_POSITION;
                float2 uv            : TEXCOORD0;
            }; 
            

            Varyings vert(Attributes input)
            {
                Varyings output = (Varyings)0;

                float4 positionOS = input.positionOS;
                float3 positionWS = TransformObjectToWorld(positionOS.xyz);
                float4 positionCS = TransformWorldToHClip(positionWS);

                output.positionCS = positionCS;
                output.uv = input.uv;
                return output;
            }

            void frag(Varyings input, out half4 outColor : SV_Target0
                                        #ifdef _WRITE_RENDERING_LAYERS
                                            , out float4 outRenderingLayers : SV_Target1
                                        #endif
                                        )
            {
                float2 baseMapUV = input.uv.xy * _BaseMap_ST.xy + _BaseMap_ST.zw;
                float4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, baseMapUV);
                
                #ifdef _WRITE_RENDERING_LAYERS
                    uint renderingLayers = GetMeshRenderingLayer();
                    outRenderingLayers = float4(EncodeMeshRenderingLayer(renderingLayers), 0, 0, 0);
                #endif

           
                float4 finalColor = texColor * _BaseColor;
                outColor = finalColor;
            }
            
            ENDHLSL
        }
    }
}

 

데칼레이어 기능이 추가된 데칼 쉐이더 (Decal Projection 컴포넌트 전용)

Shader "CatDarkGame/ScreenSpaceDecal"
{
    Properties
    { 
        _BaseColor("Color", Color) = (1, 1, 1, 1)
        _BaseMap("Texture", 2D) = "white" {}
    }

    SubShader
    {
       Tags
        {
            "RenderPipeline"="UniversalPipeline"
            "PreviewType"="Plane"
            "DisableBatching"="False"
            "ShaderGraphShader"="true"
            "ShaderGraphTargetId"="UniversalDecalSubTarget"
        }
        

        Pass
        {
            Name  "DecalScreenSpaceProjector"
            Tags {"LightMode" = "DecalScreenSpaceProjector"}


            Cull back
            ZWrite Off
            Blend SrcAlpha OneMinusSrcAlpha 

            HLSLPROGRAM
            
            #define DECAL_PROJECTOR

            #pragma target 4.5
            
            
            #pragma vertex vert 
            #pragma fragment frag

            #pragma multi_compile _ _DECAL_LAYERS

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
            #ifdef _DECAL_LAYERS
                #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareRenderingLayerTexture.hlsl"
                #include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/ShaderVariablesDecal.hlsl"
            #endif

            CBUFFER_START(UnityPerMaterial)
                float4 _BaseMap_ST;
                float4 _BaseColor;
            CBUFFER_END

            TEXTURE2D(_BaseMap);    SAMPLER(sampler_BaseMap);


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

            struct Varyings
            {
                float4 positionCS    : SV_POSITION;
                float2 uv            : TEXCOORD0;
                float4 positionSS    : TEXCOORD1;
                float4 viewRayOS     : TEXCOORD2; 
                float3 cameraPosOS   : TEXCOORD3;
            }; 
            
            
            void GetObjectSpaceViewTransform(float3 positionVS, out float4 objectToViewDir, out float3 viewPositionOS)
            {
                float viewZ = positionVS.z;
                float4x4 viewToObjectMatrix = mul(UNITY_MATRIX_I_M, UNITY_MATRIX_I_V);
                objectToViewDir.xyz = mul((float3x3)viewToObjectMatrix, positionVS.xyz * -1).xyz;
                objectToViewDir.w = viewZ;
                viewPositionOS.xyz = mul(viewToObjectMatrix, float4(0.0f, 0.0f, 0.0f, 1.0f)).xyz; 
            }


            Varyings vert(Attributes input)
            {
                Varyings output = (Varyings)0;

                float4 positionOS = input.positionOS;
                float3 positionWS = TransformObjectToWorld(positionOS.xyz);
                float4 positionCS = TransformWorldToHClip(positionWS);
                
                float4 positionSS = ComputeScreenPos(positionCS);
               
                float3 positionVS = TransformWorldToView(positionWS);
                float4 objectToViewDir = 0;
                float3 viewPositionOS = 0;
                GetObjectSpaceViewTransform(positionVS, objectToViewDir, viewPositionOS);
                
                output.viewRayOS = objectToViewDir;
                output.cameraPosOS = viewPositionOS ;
                output.positionSS = positionSS;
                output.positionCS = positionCS;
                output.uv = input.uv;
                return output;
            }

            float4 frag(Varyings input) : SV_Target
            {
                float decalLayerClip = 0;
                #ifdef _DECAL_LAYERS
                    uint surfaceRenderingLayer = LoadSceneRenderingLayer(input.positionCS.xy);
                    uint projectorRenderingLayer = uint(UNITY_ACCESS_INSTANCED_PROP(Decal, _DecalLayerMaskFromDecal));
                    decalLayerClip = 1 - (surfaceRenderingLayer & projectorRenderingLayer);
                #endif
          
                float sceneRawDepth = SampleSceneDepth(input.positionSS.xy / input.positionSS.w).r; 
                float sceneDepthVS = LinearEyeDepth(sceneRawDepth, _ZBufferParams);
                float3 viewRayOS = (input.viewRayOS.xyz / input.viewRayOS.w) * sceneDepthVS;
                float3 decalSpaceScenePos = input.cameraPosOS.xyz + viewRayOS.xyz;
             
                float3 absDecalSpaceScenePos = abs(decalSpaceScenePos);
                float decalClipAlpha = max(max(absDecalSpaceScenePos.x, absDecalSpaceScenePos.y), absDecalSpaceScenePos.z);
                clip(0.5f - decalClipAlpha - decalLayerClip);
               
                #ifndef DECAL_PROJECTOR
                    float2 decalSpaceUV = decalSpaceScenePos.xy + 0.5f;
                #else
                    float2 decalSpaceUV = decalSpaceScenePos.xz + 0.5f;
                #endif
               
                float2 uv = decalSpaceUV.xy * _BaseMap_ST.xy + _BaseMap_ST.zw;
                uv = frac(uv);
              
                float4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
                float4 finalColor = texColor * _BaseColor;
                return finalColor;
            }
            
            ENDHLSL
        }
    }
}

 

 

 


WRITTEN BY
CatDarkGame
Technical Artist dhwlgn12@gmail.com

,