TECHARTNOMAD | TECHARTFLOWIO.COM

GRAPHICS PROGRAMMING

Transparent DepthPre Pass.

jplee 2023. 8. 31. 00:53

배경.

Blend가 필요하기 때문에 투명한 오브젝트의 렌더링은 종종 많은 수의 오버Draw로 가득 차 있으며, 스택의 수가 많을수록 재질이 복잡할수록 OverDraw의 효율성 영향이 커집니다.
그러나 실제로 많은 투명 오브젝트가 완전히 투명하지는 않지만 많은 수의 픽셀이 완전히 불투명하거나 거의 불투명한 경우가 많습니다.
이 경우 Blend가 SrcAlpha, OneMinusSrcAlpha를 계속 설정하는 경우 이러한 픽셀이 뒤에 있는 픽셀을 완전히 가리를 것이라고 가정할 수 있습니다.

 

이 경우 이러한 Blend 상태에서 최종 이미지 효과에 거의 기여하지 않으므로 가려진 픽셀을 렌더링할 필요가 없습니다.
이를 통해 투명한 물체의 OverDraw를 줄이기 위해 관련 최적화가 수행될 것이라고 가정할 수 있습니다.

다음 다이어그램은 대부분의 픽셀이 완전히 불투명하고 뒤에 있는 개체가 가려지지만 주변 픽셀이 투명하다는 것을 나타냅니다.

여기서 PreZOff, PreZOn은 OpenGL ES 기반 패키지입니다. On은 Transparent DepthPre Pass를 켰다고, Off는 Transparent DepthPre Pass를 닫았다고 밝혔다.

 

기술 원리.

배경의 설명에 따라 투명도가 불투명한 픽셀에 대해 깊이를 미리 작성하기만 하면 됩니다.

그런 다음 이러한 투명 오브젝트를 실제로 렌더링할 때 Early-Z 메커니즘으로 인해 계산이 필요하지 않은 픽셀을 조기에 제거하여 OverDraw를 줄일 수 있습니다.

 

예를 들어 다음 시나리오와 같이 두 개의 투명 오브젝트가 있습니다.

일반적으로 녹색 개체는 다음과 같이 빨간색 개체를 다시 그립니다.

Transparent DepthPre Pass를 켜면 다음과 같이 깊이를 그려나자 합니다.

그런 다음 다음과 같이 투명한 오브젝트를 그립니다.
 

위의 예에서 볼 수 있듯이 Transparent DepthPre Pass를 켜면 녹색 투명 오브젝트를 그릴 때 많은 수의 픽셀이 Depth Test에서 제외됩니다.

이러한 재질은 모두 투명 재질이며 Early-Z를 파괴하지 않으므로 Early-Z를 사용하여 제거되므로 Pixel Shader가 실행되지 않았습니다.

그 이후로 OverDraw 픽셀을 낮추는 것이 달성되었습니다.
미리 개체를 그리면 Unity와 함께 제공되는 RenderObjects 기능을 활용하여 Transparent DepthPre Pass를 구현할 수 있습니다.
 

Use Feature Level.

현재 Pipeline에서 ForwardRenderere.asset 를찾아 새 Renderer Features를 추가합니다.

 
Renderer Feature 이름을 TransparentPreZ로 변경합니다( 직접 정의할 수 있습니다):

Renderer Feature의 Event를 Before Rendering Transaprents로 변경합니다.

Renderer Feature의 Queue를 Transparent로 변경합니다.

Renderer Feature의 Layer Mask를 Everything으로 변경합니다(주로 DepthPrepass를만들 려는 투명 재질을 그리는 Layer):
Renderer Feature를 설정하여 TransparentPreZ로 지정된 지정된 Shader Passes를 사용하여 객체를 그리십시오(Pass 이름을 직접 지정할 수 있습니다).
모든 투명 오브젝트의 재질 인재질shader에 TransparentPreZ Pass 추가:
TransparentPreZ Pass에서 색상 쓰기를 끄고 깊이 테스트 및 깊이 쓰기를 켭니다.

