Unity Term Book
图形与特效

Material & Shader

在 Unity 中,Shader 是定义像素如何绘制的 GPU 程序;Material 则是该 Shader 的实例,携带颜色、贴图、光滑度等具体参数。理清两者关系,能帮美术与程序调外观、做材质变体与合批,并在 URP 或 HDRP 管线下正确控制渲染风格、Keyword 开关与整体的渲染性能预算。

想象一下...

Shader 是涂料配方(木头、金属、玻璃…)。Material 是按该配方调好的一罐漆——同是金属配方,一罐金、一罐银、一罐更亮。一个 Shader 可做出成千上万个 Material,外观各不相同。GameObject 通过 Mesh Renderer 用指定 Material「刷」到网格上。

概念详解

Shader 是跑在 GPU 上的 HLSL(High Level Shading Language) 程序。至少有两级:Vertex Shader(把 3D 顶点变换到 2D 屏幕)与 Fragment/Pixel Shader(计算每个像素的最终颜色)。Unity 用 ShaderLab、 Surface Shaders,或 URP 的 Lit/Unlit Shader Graph 来封装。

Material 是引用某个 Shader 并保存其 properties 数值的资源:主色(_BaseColor)、贴图( _BaseMap)、金属度(_Metallic)、粗糙度( _Smoothness)… 每个 Renderer 可有一个或多个 Material 槽,对应子网格。

性能上,Material 实例化很关键:多个对象共享 同一 Material 资源时可合并成一次 draw call(GPU instancing 或 static batching)。 若用 renderer.material(私有副本)而非 renderer.sharedMaterial, 每个对象都会单独 draw call 并多占内存。

Unity 自带常用 shader:URP/Lit(完整 PBR)、URP/Unlit(无光照)、 Sprites/Default(2D)、Particles/Standard Unlit (粒子)。Shader Graph 与 HLSL 可做完全自定义效果。

Shader → Material → Renderer

📄 Shader (HLSL program)

URP/Lit — 定义 properties + 渲染逻辑

_BaseColor_Metallic_BaseMap
▼ instantiate

Material A

_BaseColor = Red
_Metallic = 0.9

Material B

_BaseColor = Blue
_Metallic = 0.0

Material C

_BaseColor = Gold
_Metallic = 1.0

▼ assign to

🏠 House
Mat A

🚗 Car
Mat B

🏆 Trophy
Mat C

🔑 Key
Mat C

Trophy + Key 共用 Mat C → 1 次 draw call(batching)

动手步骤

1

创建新 Material

Project → 右键 → Create → Material。起清晰名字(Metal_Gold、Ground_Grass…)。

2

选择合适的 Shader

Inspector → Shader 下拉:PBR 用 URP/Lit,自发光表面用 URP/Unlit,2D 用 Sprites/Default。

3

指定贴图并调参数

把贴图拖到 Base Map。Metallic(0=塑料,1=金属),Smoothness(0=哑光,1=镜面)。

4

赋给 MeshRenderer

拖到 MeshRenderer Materials[0],或在 Scene 视图直接拖到 GameObject 上。

5

用脚本改属性

renderer.sharedMaterial.SetColor("_BaseColor", color) —— 或用 MaterialPropertyBlock 避免克隆 Material。

交互模拟器

调节 PBR 参数,观察球体表面变化——类似 Unity 的 Material Inspector。

// Material Properties

_BaseColor = "#4488ff"

_Metallic = 0.50

_Smoothness = 0.70

_Emission = 0.00

Shader: URP/Lit

蓝色光泽塑料

代码示例

基础

在运行时修改 Material 的颜色与金属度。

using UnityEngine;

public class MaterialController : MonoBehaviour
{
  Renderer rend;

  void Start()
  {
      rend = GetComponent<Renderer>();

      // sharedMaterial: shared with every object → no extra copy
      rend.sharedMaterial.SetColor("_BaseColor", Color.red);

      // material (not shared): a private copy for this object
      rend.material.SetFloat("_Metallic", 0.9f);
      rend.material.SetFloat("_Smoothness", 0.8f);
  }

  public void Flash(Color color)
  {
      // Flash trick: set emission color
      rend.material.SetColor("_EmissionColor", color * 2f);
      DynamicGI.SetEmissive(rend, color * 2f);
  }
}

代码示例

进阶

MaterialPropertyBlock —— 按对象设属性,不克隆 Material、不打断批处理。

using UnityEngine;

// Color each instance differently WITHOUT breaking GPU Instancing/Batching
public class InstanceColorizer : MonoBehaviour
{
  static readonly int ColorID  = Shader.PropertyToID("_BaseColor");
  static readonly int EmitID   = Shader.PropertyToID("_EmissionColor");

  MaterialPropertyBlock mpb;
  Renderer rend;

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

  public void SetColor(Color color)
  {
      rend.GetPropertyBlock(mpb);          // Read current state
      mpb.SetColor(ColorID, color);       // Change color
      rend.SetPropertyBlock(mpb);          // Apply — no new Material
  }

  public void SetEmission(Color emitColor)
  {
      rend.GetPropertyBlock(mpb);
      mpb.SetColor(EmitID, emitColor);
      rend.SetPropertyBlock(mpb);
  }
}

📌 快速记忆

  • ▸Shader = 配方,Material = 带具体数值的实例
  • ▸sharedMaterial:共享,不多占 RAM
  • ▸MaterialPropertyBlock:按实例改色,保持批处理
  • ▸Metallic=1 + Smoothness=1 = 完美镜面
  • ▸Shader.PropertyToID() 比每帧用字符串更快

⚠️ 常见错误

  • ❌ 在生成循环里用 renderer.material

    每次都克隆新 Material → 内存泄漏

    ✅ 用 MaterialPropertyBlock 或 sharedMaterial

  • ❌ 忘记在 Material 上开启 Emission

    SetColor("_EmissionColor",...) 在未开启前无效

    ✅ Material Inspector → 先勾选 "Emission"