Unity Term Book
Graphics & Rendering

Material & Shader

A Shader is a GPU program that defines how pixels are drawn — a Material is an instance of that Shader with concrete values like color, texture, and smoothness.

Imagine...

A Shader is a paint recipe (wood, metal, glass...). A Material is a specific can of that paint — same metal recipe, but one can is gold, one is silver, one is glossier. You can make thousands of Materials from a single Shader, each looking different. A GameObject uses a Mesh Renderer to "paint" the mesh with the assigned Material.

The concept in detail

A Shader is an HLSL (High Level Shading Language) program that runs on the GPU. It has at least two stages: a Vertex Shader (transforms 3D vertices onto the 2D screen) and a Fragment/Pixel Shader (computes the final color of each pixel). Unity wraps this in ShaderLab, Surface Shaders, or URP’s Lit/Unlit Shader Graph.

A Material is an asset that references a Shader and stores values for its properties: main color (_BaseColor), texture ( _BaseMap), metallic (_Metallic), roughness ( _Smoothness)… Each Renderer can have one or more Material slots, matching sub-meshes.

For performance, Material instancing matters: many objects sharing the same Material asset can collapse into one draw call (GPU instancing or static batching). If you use renderer.material (a private copy) instead of renderer.sharedMaterial, each object gets its own draw call and extra memory.

Unity ships standard shaders: URP/Lit (full PBR), URP/Unlit (no lighting), Sprites/Default (2D), Particles/Standard Unlit (particles). Shader Graph and HLSL let you build fully custom effects.

Shader → Material → Renderer

📄 Shader (HLSL program)

URP/Lit — defines properties + render logic

_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 share Mat C → 1 draw call (batching)

Hands-on steps

1

Create a new Material

Project → right-click → Create → Material. Name it clearly (Metal_Gold, Ground_Grass...).

2

Pick the right Shader

Inspector → Shader dropdown: URP/Lit for PBR, URP/Unlit for self-lit surfaces, Sprites/Default for 2D.

3

Assign textures and tweak properties

Drop a texture onto Base Map. Metallic (0=plastic, 1=metal), Smoothness (0=matte, 1=mirror).

4

Assign the Material to a MeshRenderer

Drop it into MeshRenderer Materials[0], or drag it onto the GameObject in the Scene view.

5

Change a property from script

renderer.sharedMaterial.SetColor("_BaseColor", color) — or use a MaterialPropertyBlock to avoid cloning the Material.

Interactive simulator

Tune PBR parameters and watch the sphere's surface change — a stand-in for Unity's Material Inspector.

// Material Properties

_BaseColor = "#4488ff"

_Metallic = 0.50

_Smoothness = 0.70

_Emission = 0.00

Shader: URP/Lit

Shiny blue plastic

Code example

Basic

Change a Material's color and metallic at runtime.

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);
  }
}

Code example

Advanced

MaterialPropertyBlock — per-object properties without cloning the Material or breaking batching.

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);
  }
}

📌 Quick recap

  • Shader = recipe, Material = instance with concrete values
  • sharedMaterial: shared, no extra RAM
  • MaterialPropertyBlock: per-instance, keeps batching
  • Metallic=1 + Smoothness=1 = a perfect mirror
  • Shader.PropertyToID() is faster than a string every frame

⚠️ Common mistakes

  • ❌ Using renderer.material inside a spawn loop

    A new Material clone every time → memory leak

    ✅ Use a MaterialPropertyBlock or sharedMaterial

  • ❌ Forgetting to enable Emission on the Material

    SetColor("_EmissionColor",...) does nothing until Emission is on

    ✅ Material Inspector → tick the "Emission" checkbox first