☰
Unity Shader透明效果全解析:渲染队列、混合模式与排序排查
2026/10/26 4:59:13 网站建设 项目流程

提示:本文所有代码均为通用示例,落地时请按自己的项目管线(内置管线或 URP)调整。

透明效果在 Unity Shader 里算是个"看起来简单、调起来上头"的活儿。很多人第一次写透明 Shader,把Queue改成Transparent、加一行Blend SrcAlpha OneMinusSrcAlpha,跑起来发现——诶,能透,但两个透明物体一叠就穿帮,物体自己身上也出现奇怪的深浅色块。这不是代码写错了,而是没搞懂透明渲染背后的那套顺序规则和混合数学。这篇就围绕 Unity Shader 透明效果这个主题,把渲染队列、混合模式、Alpha Test 与 Alpha Blend 的取舍、双面渲染、排序故障排查一路拆开讲透,附上可以直接抄的完整 Shader 代码和调参记录。适合刚学完基础光照、准备做玻璃、烟雾、水面的朋友,也适合那些"能出来效果但不知道为啥"的同学回头补一遍底层逻辑。

1. 透明效果的本质:先搞懂渲染管线在干什么

1.1 不透明物体与透明物体的渲染顺序差异

在动手写代码之前,先把一件事摆正:透明渲染不是"让颜色变淡",而是"让后面的颜色透上来"。这两者的区别决定了整个实现思路。不透明物体的渲染逻辑很干脆——开启深度测试(ZTest LEqual)、开启深度写入(ZWrite On),谁离相机近谁覆盖谁,因为最终看到的就是最近的那个表面,前面被挡住的东西画不画都无所谓(当然为了性能会有 Early-Z 剔除)。所以不透明物体的渲染顺序理论上可以随便打乱,结果一致。

透明物体就不行了。透明物体必须把后面已经画好的颜色"读"出来,跟自己这一层颜色按比例混合,才能得到最终像素。也就是说,透明物体的绘制依赖"它后面已经存在的东西"。这一条约束直接推出两个结论:第一,所有不透明物体必须先画完,否则透明层混合时背景是空的;第二,透明物体之间必须从远到近绘制,因为每一层都要叠加在已经混合好的结果上,顺序反了,混合出来的颜色就完全不同。

我举个生活化的例子。假设你有红色和蓝色两块半透明玻璃,透过它们看白墙。先叠红再叠蓝,和先叠蓝再叠红,最终你眼睛看到的那块紫,色相是不同的。显卡做的事跟这个一模一样,它按提交顺序逐个混合,所以顺序本身就是结果的一部分。Unity 用渲染队列(Render Queue)来做第一层保证——不透明队列全部先于透明队列;用物体排序来做第二层保证——透明队列内部按距离从远到近排。这两层机制只要有一层被绕过,画面就会出问题。

1.2 渲染队列的数值分配与背后的排序逻辑

Unity 的Tags { "Queue" = "XXX" }里那几个名字,本质就是一组整数编号,可以用Queue" = "Transparent+10"这种写法手动偏移。常用的几档,我列个表,方便你建立数值直觉:

队列名整数值典型用途深度写入
Background1000天空盒、远景背景通常是关闭或特殊处理
Geometry2000绝大多数不透明物体开启
AlphaTest2450镂空叶片、铁丝网开启
GeometryLast2500不透明队列的上界开启
Transparent3000玻璃、火、烟、水关闭
Overlay4000镜头光晕、UI 特效视情况

数值越小越先画,这一点要刻进肌肉记忆。AlphaTest放在 2450 这个位置是有讲究的:它介于不透明和透明之间,但因为 Alpha Test 是"要么全留要么全丢",留下的像素可以当不透明像素处理,所以它可以并且应该开启深度写入。这一点很多人踩坑——把 Alpha Test 的材质丢进 Transparent 队列,结果发现镂空边缘出现奇怪的深度冲突或者被后面的东西盖住,就是队列位置放错了。

同一个队列内部的排序规则,Unity 默认是按物体包围盒中心到相机的距离来排。注意是"中心",不是"最近点"。这就是为什么一个又长又大的透明平面(比如一条河)跨在相机侧面时,排序经常出错:它中心可能很远,但实际有一部分离相机很近,结果它被排在了别的物体前面画,导致错误遮挡。这个问题后面第 5 章会专门展开讲怎么绕。

1.3 为什么透明物体必须关闭深度写入

