Unity Term Book
그래픽 & 이펙트

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[ ]

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

실습 단계

1

모델 임포트 — MeshFilter가 자동 추가

.fbx/.obj를 Project에, 이어 Scene에 넣으면 Unity가 MeshFilter + MeshRenderer와 Material 슬롯을 만듭니다.

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을 보고 비용이 큰 오브젝트를 찾습니다.

인터랙티브 시뮬레이터

절차적으로 메시를 만들고 — topology를 고른 뒤 wireframe / normals를 검사하세요.

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으로 배칭을 유지한 채 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만 갱신