资讯动态

Unity透明物体深度数据获取:原理、Shader实现与URP适配

发布时间:2026/10/5 6:10:40 来源:尧图企业网站定制
在Unity里做透明物体相关的深度处理真的是一个让不少人栽过跟头的点。透明物体默认不写入深度缓冲这导致你在后处理、遮挡剔除、深度训练数据采集、MR遮挡合成等场景里拿不到透明物体的可靠深度数据。我最近刚好有一个数字孪生项目里要用半透明管道做避障检测折腾了一圈把获取透明物体深度数据这条路彻底摸了一遍这里把完整思路和可以直接复用的代码整理出来希望能给正被这个需求折磨的人一点帮助。Unity获取透明物体深度数据先说明白一个问题我们说的“透明物体深度数据”指的是把半透明或全透明物体当作一个有实际立体结构的对象获得它在空间中的深度信息而不是主相机深度图里默认记录的那一份。为什么要单独获取因为Unity默认情况下透明物体不参与深度写入你需要换一种思路把它单独抽出来渲染或者用额外相机单独记录。这篇文章会从渲染原理、方案选型、具体Shader与C#实现到URP管线、性能优化、常见坑位排查一次性讲透。1. 先理清问题透明物体为什么拿不到正确深度1.1 透明渲染管线与深度缓冲的矛盾Unity内置管线里物体的渲染顺序是“先不透明、后透明”。不透明物体的渲染队列通常在Geometry2500区间而半透明物体在Transparent3000区间靠后的原因是要让透明物体能够与已经渲染好的场景进行Alpha混合。混合的标准姿势是这样的Blend SrcAlpha OneMinusSrcAlpha ZWrite OffZWrite Off意味着这个物体不会往深度缓冲里写任何东西。这样一来主相机的深度纹理_CameraDepthTexture里就不可能包含透明物体的深度信息。你可以把深度缓冲理解成一张记录“谁离相机最近”的登记表不透明物体老老实实登记了透明物体是个隐形人登记表上永远没有它的名字。还有个更隐蔽的细节半透明物体的排序是按距离从远到近画的从后往前但如果多个半透明物体彼此嵌套或者某个半透明物体和不透明物体相互穿插排序算法偶尔会抽风出现某几帧看起来混叠不对的现象。这种情况下你即使想手动改深度也没有统一规则可循。所以正确做法不是去试图修主渲染流程里的深度记录而是把“透明物体的深度”这件事从正常画面渲染中拆出来单独解决。1.2 什么场景下你真的需要透明物体的深度不是所有透明物体都需要深度数据但以下场景就需要认真对待数字孪生项目里的管道、玻璃围栏、半透明设备外壳。你要做物流路径规划和机械臂避障检测如果深度图里没有管道位置算法等于瞎了。MR/AR遮挡合成。Pico、Quest这类设备做透视时虚拟的半透明遮挡物需要真实反映前后遮挡关系否则半透明物体穿模。深度估计训练数据集。很多做深度网络训练的同学需要给标注图像配一张深度真值图如果场景里全是半透明材质自带的深度图也会缺失这些区域。雷达点云模拟、机器人导航语义分割。透明障碍物在激光雷达里可能返回不了太多点但视觉深度图里如果也不存在透明物体导航会直接撞上去。自定义后处理描边、雾气遮挡、体积光遮挡。不少效果需要从主相机深度纹理里采样但透明物体不在里面描边就描不中半透明物体。1.3 那“不透明深度”的常规套路为什么行不通常规做法是在C#里把主相机的depthTextureMode设为DepthTextureMode.Depth然后通过_CameraDepthTexture取深度。这个方法对不透明物体没问题对透明物体就直接失效。原因就是你Shader里透明物体根本不会写深度它只写了颜色深度缓冲那一栏是空的。还有人会用Graphics.DrawMeshNow配合MaterialPropertyBlock直接画一个深度图这个思路是对的但如果你直接复用不透明Shader半透明物体上那些带有Alpha Test的贴图又会出问题裁剪掉的像素需要剔除没裁剪掉的像素要计算正确的表面深度。这就要单独写Shader了。所以核心思路就是把透明物体当成“不透明物体”再渲染一遍但这次只输出深度不混合颜色不进真正的画面。2. 方案选型五条主流路径怎么选2.1 方案一额外相机独立渲染深度简单粗暴给透明物体单独分配一个Layer然后创建一个只渲染这个Layer的相机把相机的RenderTexture设成一张深度纹理。相机Clear Flags设为Depth Only相机Culling Mask只留目标Layer然后把这个相机Target Texture指到一张RenderTexture上。这个方法的优点是不用写任何自定义Shader你可以直接把透明物体的材质挂上ZWrite On相机输出深度。缺点是要多渲染一遍场景里所有透明物体还容易遇到“贴图纹理正常但深度出现空洞”的问题。实际项目中我一般把这种相机当成备用方案或者只在调试阶段拿来验证深度对不对。用这个方案还需要注意一点如果半透明物体材质本身是Transparent队列且ZWrite没关渲染出来虽然能进深度图但半透明物体之间可能因为队列排序的原因产生遮挡错误。解决办法是把深度专用相机里的Render Queue强制改成Geometry1把透明物体当成不透明物体渲染。在一些写死的材质上可能需要复制材质再改会比较痛苦。2.2 方案二自定义Shader输出线性深度到RenderTexture强烈推荐这个方案是目前工程上最稳的一种。思路是给透明物体单独做一个“深度输出”Shader这个Shader只负责计算顶点在相机空间里的Z值然后把它写进一张颜色纹理的R通道。视觉上你看到的是“灰度图”但按内容它就是一张深度图。为什么要比独立相机方案更好因为你可以用同一套材质既能在主相机里正常显示半透明效果又能把深度输出到任意RenderTexture里不需要复制场景。这个方案适配内置管线和URP代码量也不大我会在第三章重点展开。2.3 方案三颜色编码深度 后处理解码透明物体Shader根本不写深度纹理那你把深度压成RGBA颜色值写到一张RenderTexture里然后后续Pass里再解码是不是也能拿到能。深度值从0到1如果用8位纹理存只有256级灰度远处的精度会崩掉。所以有人会把深度拆成4个8位通道用类似pack的方式编码把精度提升到24位以上。比如// 编码把深度[0,1]拆成RGBA四个通道 float4 EncodeFloatRGBA(float v) { float4 enc float4(1.0, 255.0, 65025.0, 16581375.0) * v; enc frac(enc); enc - enc.yzww * float4(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0, 0.0); return enc; }解码时再用权重把四个通道合回去。这种做法在老手机上兼容性好因为老的浮点纹理支持不全但需要额外写编解码逻辑而且如果后续直接把RenderTexture喂给深度网络训练你还得再写一个解码逻辑比较绕。所以我个人认为颜色编码方案更适合老机型或者特殊无法使用浮点纹理的环境现代Unity项目里我更推荐方案二直出深度值。2.4 方案四GrabPass或深度拷贝GrabPass在旧内置管线下用的多它能把当前屏幕内容抓进一张纹理。但抓的是颜色不是深度。你如果想要主相机深度纹理里包含透明物体深度可以先让透明物体正常渲染再在后处理Pass里额外跑一个全屏Pass把深度“复制”一份出来。这听着可行但深度缓冲的读取和拷贝在移动端和WebGL上的限制特别多很多时候_CameraDepthTexture本身就是可选功能不一定能直接取到。如果你只是想要主相机最终画面里透明物体的深度和别的物体一起合并那这条路要处理相机的CameraEvent注入复杂度不低。等你有时间调试注入时机和渲染队列的时候方案二早就跑通了。2.5 方案五用Compute Shader在下一帧读取深度纹理理论上你可以用CommandBuffer或ComputeShader在透明渲染完成后再请求一次_CameraDepthTexture但Unity的深度纹理更新是在相机渲染某个阶段才填充的透明渲染之后再去拿纹理内容不是实时更新的。真要这么做你得在用AfterForwardAlpha时机插入自定义Pass把这时的深度读出到固定纹理。可行性有但从稳定性和跨平台兼容来看并不比自定义Shader方案好。2.6 方案横向对比速查表方案是否需要自定义Shader能否直接拿到线性深度移动端兼容性性能消耗工程复杂度额外相机深度渲染否是一般中低自定义Shader输出深度到RT是是好低中颜色编码深度是需要解码好中中高GrabPass/深度拷贝需要视情况注入不一定一般高高ComputeShader读取深度纹理是需要处理时序一般中高高综合下来我的建议是如果是一两个透明物体直接用额外相机方案如果透明物体数量多、且你需要精确线性深度做算法优先自定义Shader输出深度到RenderTexture。3. 核心实操透明物体深度数据采集与可视化这一章我们就用自定义Shader 目标RenderTexture的法子亲手做一套深度采集方案。我以内置管线为主讲URP会在3.4节给出适配说明。3.1 C#侧搭建深度输出相机与RT管理先写一个C#脚本挂在某个管理对象上。它会在场景里隐藏创建一台专用相机配置好Culling Mask和Clear Flags并且把整个透明物体深度输出逻辑和RenderTexture生命周期串起来。using UnityEngine; using UnityEngine.Rendering; public class TransparentDepthCapture : MonoBehaviour { [Header(透明物体所在的Layer)] public LayerMask transparentLayer; [Header(深度图分辨率)] public int depthTexWidth 640; public int depthTexHeight 480; [Header(深度相机近远裁剪面)] public float nearPlane 0.1f; public float farPlane 100f; private Camera depthCam; private RenderTexture depthRT; void Start() { // 创建专用相机 GameObject camObj new GameObject(TransparentDepthCam); camObj.transform.SetParent(transform); camObj.transform.localPosition Vector3.zero; camObj.transform.localRotation Quaternion.identity; depthCam camObj.AddComponentCamera(); depthCam.CopyFrom(Camera.main); depthCam.clearFlags CameraClearFlags.Depth; depthCam.cullingMask transparentLayer; depthCam.enabled false; // 我们手动 Render depthCam.nearClipPlane nearPlane; depthCam.farClipPlane farPlane; // 创建深度RTR16_FLOAT存深度足以支撑精度 depthRT new RenderTexture(depthTexWidth, depthTexHeight, 16, RenderTextureFormat.RFloat); depthRT.name TransparentDepthRT; depthRT.filterMode FilterMode.Point; depthRT.wrapMode TextureWrapMode.Clamp; depthRT.Create(); depthCam.targetTexture depthRT; } void Update() { if (depthCam null) return; // 把专用相机摆到和主相机相同的位置 Camera mainCam Camera.main; if (mainCam ! null) { depthCam.transform.position mainCam.transform.position; depthCam.transform.rotation mainCam.transform.rotation; } // 渲染一次深度到 RT depthCam.Render(); } public RenderTexture GetDepthTexture() { return depthRT; } void OnDestroy() { if (depthRT ! null) { depthRT.Release(); Destroy(depthRT); } if (depthCam ! null) { Destroy(depthCam.gameObject); } } }这段脚本有几个设计点要说明第一CameraClearFlags.Depth很关键。如果设成SolidColor它会把颜色也清掉我们只需要depth buffer清空即可Depth模式下颜色缓冲内容未定义但我们只关心深度无妨。第二RenderTextureFormat.RFloat存储单通道32位浮点深度精度完全够。如果你在WebGL或者某些移动端遇到RFloat不支持的情况可以退回RenderTextureFormat.RHalf16位半精度浮点大多数情况下也够用。第三我不让这个相机enabled而是手动在Update()里调用Render()。这样渲染时机完全由自己控制不会和主相机渲染冲突也方便你自己决定什么时候采集、采集几帧。第四相机位置每一帧都要从主相机同步。如果主相机的Camera.main频繁切换记得写个缓存别每一帧都GetComponent。3.2 Shader侧透明物体深度输出Shader深度相机的关键是给透明物体准备一个合适的Shader。这个Shader和普通透明Shader的区别在于ZWrite On确保深度写入。不需要Alpha混合直接把深度值输出到R通道。需要处理Alpha Test保证镂空区域深度正确。以下是一份可以在内置管线下直接使用的Unlit深度输出ShaderShader Custom/TransparentDepthOutput { Properties { _MainTex (Alpha Mask (R), 2D) white {} _Cutoff (Alpha Cutoff, Range(0,1)) 0.5 } SubShader { Tags { QueueGeometry1 RenderTypeTransparentCutout } Cull Off ZWrite On ZTest LEqual ColorMask R Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc sampler2D _MainTex; float4 _MainTex_ST; float _Cutoff; struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; float4 screenPos : TEXCOORD1; }; v2f vert (appdata v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); // 取屏幕坐标用于计算深度也可以用 o.pos.z o.screenPos ComputeScreenPos(o.pos); return o; } fixed4 frag (v2f i) : SV_Target { // Alpha Test镂空区域不输出深度 fixed4 col tex2D(_MainTex, i.uv); clip(col.a - _Cutoff); // 这里的深度是投影后的非线性深度范围 [0,1] // 我们可以直接输出 o.pos.z但因为经过透视除法后在片段里取 i.pos.z 更方便 // 用 screenPos 还原出 NDC.z float2 uv i.screenPos.xy / i.screenPos.w; float depth i.pos.z / i.pos.w; // 注意这里需要在片段中拿到裁剪空间Z // 如果你要线性深度也可以在这里还原但先直接输出NDC深度 return depth; } ENDCG } } }这里有一个容易搞错的地方片段Shader里输入结构体v2f里的pos是SV_POSITION它代表的是裁剪空间坐标。经过光栅化后硬件会自动做透视除法但你在片段里读取这个变量时得到的是经过插值后的裁剪坐标不是NDC。所以你需要自己除一下w分量才能得到真正的NDC深度。很多入门者直接把i.pos.z输出得到的深度永远是接近远裁剪面的无效值就是这个原因。如果你需要线性深度可以在片段里手动转换float depth i.pos.z / i.pos.w; #if defined(SHADER_API_D3D11) || defined(SHADER_API_METAL) // DirectX与Metal的NDC深度范围是[0,1] float linearZ Linear01Depth(depth); #else // OpenGL的NDC深度范围是[-1,1]需要再映射一次 float linearZ Linear01Depth(depth * 0.5 0.5); #endifLinear01Depth是UnityCG.cginc里现成的函数可以把NDC深度还原成视空间里的线性距离再除以远裁剪面归一化到[0,1]区间。这样拿到的深度值就是均匀线性的不会出现近处变化大、远处压成一坨的非线性问题。3.3 用全屏Shader验证深度图质量拿到RT后怎么确认深度是对的最快的办法是把它实时显示在UI上或全屏后处理里。写一个简单的显示ShaderShader Debug/ShowDepthRT { Properties { _MainTex (Depth RT, 2D) black {} } SubShader { Tags { QueueOverlay } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc sampler2D _MainTex; struct v2f { float4 pos : SV_POSITION; float2 uv : TEXCOORD0; }; v2f vert (appdata_full v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.uv v.texcoord; return o; } fixed4 frag (v2f i) : SV_Target { float depth tex2D(_MainTex, i.uv).r; // 深度越大越亮距离越远越白 return depth; } ENDCG } } }把这个材质挂在场景里的一个全屏Quad上或者用OnRenderImage输出你就能直观看到透明物体在视野中的深度值。正常效果应该是一个离相机近的透明物体显示为深灰逐渐变远为浅灰或者白色。如果你看到一片黑说明深度没写进去如果一片白说明深度始终是1可能你的深度没有正确写入或者RT格式/深度清除有问题。3.4 URP下的适配说明URP和内置管线最大的差异是渲染路径和设置方式不同。URP里你依然可以用额外相机方案但深度输出Shader要写成HLSL版本并且配合Universal Render Pipeline的SRP Batcher。还有一个更推荐的做法是使用RenderObjects这个Renderer Feature把透明物体的深度输出注入到指定Pass。如果你要在URP里用额外相机方案请把深度相机的UniversalAdditionalCameraData组件设置好关掉后处理设置renderType为CameraRenderType.Overlay或者直接用Base相机手动Render。Shader部分需要注意使用HLSLPROGRAM而不是CGPROGRAM引用Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl用GetVertexPositionInputs获取裁剪空间坐标深度转换用LinearEyeDepth或Linear01DepthURP里这两个函数的入参是采样深度纹理后得到的原始值在URP里ZWrite On的处理逻辑和内置管线是一样的。比较常见的坑是URP的默认RenderObjects Feature会把不透明的Pass数据也混进来你需要在Shader的Tags里指定RenderTypeTransparentCutout或自定义LightMode标签这样Feature才只匹配你想输出的物体。3.5 怎么把深度数据松松喂给训练任务如果你的目的是训练深度网络通常你还需要把深度图按网络输入的格式导出。最简单的方式是把RT读回CPU转成Texture2D再保存为PNG。但注意PNG是8位整数通道存线性深度一定会损失精度。深度训练的正确姿势是用Texture2D.ReadPixels把深度值读出来转成float[]直接存成二进制文件.raw或者NPY精度是float32网络训练完全够如果非要存图片推荐存16位PNG。用Texture2D的RGBAHalf格式读回来再手动打包成16位PNG保存但工程复杂度会高一些把深度图和RGB图绑定在一起导出时注意视口偏移和相机FOV一致性别训练完发现深度图和颜色图对不上。实际我遇到过一种情况场景里有动态透明物体在移动训练时深度图要对应每个时刻的快照。我当时的做法是固定相机位置把主相机和深度相机放在同一坐标两个相机共用一个RenderTexture尺寸每一帧同步执行Render()然后把两张图同时截出来。这样时间和空间同步问题直接消失。4. 性能优化与工程落地4.1 分辨率、RT格式与带宽开销深度RT的分辨率不必和主相机一致。如果你只是做后处理640x480足够如果是给深度网络做训练数据一般1280x720也够了再高就是在给带宽和显存上压力。RFloat格式是4字节每像素1080p一帧大约8.3MB。这个开销如果每个帧都渲染移动端会比较吃紧。所以在移动端我通常用RHalf精度稍微降低一点带宽减半。如果场景透明物体数量很多相机每帧把所有透明物体都画一遍确实会有额外DrawCall。但实际上透明物体本来就是画面的一部分你等于多渲染了一次它们这个成本相对可接受。我测试过一个中等场景里大约200个半透明管道深度相机多出来的开销大概占总帧时间的4%到6%没有再额外的后处理消耗。4.2 避免每帧重复创建RT很多项目喜欢在OnRenderImage里临时RenderTexture.GetTemporary用完再释放这套模式在不透明深度上是可行的但我们这里深度RT要长期存在且要被算法或网络反复采样不建议频繁创建销毁。我通常采用池化的方式初始化时直接创建一个目标RT常驻需要更新分辨率时才重新创建。别忘了在OnDestroy里释放否则移动端很容易摸到内存红线。4.3 与后处理/遮罩的配合拿到透明物体深度后最常见的用法是把深度合并到主相机深度里用于后处理。你可以写一个自定义后处理Pass在主相机完成渲染后把主相机深度纹理和透明物体深度RT做一次逐像素比较// 伪代码判断透明物体是否比主相机里的物体更近 float sceneDepth tex2D(_CameraDepthTexture, uv); float transDepth tex2D(_TransDepthRT, uv); if (transDepth sceneDepth) { // 透明物体更近把它当作距离信息参与遮挡 }这里的核心逻辑是“谁的深度小谁离相机更近”。因为主相机深度图里透明物体区域是空的值可能很大远裁剪面所以你用min合并即可。个别情况下你会遇到透明物体和不透明物体交叉这时候要以不透明物体的深度为准判断遮挡关系否则半透明玻璃会错误地挡住背后的墙。4.4 透明物体排序导致抖动怎么办有些半透明材质ZWrite关掉后深度相机会输出一坨怪值因为深度相机走的还是普通渲染管线而透明物体的渲染队列是按从远到近排的。如果你把深度相机的Shader队列改成了Geometry1它能保证按不透明的方式排序深度写入稳定。不过这意味着深度相机渲染的其实是“看起来不透明的透明物体”颜色输出无关紧要深度却是实打实的。如果你有多个半透明物体交叉嵌入比如玻璃板里泡着一根半透明塑料管深度相机会严格按几何面对比深度这时得到的结果是物理上正确的不会出现透明混合排序的伪影。这也算这个方案的一个隐藏优点。5. 常见问题与排查5.1 深度图全黑全黑一般是深度没有写入或RT格式不对。按下面顺序排查确认深度相机清屏方式是否为Depth Only确认透明物体的深度输出Shader里ZWrite On且没有ColorMask 0你把颜色输出屏蔽了深度自然没了确认RT格式是RFloat或RHalf如果用ARGB32存R通道依然有深度值但要确认Shader输出的是float不是half4在Update里调用了depthCam.Render()但相机enabled为true有时两处都触发会重复渲染。建议保持enabled false只手动渲染。5.2 深度图全白或远处一片白全白说明深度接近1。常见原因是深度输出的是NDC深度后被当成了原始深度或者farPlane设得太小导致远处物体全被裁剪到远端。调试时可以先显示一个平面距离较近的透明物体如果它显示出灰色而不是白色说明深度方向是对的。5.3 镂空物体的透明区域出现深度空洞这个很好理解深度输出Shader里Alpha Test没生效或者贴图Alpha没有正确采样。我在3.2节的Shader里已经用clip(col.a - _Cutoff)处理了镂空区域。如果你遇到镂空区域仍然有深度检查_MainTex里是否用的是Alpha通道不是RGB颜色。很多贴图设计者把Alpha放在透明通道RGB是一样的但有些情况下RGB不同你要手动确认。5.4 多个透明物体互相遮挡时深度顺序不对如果你发现透明物体A明明在物体B前面深度图里却是A较远先检查深度输出Shader的ZTest。默认ZTest LEqual是“深度小于等于当前缓冲深度即可通过”但如果你在深度相机里清空的是深度缓冲物体按场景里的几何顺序判断是正常的。问题往往出在渲染队列上——如果某些Shader用了QueueTransparent即使ZWrite On渲染顺序还是从远到近深度测试会让近处物体反而被远处物体覆盖。统一改成QueueGeometry1后问题会消失。5.5 C#读取RT图像时全是0读取RT前必须确保GPU已经把渲染结果同步到CPU可读状态。使用AsyncGPUReadback推荐用于性能关键路径但同步读取时用Texture2D.ReadPixels就够了且别忘了在读取之前调用RenderTexture.active depthRT。很多人在这里栽过跟头读取用的RenderTexture.active没设置读回来的自然全是黑。另外depthRT.Create()必须在首次Render()前调用不然RT是无效状态。5.6 WebGL和移动端的RT兼容问题WebGL支持RFloat存在兼容性风险老机型上不支持浮点纹理渲染。如果你要发布到WebGL建议使用RHalf或者退化到ARGB32加颜色编码深度方案。移动端在OpenGL ES 3.0及以上支持RG16F但低端设备可能只支持RGBA16F所以RT格式建议用RenderTextureFormat.RGHalf然后Shader用.rg或者干脆写一个运行时判断函数用SystemInfo.SupportsRenderTextureFormat做容错。5.7 透明物体同时有普通颜色渲染需求如果你透明物体既要正常显示又要在深度相机里输出深度有两种做法。一种是复制一份材质另一个是直接用Shader的LOD或者multi_compile区分开关。我更推荐后者同一个Shader通过关键字切换Pass在正常渲染时走混合Pass在深度相机渲染时走深度输出Pass。这样省材质也不会出现材质被改乱的问题。// C# 侧深度相机渲染前开启关键字 Shader.EnableKeyword(_TRANSPARENT_DEPTH_ON); depthCam.Render(); Shader.DisableKeyword(_TRANSPARENT_DEPTH_ON);然后再在Shader里根据#ifdef _TRANSPARENT_DEPTH_ON切换不同的输出逻辑。这一招在项目里实测下来很稳也不影响主相机的正常渲染流程。6. 最后再说几个实践中的体会透明物体深度这件事看起来是个小需求但真做起来牵扯到的渲染知识一点都不少。如果只是想要一个粗略的深度图额外相机方案最省事如果透明物体数量多、材质复杂、后续还要做深度训练或者避障算法自定义Shader输出深度RT这条路的性价比最高。我自己的习惯是把深度采集脚本封装成独立的组件深度相机与主相机完全解耦业务方只关心拿到的那张RenderTexture。这样后来接手的人不需要理解底层Shader只要知道怎么读取深度图就行。另外调试阶段我会在UI右上角挂一个小Quad实时显示深度RT方便我在场景里挪动物体后马上看到深度变化。这个小工具虽然不起眼但排查问题时能省大量时间。还有一个隐藏很深的坑是半透明物体加ZWrite On后虽然深度相机里得到正确深度但它在主相机渲染时如果你的Shader没有正确处理会把主相机里的半透明排序也搞乱。所以强烈建议在主相机渲染时保持透明物体原本的渲染设置不变只在深度相机里换用深度输出Shader。这个“双Shader切换”的思路是我从多个项目里踩坑后总结出的最稳做法。透明物体能产生正确的深度数据以后后续的玩法就多了可以做透明障碍物避障、可以做MR遮挡、可以给深度估计网络提供完整真值、也可以在数字孪生场景里实现半透明管线和机械臂的精确碰撞检测。关键是先把最底层的“深度数据”这一步走扎实后面所有上层应用就都是水到渠成。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