WinCNT

커스텀 셰이더에 Meta Pass를 추가했더니 SRP Batcher가 제대로 작동하지 않게 된 이슈 본문

Unity/Unity 개발 중 발생한 이슈 정리

커스텀 셰이더에 Meta Pass를 추가했더니 SRP Batcher가 제대로 작동하지 않게 된 이슈

WinCNT_SSS 2023. 7. 21. 11:17

발생한 이슈

SRP Batcher가 작동하지 않아서 모든 오브젝트를 하나씩 그리고 있는 상황이 발생했다


이슈 상세

직접적인 원인 자체는 단순했다

라이트 맵 구현을 위해 셰이더에 Meta Pass를 추가했는데 그로 인해 CBuffer에 문제가 생겨서 SRP Batcher의 호환성이 깨져버린 것이었다


발생 원인

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

//#include "Packages/com.unity.render-pipelines.universal/Shaders/LitInput.hlsl"
//#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/UniversalMetaPass.hlsl"

발생 원인 중 하나는 Meta Pass의 구현을 위해 include한 UnityStandardMeta.cginc!!

…에서 include하고 있는 UnityCG.cginc였다

이 녀석은 URP에서는 이제 사용하지 않는다

 

다른 원인은 Meta Pass의 CBuffer의 사이즈가 기존 셰이더의 CBuffer와 일치하지 않는다는 점이었다


해결법

요컨대 UnityCG.cginc를 다른 코드로 변경하고, SubShader 내의 모든 Pass는 같은 CBUFFER_START(UnityPerMaterial)를 가질 필요가 있다는 것이다

 

CBuffer의 문제는 예전 ShadowCaster 때의 이슈와 비슷했다고 볼 수 있다

그 때는 연습용이였고 꼼수로 넘어갔지만, 그 업보를 청산할 때가 되었다

즉 코드를 분석해서 필요한 부분을 가져올 수 밖에 없었다

 

그리고 UnityStandardMeta.cginc를 Core.hlsl와 MetaInput.hlsl로 교체해면 vert_meta라는 버텍스 셰이더를 사용할 수 없게 되므로 그쪽도 따로 구현해줘야 했다

Pass
{
    // 커스텀 셰이더의 Pass
    // 자세한 내용들은 생략
    HLSLPROGRAM

    // 커스텀 셰이더 Pass의 CBUFFER
    // sampler2D와 같은 텍스처는 사실 CBUFFER의 사이즈에 영향을 주지 않는다
    // (CBUFFER 밖으로 빼도 됨)
    // 따라서 sampler2D를 제외하면 밑의 Meta Pass와 같은 사이즈인 상태이다
    CBUFFER_START(UnityPerMaterial)
    sampler2D _Control;

    sampler2D _MainTex0;
    float4    _MainTex0_ST;
    float4    _EmissionMap0_HDR;
    sampler2D _EmissionMap0;
    sampler2D _BumpMap0;

    sampler2D _MainTex1;
    float4    _MainTex1_ST;
    float4    _EmissionMap1_HDR;
    sampler2D _EmissionMap1;
    sampler2D _BumpMap1;
    CBUFFER_END
    ENDHLSL

    // 기타 내용들은 생략...
}

