Mesh Renderer & Filter
Mesh Filter는 메시 데이터를 들고 Mesh Renderer는 Material·그림자로 그립니다. Static Batching·GPU Instancing·절차적 Mesh까지 다루며 draw call을 줄입니다. 3D 지오메트리를 화면에 올리는 렌더링 기초 가이드입니다.
상상해 보세요...
Mesh Filter는 틀 — 3D 형태(정점·모서리·면)를 담습니다. Mesh Renderer는 화가 — 그 틀에 Material(페인트·텍스처)을 입혀 GPU로 보냅니다. 짝으로 동작합니다: Mesh Filter 없으면 Renderer가 그릴 것이 없고, Renderer 없으면 메시가 보이지 않습니다. 빌드 시 Unity는 수천 Mesh Renderer를 draw call 배치로 묶어 GPU를 효율적으로 돌립니다.
개념 자세히
MeshFilter는 Mesh 에셋 참조를 잡습니다 — 지오메트리 데이터:
vertices, triangles(인덱스 삼중), normals(조명용),
UVs(텍스처 매핑), 선택적으로 tangents와 colors. 메시는
임포트 에셋(.fbx/.obj)이거나 코드로 절차적으로 만들 수 있습니다.
MeshRenderer는 Materials 배열(sub-mesh당 하나), Shadow
Casting/Receiving, Lightmap baking, Probe Anchor, 그리고 무엇보다 Layer와
Rendering Layer Mask(URP 조명)를 잡습니다. bounds(바운딩
박스)가 frustum culling을 구동 — bounds가 카메라 frustum 밖이면 스킵됩니다.
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가 MeshFilter + MeshRenderer와 Material 슬롯을 만듭니다.
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을 보고 비용이 큰 오브젝트를 찾습니다.
인터랙티브 시뮬레이터
절차적으로 메시를 만들고 — topology를 고른 뒤 wireframe / normals를 검사하세요.
코드 예제
기본절차적 삼각형 메시를 만들고 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으로 배칭을 유지한 채 tint.
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)
- ▸vertices 변경 후 RecalculateNormals()
⚠️ 흔한 실수
❌ 메시가 뒤집혀 보임(face culled)
삼각형 인덱스가 반시계 → Unity가 그 면을 cull
✅ 인덱스 뒤집기: {0,1,2} → {0,2,1}, 또는 Double Sided 셰이더
❌ Play Mode에서 meshFilter.mesh를 여러 번 할당
할당마다 새 Mesh → 메모리 누수
✅ Awake()에서 Mesh를 한 번 만들고 Update에서는 vertices만 갱신