TransparentPreZ Pass에서 실제 렌더링 시 투명도가 계산된 방식에 따라 다시 계산한 다음 지정된 임계값에 따라 discard가 필요한지 여부를 결정합니다.

 
Shader "Custum/TransparentPreZ"
{
    Properties
    {
        _BaseMap("Texture", 2D) = "white" {}
        _BaseColor("Color", Color) = (1, 1, 1, 1)
        _Cutoff("AlphaCutout", Range(0.0, 1.0)) = 0.5
 
        // BlendMode
        [HideInInspector] _Surface("__surface", Float) = 0.0
        [HideInInspector] _Blend("__blend", Float) = 0.0
        [HideInInspector] _AlphaClip("__clip", Float) = 0.0
        [HideInInspector] _SrcBlend("Src", Float) = 1.0
        [HideInInspector] _DstBlend("Dst", Float) = 0.0
        [HideInInspector] _ZWrite("ZWrite", Float) = 1.0
        [HideInInspector] _Cull("__cull", Float) = 2.0
 
        // Editmode props
        [HideInInspector] _QueueOffset("Queue offset", Float) = 0.0
 
        // ObsoleteProperties
        [HideInInspector] _MainTex("BaseMap", 2D) = "white" {}
        [HideInInspector] _Color("Base Color", Color) = (0.5, 0.5, 0.5, 1)
        [HideInInspector] _SampleGI("SampleGI", float) = 0.0 // needed from bakedlit
    }
 
    SubShader
    {
        Tags { "RenderType" = "Opaque" "IgnoreProjector" = "True" "RenderPipeline" = "UniversalPipeline" }
        LOD 100
 
        Blend[_SrcBlend][_DstBlend]
        ZWrite[_ZWrite]
        Cull[_Cull]
 
        Pass
        {
            Name "Unlit"
            HLSLPROGRAM
            // Required to compile gles 2.0 with standard srp library
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
 
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature _ALPHATEST_ON
            #pragma shader_feature _ALPHAPREMULTIPLY_ON
 
            // -------------------------------------
            // Unity defined keywords
            #pragma multi_compile_fog
            #pragma multi_compile_instancing
 
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
 
            struct Attributes
            {
                float4 positionOS       : POSITION;
                float2 uv               : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            struct Varyings
            {
                float2 uv        : TEXCOORD0;
                float fogCoord : TEXCOORD1;
                float4 vertex : SV_POSITION;
 
                UNITY_VERTEX_INPUT_INSTANCE_ID
                UNITY_VERTEX_OUTPUT_STEREO
            };
 
            Varyings vert(Attributes input)
            {
                Varyings output = (Varyings)0;
 
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
 
                VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
                output.vertex = vertexInput.positionCS;
                output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
                output.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);
 
                return output;
            }
 
            half4 frag(Varyings input) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
 
                half2 uv = input.uv;
                half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
                half3 color = texColor.rgb * _BaseColor.rgb;
                half alpha = texColor.a * _BaseColor.a;
                AlphaDiscard(alpha, _Cutoff);
 
    #ifdef _ALPHAPREMULTIPLY_ON
                color *= alpha;
    #endif
 
                color = MixFog(color, input.fogCoord);
 
                return half4(color, alpha);
            }
            ENDHLSL
        }
 
        Pass
        {
            Tags{"LightMode" = "DepthOnly"}
 
            ZWrite On
            ColorMask 0
 
            HLSLPROGRAM
            // Required to compile gles 2.0 with standard srp library
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma target 2.0
 
            #pragma vertex DepthOnlyVertex
            #pragma fragment DepthOnlyFragment
 
            // -------------------------------------
            // Material Keywords
            #pragma shader_feature _ALPHATEST_ON
 
            //--------------------------------------
            // GPU Instancing
            #pragma multi_compile_instancing
 
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/DepthOnlyPass.hlsl"
            ENDHLSL
        }
 
        // This pass it not used during regular rendering, only for lightmap baking.
        Pass
        {
            Name "Meta"
            Tags{"LightMode" = "Meta"}
 
            Cull Off
 
            HLSLPROGRAM
            // Required to compile gles 2.0 with standard srp library
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma vertex UniversalVertexMeta
            #pragma fragment UniversalFragmentMetaUnlit
 
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitMetaPass.hlsl"
 
            ENDHLSL
        }
 
        Pass
        {
            Name "TransparentPreZ"
            Tags{"LightMode" = "TransparentPreZ"}
 
            ColorMask 0
            ZWrite On
 
            HLSLPROGRAM
 
            // Required to compile gles 2.0 with standard srp library
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
 
            #pragma vertex vert
            #pragma fragment frag
            #pragma shader_feature _ALPHATEST_ON
            #pragma shader_feature _ALPHAPREMULTIPLY_ON
 
            // -------------------------------------
            // Unity defined keywords
            #pragma multi_compile_fog
            #pragma multi_compile_instancing
 
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"
 
            struct Attributes
            {
                float4 positionOS       : POSITION;
                float2 uv               : TEXCOORD0;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };
 
            struct Varyings
            {
                float2 uv        : TEXCOORD0;
                float fogCoord : TEXCOORD1;
                float4 vertex : SV_POSITION;
 
                UNITY_VERTEX_INPUT_INSTANCE_ID
                UNITY_VERTEX_OUTPUT_STEREO
            };
 
            Varyings vert(Attributes input)
            {
                Varyings output = (Varyings)0;
 
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_TRANSFER_INSTANCE_ID(input, output);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
 
                VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);
                output.vertex = vertexInput.positionCS;
                output.uv = TRANSFORM_TEX(input.uv, _BaseMap);
                output.fogCoord = ComputeFogFactor(vertexInput.positionCS.z);
 
                return output;
            }
 
            half4 frag(Varyings input) : SV_Target
            {
                half2 uv = input.uv;
                half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv);
                half3 color = texColor.rgb * _BaseColor.rgb;
                half alpha = texColor.a * _BaseColor.a;
 
                if (alpha < 0.95) discard;
 
                return half4(color, alpha);
            }
 
            ENDHLSL
        }
    }
 
    FallBack "Hidden/Universal Render Pipeline/FallbackError"
    CustomEditor "UnityEditor.Rendering.Universal.ShaderGUI.UnlitShader"
}

 

 