Pass
{
    Name "META"
    Tags {"LightMode"="Meta"}
    Cull Off
    
    HLSLPROGRAM
    #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    #include "Packages/com.unity.render-pipelines.universal.custom/ShaderLibrary/MetaInput.hlsl"
    
    #pragma vertex vert_meta_CustomToon
    #pragma fragment frag_meta_CustomToon
    
    #pragma shader_feature _EMISSION
    // #pragma shader_feature _METALLICGLOSSMAP
    // #pragma shader_feature ___ _DETAIL_MULX2
    // #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
    // #pragma shader_feature EDITOR_VISUALIZATION
    
    // Meta Pass에서는 sampler2D를 CBUFFER 밖에 위치시켰다
    // sampler2D를 제외하면 위의 Pass와 CBUFFER_START의 사이즈가 같으므로
    // 현 상태로도 SRP Batcher의 호환성은 문제 없다
    sampler2D _Control;
    sampler2D _MainTex0;
    sampler2D _EmissionMap0;
    sampler2D _MainTex1;
    sampler2D _EmissionMap1;

    CBUFFER_START(UnityPerMaterial)
    float4    _MainTex0_ST;
    float4    _EmissionMap0_HDR;
    float4    _MainTex1_ST;
    float4    _EmissionMap1_HDR;
    CBUFFER_END
    
    struct VertexInput_CustomToon
    {
        float3 pos : POSITION;
        float2 uv : TEXCOORD0;
        float2 lightmapUV: TEXCOORD1;
        float2 controlUV: TEXCOORD2;
        UNITY_VERTEX_INPUT_INSTANCE_ID
    };
    
    struct v2f_meta_CustomToon
    {
        float4 pos : SV_POSITION;
        float4 uv : TEXCOORD0;
        float2 controlUV: TEXCOORD1;
    };

    // UnityStandardMeta.cginc의 vert_meta와 MetaPass.hlsl의 UnityMetaVertexPosition를 바탕으로 만들었다
    v2f_meta_CustomToon vert_meta_CustomToon (VertexInput_CustomToon v)
    {
        v2f_meta_CustomToon o;

        if (unity_MetaVertexControl.x)
        {
            v.pos.xy = v.lightmapUV * unity_LightmapST.xy + unity_LightmapST.zw;
            v.pos.z = v.pos.z > 0 ? REAL_MIN : 0.0f;
        }
        o.pos = TransformWorldToHClip(v.pos);
        
        // 커스텀 셰이더의 독자적인 부분
        // (라이트 맵에 영향을 미치는 부분만을 똑같이 구현)
        o.uv.xy = v.uv * _MainTex0_ST.xy + _MainTex0_ST.zw;
        o.uv.zw = v.uv * _MainTex1_ST.xy + _MainTex1_ST.zw;
        o.controlUV = v.controlUV;
        
        return o;
    }
    
    // 커스텀 셰이더의 독자적인 부분
    // (라이트 맵에 영향을 미치는 부분만을 똑같이 구현)
    #define lerp_tex2d(texName, uv, rate) lerp(tex2D(texName##0, uv.xy),tex2D(texName##1, uv.zw), rate)
    float4 frag_meta_CustomToon(v2f_meta_CustomToon i): SV_Target
    {
        UnityMetaInput o;
        o = (UnityMetaInput)0;
        
        float control = tex2D(_Control, i.controlUV).x;
        float3 color = lerp_tex2d(_MainTex, i.uv, control).rgb;
        o.Albedo = color;
        
        o.Emission = lerp_tex2d(_EmissionMap, i.uv, control).rgb;
        return UnityMetaFragment(o);
    }
    ENDHLSL
}

결과

SRP Batcher가 호환 가능으로 바뀌었으며, 프레임 디버거에서도 오브젝트를 하나씩 그리지 않게 되었다


마무리

URP, 커스텀 셰이더, Meta Pass의 콜라보에 의한 이슈였다

다들 자료들이 별로 없어 정말 힘들다

아무튼 해결해서 다행이다!

그런데 커스텀 셰이더에서 다른 Pass를 사용하려면 이렇게 다 직접 구현해야 하는 게 맞나?


참고 자료

SRP Batcher に対応した UnityShaderVariables

 

SRP Batcher に対応した UnityShaderVariables | Nyahoon Games Pte. Ltd.

前回、SRP Batcher に対応するために CBUFFER を有効にしてから UnityCG.cginc をインクルードするという内容の記事を書いたんですが、今回はその続きです。 UnityCG.cginc (UnityShaderVariables.cginc) では SRP

nyahoon.com

SRP Batcher:レンダリングをスピードアップ | Unity Blog

 

SRP Batcher:レンダリングをスピードアップ | Unity Blog

Unity エディターには非常に柔軟性のあるレンダリングエンジンが搭載されています。フレームの間はいつでも、どの Material プロパティーでも更新することができます。さらに、Unity は、歴史

blog.unity.com