Unity Term Book
Graphics & Rendering

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 Rendererthợ 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: LayerRendering 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[ ]

Materials[0] = Body_Mat
Materials[1] = Window_Mat

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

1

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.

2

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.

3

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.

4

Tạo Mesh procedurally

Tạo new Mesh(), set vertices[], triangles[], normals[], uv[] → gán vào GetComponent().mesh.

5

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.

Vertices: 24Triangles: 12Draw Calls: 1

Ví dụ Code

Cơ bản

Tạ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 cao

Thay đổ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()