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 资源的引用——几何数据:
vertices、triangles(索引三元组)、normals(用于光照)、
UVs(贴图坐标),以及可选的 tangents 与 colors。网格可以是导入
资源(.fbx/.obj),也可以用代码程序化生成。
MeshRenderer 持有 Materials 数组(每个子网格一个)、Shadow
Casting/Receiving、Lightmap baking、Probe Anchor,以及最重要的 Layer
与 Rendering 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.SetVertexBufferData、Mesh.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[ ]
动手步骤
导入模型——自动添加 MeshFilter
把 .fbx/.obj 拖进 Project,再拖进 Scene——Unity 会创建带 Material 槽的 MeshFilter + MeshRenderer。
配置 Shadow Casting
MeshRenderer → Cast Shadows:On/Off/Two Sided。需要接住其他物体阴影的地面开启 Receive Shadows。
为静态环境启用 Static Batching
Inspector → Static 下拉 → Batching Static。共享同一 Material 的多个静态物体可合并为一次 draw call。
程序化构建网格
new Mesh(),设置 vertices[]、triangles[]、normals[]、uv[] → 赋给 GetComponent<MeshFilter>().mesh。
用 Frame Debugger 统计 draw call
Window → Analysis → Frame Debugger:检查每次 draw call,找出开销最大的对象。
交互模拟器
程序化构建网格——选择拓扑,查看线框 / 法线。
代码示例
基础程序化构建三角形网格,并用 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() 只更新顶点