Unity Shader 深度写入与关闭ZWrite Off · 半透明排序 · 粒子穿插

半透明物体为什么必须关闭深度写入?关闭后粒子系统为什么会互相穿插? CPU 端距离排序与 Order in Layer 如何配合?某些特效为什么要故意开启 ZWrite? 本文逐一拆解这些问题,并给出可落地的 URP Shader 代码。

Section 01

深度缓冲的工作原理回顾

深度缓冲(Depth Buffer,又称 Z-Buffer)是 GPU 中与颜色缓冲等大的一张纹理, 每个像素存储一个归一化后的深度值 d ∈ [0, 1](Direct3D 约定;OpenGL 为 [-1,1] 映射至 [0,1])。

每次 Fragment Shader 输出一个片元,GPU 会先做 深度测试(Depth Test): 将当前片元的深度与缓冲中已有的值做比较(默认 ZTest LEqual,即"离相机更近或相等则通过")。 测试通过后,如果 ZWrite On,则用当前深度值覆盖缓冲;测试失败则整个片元被丢弃,颜色缓冲也不写入。

注意:深度测试(ZTest)和深度写入(ZWrite)是两个独立的开关。 测试决定"此片元是否可见",写入决定"通过后是否更新深度缓冲"。 半透明渲染的核心矛盾,正是围绕这两个开关展开的。


Section 02

ZWrite On vs ZWrite Off — 核心区别

属性ZWrite OnZWrite Off
深度缓冲更新
后续物体是否被遮挡会被遮挡(正确)不会被遮挡
颜色混合(Blend)通常不启用必须配合 Blend 使用
典型用途不透明物体、地面、建筑玻璃、烟雾、火焰、粒子
渲染队列Geometry (2000)Transparent (3000)
排序依赖依赖深度缓冲自动排序必须手动或 CPU 排序

Section 03

半透明为什么必须关闭深度写入

半透明的本质是颜色混合(Alpha Blending):最终颜色 = 前景色 × α + 背景色 × (1−α)。 这个公式依赖"背景已经在颜色缓冲里"。若半透明物体写入深度,就会遮挡自己身后的物体, 导致背景被剔除、无法参与混合,透明效果就消失了。

核心规则

半透明物体(Alpha Blending)必须使用 ZWrite Off, 否则它会向深度缓冲写入一个不透明的深度值,把自己身后的物体全部"遮死",透明混合就无从谈起。

正确的 URP 半透明 Shader 配置

核心规则
半透明物体(Alpha Blending)必须使用 ZWrite Off, 否则它会向深度缓冲写入一个不透明的深度值,把自己身后的物体全部"遮死",透明混合就无从谈起。
正确的 URP 半透明 Shader 配置
ShaderLab
TransparentSurface.shader
Shader "Custom/URP_Transparent"
{
    Properties
    {
        _BaseColor ("Base Color", Color) = (1,1,1,0.5)
        _BaseMap   ("Albedo",    2D)    = "white" {}
    }
    SubShader
    {
        // ① 渲染队列必须设为 Transparent
        Tags
        {
            "RenderType"      = "Transparent"
            "Queue"           = "Transparent"  // 3000,在不透明之后渲染
            "RenderPipeline"  = "UniversalPipeline"
        }
        Pass
        {
            // ② 混合模式:标准 Alpha Blending
            Blend SrcAlpha OneMinusSrcAlpha
            // ③ 关键:关闭深度写入
            ZWrite Off
            // ④ 深度测试保持开启(读取不透明物体的深度)
            ZTest  LEqual
            // ⑤ 双面渲染(玻璃类效果去掉背面剔除)
            Cull   Off
            // HLSL 程序 ... (省略)
        }
    }
}

要点

ZWrite Off 只是"不写入",但仍然可以读取深度缓冲(ZTest 仍起效)。 因此半透明物体依然会被不透明物体正确遮挡——它只是不阻断身后的其他东西。


Section 04

关闭后的代价:粒子系统互相穿插

当所有粒子都使用 ZWrite Off,它们在深度缓冲层面"彼此透明"。 两个粒子系统同时绘制时,谁后画谁就叠在上面——但后画者同样不写深度, 下一帧先画者又可能叠到上面,产生闪烁穿插(Z-fighting for Transparents)的视觉问题。

穿插的根本原因

