在 Unity Universal Render Pipeline 中透明渲染是开发中绕不开的话题。本文从底层原理出发深入讲解 Alpha Test硬边缘透明度测试和 Alpha Blend半透明度混合的实现方式、Shader 编写以及在实践中的排序问题与优化策略。一、为什么需要透明渲染在 3D 渲染中不透明物体可以简单依赖深度缓冲Z-Buffer来正确遮挡——离摄像机近的像素覆盖远的像素。但现实世界中大量物体并非完全不透明玻璃、水面、烟雾、树叶、UI 元素……它们要么有明确的切割边缘如树叶贴图中的镂空部分要么呈现连续的半透明效果如毛玻璃。这就是透明渲染要解决的两类问题。在 Unity URP 中透明渲染的核心分为两种策略Alpha Test透明度测试通过阈值判断像素的去留产生硬边缘的镂空效果Alpha Blend透明度混合将像素颜色与帧缓冲中已有颜色按比例混合产生平滑的半透明效果二、Alpha Test 透明度测试硬边缘2.1 基本原理Alpha Test 的核心逻辑非常简单在片元着色器Fragment Shader中取当前像素的 Alpha 值与一个阈值Cutoff比较。如果 Alpha 值低于阈值就丢弃discard该片元不写入颜色缓冲和深度缓冲否则正常写入。if (alpha cutoff) → discard丢弃片元if (alpha ≥ cutoff) → 正常渲染写入 Color Depth关键特性Alpha Test 的每个像素最终只有两种结果——完全显示或完全丢弃不存在中间状态。因此它产生的是硬边缘效果常见于树叶、栅栏、字符等贴图的镂空处理。2.2 HLSL 实现在 URP 中Alpha Test 可以在片元着色器中使用clip()函数实现也可以使用 ShaderLab 的AlphaToMask On指令对应旧的AlphaTest。以下是一个完整的 URP Alpha Test Shader 示例Shader Custom/URP/AlphaTest { Properties { _BaseMap(Base Map, 2D) white {} _BaseColor(Base Color, Color) (1,1,1,1) _Cutoff(Alpha Cutoff, Range(0,1)) 0.5 } SubShader { Tags { RenderPipeline UniversalPipeline RenderType TransparentCutout Queue AlphaTest } Pass { Name ForwardLit Tags { LightMode UniversalForward } // 开启 AlphaToMask等效旧版 AlphaTest AlphaToMask On Cull Back ZWrite On ZTest LEqual HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; half fogFactor : TEXCOORD1; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; float4 _BaseColor; half _Cutoff; CBUFFER_END Varyings vert(Attributes input) { Varyings output; VERTEX_SETUP(input); output.positionCS TransformObjectToHClip(input.positionOS.xyz); output.uv TRANSFORM_TEX(input.uv, _BaseMap); output.fogFactor ComputeFogFactor(output.positionCS.z); return output; } half4 frag(Varyings input) : SV_Target { half4 baseColor SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; // 核心Alpha Test // clip() 在参数 0 时丢弃当前片元 clip(baseColor.a - _Cutoff); baseColor.rgb MixFog(baseColor.rgb, input.fogFactor); return baseColor; } ENDHLSL } } }代码要点第 24 行的AlphaToMask On告诉 GPU 在片元着色器之后执行 Alpha 测试第 71 行的clip(baseColor.a - _Cutoff)是 HLSL 中实现 Alpha Test 的标准写法。当baseColor.a - _Cutoff 0时片元被丢弃。2.3 AlphaToMask 与 Multi-Sample Anti-Aliasing当启用AlphaToMask On时GPU 不会简单地使用clip()而是利用MSAA多重采样抗锯齿的覆盖率Coverage机制。在 MSAA 启用的条件下每个像素的多个采样点会根据 Alpha 值独立决定是否通过测试从而在边缘处产生近似抗锯齿的效果。这是一种免费的边缘柔化方案。三、Alpha Blend 透明度混合半透明3.1 基本原理Alpha Blend 不丢弃任何片元而是将片元的颜色与帧缓冲中已有的颜色进行加权混合。混合公式为Cresult Csrc × αsrc Cdst × (1 − αsrc)其中Csrc是当前片元颜色αsrc是片元的 Alpha 值Cdst是帧缓冲中已有的颜色。混合的最终结果取决于片元渲染的先后顺序——这是 Alpha Blend 产生排序问题的根源。3.2 ShaderLab 混合指令Unity ShaderLab 中通过Blend指令配置混合参数// 标准 Alpha 混合最常用 Blend SrcAlpha OneMinusSrcAlpha // 预乘 Alpha 混合性能更好 Blend One OneMinusSrcAlpha // 加法混合常用于粒子、发光效果 Blend SrcAlpha One // 完整语法Blend SrcFactor DstFactor, SrcFactorA DstFactorA // 前两个参数控制 RGB 混合后两个控制 Alpha 混合3.3 HLSL 实现以下是 URP 中的完整 Alpha Blend ShaderShader Custom/URP/AlphaBlend { Properties { _BaseMap(Base Map, 2D) white {} _BaseColor(Base Color, Color) (1,1,1,0.5) } SubShader { Tags { RenderPipeline UniversalPipeline RenderType Transparent Queue Transparent } Pass { Name ForwardLit Tags { LightMode UniversalForward } // ★ 关键区别AlphaBlend 需要 ZWrite Off Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Cull Back ZTest LEqual HLSLPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fog #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl struct Attributes { float4 positionOS : POSITION; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; half fogFactor : TEXCOORD1; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); CBUFFER_START(UnityPerMaterial) float4 _BaseMap_ST; float4 _BaseColor; CBUFFER_END Varyings vert(Attributes input) { Varyings output; VERTEX_SETUP(input); output.positionCS TransformObjectToHClip(input.positionOS.xyz); output.uv TRANSFORM_TEX(input.uv, _BaseMap); output.fogFactor ComputeFogFactor(output.positionCS.z); return output; } half4 frag(Varyings input) : SV_Target { half4 baseColor SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; // 注意不需要 clip()让所有片元都参与混合 // 返回值中的 Alpha 将被 Blend 指令使用 baseColor.rgb MixFog(baseColor.rgb, input.fogFactor); return baseColor; } ENDHLSL } } }⚠️ 核心区别Alpha Blend 必须设置ZWrite Off第 24 行。因为如果透明物体写入深度后面的物体无论透明或不透明都会被错误地遮挡。同时片元着色器中不使用clip()——所有片元都参与混合。四、Alpha Test vs Alpha Blend 对比特性Alpha Test透明度测试Alpha Blend透明度混合ShaderLab 指令AlphaToMask OnBlend SrcAlpha OneMinusSrcAlphaRenderTypeTransparentCutoutTransparentRender QueueAlphaTest2450Transparent3000深度写入✅ZWrite On❌ZWrite OffHLSL 关键函数clip(alpha - cutoff)无直接输出颜色 Alpha视觉效果硬边缘像素级取舍平滑半透明连续过渡排序依赖不依赖排序深度缓冲足够严格依赖绘制顺序典型用途树叶、栅栏、字符镂空玻璃、水、烟雾、UI性能较高Early-Z 仍有效较低需要排序 Overdraw五、透明渲染的排序问题5.1 为什么需要排序前面提到Alpha Blend 的混合公式具有不可交换性(A 混合 B) ≠ (B 混合 A)例红色(α0.5) 混合 蓝色(α0.5)正序C 0.5×红 0.5×蓝 → 偏紫反序C 0.5×蓝 0.5×红 → 同样偏紫此例相同但当 α 不同时红(α0.3)→蓝(α0.8) ≠ 蓝(α0.8)→红(α0.3)因此要获得正确的透明混合结果必须按照从远到近Back-to-Front的顺序渲染半透明物体。这个约束在只有一个半透明物体时不构成问题但当多个半透明物体相互交叉时就变得非常棘手。5.2 排序的三种级别物体级排序Object Sorting按物体包围盒中心到摄像机的距离排序。简单但粗糙无法处理交叉物体。面级排序Triangle Sorting按每个三角面到摄像机的距离排序。更精确但开销更大。像素级排序Per-Pixel Sorting逐像素排序所有重叠的片元。最精确但性能开销最大OIT 技术。5.3 物体交叉问题核心矛盾基于深度值的排序无论物体级还是面级只能处理不交叉的情况。当两个半透明物体的几何体相互穿插时不存在一个正确的整体排序——A 的某些部分在 B 前面某些在 B 后面。这就是经典透明排序问题的本质。5.4 ZWrite Off 带来的连锁问题Alpha Blend 关闭深度写入后还会引发一系列衍生问题不透明物体错误遮挡透明物体如果渲染顺序不当后渲染的不透明物体可能覆盖已渲染的透明物体。URP 通过 Render Queue 机制不透明Geometry 2000透明Transparent 3000来保证不透明物体先渲染。透明物体之间的深度测试失效由于不写入深度两个透明物体之间的遮挡关系完全依赖排序深度测试ZTest只能用于裁剪在完全不透明物体背后的透明片元。后处理效果异常某些后处理效果如 SSAO、深度重建依赖深度缓冲的完整性ZWrite Off 会导致透明物体在这些效果中消失。六、URP 中的透明渲染管线Unity URP 通过Render Queue值来控制渲染顺序确保不透明物体在透明物体之前渲染// URP 默认 Render Queue 值 Background → Queue 1000 // 天空盒、背景 Geometry → Queue 2000 // 不透明物体默认 AlphaTest → Queue 2450 // 透明度测试在不透明之后 Transparent → Queue 3000 // 半透明物体 Overlay → Queue 4000 // UI、镜头光晕等覆盖层 // URP Forward Renderer 渲染流程 // 1. Setup → 2. Main Light Shadow → 3. Additional Shadows // 4. Depth Prepass → 5. Opaque Objects → 6. Skybox // 7. Transparent Objects自动按距离排序→ 8. Post ProcessingURP 的透明排序在 URP 的 Forward Rendering 路径中透明物体Queue ≥ 3000会按照物体中心点到摄像机的距离自动进行 Back-to-Front 排序。这是物体级排序对于大多数简单场景已经足够但无法处理物体交叉的情况。七、排序问题的解决方案7.1 常规优化策略避免不必要的透明能用 Alpha Test 解决的场景如树叶镂空不要使用 Alpha Blend。Alpha Test 写入深度不依赖排序。拆分交叉物体将交叉的半透明物体拆分为不交叉的子网格减少排序冲突。使用双 Pass 渲染第一 Pass 用Cull Front渲染背面第二 Pass 用Cull Back渲染正面保证每个物体内部的面排序正确。手动设置 Render Queue通过Queue Transparent100手动调整透明物体的渲染顺序。启用 Depth Prepass在 URP 中启用 Depth Prepass 可以提前构建深度缓冲帮助裁剪被遮挡的透明片元。7.2 双 Pass 透明渲染示例对于凸面半透明物体如玻璃球使用双 Pass 可以完美解决物体自身的排序问题// Pass 1: 渲染背面先渲染远处的面 Pass { Name Back Cull Front // 剔除正面只渲染背面 ZWrite Off Blend SrcAlpha OneMinusSrcAlpha // ... HLSL 省略同上 } // Pass 2: 渲染正面后渲染近处的面 Pass { Name Front Cull Back // 剔除背面只渲染正面 ZWrite Off Blend SrcAlpha OneMinusSrcAlpha // ... HLSL 省略同上 }7.3 OIT排序无关透明对于复杂的透明场景传统的排序方法可能完全不够用。这时可以考虑OITOrder-Independent Transparency排序无关透明技术。OIT 的核心思想是不依赖绘制顺序在像素级别正确地混合所有透明片元。各方案的优缺点技术精度性能开销实现难度适用场景Depth Peeling精确高N 层 N 个 Pass中透明层数少的场景Weighted Blended近似低单 Pass低粒子、体积雾等大量半透明Per-Pixel Linked List精确中~高显存占用大高复杂透明场景八、实战建议与性能优化8.1 选择正确的透明策略能用不透明就用不透明不透明物体性能最好有完整的深度缓冲和 Early-Z 优化。需要镂空 → Alpha Test树叶、栅栏、字符等场景优先使用 Alpha Test clip()。它写入深度不依赖排序。需要半透明 → Alpha Blend玻璃、水、烟雾等必须使用 Alpha Blend但要注意排序问题。两者结合有些材质既有镂空又有半透明如带镂空的半透明旗帜可以用 Alpha Test 处理镂空Alpha Blend 处理半透明区域。在片元着色器中先clip()再输出混合颜色。8.2 性能注意事项Alpha Test 的 Early-Z 破坏虽然 Alpha Test 写入深度但clip()在片元着色器中执行可能导致部分 GPU 的 Early-Z 优化失效。现代 GPUNVIDIA Turing、AMD RDNA有Early DepthStencil Test和Conservative Depth等机制来缓解。Alpha Blend 的 Overdraw透明物体不会因深度测试而跳过片元导致大量 Overdraw。控制屏幕上透明物体的覆盖面积是关键。排序开销URP 每帧对透明物体进行 CPU 端排序Quick Sort透明物体数量过多时排序本身成为瓶颈。建议控制透明物体总数。预乘 AlphaPre-multiplied Alpha使用Blend One OneMinusSrcAlpha可以在某些情况下避免渲染顺序问题是移动端推荐的半透明方案。8.3 URP 调试透明问题URP 提供了多种调试透明渲染问题的方法// 1. Frame DebuggerWindow → Analysis → Frame Debugger // 可以逐步查看每个 Draw Call 的渲染结果和排序顺序 // 2. Wireframe 模式查看透明物体的面片排序 // Scene 视图左上角 → Wireframe // 3. 在 Shader 中临时输出深度值来验证深度缓冲 half4 frag(Varyings input) : SV_Target { // 调试可视化深度值 float depth input.positionCS.z; return half4(depth, depth, depth, 1.0); } // 4. Render Doc 捕获帧逐像素查看深度和模板值九、总结Alpha Test和Alpha Blend是图形渲染中最基础也最重要的两种透明处理方式。理解它们的底层原理、正确配置 ShaderLab 参数以及清醒地认识排序问题的本质是每个图形程序员必备的能力。在实际项目中没有银弹——每种技术都有其适用范围和局限性。关键是根据场景需求做出正确的选择并通过合理的排序策略和优化手段来弥补不足。在 URP 中合理利用 Render Queue、双 Pass 渲染、以及 Frame Debugger 调试工具可以解决绝大多数透明渲染问题。