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
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.
Đá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.
Bake Occlusion Data
Window → Rendering → Occlusion Culling → Bake. Thêm Occlusion Area để kiểm soát vùng bake.
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.
Đ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
Ví dụ Code
Cơ bảnThiế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 caoThiế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