Transparent 队列中的物体,Unity 默认按物体包围盒中心到相机的距离从远到近排序(Painter's Algorithm)。 但粒子系统是"一个 Renderer 对应多个 Billboard 粒子",整个系统只有一个深度值参与排序。 当两个粒子系统的位置非常接近甚至重叠时,任何一帧微小的相机移动都可能让排序反转, 造成帧间闪烁。粒子系统内部的粒子彼此之间也没有逐粒子的深度比较。

注意

粒子系统"穿插"与传统的"Z-fighting"是两个不同问题: 前者是多个半透明 Renderer 排序不稳定;后者是两个不透明面片深度值精度相近造成交替可见。 解决方案也完全不同。


Section 05

CPU 端距离排序 与 Order in Layer

方案一:Particle System 内置排序

Unity Particle System 的 Renderer 模块提供了 Sort Mode 属性, 可以对同一个粒子系统内部的粒子按距离排序:

Sort Mode说明性能
None不排序(默认),按生成顺序绘制最快
By Distance每帧 CPU 计算每个粒子到相机距离并排序中等(粒子数 > 500 开始明显)
Oldest in Front生命周期最长的粒子先画(模拟烟雾老粒子在底)
Youngest in Front最新生成的粒子最后画
using UnityEngine;
// 运行时动态修改粒子排序模式
public class ParticleSortSetup : MonoBehaviour
{
    void Start()
    {
        var ps  = GetComponent<ParticleSystem>();
        var psr = ps.GetComponent<ParticleSystemRenderer>();
        // 开启逐粒子距离排序
        psr.sortMode = ParticleSystemSortMode.Distance;
        // sortingFudge:正值让此系统整体靠后绘制
        // 对多系统穿插问题可手动微调
        psr.sortingFudge = 5f;
    }
}

方案二:Order in Layer(排序图层)

Sorting Layer + Order in Layer 是 Unity 的 2D/粒子排序系统, 适用于同一 3D 位置的多个粒子系统需要稳定绘制顺序的场景。 数值越大,越晚绘制(越靠近屏幕前方)。

using UnityEngine;
public class ParticleLayerOrder : MonoBehaviour
{
    [SerializeField] int orderInLayer = 10;
    void Awake()
    {
        var renderer = GetComponent<ParticleSystemRenderer>();
        // 设置排序图层名称(需在 Tags & Layers 中预先定义)
        renderer.sortingLayerName  = "FX";
        // 同图层内的精细顺序
        renderer.sortingOrder      = orderInLayer;
    }
}

方案三:sortingFudge(微调偏移)

ParticleSystemRenderer.sortingFudge 是一个纯粹的"数值偏移", 会被加到该粒子系统到相机的距离计算结果上,从而影响在 Transparent 队列中的排序位置。 正值 = 人为增大距离 = 更先绘制(靠后显示);负值 = 更后绘制(靠前显示)。 适合做轻量微调,而不需要修改 Sorting Layer。

推荐组合策略

对于复杂特效(烟+火+火花+UI),建议:
① 用 Sorting Layer 划分大类(地面FX / 角色FX / 界面FX);
② 用 Order in Layer 控制同类内的顺序;
③ 用 sortingFudge 做同 Order 内的微调;
④ 性能允许时再开启 Sort Mode: By Distance 解决单系统内部粒子排序。


Section 06

故意开启 ZWrite 的特效场景

并非所有特效都要关闭深度写入。某些情况下,刻意让特效写入深度缓冲可以制造更真实的遮挡关系, 或解决特定的排序问题。以下是三种常见场景:

场景一:遮挡后方粒子的"硬壳"效果

爆炸中心通常有一个不透明的核心火球,希望它遮住自己身后的烟雾粒子。 将核心火球 Shader 设为 ZWrite On,队列设为 Transparent-1(先于普通半透明绘制), 即可让它向深度缓冲写入一个"硬遮挡",后续烟雾在该区域的像素会被深度测试丢弃。

场景二:软粒子(Soft Particles)的深度采样

URP 的 Soft Particles 功能需要读取不透明物体的深度(来自 _CameraDepthTexture) 计算粒子与场景的交叉处软化值。这要求不透明物体必须先写入深度缓冲, 而粒子自身仍然 ZWrite Off。两者分开,互不干扰。

场景三:"伪不透明"蒙版粒子

某些低多边形风格游戏使用 AlphaTest 粒子(透明度低于阈值的像素直接丢弃,高于阈值的完全不透明)。 此时可以 ZWrite On + AlphaToMask On,让粒子参与深度排序,避免穿插。

Shader "Custom/URP_FX_OpaqueCore"
{
    SubShader
    {
        Tags
        {
            "RenderType"  = "Transparent"
            // 比普通半透明(3000)早一个单位绘制
            "Queue"       = "Transparent-1"
        }
        Pass
        {
            // ① 先写入深度,为后续粒子建立遮挡关系
            ZWrite On
            ZTest  LEqual
            // ② 仍然混合(半透明边缘),但核心区域 alpha≈1
            Blend SrcAlpha OneMinusSrcAlpha
            // HLSL 程序 ...
        }
    }
}
Shader "Custom/URP_FX_OpaqueCore"
{
    SubShader
    {
        Tags
        {
            "RenderType"  = "Transparent"
            // 比普通半透明(3000)早一个单位绘制
            "Queue"       = "Transparent-1"
        }
        Pass
        {
            // ① 先写入深度,为后续粒子建立遮挡关系
            ZWrite On
            ZTest  LEqual
            // ② 仍然混合(半透明边缘),但核心区域 alpha≈1
            Blend SrcAlpha OneMinusSrcAlpha
            // HLSL 程序 ...
        }
    }
}
ShaderLab
AlphaTestParticle.shader — AlphaToMask
Shader "Custom/URP_AlphaTest_Particle"
{
    Properties
    {
        _Cutoff ("Alpha Cutoff", Range(0,1)) = 0.5
    }
    SubShader
    {
        Tags { "Queue" = "AlphaTest"  /* 2450, 不透明之后半透明之前 */ }
        Pass
        {
            // AlphaTest 可以写深度,参与正常不透明排序
            ZWrite On
            // 利用 MSAA 把 Alpha 映射为多重采样遮罩,边缘更平滑
            AlphaToMask On
            HLSLPROGRAM
            // ... fragment shader 中使用 clip(alpha - _Cutoff)
            ENDHLSL
        }
    }
}

注意:ZWrite On + Blend 的矛盾

同时开启 ZWrite On 和 Blend SrcAlpha OneMinusSrcAlpha 时, 深度写入的是"当前面片的深度",而颜色则按 alpha 混合。 这意味着即使边缘几乎透明,深度缓冲里也会留下该面片的深度值。 后续绘制与该面片深度相近的半透明物体可能被错误裁剪。 仅在核心区域完全不透明时推荐这种用法


Section 07

完整 Shader 代码示例

下面是一个支持动态切换 ZWrite 模式的 URP 粒子 Shader, 可以通过 Material 属性在 Inspector 中控制: 0 = ZWrite Off(标准半透明),1 = ZWrite On(遮挡型特效)。

注意:ZWrite On + Blend 的矛盾
同时开启 ZWrite On 和 Blend SrcAlpha OneMinusSrcAlpha 时, 深度写入的是"当前面片的深度",而颜色则按 alpha 混合。 这意味着即使边缘几乎透明,深度缓冲里也会留下该面片的深度值。 后续绘制与该面片深度相近的半透明物体可能被错误裁剪。 仅在核心区域完全不透明时推荐这种用法。
Section 07
完整 Shader 代码示例
下面是一个支持动态切换 ZWrite 模式的 URP 粒子 Shader, 可以通过 Material 属性在 Inspector 中控制: 0 = ZWrite Off(标准半透明),1 = ZWrite On(遮挡型特效)。

ShaderLab + HLSL
URP_ParticleFX.shader
Shader "Custom/URP_ParticleFX"
{
    Properties
    {
        _BaseMap    ("Particle Texture", 2D)           = "white" {}
        _BaseColor  ("Color",           Color)        = (1,1,1,1)
        _SoftNear   ("Soft Near Fade",  Float)       = 0.1
        [Enum(Off, 0, On, 1)] _ZWrite ("ZWrite", Float) = 0
        [Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend", Float) = 5
        [Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend", Float) = 10
    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "RenderType"     = "Transparent"
            "Queue"          = "Transparent"
        }
        Pass
        {
            Name "ForwardUnlit"
            Tags { "LightMode" = "UniversalForward" }
            Blend  [_SrcBlend] [_DstBlend]
            ZWrite [_ZWrite]
            ZTest  LEqual
            Cull   Off
            HLSLPROGRAM
            #pragma vertex   ParticleVert
            #pragma fragment ParticleFrag
            #pragma multi_compile_particles
            #pragma multi_compile_fog
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
            TEXTURE2D(_BaseMap);  SAMPLER(sampler_BaseMap);
            CBUFFER_START(UnityPerMaterial)
                float4 _BaseColor;
                float4 _BaseMap_ST;
                float  _SoftNear;
            CBUFFER_END
            struct Attributes
            {
                float4 positionOS : POSITION;
                float2 uv         : TEXCOORD0;
                float4 color      : COLOR;
            };
            struct Varyings
            {
                float4 positionHCS : SV_POSITION;
                float2 uv          : TEXCOORD0;
                float4 color       : COLOR;
                float4 screenPos   : TEXCOORD1;
            };
            Varyings ParticleVert(Attributes IN)
            {
                Varyings OUT;
                VertexPositionInputs vpi = GetVertexPositionInputs(IN.positionOS.xyz);
                OUT.positionHCS = vpi.positionCS;
                OUT.uv          = TRANSFORM_TEX(IN.uv, _BaseMap);
                OUT.color       = IN.color;
                // 软粒子需要屏幕坐标来采样深度纹理
                OUT.screenPos   = ComputeScreenPos(vpi.positionCS);
                return OUT;
            }
            half4 ParticleFrag(Varyings IN) : SV_Target
            {
                half4 texColor = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
                half4 col      = texColor * _BaseColor * IN.color;
                // ── 软粒子:边缘与场景几何体接触处淡出 ──
                float2 screenUV   = IN.screenPos.xy / IN.screenPos.w;
                float  sceneDepth = LinearEyeDepth(
                                        SampleSceneDepth(screenUV),
                                        _ZBufferParams);
                float  partDepth  = IN.screenPos.w;
                float  softFade   = saturate((sceneDepth - partDepth) / _SoftNear);
                col.a *= softFade;
                return col;
            }
            ENDHLSL
        }
    }
}

Section 08

决策速查表

根据你的特效需求,快速查找应该使用的配置组合:

特效类型ZWriteQueueSort 方案典型问题
标准烟雾 / 气体OffTransparent (3000)By Distance + Fudge多系统穿插,调 sortingFudge
火焰粒子OffTransparent (3000)Order in Layer烟火层次,Order 分级
爆炸核心火球OnTransparent-1 (2999)—(不透明遮挡)注意边缘 alpha 问题
玻璃 / 水面OffTransparent (3000)—(单面片无需排序)背面渲染顺序 / 双面 Cull Off
低多边形叶片 (AlphaTest)OnAlphaTest (2450)GPU 深度排序开 AlphaToMask 改善锯齿
UI 血量爆发特效OffOverlay / UI (4000+)Sorting Layer: UICanvas 层级配合
贴地光圈 / DecalOffTransparent (3000)URP Decal ProjectorURP Decal Renderer Feature
软粒子(Soft Particles)OffTransparent (3000)By Distance需在 URP Asset 开启 Depth Texture

软粒子配置提示

软粒子(Soft Particles)需要在 URP Renderer Asset 中开启 Depth Texture, 否则 SampleSceneDepth() 采样结果为 0,softFade 恒为 0,粒子完全透明消失。 路径:Project Settings → Graphics → URP Asset → Depth Texture ✓

内容概要:本文研究了基于CNN-BiGRU-Attention混合神经网络模型的风电功率预测方法,旨在提升风力发电功率预测的准确性。该模型融合卷积神经网络(CNN)以提取输入变量中的局部时空特征,结合双向门控循环单元(BiGRU)充分捕捉时间序列前后向的长期依赖关系,并引入注意力机制(Attention)动态加权关键时间步的特征信息,增强模型对重要时刻的敏感度。研究采用多变量输入进行单步预测,综合纳入风速、风向、温度等多种气象因素作为模型输入,全面反映环境变量对风电输出的影响。通过Matlab平台完成模型构建、训练仿真验证,实验结果表明该混合模型在预测精度稳定性方面优于传统单一模型,有效提升了风电功率预测性能。; 适合人群:具备一定机器学习深度学习理论基础,熟悉Matlab编程环境,从事新能源发电预测、电力系统调度、智能算法应用等相关领域的科研人员、工程技术人员及高校研究生。; 使用场景及目标:①应用于风电场实际运行中的短期功率预测,提高电网调度的安全性可再生能源消纳效率;②为深度学习模型在复杂时序预测任务中的设计优化提供实践范例,推动AI技术在能源系统智能化中的深度融合;③支持学术研究复现、课程项目设计教学演示,帮助深入理解CNN、BiGRUAttention机制的协同建模范式实现细节。; 阅读建议:建议结合提供的Matlab代码进行动手实践,重点关注数据预处理流程、模型网络结构设计、超参数调优及训练收敛过程,鼓励尝试替换输入变量组合、调整网络层数或优化注意力结构,以进一步探究模型性能边界并提升预测鲁棒性。
内容概要:本文研究了基于Benders分解算法输电网-配电网运营商(TSO-DSO)协调机制的双层优化模型,旨在有效应对新能源出力波动、负荷不确定性等对现代电力系统运行带来的挑战。模型上层由输电网运营商(TSO)负责全局资源优化主网稳定性调控,下层由多个配电网运营商(DSO)实现本地分布式能源的灵活调度,通过Benders分解实现上下层之间的迭代协调信息交互,从而在保障系统安全的前提下提升整体运行的经济性鲁棒性。研究提供了完整的Matlab代码实现,涵盖数学建模、算法求解、收敛性分析及仿真结果可视化等环节,有助于深入理解双层优化架构在输配电网协同调度中的具体应用技术细节。; 适合人群:具备电力系统分析、优化理论基础及一定Matlab编程能力的研究生、科研人员,以及从事电网调度、能源系统规划等相关领域的工程技术人员。; 使用场景及目标:①掌握Benders分解在电力系统双层优化问题中的建模求解流程;②理解TSO-DSO协同机制下输配电网交互建模的核心思想实现方法;③复现并拓展高水平学术论文中的优化模型,服务于科研项目攻关或实际工程仿真需求。; 阅读建议:建议结合凸优化理论、电力系统经济调度Benders分解原理进行系统学习,优先运行并调试所提供的Matlab代码,调整关键参数以观察算法收敛行为模型性能变化,从而深化对协调机制优化机理的理解。
内容概要:本文档是一份关于经济学期刊论文复现的研究资料,聚焦核心议题“数字化转型能否促进企业的高质量发展”。文档构建了一个完整的量化分析框架,基于中国上市公司数据,实证探讨数字化转型对企业全要素生产率(TFP)及高质量发展的实际影响。内容涵盖数字化转型指标的构建、企业高质量发展评价体系的设计、计量经济模型的选择应用(如固定效应模型、GMM方法),并提供Matlab代码实现全过程,包括数据处理、模型估计稳健性检验。研究还系统梳理了OL、FE、LP、OP、GMM等多种全要素生产率的测算方法,为读者复现高水平经济学论文、深入理解数字经济时代的企业发展路径政策含义提供了详尽的技术支持理论指导。; 适合人群:具备扎实的经济学理论基础和较强的定量分析能力,熟悉Matlab或Python编程语言,正在从事经济管理、产业经济或数字经济等领域研究的研究生、高校教师及科研机构研究人员。; 使用场景及目标:①完整复现经济学顶刊论文的实证研究流程,掌握规范的学术研究范式;②学习并应用数字化转型企业绩效间的因果识别策略,提升独立开展实证研究的能力;③为撰写学位论文、申报科研课题或编制政策咨询报告中涉及数字经济效应的章节提供直接的方法论参考和代码支持; 阅读建议:建议读者务必结合文档提供的数据Matlab代码进行同步实操,重点钻研变量定义、模型设定、内生性处理和稳健性检验等关键环节,通过反复调试验证,深刻领会高水平实证研究的严谨逻辑技术细节,从而全面提升自身的科研素养论文写作水平。
内容概要:本文围绕“绿电直连型电氢氨园区优化运行”开展创新性未发表研究,提出一种集成绿色电力直接供给、电解水制氢合成氨工艺的多能耦合系统优化模型,旨在实现园区能源系统的低碳化、高效化经济化运行。研究采用MatlabPython编程语言,结合实际气象负荷数据,构建涵盖电-氢-氨能量转换、存储利用全过程的能量流、物质流及经济性协同优化框架,重点解决可再生能源出力波动导致的供需失衡问题,并通过优化电解槽、储氢罐、合成氨反应器等关键设备的运行策略容量配置,提升系统对风光能源的就地消纳能力。文中配套提供完整的仿真代码、原始数据及Word格式论文,支持结果复现模型拓展,具有较高的科研参考价值工程应用潜力。; 适合人群:具备电力系统、能源工程、优化建模或新能源技术背景,从事综合能源系统、氢能利用、碳中和园区等相关领域研究的研发人员及硕士、博士研究生。; 使用场景及目标:①研究绿电直供模式下电-氢-氨多能系统协同运行机制优化调度策略;②探索高比例可再生能源就地转化为高附加值化工产品的技术路径;③为工业园区实现深度脱碳能源自洽提供决策支持;④作为学术论文撰写、课题申报或科研复现的高质量参考资料。; 阅读建议:建议结合MatlabPython代码逐模块解析模型实现过程,重点关注目标函数构建、约束条件设定(如设备动态特性、能量平衡、安全边界)以及多场景仿真对比分析,宜在调试过程中调整权重系数参数设置,深入理解系统灵敏度优化机理,并尝试引入更多不确定性因素进行鲁棒性扩展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值