Unity Term Book
图形与特效

Culling

Culling 跳过相机看不见的内容——不绘制视锥外或被遮挡物完全挡住的对象,从而减少 Unity 的 draw call 与 GPU 压力。Frustum Culling 与 Occlusion Culling 是优化大场景帧率的关键手段,帮助 TA 与程序在开放世界、城市关卡中有效控制渲染成本。

想象一下...

Culling 是聪明的舞台管理员。不会给台上每个演员都开灯,只给观众席能看见的开灯。在幕后?关灯。不在视角内?关灯。GPU 根本不会知道它们——每帧节省成千上万次 draw call。

概念详解

Frustum Culling 是基础——Unity 用相机视锥的六个平面 (由 FOV 与 near/far clip 形成的截锥)去测每个 Renderer 的包围盒。若包围盒完全在外 → 跳过 draw call。内置、始终开启、无需配置。

Occlusion Culling 更进一步——跳过在视锥内但被完全挡住的对象。 Unity 用 Umbra 预计算遮挡数据(bake):Window → Rendering → Occlusion Culling → Bake。 在 Static 下拉中标记 occluder 与 occludee。室内场景很强,开放世界较弱。

LOD(Level of Detail) 相关——不是完全剔除,而是随距离降低 网格复杂度。LOD0 = 完整细节(近),LOD1 = 中等,LOD2 = 简化,LOD3 = 广告牌,Culled = 隐藏。 是按距离的画质/性能权衡。

Backface Culling 在 GPU 层——法线背向相机的三角形会被丢弃。 每个 shader 默认开启,封闭网格可省约 50% 片元开销。Material 上选 “Two Sided” 或 ShaderLab 中 Cull Off 可关闭。

剔除类型

📐

Frustum Culling

摄像机视野外的对象会被完全跳过。 自动,无需设置。对所有项目都有效。

Camera.layerCullDistances[]

🏗️

Occlusion Culling

被墙/建筑挡住的对象会被跳过。 需要 bake 并标记 Static。最适合室内场景。

Static → Occluder/Occludee

📏

LOD Group

对象远离摄像机时降低多边形数量。LOD0→LOD3→Culled。 需要准备 多套 mesh。适合开放世界。

LODGroup component

动手步骤

1

Frustum Culling:收紧 Camera Far Clip

Camera → Far Clip Plane:从 1000 降到 200–500,剔除远处物体。用雾掩盖切面。

2

为 Occlusion Culling 标记 Static

选中墙/地板/天花板 → Inspector → Static → Occluder Static 与 Occludee Static。

3

Bake 遮挡数据

Window → Rendering → Occlusion Culling → Bake。添加 Occlusion Area 控制 bake 体积。

4

添加 LOD Group

Add Component → LOD Group → 把 LOD0/1/2 网格拖进各槽,调节屏幕占比 %。

5

用 Rendering Statistics 测量

Game View → Stats:移动相机时观察 "Batches" 与 "Tris"。Scene 视图可开 Occlusion Culling Visualization。

交互模拟器

绕场景旋转相机,观察哪些对象被视锥剔除与遮挡剔除。

Visible: 0

Culled: 0

Visible Frustum Culled Occlusion Culled

代码示例

基础

设置按层剔除距离,并检查 renderer 是否可见。

using UnityEngine;

public class CullingSetup : MonoBehaviour
{
  void Start()
  {
      Camera cam = Camera.main;

      // Per-layer cull distance
      // 32 entries (32 layers) — 0 = use Far Clip
      float[] distances = new float[32];
      distances[8]  = 50f;   // Layer 8 "SmallProps": cull after 50m
      distances[9]  = 150f;  // Layer 9 "MediumProps": cull after 150m
      distances[10] = 0f;    // Layer 10: use Camera.farClipPlane
      cam.layerCullDistances = distances;

      // Spherical culling: from camera center, not frustum planes
      cam.layerCullSpherical = true;
  }

  // Is this renderer currently visible to a camera?
  void CheckVisibility(Renderer rend)
  {
      if (rend.isVisible)
          Debug.Log(rend.name + " is being rendered");
  }
}

代码示例

进阶

用代码配置 LOD Group,并强制指定 LOD 级别(调试 / 过场)。

using UnityEngine;

public class DynamicLOD : MonoBehaviour
{
  public Renderer[] lod0, lod1, lod2;
  LODGroup group;

  void Awake()
  {
      group = GetComponent<LODGroup>();
      if (group == null)
          group = gameObject.AddComponent<LODGroup>();

      // Configure LOD levels from code
      LOD[] lods = new LOD[3];
      lods[0] = new LOD(0.5f, lod0);  // 50%+ screen → LOD0
      lods[1] = new LOD(0.15f, lod1); // 15-50% → LOD1
      lods[2] = new LOD(0.03f, lod2); // <3% → LOD2, <0 → culled
      group.SetLODs(lods);
      group.RecalculateBounds();
  }

  // Force a specific LOD (debug or cutscene close-up)
  public void ForceLOD(int level)
  {
      group.ForceLOD(level); // -1 = disable force, use automatic
  }
}

📌 快速记忆

  • Frustum Culling:自动,无需配置
  • Occlusion Culling:需要 bake,室内最佳
  • LOD Group:随距离降低面数
  • layerCullDistances:按层设置剔除距离
  • renderer.isVisible:正在被渲染时为 true

⚠️ 常见错误

  • ❌ Occlusion 已 bake 但什么都不剔除

    忘记 Camera → Occlusion Culling = true

    ✅ Camera Inspector → Rendering → 勾选 Occlusion Culling

  • ❌ LOD 跳变(缩放时网格突然切换)

    LOD 过渡跨度太大,未开 LOD Fade

    ✅ 在 LOD Group 上设 "Fade Mode = Cross Fade" 做混合