Unity Shader 深入理解 深度冲突

从深度缓冲的基本原理出发,系统解析 Z-Fighting 的成因,并提供 URP Shader 中的实用解决方案。

一、什么是深度

在 3D 渲染中,深度 描述的是物体表面上的某个点距离摄像机有多远。 想象你站在一条笔直的马路上,近处的路灯比远处的路灯"更浅",这个"深浅"就是深度。

Unity 使用 深度缓冲 来记录屏幕上每个像素的深度值。 它是一张与屏幕分辨率相同的"灰度图"——每个像素存储的不是颜色,而是一个 [0, 1] 范围的值: 0 代表最近(靠近近裁面),1 代表最远(接近远裁面)

关键认知

深度缓冲本质上是一张逐像素的浮点纹理。URP 中默认使用 _CameraDepthTexture 来访问它,精度通常为 24 位或 32 位。


二、深度测试的工作原理

当 GPU 要绘制一个片元时,它不会直接覆盖屏幕上已有的颜色。它会先做一次深度测试

  1. 读取目标像素当前深度缓冲中的值(已有值)
  2. 用当前片元的深度值与已有值比较
  3. 如果片元更近(默认是小于),则通过测试 → 更新颜色和深度缓冲
  4. 如果片元更远,则丢弃该片元

三、什么是深度冲突

深度冲突,英文叫 Z-Fighting,是 3D 渲染中最常见的视觉 bug 之一。 当两个(或多个)面在空间中几乎重叠时,深度缓冲的精度不足以区分它们谁在前、谁在后。 GPU 在不同像素上随机判定不同的面"获胜",结果就是两个面的纹理锯齿状地交错闪烁。

典型场景

深度冲突最常见于以下情况:① 两个面恰好重合(如墙壁和贴花);② 远处物体因为深度缓冲的非线性精度而"挤在一起";③ Shadow Map 中自阴影导致的阴影斑块(Shadow Acne)。


四、深度冲突的根本原因

要理解为什么深度缓冲"不够用",需要了解深度值的非线性分布

4.1 透视投影下的深度非线性

经过透视投影矩阵变换后,深度值并不是均匀分布的。 近处的精度极高,远处则迅速降低。这个非线性关系可以表示为:

4.2 浮点精度问题

标准的 24 位深度缓冲有约 1677 万个离散深度级别。这听起来很多,但在非线性分布下:

  • 近裁面 0.1m、远裁面 1000m 时,在距离摄像机 900m 处,相邻两个深度级别之间可能相差 数米
  • 如果两个面在这个距离上间距小于这个级别差,GPU 就无法区分它们
深度缓冲格式精度适用范围备注
D16_UNorm65536 级移动端 / 低端精度最低,远距离易出问题
D24_UNorm_S8_UInt1677 万级桌面端常用带 8 位 Stencil,最通用的选择
D32_Float约 23 位尾数高端 / PC浮点格式,近处精度优于远处
D32_Float_S8_UInt约 23 位尾数高端 / 主机浮点 + Stencil,最佳选择

五、URP 中的解决方案

5.1 调整深度偏移 — Offset

HLSL/ShaderLab 提供了 Offset 命令,可以在光栅化阶段对深度值施加一个微小的偏移:

HLSL / ShaderLab
Pass {
    // Offset Factor, Units
    Offset -1, -1

    // Factor: 按斜率缩放的最大深度偏移
    // Units:  最小可分辨深度值的倍数
}

Offset factor, units 的实际偏移量为 factor × |dZ/dX| + units × r,其中 r 是深度缓冲的最小可分辨值。负值将片元"拉近"摄像机,正数推远。

实用建议

对于贴花类效果,常用 Offset -1, -1 让贴花略微浮在表面上方。对于轮廓描边,常用 Offset -1, -100 以确保描边始终可见。

5.2 调整近远裁面比例

近裁面和远裁面的比例直接决定了深度的均匀程度。增大 Near、减小 Far 可以显著改善远处精度:

