Unity Term Book
图形与特效

Mesh Renderer & Filter

Mesh Filter 加 Mesh Renderer 负责在 Unity 中显示 3D 几何:Filter 提供网格顶点数据,Renderer 用指定 Material、阴影与光照将其绘制到屏幕。这是场景道具与角色可见性的基础组合,帮助理解静态批处理、阴影投射、光照接收以及 LOD Group 相关设置。

想象一下...

Mesh Filter模具——装着 3D 形状(顶点、边、面)。Mesh Renderer油漆工——拿起模具,刷上 Material(颜料、贴图),再送给 GPU。二者成对:没有 Mesh Filter,Renderer 无物可画;没有 Renderer,网格不可见。构建时 Unity 把成千上万 Mesh Renderer 批成 draw call batches,让 GPU 保持高效。

概念详解

MeshFilter 持有对 Mesh 资源的引用——几何数据: verticestriangles(索引三元组)、normals(用于光照)、 UVs(贴图坐标),以及可选的 tangentscolors。网格可以是导入 资源(.fbx/.obj),也可以用代码程序化生成。

MeshRenderer 持有 Materials 数组(每个子网格一个)、Shadow Casting/Receiving、Lightmap baking、Probe Anchor,以及最重要的 LayerRendering Layer Mask(URP 光照)。bounds(包围盒)驱动 frustum culling——包围盒完全在相机视锥外时跳过绘制。

Unity 用多种批处理:Static Batching(标记对象为 “Static”)、 Dynamic Batching(少于 300 顶点的运动物体)、 GPU Instancing(相同 mesh + 相同 material,多实例一次 draw call)。 在 Material 上启用 GPU Instancing。

Unity 2022+ 的 Mesh API 2.0 可程序化创建与编辑网格且无需装箱: Mesh.SetVertexBufferDataMesh.SetIndexBufferData —— 直接写入 GPU 缓冲,跳过托管数组。

从 Mesh 到屏幕

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

动手步骤

1

导入模型——自动添加 MeshFilter

把 .fbx/.obj 拖进 Project,再拖进 Scene——Unity 会创建带 Material 槽的 MeshFilter + MeshRenderer。

2

配置 Shadow Casting

MeshRenderer → Cast Shadows:On/Off/Two Sided。需要接住其他物体阴影的地面开启 Receive Shadows。

3

为静态环境启用 Static Batching

Inspector → Static 下拉 → Batching Static。共享同一 Material 的多个静态物体可合并为一次 draw call。

4

程序化构建网格

new Mesh(),设置 vertices[]、triangles[]、normals[]、uv[] → 赋给 GetComponent<MeshFilter>().mesh。

5

用 Frame Debugger 统计 draw call

Window → Analysis → Frame Debugger:检查每次 draw call,找出开销最大的对象。

交互模拟器

程序化构建网格——选择拓扑,查看线框 / 法线。

Vertices: 24Triangles: 12Draw Calls: 1

代码示例

基础

程序化构建三角形网格,并用 MeshFilter + MeshRenderer 显示。

using UnityEngine;

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class ProceduralTriangle : MonoBehaviour
{
  void Start()
  {
      Mesh mesh = new Mesh();
      mesh.name = "Procedural Triangle";

      // 3 vertices of the triangle
      mesh.vertices = new Vector3[] {
          new Vector3(0f, 0f, 0f),
          new Vector3(1f, 0f, 0f),
          new Vector3(0.5f, 1f, 0f)
      };

      // Clockwise indices (viewed from the front)
      mesh.triangles = new int[] { 0, 1, 2 };

      // UV mapping: one texture coord per vertex
      mesh.uv = new Vector2[] {
          new Vector2(0f, 0f),
          new Vector2(1f, 0f),
          new Vector2(0.5f, 1f)
      };

      mesh.RecalculateNormals(); // Compute normals automatically

      GetComponent<MeshFilter>().mesh = mesh;
  }
}

代码示例

进阶

运行时切换 Material,并用 MaterialPropertyBlock 着色实例而不打断批处理。

using UnityEngine;

public class MeshRendererUtils : MonoBehaviour
{
  MeshRenderer rend;
  MaterialPropertyBlock mpb;

  void Awake()
  {
      rend = GetComponent<MeshRenderer>();
      mpb = new MaterialPropertyBlock();
  }

  // Read mesh bounds in 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 the Material on a specific sub-mesh (index 1 = window glass)
  public void SwapSubMesh(int index, Material mat)
  {
      Material[] mats = rend.materials; // Get a copy
      mats[index] = mat;
      rend.materials = mats;            // Assign back
  }

  // Per-instance color via PropertyBlock — keeps GPU Instancing
  public void SetInstanceColor(Color c)
  {
      rend.GetPropertyBlock(mpb);
      mpb.SetColor("_BaseColor", c);
      rend.SetPropertyBlock(mpb);
  }
}

📌 快速记忆

  • MeshFilter = 形状,MeshRenderer = 如何绘制
  • sharedMesh 不克隆;.mesh 会创建私有副本
  • Static + 相同 Material → Static Batching
  • triangles[] 正面须为顺时针(CW)
  • 改顶点后调用 RecalculateNormals()

⚠️ 常见错误

  • ❌ 网格翻面(被面剔除)

    三角形索引为逆时针 → Unity 剔除该面

    ✅ 翻转索引:{0,1,2} → {0,2,1},或用 Double Sided shader

  • ❌ Play Mode 中反复赋值 meshFilter.mesh

    每次赋值都新建 Mesh → 内存泄漏

    ✅ 在 Awake() 中创建一次 Mesh,Update() 只更新顶点