Unity Term Book
Graphics & Rendering

Culling

Culling bỏ qua việc render những gì camera không thấy — giảm draw calls bằng cách không vẽ object ngoài frustum hoặc bị che khuất hoàn toàn bởi occluder.

Hãy tưởng tượng...

Culling giống như người quản lý sân khấu thông minh. Thay vì bật đèn cho toàn bộ diễn viên trên sân khấu, anh ta chỉ rọi đèn vào những diễn viên đang nhìn thấy từ khán phòng. Người đứng sau tấm màn? Tắt đèn. Người đứng ngoài góc nhìn? Tắt đèn. GPU không cần biết gì về họ cả — tiết kiệm được hàng nghìn draw calls trong mỗi frame.

Khái niệm chi tiết

Frustum Culling là loại cơ bản nhất — Unity tự động tính toán 6 mặt phẳng của camera frustum (khối hình thang cụt tạo bởi FOV và near/far clip planes), sau đó kiểm tra bounding box của từng Renderer. Nếu bounds nằm hoàn toàn ngoài frustum → skip draw call. Đây là cơ chế built-in, luôn bật, không cần cấu hình.

Occlusion Culling là bước tiếp theo — skip những object tuy nằm trong frustum nhưng bị che khuất hoàn toàn bởi object khác. Unity precompute occlusion data (Umbra) trong bake step: Window → Rendering → Occlusion Culling → Bake. Cần đánh dấu occluder (vật che) và occludee (vật bị che) trong Static dropdown. Hiệu quả cao trong indoor scenes, ít hiệu quả hơn outdoor open world.

LOD (Level of Detail) là kỹ thuật liên quan — thay vì cull hoàn toàn, LOD Group giảm dần độ phức tạp mesh khi object ở xa. LOD0 = full detail (gần), LOD1 = trung bình, LOD2 = đơn giản, LOD3 = billboarding, Culled = ẩn hẳn. Cân bằng tốt giữa chất lượng và hiệu năng theo khoảng cách.

Backface Culling là cơ chế GPU-level — loại bỏ các triangle “nhìn ngược” (pháp tuyến quay về phía sau camera). Mặc định bật trong tất cả shader, tiết kiệm ~50% fragment shader work cho mesh kín. Disable bằng cách bật “Two Sided” trong Material hoặc Cull Off trong ShaderLab.

Các loại Culling

📐

Frustum Culling

Object ngoài góc nhìn camera bị skip hoàn toàn. Tự động, không cần setup. Hiệu quả = 100% tất cả project.

Camera.layerCullDistances[]

🏗️

Occlusion Culling

Object bị che khuất bởi tường/nhà bị skip. Cần bake và đánh dấu Static. Tốt nhất cho indoor scene.

Static → Occluder/Occludee

📏

LOD Group

Giảm polygon count khi object xa camera. LOD0→LOD3→Culled. Cần chuẩn bị nhiều mesh. Tốt cho open world.

LODGroup component

Hướng dẫn thực hành

1

Frustum Culling: điều chỉnh Camera Far Clip

Camera → Far Clip Plane: giảm từ 1000 xuống 200-500 để cull nhiều object xa hơn. Bù bằng fog để ẩn đường cắt.

2

Đánh dấu Static cho Occlusion Culling

Chọn tất cả tường/sàn/trần → Inspector → Static → Occluder Static và Occludee Static.

3

Bake Occlusion Data

Window → Rendering → Occlusion Culling → Bake. Thêm Occlusion Area để kiểm soát vùng bake.

4

Thêm LOD Group

Add Component → LOD Group → kéo thả các mesh LOD0/1/2 vào từng slot, điều chỉnh % screen size.

5

Đo hiệu quả bằng Rendering Statistics

Game View → Stats: kiểm tra "Batches" và "Tris" khi di chuyển camera. Occlusion Culling Visualization trong Scene View.

Trình mô phỏng tương tác

Xoay camera quanh scene để thấy object nào bị Frustum Culling và Occlusion Culling.

Visible: 0

Culled: 0

Visible Frustum Culled Occlusion Culled

Ví dụ Code

Cơ bản

Thiết lập per-layer cull distance và kiểm tra renderer visibility.

using UnityEngine;

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

      // Thiết lập khoảng cull cho từng Layer
      // Array 32 phần tử (32 layers) — 0 = dùng Far Clip
      float[] distances = new float[32];
      distances[8]  = 50f;   // Layer 8 "SmallProps": cull sau 50m
      distances[9]  = 150f;  // Layer 9 "MediumProps": cull sau 150m
      distances[10] = 0f;    // Layer 10: dùng Camera.farClipPlane
      cam.layerCullDistances = distances;

      // Spherical culling: tính từ tâm camera, không dùng mặt phẳng
      cam.layerCullSpherical = true;
  }

  // Kiểm tra renderer có đang visible với camera hay không
  void CheckVisibility(Renderer rend)
  {
      if (rend.isVisible)
          Debug.Log(rend.name + " đang được render");
  }
}

Ví dụ Code

Nâng cao

Thiết lập LOD Group qua code và ép LOD level khi cần (debug / cutscene).

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>();

      // Cấu hình LOD levels bằng 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();
  }

  // Ép LOD cụ thể (debug hoặc cutscene close-up)
  public void ForceLOD(int level)
  {
      group.ForceLOD(level); // -1 = tắt force, dùng automatic
  }
}

📌 Ghi nhớ nhanh

  • Frustum Culling: tự động, không cần setup gì
  • Occlusion Culling: cần bake, tốt cho indoor
  • LOD Group: giảm poly count theo khoảng cách
  • layerCullDistances: set per-layer cull range
  • renderer.isVisible: true khi đang được render

⚠️ Lỗi thường gặp

  • ❌ Occlusion bake xong nhưng vẫn không cull

    Quên đặt Camera → Occlusion Culling = true

    ✅ Camera Inspector → Rendering → check Occlusion Culling

  • ❌ LOD pop (mesh nhảy đột ngột khi zoom)

    Transition giữa LOD levels quá rộng, không có LOD Fade

    ✅ Bật "Fade Mode = Cross Fade" trong LOD Group để blend mượt