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 + 渲染逻辑
Material A
_BaseColor = Red
_Metallic = 0.9
Material B
_BaseColor = Blue
_Metallic = 0.0
Material C
_BaseColor = Gold
_Metallic = 1.0
🏠 House
Mat A
🚗 Car
Mat B
🏆 Trophy
Mat C
🔑 Key
Mat C
Trophy + Key 共用 Mat C → 1 次 draw call(batching)
动手步骤
创建新 Material
Project → 右键 → Create → Material。起清晰名字(Metal_Gold、Ground_Grass…)。
选择合适的 Shader
Inspector → Shader 下拉:PBR 用 URP/Lit,自发光表面用 URP/Unlit,2D 用 Sprites/Default。
指定贴图并调参数
把贴图拖到 Base Map。Metallic(0=塑料,1=金属),Smoothness(0=哑光,1=镜面)。
赋给 MeshRenderer
拖到 MeshRenderer Materials[0],或在 Scene 视图直接拖到 GameObject 上。
用脚本改属性
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"