Mesh Renderer & Filter
Mesh Filter và Mesh Renderer hiển thị hình học 3D — Filter cung cấp dữ liệu mesh, Renderer vẽ nó với Material được gán, kèm bóng đổ, lighting và culling.
Hãy tưởng tượng...
Mesh Filter giống như khuôn đúc — chứa hình dạng 3D (đỉnh, cạnh, mặt). Mesh Renderer là thợ tô màu — lấy khuôn đúc đó, áp Material lên (sơn, texture) rồi gửi đến GPU để vẽ. Hai component hoạt động cùng nhau: không có Mesh Filter thì Renderer không biết vẽ gì; không có Renderer thì Mesh vô hình. Khi build game, hàng nghìn Mesh Renderer được Unity gom vào draw call batches để GPU xử lý hiệu quả nhất.
Khái niệm chi tiết
MeshFilter lưu tham chiếu đến Mesh asset — dữ liệu hình học gồm:
mảng vertices (đỉnh), triangles (bộ ba index tạo tam giác),
normals (hướng bề mặt để tính ánh sáng), UVs (toạ độ texture mapping), và
tuỳ chọn tangents, colors. Mesh có thể là asset (file .fbx/.obj import) hoặc
tạo procedurally từ code.
MeshRenderer chứa mảng Materials (1 per sub-mesh), điều khiển Shadow
Casting/Receiving, Lightmap baking, Probe Anchor, và quan trọng nhất: Layer
và Rendering Layer Mask (dùng với URP Lighting). Thuộc tính
bounds (bounding box) được dùng cho frustum culling — object ẩn khi bounds nằm
ngoài camera frustum.
Để tối ưu rendering, Unity dùng nhiều kỹ thuật: Static Batching (gom draw calls cho static objects — đánh dấu “Static” trong Inspector), Dynamic Batching (cho moving objects dưới 300 vertices), GPU Instancing (cùng mesh + cùng material, dùng một draw call cho nhiều instance). Bật GPU Instancing bằng cách check “Enable GPU Instancing” trong Material.
Từ Unity 2022+, Mesh API 2.0 cho phép tạo và sửa Mesh procedurally mà không
cần boxing/allocation: Mesh.SetVertexBufferData,
Mesh.SetIndexBufferData — trực tiếp ghi vào GPU buffer không qua managed array.
Pipeline từ Mesh đến Màn hình
MeshFilter
Mesh Asset
vertices, triangles
normals, UVs
MeshRenderer
Materials[ ]
Shadow settings
Lightmap/Probe
Batching / Culling
Static/Dynamic Batch
Frustum Culling
Occlusion Culling
GPU Draw Calls
Vertex Shader
Fragment Shader
→ Pixels
Sub-mesh & Materials
Mesh (2 sub-mesh)
Materials[ ]
Hướng dẫn thực hành
Import model và nhận MeshFilter tự động
Kéo file .fbx/.obj vào Project → kéo vào Scene → Unity tự tạo MeshFilter + MeshRenderer với Materials slot.
Cấu hình Shadow Casting
MeshRenderer → Cast Shadows: On/Off/Two Sided. Receive Shadows: bật cho mặt đất và nhận đổ bóng từ object khác.
Bật Static Batching cho môi trường tĩnh
Inspector → Static dropdown → Batching Static. Nhiều object tĩnh cùng Material sẽ gộp thành 1 draw call.
Tạo Mesh procedurally
Tạo new Mesh(), set vertices[], triangles[], normals[], uv[] → gán vào GetComponent
Dùng Frame Debugger để đếm draw calls
Window → Analysis → Frame Debugger: xem từng draw call, biết object nào đang tốn tài nguyên nhất.
Trình mô phỏng tương tác
Tạo Mesh procedurally — chọn topology và xem wireframe / normals trực tiếp.
Ví dụ Code
Cơ bảnTạo một mesh hình tam giác procedurally và hiển thị với MeshFilter + MeshRenderer.
using UnityEngine;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class ProceduralTriangle : MonoBehaviour
{
void Start()
{
Mesh mesh = new Mesh();
mesh.name = "Procedural Triangle";
// 3 đỉnh của tam giác
mesh.vertices = new Vector3[] {
new Vector3(0f, 0f, 0f),
new Vector3(1f, 0f, 0f),
new Vector3(0.5f, 1f, 0f)
};
// Index theo chiều kim đồng hồ (nhìn từ front)
mesh.triangles = new int[] { 0, 1, 2 };
// UV mapping: mỗi đỉnh một toạ độ texture
mesh.uv = new Vector2[] {
new Vector2(0f, 0f),
new Vector2(1f, 0f),
new Vector2(0.5f, 1f)
};
mesh.RecalculateNormals(); // Tính normals tự động
GetComponent<MeshFilter>().mesh = mesh;
}
}Ví dụ Code
Nâng caoThay đổi Material runtime và dùng MaterialPropertyBlock để tô màu từng instance không phá batching.
using UnityEngine;
public class MeshRendererUtils : MonoBehaviour
{
MeshRenderer rend;
MaterialPropertyBlock mpb;
void Awake()
{
rend = GetComponent<MeshRenderer>();
mpb = new MaterialPropertyBlock();
}
// Đọc bounds của mesh trong world space
void LogMeshInfo()
{
Mesh mesh = GetComponent<MeshFilter>().sharedMesh;
Debug.Log($"Vertices: {mesh.vertexCount}");
Debug.Log($"Triangles: {mesh.triangles.Length / 3}");
Debug.Log($"Bounds: {rend.bounds.size}");
}
// Swap Material ở sub-mesh cụ thể (index 1 = window glass)
public void SwapSubMesh(int index, Material mat)
{
Material[] mats = rend.materials; // Lấy bản sao
mats[index] = mat;
rend.materials = mats; // Gán lại
}
// Per-instance color qua PropertyBlock — giữ GPU Instancing
public void SetInstanceColor(Color c)
{
rend.GetPropertyBlock(mpb);
mpb.SetColor("_BaseColor", c);
rend.SetPropertyBlock(mpb);
}
}📌 Ghi nhớ nhanh
- ▸MeshFilter = hình dạng, MeshRenderer = cách vẽ
- ▸sharedMesh không tạo copy, mesh tạo copy riêng
- ▸Static + cùng Material → Static Batching tự động
- ▸triangles[] phải theo chiều kim đồng hồ (CW) nhìn từ trước
- ▸RecalculateNormals() sau khi thay đổi vertices
⚠️ Lỗi thường gặp
❌ Mesh mặt ngược (face culled)
Triangle index ngược chiều kim đồng hồ → Unity culls mặt đó
✅ Đảo thứ tự index: {0,1,2} → {0,2,1}, hoặc dùng Double Sided shader
❌ Chỉnh meshFilter.mesh trong play mode nhiều lần
Mỗi lần gán tạo Mesh mới → rò rỉ bộ nhớ
✅ Tạo Mesh một lần trong Awake(), chỉ cập nhật vertices trong Update()