성능 데이터입니다.
다음은 테스트 데모의 테스트 데이터입니다.

 

뎁스 프리패스의 필수 요소

뎁스 프리패스

깊이 경로만 먼저 그려서 조각 처리를 가능하게 하는 기법으로, 조각(픽셀) 처리를 할 때 미리 처리된 깊이를 사용하는 것만으로도 알파 테스트의 문제를 해결할 수 있다고 합니다. 캐시의 무결성을 보장할 수 없다는 문제도 해결하셨나요...? 이 기능을 활성화하면 픽셀 처리 전에 섀도잉 후 깊이만 먼저 그리는 패스가 있습니다.

물론 이 만병통치약 같은 뎁스 프리패스는 기본적으로 켜져 있지 않습니다. 기본값이 꺼져 있기 때문에 이전 알파 테스트에서 여전히 문제가 발생할 수 있지만 일부 특이한 경우에는 뎁스 프리패스를 켤 수 있습니다. 이러한 특이한 경우 중 하나는 '뎁스 텍스처' 옵션으로, 부드러운 파티클, 리플 뎁스 페이드 기능 등에 사용해야 합니다.


💡 이 옵션을 활성화하면 불투명을 그리기 전에 뎁스만 먼저 그리기 때문에 뎁스 프리패스가 실행되어 알파 테스트가 무거워지는 문제를 해결할 수 있다고 합니다.


이 기능을 끄면 뎁스 프리패스가 사라지고 알파 테스트가 무거워지는 문제가 다시 발생합니다. 캐시를 사용할 수 없고 메모리에 계속 접근해서 원래의 깊이를 읽어야 합니다.

사양을 낮게 만들면 > 옵션을 끄고 무겁게 만들 수 있다고 합니다.


최신 기술 동향

URP가 등장한 이후 많은 변화가 있었습니다. 동시에 하드웨어의 세계도 바뀌었습니다.
💡 알파 테스트의 가장 큰 난제는 '깊이 처리' 문제와 'TBDR 깨기'였지만, 최근에는 TBDR 문제가 해결되고 있습니다.


PowerVR 칩만 이 기술을 사용하고 있었는데, 아이폰7 이후 주요 휴대폰 업체들이 더 이상 이 칩을 사용하지 않기 때문이죠... 즉, TBDR은 더 이상 하드웨어를 거의 사용하지 않습니다.