新手最常问的一个问题:既然透明物体也有深度,为什么不让它写深度?

原因是这样的。假设你面前有两层玻璃 A(近)和 B(远)。如果 A 先画并且写了深度,那么 B 在片元阶段做深度测试时,会发现"A 的深度比 B 更近",于是 B 的片元被丢弃——可是 B 应该透过 A 被看见啊,B 的颜色本该参与混合。结果就是你只能看到 A,B 整个消失。所以透明物体一旦写深度,就会把后面的透明层"挡死"。

因此标准做法是ZWrite Off。关闭深度写入后,A 画完不留下深度记录,B 仍然能通过深度测试(和更早的不透明场景比较),正常绘制并混合到 A 已经混合好的结果上。这时ZTest LEqual依然要保留,因为你需要它来跟不透明场景比——如果透明物体被一堵墙挡住,它就该被剔除,这个测试不能省。

禁用深度写入的代价是:透明物体自身的不同面之间没法正确遮挡。一个玻璃球,正面和背面都在同一个网格里,关闭 ZWrite 之后背面会先画或后画,取决于三角形提交顺序,结果就是球体内部深浅不一、看着像贴图错乱。这也是第 5 章要讲的"双面渲染"问题的根源。

2. 混合模式:透明效果的核心开关

2.1 Blend 命令的语法与常用组合

Unity ShaderLab 里混合的入口就是Blend指令,最完整的形式是Blend SrcFactor DstFactor。它的数学含义就是一句话:

最终颜色 = 源颜色 × SrcFactor + 目标颜色 × DstFactor

"源颜色"是当前这个片元着色器输出的颜色,"目标颜色"是颜色缓冲区里已经存在的内容(也就是背景 + 先前画好的透明层)。两个 Factor 就是你控制的系数,可以是常数(One、Zero),也可以是当前像素的通道值(SrcAlpha、OneMinusSrcAlpha、DstColor等)。理解了这个公式,所有 Blend 写法都不需要背。

最常见的三行,我建议你直接记住含义而不是记字符串:

// 传统 Alpha 混合:源颜色乘以自身 alpha,背景乘以 (1-alpha) Blend SrcAlpha OneMinusSrcAlpha // 预乘 Alpha 混合:源颜色已经乘过 alpha,这里只乘 1 Blend One OneMinusSrcAlpha // 叠加/发光类效果:源和目标直接相加 Blend One One

第一行是最常用的,它的效果是"alpha 为 0 完全透明,alpha 为 1 完全不透明",符合直觉。第三行的Blend One One是纯加法,颜色会越叠越亮,做光晕、火焰叠加、激光束非常好用,但要注意它会很快过曝成白色,通常需要配合较低的初始亮度或者 HDR 色调映射。

还有两个容易被忽略的变体:BlendOp用来改变混合运算本身(Add/Sub/RevSub/Min/Max),比如做"变暗"效果可以用BlendOp Min;而Blend后面跟四个参数的形式则允许 RGB 和 A 通道使用不同的因子,做特殊合成时才会用到。日常项目里,Blend SrcAlpha OneMinusSrcAlpha加上偶尔一次加法混合,基本能覆盖 90% 的需求。

2.2 混合因子的数学含义与常见搭配表

不把这几个 Factor 的物理意义搞明白,调效果基本靠猜。我按"它乘在谁身上"来归类:

因子数值含义典型场景
One1.0,原样保留预乘 Alpha 的源、加法混合
Zero0.0,完全丢弃只保留源或只保留目标
SrcAlpha当前片元的 alpha传统 Alpha 混合的源因子
OneMinusSrcAlpha1 - 当前片元 alpha传统 Alpha 混合的目标因子
DstColor背景颜色正片叠底式的暗化
OneMinusDstColor1 - 背景颜色提亮式叠加

有一组搭配我觉得值得单独拎出来说:Blend DstColor Zero,这是标准的"正片叠底"(multiply)。源颜色乘上背景色、目标乘以 0 被丢弃,结果就是两层颜色相乘,做阴影层、做暗角、做镜头脏污叠加都很好用。反过来Blend OneMinusDstColor One是"滤色"(screen)方向,适合做发光。

再补一个实际调试经验:alpha 通道本身也可以控制。默认Blend后的四个参数作用于 RGB,alpha 通道怎么混合由第二组参数决定(四个参数形式)。如果你的透明 Shader 后面还要被别的后处理读 alpha(比如做软粒子、做深度淡化),那就得留意输出 alpha 是否正确,否则会出现"看着透明、粒子边缘却是硬切"的诡异现象。

