Transparent DepthPre Pass.
배경.
다음 다이어그램은 대부분의 픽셀이 완전히 불투명하고 뒤에 있는 개체가 가려지지만 주변 픽셀이 투명하다는 것을 나타냅니다.
![](https://blog.kakaocdn.net/dn/sCOFV/btsswVkmr8F/xTXxsGZx0AVSGhwpGBgNM1/img.png)
여기서 PreZOff, PreZOn은 OpenGL ES 기반 패키지입니다. On은 Transparent DepthPre Pass를 켰다고, Off는 Transparent DepthPre Pass를 닫았다고 밝혔다.
기술 원리.
그런 다음 이러한 투명 오브젝트를 실제로 렌더링할 때 Early-Z 메커니즘으로 인해 계산이 필요하지 않은 픽셀을 조기에 제거하여 OverDraw를 줄일 수 있습니다.
예를 들어 다음 시나리오와 같이 두 개의 투명 오브젝트가 있습니다.
![](https://blog.kakaocdn.net/dn/cFqOQi/btssvVLJeBJ/8MZu7qk5kw4dWHhVfqghe1/img.png)
![](https://blog.kakaocdn.net/dn/cllrXA/btssGyajyxd/6T5zojm4zHKUUdKeRtKo10/img.png)
위의 예에서 볼 수 있듯이 Transparent DepthPre Pass를 켜면 녹색 투명 오브젝트를 그릴 때 많은 수의 픽셀이 Depth Test에서 제외됩니다.
이러한 재질은 모두 투명 재질이며 Early-Z를 파괴하지 않으므로 Early-Z를 사용하여 제거되므로 Pixel Shader가 실행되지 않았습니다.
Use Feature Level.
현재 Pipeline에서 ForwardRenderere.asset 를찾아 새 Renderer Features를 추가합니다.
Renderer Feature의 Queue를 Transparent로 변경합니다.
![](https://blog.kakaocdn.net/dn/p7pmx/btssCsPfkg4/fxXk93fDZ4PkLRwOSvJgTK/img.png)
![](https://blog.kakaocdn.net/dn/q9xpo/btssALu8v0O/k9NzHKlBnYkYRwWAN9guiK/img.png)
![](https://blog.kakaocdn.net/dn/R78qS/btssHg8roOT/YItGbZmGsxm7kFUeIu7ke1/img.png)
![](https://blog.kakaocdn.net/dn/sz1oG/btssAzBiKb4/cweIpQ8moXy1OffB3SfHd0/img.png)
![](https://blog.kakaocdn.net/dn/bXI46h/btssAC52okb/E6AyxttM2vfPAbm8pA2mKK/img.png)
TransparentPreZ Pass에서 실제 렌더링 시 투명도가 계산된 방식에 따라 다시 계산한 다음 지정된 임계값에 따라 discard가 필요한지 여부를 결정합니다.
![](https://blog.kakaocdn.net/dn/cFIa10/btssHyOEODo/jSgPoY6R9AyVCNGVRaexk1/img.png)
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은 더 이상 하드웨어를 거의 사용하지 않습니다.