
1. 项目概述从“看不见”的优化说起做图形渲染尤其是写Shader我们总在追求一个目标用最少的计算画出最正确的画面。听起来像句废话但真正做起来你会发现大量的计算其实花在了“画一些根本看不见的东西”上。比如一个立方体背对着你的那一面或者一个角色模型被墙壁挡住的那些三角形。这些像素最终并不会出现在屏幕上但GPU依然忠实地为它们执行了顶点变换、光栅化、片元着色等一系列昂贵的操作。这无疑是巨大的性能浪费。今天要聊的Cull命令就是ShaderLab中用来解决这个问题的“第一道防线”它直接告诉GPU“哪些面是根本不需要画的直接扔掉吧。”Cull中文常译为“剔除”或“背面剔除”是Shader中一个看似简单、实则影响深远的指令。它不是一个数学公式也不是一个复杂的算法而是一个决策开关。这个开关决定了在渲染管线早期哪些图元Primitive通常是三角形会被直接丢弃连进入后续流水线的资格都没有。理解并正确使用Cull是Shader性能优化的基本功也是解决许多渲染诡异问题的关键。无论是制作一个双面显示的树叶还是优化一个复杂场景的渲染开销Cull都是你必须熟练掌握的工具。接下来我们就从它的核心原理开始一步步拆解它的实战应用与那些容易踩坑的细节。2. 核心原理为什么能“看不见”就“不画”要理解Cull我们必须先建立两个基础认知面的朝向和渲染管线的工作流程。2.1 面的朝向法线与缠绕顺序在3D图形学中一个三角形面片有“正面”和“背面”之分。这个区分不是由纹理或颜色决定的而是由它的顶点缠绕顺序和观察方向共同决定的。顶点缠绕顺序当我们定义三角形的三个顶点时例如在Mesh中它们的顺序比如v0 - v1 - v2隐含地定义了一个“法线”方向。你可以用“右手定则”来想象四指弯曲方向沿着顶点顺序拇指所指的方向就是该面的“正面”法线方向。默认情况下逆时针Counter-Clockwise, CCW顺序定义的面被认为是“正面”。观察方向从摄像机或眼睛位置看向这个三角形。GPU在光栅化之前会进行一个快速检查根据变换到裁剪空间或屏幕空间的顶点位置计算这个三角形从摄像机看去其顶点顺序是顺时针还是逆时针。如果顺序与预设的“正面”顺序一致通常是CCW则判定为“正面”否则为“背面”。注意这个“正面/背面”的判断是基于屏幕空间的投影结果而不是世界空间中的模型法线。一个三角形即使在世界空间里扭成了麻花只要它在屏幕上投影的顶点顺序是逆时针的它就被认为是正面。2.2 渲染管线中的位置早剔除的巨大收益Cull指令发生在渲染管线的图元装配和裁剪阶段之后光栅化之前。这是一个非常靠前的位置。它的决策逻辑非常简单GPU判断当前三角形是正面还是背面。根据Shader中Cull命令的设置如Cull Back决定是否丢弃这个三角形。一旦被Cull丢弃这个三角形及其所包含的所有潜在像素都将不会经历后续任何阶段包括顶点着色器虽然顶点变换可能已部分完成但不同架构处理不同曲面细分着色器几何着色器最重要的是片元着色器片元着色器通常是GPU渲染中最耗时、最复杂的阶段尤其是当你的Shader包含了复杂的光照计算、纹理采样、屏幕空间效果时。通过Cull提前剔除掉一个背面三角形可能就直接避免了成百上千个无效的片元着色器调用。这种优化是指数级的对于由大量封闭实体如角色、建筑、道具构成的场景开启背面剔除通常能直接减少约50%的片元着色器工作量帧率提升立竿见影。2.3 ShaderLab中的语法在Unity的ShaderLab中Cull命令的语法非常简洁Cull [Back | Front | Off]Cull Back默认值。剔除背面。这是最常用的设置用于渲染不透明的封闭物体。Cull Front剔除正面。这个用得较少但在一些特殊情况下很有用比如渲染物体的内部如挖空的立方体或实现某些轮廓效果。Cull Off关闭剔除。正反面都会渲染。这是渲染双面物体如树叶、纸张、铁丝网、透明布料时必须的设置。3. 实战应用场景深度解析知道了原理和语法我们来看看Cull在真实项目里到底怎么用以及为什么这么用。3.1 场景一标准不透明物体Cull Back这是Cull的默认也是主要战场。几乎你看到的所有实体模型——角色、武器、场景道具、建筑——都应该使用Cull Back。为什么因为这些物体在现实世界中是“实心”的摄像机不可能同时看到它的正面和背面。你看到的是一个物体的外表面。渲染其内表面背面纯属浪费。例如一个球体你永远只能看到它朝向你的那一半面片。实操配置在你的不透明Shader的SubShader或Pass块中通常不需要显式写Cull Back因为它是默认状态。但显式写出是一个好习惯能让代码意图更清晰。SubShader { Tags { RenderTypeOpaque } LOD 100 Pass { Cull Back // 显式声明强调这是不透明物体 // ... 其他的CGPROGRAM代码 ... } }3.2 场景二双面渲染物体Cull Off当我们需要看到物体的正反两面时就必须关闭剔除。典型应用包括单面材质物体树叶、公告板Billboard、纸张、旗帜、铁丝网。这些物体在3D空间中本质上是一个平面从背面看应该和正面看起来一样或类似如果剔除背面从另一面看这个物体就“消失”了。透明物体许多透明Shader需要Cull Off。因为透明渲染通常涉及混合从不同角度可能会同时看到正反面的混合效果。例如一个玻璃杯、一扇窗户、一团烟雾。特殊效果比如能量护盾、全息投影为了营造一种“非实体”的感觉也会关闭剔除。重要注意事项渲染顺序与性能Cull Off意味着渲染工作量翻倍理论上因此必须谨慎使用。一个关键优化点是渲染顺序。不透明物体始终使用Cull Back并利用深度缓冲Z-Buffer进行早期深度测试最大限度减少过度绘制。透明物体Cull Off必须从后往前渲染在Shader中使用Tags { QueueTransparent }因为透明混合依赖于正确的背景颜色。这本身就会带来性能开销和排序问题。性能取舍如果一个“双面”物体在大部分镜头下其实只能看到一面比如贴在地面上的草可以考虑使用两个交叉的平面来模拟并对每个平面使用Cull Back这有时比一个Cull Off的平面更高效因为它仍然能利用背面剔除。示例一个简单的双面树叶ShaderShader Custom/DoubleSidedLeaf { Properties { _MainTex (Texture, 2D) white {} _Color (Color, Color) (1,1,1,1) } SubShader { Tags { RenderTypeTransparentCutout QueueAlphaTest } // 使用AlphaTest队列适用于树叶这种要么完全透明要么完全不透明的物体 LOD 100 Pass { Cull Off // 关键关闭剔除正反面都渲染 AlphaToMask On // 或使用clip指令用于处理纹理的透明通道 CGPROGRAM #pragma vertex vert #pragma fragment frag // ... 具体的顶点和片元着色器代码采样_MainTex并输出颜色 ... // 通常会在片元着色器中根据纹理的Alpha值进行clip实现镂空 ENDCG } } }3.3 场景三剔除正面Cull Front的妙用Cull Front是一个相对小众但功能强大的设置。它渲染物体的“内部”而非外部。渲染物体内部想象一个挖空的立方体比如一个房间摄像机在内部。默认的Cull Back会剔除掉房间的内墙面因为从内部看墙的正面是背对摄像机的。这时设置Cull Front反而能正确渲染出内墙面。这种技术常用于制作天空盒、传送门内部视图等。实现轮廓描边方法之一一种经典的描边技术是使用两个Pass第一个PassCull Front将模型顶点沿法线方向稍微向外膨胀并渲染成纯色如黑色。这相当于画出了物体背面的“壳”即轮廓。第二个PassCull Back正常渲染物体本身。 这样就能得到一个包裹物体的轮廓线。这种方法性能较好但对于复杂形状或锐利边缘可能效果不完美。调试与可视化在开发中你可以临时将某个Shader改为Cull Front来检查模型的内部结构或面片方向是否正确。示例简易膨胀轮廓效果Shader Custom/SimpleOutline { Properties { _OutlineColor (Outline Color, Color) (0,0,0,1) _OutlineWidth (Outline Width, Range(0, 0.1)) 0.03 _MainTex (Main Tex, 2D) white {} } SubShader { // 轮廓Pass Pass { Name OUTLINE Cull Front // 关键剔除正面只渲染背面膨胀的部分 CGPROGRAM #pragma vertex vert_outline #pragma fragment frag_outline // 在vert_outline中将顶点位置沿法线方向挤出 _OutlineWidth // 在frag_outline中直接返回 _OutlineColor ENDCG } // 正常渲染Pass Pass { Name MAIN Cull Back // 正常剔除背面渲染物体本身 CGPROGRAM #pragma vertex vert_main #pragma fragment frag_main // 正常的纹理采样和光照计算 ENDCG } } }4. 深度问题排查与性能权衡使用Cull时如果设置不当不仅会影响性能还会导致渲染错误。下面是一些常见问题及解决方法。4.1 模型“破洞”或闪烁这是新手最常见的问题一个本该是实心的模型在某些角度会出现破洞或者面片闪烁。原因分析模型法线问题模型的某些面片法线方向错误比如朝内了。对于这些面片GPU判断的“正面”实际上是模型的内部。当摄像机看到这些面时因为Cull Back的设置它们被当作背面剔除了于是就出现了“洞”。缩放包含负值如果模型的缩放Scale包含了负值如(-1, 1, 1)会改变顶点缠绕顺序的判定结果可能导致整个模型的剔除方向反转。解决方案检查并修正模型在3D建模软件如Blender, Maya中检查并重新计算法线Recalculate Normals确保所有法线朝外。这是最根本的解决方法。在Shader中统一处理如果你无法控制模型资产可以在顶点着色器中通过判断模型的缩放是否包含负值来动态修正法线或调整剔除逻辑。但这比较复杂通常不推荐。临时使用Cull Off调试在调试时可以临时将Shader改为Cull Off。如果破洞消失那么几乎可以肯定是模型面片朝向问题。4.2 透明物体的渲染错误透明物体使用Cull Off后可能会遇到奇怪的渲染顺序问题比如后面的物体透过前面物体的“背面”显示出来。原因分析这通常是深度写入ZWrite和渲染队列Queue共同导致的问题。透明物体通常需要关闭深度写入ZWrite Off并进行从后往前排序的混合。如果一个双面透明物体的正面和背面片元深度接近GPU的排序可能无法完美处理谁先谁后导致混合错误。解决方案与经验明确渲染队列确保透明物体的Shader标签是QueueTransparent。谨慎使用深度写入对于标准透明混合保持ZWrite Off。但对于使用Alpha Test镂空的物体如树叶可以尝试ZWrite On并配合AlphaToMask或clip指令这能提供更稳定的深度排序。考虑分Pass渲染对于非常复杂的透明物体如一棵有很多双面树叶的树一个取巧的办法是分两个Pass渲染第一个Pass用Cull Back只渲染正面第二个Pass用Cull Front只渲染背面。这可以强制一个粗略的排序有时能缓解混合问题但代价是Draw Call翻倍。性能意识时刻记住Cull Off的性能成本。对于移动平台尽量避免对大面积、高面数的物体使用Cull Off。可以评估是否能用Cull Back的Alpha Test材质替代。4.3 性能分析与选择策略如何评估Cull设置带来的性能影响使用渲染调试工具Unity的Frame Debugger或RenderDoc等工具可以直观地看到每个Draw Call渲染的三角形数量。对比开启和关闭剔除时同一物体渲染的三角形数就能量化其收益。遵循经验法则封闭实体一律Cull Back。这是铁律。平面物体需双面看评估其重要性。如果是小面积的装饰物如树叶、铁丝网Cull Off可以接受。如果是大面积的平面如海洋、地形贴花应极力避免Cull Off可以考虑使用法线翻转技术或专门的双面Shader在一个Pass里计算两次光照。透明物体优先考虑是否真的需要双面。很多透明效果如窗口玻璃从背面看是不透明的这时背面可以被遮挡或使用简单颜色无需Cull Off。平台差异在PC和主机上顶点处理的压力相对较小Cull Off的代价更多体现在片元着色器。而在移动端顶点变换和片元着色都受带宽和算力限制Cull Off需要更加谨慎。5. 高级技巧与组合应用Cull指令可以和其他ShaderLab命令、渲染状态灵活组合实现更精细的控制。5.1 与深度测试ZTest的配合Cull决定画不画ZTest决定画了之后能不能通过深度比较显示出来。它们协同工作。一个被Cull Back的三角形根本不会参与ZTest。对于透明物体Cull Off,ZWrite OffZTest通常设置为LEqual默认确保它只在比背景更近时渲染。但你可以改为Always来强制渲染用于全屏后处理等这时Cull的设置依然有效。5.2 在Multi-Pass Shader中的独立控制一个SubShader中可以包含多个Pass每个Pass都可以有自己的Cull设置。这为实现复杂效果提供了可能如前文提到的轮廓描边。 另一个例子是“雪地脚印”效果第一个Pass用Cull Back渲染正常地形第二个Pass用Cull Front渲染脚印凹陷处的“内部”面并应用不同的法线/高度纹理模拟踩下去的效果。5.3 基于材质属性的动态剔除你可以将Cull模式暴露为材质属性让美术或策划在特定情况下动态调整。Properties { [Enum(UnityEngine.Rendering.CullMode)] _CullMode (Cull Mode, Float) 2 // 2 对应 Back } SubShader { Pass { Cull [_CullMode] // ... } }这样在编辑器中就可以将同一个Shader的材质实例在“Back”、“Front”、“Off”之间切换用于调试或实现某些特殊物体的动态效果比如一个可以“打开”的盒子打开时需要看到内部。5.4 在曲面细分和几何着色器中的考量如果你在Shader中使用了曲面细分Tessellation或几何着色器Geometry ShaderCull的阶段会受到影响。因为这些着色器可能会创建或细分新的图元。通常背面剔除仍然在这些阶段之后、光栅化之前进行。但需要注意的是在几何着色器中手动生成三角形时你必须确保其顶点顺序符合你期望的正面朝向否则可能导致意外的剔除结果。Cull命令是Shader编写中最基础、最有效的优化手段之一。它原理简单但应用起来需要结合具体的渲染对象、性能目标和平台特性进行深思熟虑。正确的剔除策略是构建高效、稳定渲染管线的基石。下次当你编写或调试一个Shader时不妨多花一分钟思考一下这个物体真的需要渲染它的背面吗