2.3 预乘 Alpha:为什么有些效果必须用它

预乘 Alpha(Premultiplied Alpha)这个概念初看很绕,但它解决了一个真实痛点:边缘发白或发黑。

传统Blend SrcAlpha OneMinusSrcAlpha的写法里,源颜色在混合时才乘 alpha。如果你的纹理是压缩格式(比如某些 DXT 压缩),RGB 和 A 是分开压缩的,在 alpha 接近 0 的边缘区域,RGB 的重建误差会被放大——因为那里颜色本来就没被乘过 alpha,误差是"裸"的。混合之后就会出现边缘一圈脏色或者发白。

预乘的做法是:在纹理制作阶段或者片元着色器里,先把 RGB 乘上 alpha 存起来,混合时源因子用One。

fixed4 c = tex2D(_MainTex, i.uv) * _Color; c.rgb *= c.a; // 预乘 return c; // 配合 Blend One OneMinusSrcAlpha

好处有两个:一是插值和压缩带来的边缘误差被 alpha 压制住了,边缘干净;二是多次叠加时数学上更"可加",不容易出现 alpha 溢出到 1 之后颜色过饱和的问题。粒子特效、光效叠加这两块,我现在的习惯是一律走预乘,出问题的概率明显低。

代价是:纹理制作流程要改,美术出的图得是预乘过的,或者你得在 Shader 里现算(现算会多一次乘法,性能影响可以忽略)。另外,如果你的纹理是从普通 PNG 直接导入的,直接用预乘流程会显得整体偏暗,因为 RGB 被乘了一次 alpha。这一点必须跟美术对齐,否则会来回改图。

3. 三种透明方案:Alpha Test、Alpha Blend 与混合策略

3.1 Alpha Test:用 clip 做"要么全留要么全丢"

Alpha Test 严格来说不叫"透明",它叫"镂空"。原理极其简单,在片元着色器里做一次判断,低于阈值的像素直接丢弃:

fixed4 c = tex2D(_MainTex, i.uv) * _Color; clip(c.a - _Cutoff); // alpha 小于 _Cutoff 的片元直接不生成 return c;

clip(x)的语义是:x 小于 0 就丢弃该片元,否则保留。所以写clip(c.a - _Cutoff),就是"alpha 小于阈值就丢"。

它的优势非常明显:留下的像素是彻底不透明的,可以正常写深度、正常接受光照阴影、正常参与不透明队列排序。做树叶、草地、铁丝网、破布、镂空窗花,Alpha Test 是最省事也最稳的方案,性能上也比 Alpha Blend 好——因为它不产生混合,不需要读背景,还能被 Early-Z 优化。

缺点同样明显:边缘是硬切的,没有过渡。哪怕你把_Cutoff调到 0.5,边缘仍然是锯齿状的,放大看就是一堆硬边。抗锯齿在 Alpha Test 上基本无效,MSAA 也帮不上太多忙(除非用 Alpha To Coverage)。另外,移动端上clip会打断某些 GPU 的早期深度优化,在低端机上有性能代价,如果场景里镂空物件特别多,需要留意一下。

顺带提一个进阶技巧:Alpha To Coverage。开启AlphaToMask On之后,硬件会把 alpha 值转换成多重采样的覆盖率,边缘会得到一种类似抖动的柔和过渡。它在有 MSAA 的管线里效果不错,尤其是植被。但它依赖 MSAA,如果项目用的是后处理抗锯齿(比如 TAA/FXAA),效果就没了。这个取舍需要按项目定。

3.2 Alpha Blend:真正的半透明与它的代价

Alpha Blend 才是大家心目中的"透明"。它不做丢弃,所有像素都画,靠混合公式让背景透上来。完整的最小实现长这样:

