ARTICLE DETAIL

资讯详情

深耕网站SEO优化与搜索引擎排名提升的一线实战洞察。

Unity双面显示Shader全解析:从原理到实战优化

Unity双面显示Shader全解析:从原理到实战优化 1. 项目概述为什么需要双面显示在Unity里做项目尤其是涉及到一些特殊模型比如单面建模的树叶、旗帜、纸张或者一些透明材质时你肯定遇到过这样的问题从正面看模型好好的一旦镜头转到背面模型就“消失”了或者变成了全黑。这背后的原因是Unity以及绝大多数3D渲染引擎默认的渲染优化机制——背面剔除。为了提升渲染效率GPU默认只渲染三角形网格的“正面”。这个正面由顶点的连接顺序环绕顺序决定。当摄像机看到的是三角形的背面时这个三角形就会被直接丢弃不进行任何光照和着色计算所以你看不到它。这对于封闭的实体物体比如一个盒子、一个角色来说完全没问题还能省下一半的性能。但对于我们刚才提到的那些需要两面都可见的物体这就成了大问题。“双面显示Shader”就是解决这个问题的钥匙。它不是一个单一的文件而是一类Shader技术的统称核心目标就是让模型的正反两面都能被正确渲染。网上有很多现成的双面Shader资源但如果不理解其原理用起来要么效果不对要么性能堪忧。今天我就结合自己踩过的坑从原理到实战把Unity里实现双面显示的各种方法掰开揉碎了讲清楚让你不仅能“抄作业”更能“出考题”。2. 核心原理与方案选型不止是关掉剔除那么简单实现双面显示听起来简单做起来却有几个不同层次的方案各有优劣。选对方案事半功倍。2.1 方案一禁用背面剔除Two Pass这是最直观、最“暴力”的方法。思路很简单既然默认只画正面那我让Shader画两遍不就行了第一遍画正面第二遍画背面。实现核心在SubShader中使用两个Pass。第一个Pass正常渲染Cull Back剔除背面第二个Pass专门渲染背面此时需要将剔除模式设置为Cull Front剔除正面并且通常需要将背面三角形的顶点顺序翻转或者处理法线以确保光照计算正确。SubShader { Tags { RenderTypeOpaque } LOD 100 // Pass 1: 渲染正面 Pass { Cull Back // ... 正常的顶点/片元着色器代码 ... } // Pass 2: 渲染背面 Pass { Cull Front // 通常需要在这里对法线进行反转例如normal * -1; // ... 与Pass 1类似的着色器代码但处理反转的法线 ... } }优点概念清晰最容易理解几乎所有的内置和URP/HDRP的“Double Sided”选项底层都是这个思路或它的变种。控制灵活可以分别对正面和背面使用不同的渲染状态比如给背面一个不同的颜色或透明度。缺点性能开销大每个物体至少需要渲染两次如果透明还可能更多Draw Call翻倍对性能影响显著在移动端或大量使用时要非常小心。光照问题背面的法线方向是反的如果不处理光照会完全错误看起来是内凹的。需要在第二个Pass中手动反转法线或使用特定的光照模型。实操心得这个方案适合用于调试或者场景中仅有极少数需要双面显示的物体时。对于URP/HDRP官方材质提供的“Double Sided”选项通常已经优化了这个过程比自己写Two Pass要高效和稳定应优先使用。2.2 方案二单Pass双面渲染与法线处理更高级的方案是只用一个Pass但让GPU在渲染时同时处理正反两面的光照。这通常需要图形API的支持例如OpenGL的gl_FrontFacingHLSL的SV_IsFrontFace来判断当前渲染的像素是属于正面还是背面三角形。实现核心在片元着色器中使用SV_IsFrontFace语义输入一个布尔值来判断当前像素是否属于正面三角形。struct v2f { float4 pos : SV_POSITION; float3 worldNormal : TEXCOORD0; // ... }; float4 frag (v2f i, bool face : SV_IsFrontFace) : SV_Target { // 根据正面/背面修正法线 float3 normal i.worldNormal; if (!face) { normal -normal; // 如果是背面反转法线 } // 使用修正后的normal进行光照计算... // ... }优点性能更优相比Two PassDraw Call没有增加仅增加了片元着色器中的一点点分支判断开销。效果统一正反面可以使用完全相同的着色逻辑只需修正法线更容易保证视觉一致性。缺点实现稍复杂需要理解SV_IsFrontFace的机制并正确地在光照计算前修正数据。对透明物体排序挑战在渲染透明物体时正反面三角形的交错可能会引发深度排序问题导致渲染错误。2.3 方案三URP/HDRP内置的双面支持对于使用可编程渲染管线URP/HDRP的项目这是首选且最推荐的方案。Unity官方已经将双面渲染作为材质的一个标准属性集成进来无需自己写复杂的Shader代码。在URP Lit Shader中创建一个Lit材质。在材质Inspector的“Surface Options”部分找到“Render Face”选项。将其从“Front”改为“Both”。在HDRP Lit Shader中如网络资料所示创建HDRP Lit材质。在“Surface Options”下启用“Double Sided”复选框。下方会出现“Normal Mode”选项用于控制背面法线的处理方式Flip将背面法线翻转180度。适用于两面看起来完全相同的薄物体如纸张、卡片。Mirror将背面法线镜像。适用于像树叶这样正反面是镜像对称的物体。None不修改背面法线。适用于一些特殊需求比如你想让背面看起来是内凹的。优点开箱即用无需编码一键启用。高度优化Unity底层做了大量优化性能通常优于自己写的通用Two Pass Shader。功能完整完美集成到光照、阴影、后期处理等管线流程中。缺点管线限制仅适用于URP或HDRP项目。Built-in RP内置渲染管线没有这个便捷选项。定制性降低如果你需要对正反面做差异极大的渲染比如正面是岩石背面是苔藓内置材质可能不够灵活仍需自定义Shader。3. 实战从零构建一个自定义双面透明Shader理解了原理我们来动手写一个。假设我们在Built-in管线中需要一个双面显示的透明树叶Shader并且希望正反面颜色略有不同以增加细节。3.1 Shader代码实现我们将采用单Pass SV_IsFrontFace判断的方案兼顾性能和效果。Shader Custom/DoubleSidedLeaf { Properties { _MainTex (Albedo (RGB), 2D) white {} _FrontColor (Front Color Tint, Color) (1,1,1,1) _BackColor (Back Color Tint, Color) (0.8, 0.9, 0.7, 1) // 背面给一个淡淡的青绿色 _Glossiness (Smoothness, Range(0,1)) 0.5 _Metallic (Metallic, Range(0,1)) 0.0 _AlphaCutoff (Alpha Cutoff, Range(0,1)) 0.5 // 用于裁剪树叶形状 } SubShader { Tags { QueueAlphaTest RenderTypeTransparentCutout IgnoreProjectorTrue } LOD 200 Cull Off // 关键步骤关闭剔除让正反面三角形都进入渲染管线 CGPROGRAM // 使用表面着色器简化光照模型并启用面判断 #pragma surface surf Standard fullforwardshadows addshadow vertex:vert #pragma target 3.0 sampler2D _MainTex; fixed4 _FrontColor, _BackColor; half _Glossiness; half _Metallic; half _AlphaCutoff; struct Input { float2 uv_MainTex; fixed4 faceColor; // 用于传递正面/背面色调 }; // 顶点着色器预计算正面/背面色调 void vert (inout appdata_full v, out Input o) { UNITY_INITIALIZE_OUTPUT(Input, o); o.faceColor _FrontColor; // 默认设为正面色 } // 表面着色器根据SV_IsFrontFace决定最终颜色 void surf (Input IN, inout SurfaceOutputStandard o, bool face : SV_IsFrontFace) { fixed4 c tex2D(_MainTex, IN.uv_MainTex); // 根据正面/背面混合主纹理和对应的色调颜色 fixed4 tintedColor c * (face ? _FrontColor : _BackColor); // Alpha测试低于阈值的像素完全透明 clip(tintedColor.a - _AlphaCutoff); o.Albedo tintedColor.rgb; o.Metallic _Metallic; o.Smoothness _Glossiness; o.Alpha tintedColor.a; } ENDCG } FallBack Transparent/Cutout/VertexLit }3.2 关键代码解析与避坑指南Cull Off这是实现双面显示的基础。在SubShader或Pass中设置意味着不剔除任何面正面和背面的三角形都会提交光栅化。注意这可能会略微影响性能因为不可见的背面比如被树叶丛自身遮挡的部分也会被处理。#pragma surface ... vertex:vert我们使用了Surface Shader并通过vertex:vert指定了一个自定义的顶点函数。这里主要是为了将_FrontColor传递给Input结构作为一个初始值。实际上在surf函数中我们并未使用这个传入的值而是直接根据face判断。这个结构展示了如何传递自定义数据。bool face : SV_IsFrontFace这是片元着色器的额外输入。true表示当前片元属于正面三角形false则表示属于背面。这是本Shader的核心逻辑判断点。颜色混合与Alpha裁剪tintedColor c * (face ? _FrontColor : _BackColor);根据正面/背面选择不同的颜色色调与纹理相乘实现正反面视觉差异。clip(tintedColor.a - _AlphaCutoff);clip函数会丢弃Alpha值低于_AlphaCutoff的像素。这是渲染树叶、毛发等不规则边缘透明物体的常用技术Alpha Test比Alpha Blend透明度混合的顺序无关性更好但边缘会有锯齿。FallBack指定一个备用Shader。当当前Shader不被支持时会使用“Transparent/Cutout/VertexLit”这个更简单的、Unity内置的透明裁剪Shader保证最基本的显示效果。避坑指南透明与深度排序双面透明物体是渲染排序的“噩梦”。当你关闭剔除Cull Off后一个树叶模型的正反面三角形会交错在一起。如果使用Alpha Blend透明度混合可能会出现背面穿透过正面显示的错误。因此对于像树叶这样有明确形状的物体优先使用Alpha Testclip。它要么完全透明要么完全不透明没有半透明状态从而避免了复杂的深度排序问题。如果必须使用Alpha Blend例如玻璃、水泡请确保物体的网格不是单层薄片或者考虑使用更复杂的渲染技术如深度预通道。4. 性能优化与高级技巧双面渲染必然带来额外的性能负担在移动端或大型场景中需要精打细算。4.1 性能优化策略按需启用不要给所有材质都开启双面。只为那些真正需要从两面看到的物体如植被、铁丝网、布料启用。选择正确的方案URP/HDRP项目无条件使用管线内置的“Double Sided”选项。这是性能最优、最稳定的选择。Built-in RP项目少量物体使用自定义Shader优先尝试单Pass SV_IsFrontFace方案。Built-in RP项目需要正反面完全不同效果考虑使用Two Pass但务必做好性能预算。利用LOD细节层次为远处的双面物体创建简化版本的Shader或材质甚至直接使用单面渲染因为远处细节看不清。减少Overdraw双面渲染尤其是Cull Off会显著增加Overdraw像素重复绘制。确保场景使用合理的遮挡剔除Occlusion Culling和相机的远裁剪平面设置。4.2 处理阴影双面物体投射阴影也需要特殊处理。默认的阴影Pass可能只渲染正面。为了让背面也能产生正确的阴影你需要在自定义Shader中添加addshadow指令如我们示例中的#pragma surface ... addshadow这会为Surface Shader生成一个支持双面的阴影投射Pass。检查阴影接收双面物体接收阴影通常是没问题的但如果你发现阴影在背面显示异常可能需要检查光照贴图或屏幕空间阴影的生成设置。4.3 与Shader Graph配合URP/HRDP如果你在使用Shader Graph实现双面显示更加可视化在Master Node的“Material”设置中将“Render Face”从“Front”改为“Both”。要区分正反面可以使用“Is Front Face”节点。这个节点会输出一个布尔值true代表正面片元。你可以用它来作为Lerp或条件的输入混合不同的颜色、纹理或效果。5. 常见问题与排查实录即使理解了原理实战中还是会遇到各种妖魔鬼怪。这里记录几个我常遇到的问题和解决方法。5.1 问题启用了双面但背面还是黑的排查步骤检查法线这是最常见的原因。背面三角形的法线指向模型内部光照计算结果是黑的。确保你在Shader中正确处理了背面法线如使用SV_IsFrontFace反转法线或HDRP中选择了正确的Normal Mode。检查灯光模式某些旧的“VertexLit”或“Unlit”模式可能不支持双面光照。尝试使用“Standard”或“Standard (Specular setup)”等更完整的光照模型。检查材质属性确认双面选项确实已启用Cull Off或材质面板选项勾选。5.2 问题双面透明物体渲染顺序错乱互相穿透原因与解决原因Alpha混合Blend依赖于渲染顺序。当多个透明三角形重叠时错误的绘制顺序会导致颜色混合错误。解决首选Alpha Test如树叶、镂空贴图用clip进行裁剪避免混合。拆分渲染队列如果必须混合尝试调整物体的“Queue”标签例如从“Transparent”改为“Transparent10”让某些物体强制在另一些之后渲染。简化模型避免使用大面积的、完全重叠的双面透明薄片。可以考虑给模型一点点厚度。5.3 问题使用URP内置双面材质后性能下降明显排查与优化Profile分析使用Unity的Profiler特别是GPU模块和Frame Debugger确认Draw Call和三角形数量的增长是否在预期内。检查Overdraw在Scene视图中打开“Overdraw”渲染模式可能需要安装RenderDoc等工具更精确查看。红色越深表示像素被绘制的次数越多。双面物体会导致Overdraw翻倍。优化策略视锥体裁剪确保相机看不到的物体不会被渲染。遮挡剔除对于室内或复杂场景烘焙或实时计算遮挡剔除。减少使用重新评估是否每个物体都需要双面。有时通过修改模型给一个微小厚度或使用两张交叉的面片Billboard来模拟可能更高效。5.4 问题导入的第三方双面Shader报错或显示粉色解决流程检查渲染管线兼容性确认Shader是为Built-in RP、URP还是HDRP编写的。不同管线的Shader不兼容。粉色通常意味着缺少必要的SubShader或FallBack。检查Shader Target级别一些高级特性需要更高的Shader Model支持如#pragma target 3.5。在移动端确保目标设备支持该级别。逐步简化注释掉Shader中可能出错的复杂部分如自定义光照函数、屏幕空间效果先让基础颜色显示出来再逐一排查。实现双面显示是Unity Shader学习中的一个经典课题它串联了渲染状态管理、光照计算、透明渲染和性能优化等多个知识点。从最初简单地关闭Cull到理解正反面法线处理再到根据项目管线选择最优方案这个过程本身就是一个对渲染管线理解不断加深的过程。我个人习惯是在URP/HDRP中毫不犹豫使用官方选项在Built-in管线中对于简单需求自己写一个单Pass的Shader对于复杂效果则谨慎评估Two Pass的性能成本。记住没有最好的方案只有最适合你当前项目需求和性能预算的方案。
返回列表