C# / URP RenderPipelineAsset
// 在 URP Asset 中设置
// 或者通过代码:
var cameraData = renderingData.cameraData;
cameraData.camera.nearClipPlane = 0.3f;   // 不要设置得太小!
cameraData.camera.farClipPlane  = 500f;    // 按场景需要调整
  • Near = 0.01 会使 99% 的深度精度浪费在摄像机前 1 米内
  • 推荐 Near ≥ 0.1(VR 场景 ≥ 0.05),Far 按实际可见距离设置

5.3 使用 Reverse-Z

Reverse-Z 是近年来最重要的深度优化之一。它将深度值反转:近处 = 1,远处 = 0。 这样做的好处是让浮点精度在远处也能保持较高分辨率——因为浮点数在 [0, 0.5] 区间的精度远高于 [0.5, 1]。

URP 中的 Reverse-Z

URP 从 Unity 2020.3+ 开始默认启用 Reverse-Z。如果你的项目使用旧版 URP,可以在 URP Asset → Quality → 勾选 "Depth Texture" 来确保深度纹理格式正确。

5.4 分层渲染 & Render Queue

有时深度冲突并非精度问题,而是渲染顺序的问题。调整 Render Queue 确保正确的绘制顺序:

HLSL / ShaderLab
Tags {
    "Queue" = "Geometry"      // 不透明几何体: 2000
    "Queue" = "AlphaTest"     // Alpha测试:     2450
    "Queue" = "Transparent"  // 透明物体:      3000
    "Queue" = "Overlay"      // 覆盖层:        4000
}

// 自定义偏移:
"Queue" = "Geometry+50"  // 在不透明几何体之后渲染

5.5 使用深度偏移值 — DepthBias

在某些情况下(如 Shadow Map、自定义深度写入),可以在 Shader 中手动添加深度偏移:

HLSL / URP Shader
// 方法一:在顶点着色器中手动偏移
Varyings vert(Attributes input) {
    Varyings output;
    VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);

    // 沿法线方向略微偏移顶点
    float3 normalWS = TransformObjectToWorldNormal(input.normalOS);
    float3 posWS = vertexInput.positionWS + normalWS * 0.001;

    output.positionCS = TransformWorldToHClip(posWS);
    return output;
}
HLSL / URP Shader
// 方法二:在片元着色器中修改深度输出
half4 frag(Varyings input, out float outDepth : SV_Depth) : SV_Target {
    half4 color = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);

    // 获取当前深度并略微推远
    outDepth = input.positionCS.z / input.positionCS.w;
    outDepth += 0.00001;  // 小心!过大可能出现孔洞

    return color;
}

⚠ 注意

直接修改 SV_Depth 会禁用 Early-Z 优化,对性能有显著影响。仅在必要时使用,优先选择 Offset 命令或顶点偏移方案。


六、常见深度冲突场景实战

场景一:贴花 (Decal) 闪烁

在墙壁上放置弹孔贴花时,贴花面与墙面几乎共面。解决方案:

  1. 使用 Offset -1, -1 将贴花略微拉近
  2. 或将贴花面沿法线偏移 0.001-0.005 单位
  3. 使用 URP Decal Projector 或 DBuffer Decal(如果已启用)
HLSL — Decal Shader Pass
Pass {
    Name "Decal"
    Tags { "LightMode" = "UniversalForward" }

    ZWrite Off
    ZTest LEqual
    Offset -1, -1      // 核心:轻微拉近贴花

    Blend SrcAlpha OneMinusSrcAlpha
}

场景二:两个平面重叠

当地形与水面、或两层地面材质重叠时:

  1. 增大层间距(最简单的方案)
  2. 使用 ZTest GEqual + ZWrite Off 确保仅在不透明物体之后渲染
  3. 调整 Render Queue 控制渲染顺序

场景三:Shadow Map 自阴影斑块

这不是典型的深度冲突,但本质同样是深度精度不足。URP 通过 ShadowCaster Pass 中的 normalBias 和 depthBias 来处理。 你可以在 URP Asset → Shadows 中调整这些参数。

