URP 渲染实战 / 第 3 章 第一个 URP Shader / 案例 3.4
案例 3.4透明度裁剪 Alpha Clip入门
目标:让贴图 alpha 低于阈值的像素“消失”,做出镂空的树叶、栅栏、铁丝网。
Assets/Cookbook/Ch03/C03_04_AlphaClip.shader
// 第 4 步:透明度裁剪(Alpha Clip / Alpha Test)。alpha 低于阈值的像素直接丢弃。 Shader "Cookbook/Ch03/AlphaClip" { Properties { [MainTexture] _BaseMap ("Base Map", 2D) = "white" {} [MainColor] _BaseColor ("Base Color", Color) = (1, 1, 1, 1) _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5 // 剪掉的地方能看到背面,所以通常关掉背面剔除:0=Off 1=Front 2=Back [Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 0 } SubShader { // AlphaTest 队列(2450)排在普通不透明物体之后,RenderType 让替换着色/工具识别它 Tags { "RenderType"="TransparentCutout" "RenderPipeline"="UniversalPipeline" "Queue"="AlphaTest" } Pass { Name "Unlit" Tags { "LightMode"="UniversalForward" } Cull [_Cull] // 渲染状态可以引用属性,用方括号 HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; half4 color : COLOR; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; half4 color : COLOR; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); float4 _BaseMap_ST; half4 _BaseColor; half _Cutoff; Varyings vert (Attributes input) { Varyings output; output.positionCS = TransformObjectToHClip(input.positionOS.xyz); output.uv = TRANSFORM_TEX(input.uv, _BaseMap); output.color = input.color; return output; } half4 frag (Varyings input) : SV_Target { half4 col = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor * input.color; // clip(x):x < 0 时丢弃当前片元,不写颜色也不写深度 clip(col.a - _Cutoff); return half4(col.rgb, 1); // 不透明物体输出 alpha=1 } ENDHLSL } } }
// 第 4 步:透明度裁剪(Alpha Clip / Alpha Test)。alpha 低于阈值的像素直接丢弃。 Shader "Cookbook/Ch03/AlphaClip" { SubShader { // AlphaTest 队列(2450)排在普通不透明物体之后,RenderType 让替换着色/工具识别它 Tags { "RenderType"="TransparentCutout" "RenderPipeline"="UniversalPipeline" "Queue"="AlphaTest" } Pass { Name "Unlit" Tags { "LightMode"="UniversalForward" } Cull [_Cull] // 渲染状态可以引用属性,用方括号 GLSLPROGRAM VARYING vec2 uv; VARYING vec4 vColor; uniform sampler2D _BaseMap; uniform vec4 _BaseMap_ST; uniform vec4 _BaseColor; uniform float _Cutoff; #ifdef VERTEX void main() { gl_Position = TransformObjectToHClip(positionOS.xyz); uv = TransformTex(texcoord0.xy, _BaseMap_ST); vColor = color; } #endif #ifdef FRAGMENT void main() { vec4 col = texture(_BaseMap, uv) * _BaseColor * vColor; // clip(x):x < 0 时丢弃当前片元,不写颜色也不写深度。GLSL 里写成 discard if (col.a - _Cutoff < 0.0) discard; fragColor = vec4(col.rgb, 1); // 不透明物体输出 alpha=1 } #endif ENDGLSL } } }
核心只有一行 clip(col.a - _Cutoff)。clip(x) 是 HLSL 内置函数:参数的任一分量小于 0 时,丢弃(discard)当前片元——不写颜色,也不写深度,就像这个像素从没被画过。其余几处配套改动同样重要:
- Queue 改成 AlphaTest(2450):让镂空物体在普通不透明物体之后画。原因与 Early-Z 有关:GPU 通常在片元着色器之前就做深度测试,把被挡住的像素提前剔掉;但含
clip/discard的着色器无法在执行前确定深度是否要写,部分硬件会因此关闭提前深度写入。先画完“确定写深度”的普通物体,再画镂空物体,能让后者也从前者写好的深度里受益。 - RenderType 改成 TransparentCutout:给依赖 RenderType 的工具一个正确分类。
- Cull [_Cull]:镂空后能透过孔看到物体背面,所以默认关闭背面剔除(Cull Off)。渲染状态可以用方括号引用属性值,配合
[Enum(UnityEngine.Rendering.CullMode)]就能在面板上选 Off/Front/Back。 - 返回的 alpha 写成 1:不透明物体的 alpha 一般没人读,但如果开启了某些依赖 alpha 通道的后处理或截图,写 1 更安全。URP 用
OutputAlpha函数处理这件事。

URP 自带 Shader 并不直接写 clip,而是用 ShaderVariablesFunctions.hlsl 里的 AlphaDiscard,并且只有在 _ALPHATEST_ON 关键字开启时才裁剪:
// 摘自 URP 14.0.12 ShaderLibrary/ShaderVariablesFunctions.hlsl real AlphaDiscard(real alpha, real cutoff, real offset = real(0.0)) { #if defined(_ALPHATEST_ON) if (IsAlphaDiscardEnabled()) alpha = AlphaClip(alpha, cutoff + offset); #endif return alpha; }
用关键字包起来的原因是:只要着色器里出现了 discard,哪怕运行时从不触发,也可能影响 Early-Z 和移动端 TBDR 架构上的隐藏面剔除效率。所以不需要镂空的材质,最好连这条指令都不要编译进去。我们会在案例 3.8 用关键字实现同样的开关;它对性能的具体影响在第 29 章讨论,Alpha to Coverage 等让镂空边缘更柔和的技巧在第 16 章。