Shader "Custom/SimpleTransparent" { Properties { _MainTex ("Base Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) } SubShader { Tags { "Queue" = "Transparent" "RenderType" = "Transparent" "IgnoreProjector" = "True" } Pass { Tags { "LightMode" = "ForwardBase" } ZWrite Off Blend SrcAlpha OneMinusSrcAlpha Cull Back CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" #include "Lighting.cginc" sampler2D _MainTex; float4 _MainTex_ST; fixed4 _Color; struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; }; v2f vert (appdata v) { v2f o; o.pos = UnityObjectToClipPos(v.vertex); o.uv = TRANSFORM_TEX(v.uv, _MainTex); o.worldNormal = UnityObjectToWorldNormal(v.normal); o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag (v2f i) : SV_Target { fixed4 tex = tex2D(_MainTex, i.uv); fixed4 col = tex * _Color; float3 N = normalize(i.worldNormal); float3 L = normalize(_WorldSpaceLightPos0.xyz); float ndotl = saturate(dot(N, L)); fixed3 diffuse = _LightColor0.rgb * ndotl; col.rgb *= diffuse + unity_AmbientSky.rgb; return col; } ENDCG } } }

关键就三行状态:Queue = Transparent、ZWrite Off、Blend SrcAlpha OneMinusSrcAlpha。少任何一行,效果都会跑偏:少了队列,它会被当不透明物体在不透明阶段绘制,背景还没画完就开始混合,结果是一片黑或者一片奇怪的颜色;少了ZWrite Off,后面的透明物体会被它挡死;少了Blend,它就是个不透明的贴图。

要注意的是,这个 Shader 里光照部分做了简化处理——我用了一个方向光加环境色近似,没有写完整的 ForwardBase 光照循环。真实项目里如果透明物体需要接受多光源、阴影、雾效,建议直接基于 Unity 内置的Unlit/Transparent或标准光照模板改,别从零手写光照,容易漏掉UNITY_LIGHT_ATTENUATION、SHADOW_COORDS这些宏,导致阴影对不上。这算是我的一个教训:早期为了"干净"手写光照,结果透明物体在阴影里不暗、在有雾场景里不雾化,排查了半天。

3.3 双面渲染:Cull Off 的诱惑与陷阱

玻璃杯、树叶、纸张这类物件,你需要看到正反两面。直觉做法是加一句Cull Off,把背面剔除关掉。但关掉剔除之后你会立刻发现一个问题:正反面的绘制顺序不确定,导致看到的那一面可能是背面。

原因是这样的。关闭剔除后,正反面的三角形都进入光栅化,但它们在同一批里提交顺序由网格数据决定。因为透明物体ZWrite Off,深度测试无法阻止后画的覆盖先画的(深度测试只跟不透明场景比),于是背面可能画在正面之上,视觉上就"翻"了。

标准解法是两趟 Pass:第一趟只画背面(Cull Front),第二趟只画正面(Cull Back)。这样保证背面先混合到背景上,正面再叠加上去,顺序就是物理上正确的"透过正面看到背面"。

Pass { Cull Front ZWrite Off Blend SrcAlpha OneMinusSrcAlpha // ... 正面同样的着色代码 } Pass { Cull Back ZWrite Off Blend SrcAlpha OneMinusSrcAlpha // ... }

两趟 Pass 的代价是顶点和片元都跑两次,性能约等于翻倍。所以我的建议是:能用单面就别用双面。玻璃杯如果只是从外面看,单面完全够;只有需要"看到杯子内部"的场景才值得开双面。另外,如果两面颜色不同(比如叶子的正面亮、背面暗),可以用VFACE语义或者FRONT_FACING判断当前面朝向,在一次 Pass 里切换颜色,省一趟绘制——不过仍然解决不了顺序问题,还是得靠两趟。

还有个小细节:双面渲染时,法线要翻转。背面片元的法线应该取反,否则背面光照会算错,看起来像打了反向光。这个在写自定义光照时特别容易忘。

4. 实操:一套可复用的透明 Shader 模板

4.1 属性与渲染状态的搭建顺序

我在实际项目里沉淀下来一个习惯:写透明 Shader,先定状态,再写代码。因为状态错了代码再对也白搭。顺序是这样:

  1. 先确定这个材质到底是AlphaTest还是Transparent队列。判断标准是"有没有半透明像素"。有 → Transparent;只有全透或全不透 → AlphaTest。
  2. 根据队列定ZWrite。Transparent 一律Off,AlphaTest 一律On。
  3. 根据美术需求定Blend。要半透明 →SrcAlpha OneMinusSrcAlpha;要发光叠加 →One One;要预乘 →One OneMinusSrcAlpha。
  4. 根据是否双面可见定Cull。
  5. 最后写顶点/片元代码。

属性块上,除了_MainTex和_Color,我建议加两个:_Cutoff(如果走 AlphaTest,配上[Range(0,1)])和_AlphaScale(一个全局透明度缩放,做淡入淡出、消散效果时特别有用,可以脚本直接调material.SetFloat)。

Properties { _MainTex ("Texture", 2D) = "white" {} _Color ("Tint", Color) = (1,1,1,1) _AlphaScale ("Alpha Scale", Range(0, 1)) = 1 [Toggle] _UseAlphaTest ("Use Alpha Test", Float) = 0 _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5 }

加_AlphaScale这个参数看着多余,但真到做特效的时候你会发现它是刚需。boss 出场、道具消失、场景切换,基本都要一个"整体淡出"。有这个参数,动画曲线或者 Timeline 直接 K 它就行,不用改材质。

4.2 片元着色器里的关键几行与它们的意图

片元部分的核心逻辑可以归纳成一条流水线:

fixed4 frag (v2f i) : SV_Target { fixed4 tex = tex2D(_MainTex, i.uv); fixed4 col = tex * _Color; col.a *= _AlphaScale; #ifdef _USEALPHATEST_ON clip(col.a - _Cutoff); #endif // 半透明 Lambert 光照 float3 N = normalize(i.worldNormal); float3 L = normalize(_WorldSpaceLightPos0.xyz); float ndotl = saturate(dot(N, L)); fixed3 lighting = _LightColor0.rgb * ndotl + unity_AmbientSky.rgb; col.rgb *= lighting; return col; }

逐行说一下意图。tex * _Color是让材质的 Tint 生效,方便同一张贴图复用出不同颜色的透明物件;col.a *= _AlphaScale提供全局淡出控制;#ifdef里那段是可选分支,通过#pragma multi_compile _ _USEALPHATEST_ON生成两个变体,一个走镂空一个不走,这样同一个 Shader 文件能同时服务两种需求,减少维护成本。

光照部分我用的是"半透明 Lambert"的极简写法——不计算高光和阴影,只保留方向光加环境色。理由是透明物体本身已经足够"轻",加太多光照细节反而显得脏;而且很多透明效果(火、烟、光效)根本不需要光照,直接输出自发光颜色更干净。如果确实需要,把worldNormal保留着,需要时插一段完整光照宏进来就行。

有个坑得提一下:fixed精度在移动端。fixed在部分移动 GPU 上是 10 位定点,col.a *= _AlphaScale这种连乘在极端值附近可能出现精度阶梯,导致淡出过程中出现色带。稳妥做法是 alpha 计算用half甚至float,RGB 用fixed就够了。

4.3 在场景里怎么验证效果是否真的对了

写完 Shader,别急着扔进项目里用。我通常在场景里按下面这个顺序验证,能提前发现 80% 的问题:

第一步,摆两个透明面片前后重叠,从正面看,看两者叠加区域的颜色是不是比单层更深。如果一样,说明混合没生效,检查Blend和队列。

第二步,把一个面片转到侧面对着相机,绕圈观察。如果转到某个角度突然消失或者被别的东西切掉,说明深度测试或队列有问题。

第三步,放一个不透明方块挡住透明面片的一半。被挡的部分应该是完全不显示的(不是半透明),这验证ZTest是开着的。

第四步,把透明面片放进一个不透明的玻璃球里(模拟透明包透明)。观察两者都是半透明时的叠加是否正确,同时确认球自身的正反面没穿帮。

第五步,如果是需要淡出的物件,把_AlphaScale从 1 拖到 0,看整个过程有没有突然的亮度跳变。跳变通常意味着你用了预乘但纹理没预乘,或者 alpha 通道被写了奇怪的值。

这套流程走完,基本能确定这个 Shader 的"下限"是可靠的。真正复杂的是跟别的透明物体的交互,那就得进第 5 章。

5. 排序问题与故障排查实录

5.1 透明物体互相穿插、消失、闪面的真实原因

透明相关的 bug,百分之八十都指向同一个根因:顺序错了。而顺序错又分几种不同的表现,需要区分。

第一种表现是"该透的没透,被别的东西遮住了"。这通常是因为透明物体的ZWrite没关,或者队列没设成Transparent(被当不透明提前画了但没写背景)。还有一种是RenderType标签没设,虽然RenderType主要给替换渲染用,但它会影响某些后处理的判断,最好一起写上。

第二种表现是"两个透明物体交叠的地方出现一条硬边或者颜色断层"。这是典型的排序按包围盒中心导致的问题。两个细长的透明面片呈 X 交叉,因为中心点在同一个位置附近,Unity 可能把它们排成"近的先画",于是远的那层被错误地叠在了近的上面,交叉处就露馅了。这种情况没什么完美的通用解,常见处理是:把交叉的大物体拆成若干小块,让每个小块的中心更贴近它实际的深度;或者手动设置renderer.sortingOrder(只对同一队列内有效);或者在场景设计上就避免大面积透明面片交叉。

第三种表现是"透明物体自己在动的时候会闪"。原因多半是相机的远近距离变化导致排序距离在临界点附近来回翻转,两个物体谁先谁后一秒一变。这种抖动的排查方法是:把Camera.position打印出来,或者干脆在编辑器里拖着相机走,看闪烁的频率是否跟着距离变。缓解方案是把关键透明物件手动指定sortingOrder,或者用RenderQueue的偏移(比如一个用Transparent,一个用Transparent+1)强制固定顺序。

第四种表现是"透明物体内部有深浅不一的色块"。这就是前面说的ZWrite Off导致自身面片互相覆盖。解决方向只有两条:单面渲染(Cull Back)避开自身遮挡,或者用双 Pass 明确顺序。

5.2 常见问题速查表

下面这张表是我这几年被问得最多、自己踩过最多的组合,建议直接存下来,遇到问题先对号入座:

现象最可能原因排查与修复
整体漆黑一片队列为 Transparent 但背景未绘制,或 Blend 因子写反检查Queue,检查是否Blend Zero One这类反了
完全看不到物体ZTest被误关或Cull方向错试ZTest LEqual,试Cull Off定位
边缘发白/发黑未预乘但用预乘混合,或反之统一混合公式与纹理流程
被其他透明挡住未设ZWrite Off加ZWrite Off
自身面片穿插双面渲染顺序问题改单面或两趟 Pass
淡出时色带fixed精度不足alpha 计算改用half
阴影对不上未加SHADOW_COORDS等宏用内置光照模板或在ForwardBase补全
添加发光后过曝Blend One One无限叠加降低基础亮度或改用Blend SrcAlpha One

表里最后一行值得单独说一句:Blend SrcAlpha One是我常用的"软加法",它既保留 alpha 控制透明度的能力,又能提亮,比纯One One可控得多。做体积光、能量罩这类效果时,Blend One One一开就白,改成Blend SrcAlpha One立刻正常。

5.3 几条不太写在文档里的经验

第一条,能用 Alpha Test 就绝不用 Alpha Blend。这不是性能洁癖,而是 Alpha Test 的排序问题几乎为零,深度正确、阴影正确、后处理正确。只有真正需要渐变透明(雾、玻璃、光效)时才上 Blend。我见过太多项目,树叶也用 Blend,结果满屏镂空边缘的排序错误,调起来极其痛苦。

第二条,透明物体的网格尽量小、尽量简单。排序是按整个 Renderer 算的,一个巨型的透明网格跨越大半个场景,排序必然出错。把长条形的河流、走廊的玻璃拆成若干段,问题会少一大半。这个改动看着土,但比任何 Shader 技巧都有效。

第三条,别在透明 Shader 里写复杂的屏幕空间效果。因为透明物体不写深度,抓取屏幕纹理(GrabPass或 URP 的_CameraOpaqueTexture)时拿到的内容里可能已经有其他透明物体的画面,叠加起来会很脏。如果一定要做折射,务必在透明队列的最前端(比如Transparent-100)做,保证抓取时还没有透明内容混进去。

第四条,调试时善用 Frame Debugger。透明相关的 bug 大部分是"绘制顺序不符合预期",而 Frame Debugger 会严格按照实际提交顺序列出每一次 Draw Call,配合着色器名和队列值,一眼就能看出是谁先谁后。这比在代码里加一堆Debug.Log高效一百倍。我现在的习惯是:只要透明效果和预期不符,先开 Frame Debugger 看顺序,再回头改代码。

写到这儿,关于 Unity Shader 透明这一块的主干基本都覆盖了。回到最初那个"加一行 Blend 就完了"的错觉——你现在应该清楚了,那一行只是结果,背后是队列、深度、排序、混合四件事一起配合。我个人在实际操作中的体会是:调透明效果的时间,八成花在排查顺序上,而不是写代码上;所以与其背公式,不如把上面那套"摆两个面片验证"的流程练成条件反射,问题定位会快得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询