C# — Light Inspector
// URP Asset → Shadows:
// Depth Bias:    控制阴影深度偏移量
// Normal Bias:   沿法线方向偏移采样位置
// Soft Shadows:  启用后叠加 PCF 滤波

// 或者通过脚本调整定向光:
Light light = GetComponent<Light>();
light.shadowBias = 0.05f;
light.shadowNormalBias = 0.4f;

七、排查清单 & 总结

当你在 URP 项目中遇到视觉闪烁/锯齿交错时,按以下顺序排查:

  1. 确认是深度冲突:移动摄像机时,闪烁是否持续?两个面的纹理是否交替出现?
  2. 检查面间距:在场景编辑器中拉近查看,两个面是否几乎重合?
  3. 调整 Near/Far:Near 是否设置得过小(< 0.05)?
  4. 尝试 Offset:在 Shader 中加 Offset -1, -1
  5. 调整 Render Queue:确保渲染顺序符合预期
  6. 检查深度格式:是否使用了 16 位深度?换用 24 位或 32 位
  7. 考虑 Reverse-Z:新版 URP 默认启用,确认未被意外禁用
解决方案适用场景成本效果
Offset 命令贴花、描边、UI 叠加几乎为零★★★★★
增大 Near / 减小 Far大场景、远处物体★★★★☆
Reverse-Z所有场景(URP 默认)★★★★★
顶点法线偏移双层平面、墙面覆盖★★★★☆
Render Queue 调整同位置多层物体★★★☆☆
升级深度格式 (32-bit)整体精度不足带宽增加★★★★☆
SV_Depth 手动偏移复杂自定义需求高(禁用 Early-Z)★★★☆☆

深度冲突本质上是深度缓冲的精度与场景需求之间的资源竞争。 理解深度值的非线性分布,合理设置近远裁面,利用 Offset 和 Reverse-Z 等工具,大多数深度冲突都能在不牺牲性能的前提下优雅解决。

内容概要:本文档是一份涵盖多个科研领域的Matlab、Python及Simulink代码实现资源集,重点包括通信系统中的GMSK调制二比特差分解调、Turbo码结合BPSK或GMSK的调制解调技术研究,以及永磁同步电机控制、微电网优化、路径规划、负荷预测、风电功率预测、无人机控制、电力系统仿真、信号处理、图像处理、雷达技术、车间调度、智能优化算法等多个方向的技术实现。文档详细列举了大量基于Matlab/Simulink的仿真项目,如自抗扰控制、模型预测控制、涡轮编码调制、智能优化算法等,并提供了相关代码资源的网盘链接。同时,文档强调科研过程中逻辑思维、创新意识与“借力”工具的重要性,倡导系统性学习与实践相结合,帮助研究者高效推进课题研究与论文复现工作。; 适合人群:具备一定Matlab、Python或Simulink编程基础,从事电子信息、通信工程、电气工程、自动化、控制科学与工程、电力系统、计算机科学等相关领域的研究生、科研人员及工程师,尤其适合开展仿真类课题或需要复现顶刊论文的研究者。; 使用场景及目标:① 学习和复现现代通信系统中GMSK、BPSK调制与Turbo码结合的仿真流程;② 掌握永磁同步电机控制策略(如自抗扰、滑模控制、模型预测控制)的建模与仿真方法;③ 实现微电网能量管理、路径规划、负荷预测、风电功率预测等复杂系统的算法开发与仿真验证;④ 辅助科研论文写作与课题研究,快速搭建仿真模型并优化算法性能;⑤ 借助智能优化算法解决生产调度、路径规划、资源配置等复杂工程问题。; 阅读建议:建议读者按照文档中项目分类循序渐进地学习,优先关注自身研究方向相关的代码实例。应结合理论知识,深入理解代码逻辑,并尝试在提供的仿真模型基础上进行参数调整与功能扩展,以达到掌握核心技术与提升科研效率的目标。注意资源来源于第三方,使用时需尊重版权,避免用于商业用途。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值