Default Unity materials are a trap. They get you moving fast, but they cap your visual ceiling and give every asset that unmistakable "stock Unity" look. If you want your game to look like *yours*, you need custom shaders — and Unity Shader Graph makes that achievable without writing a single line of HLSL.
This guide covers the full workflow: setting up your URP project, understanding the Shader Graph node system, building a production-ready PBR shader from scratch, and optimizing it for runtime performance.
What Is Shader Graph and Why Should You Use It?
Shader Graph is Unity's node-based visual shader editor, introduced with the Universal Render Pipeline (URP) and High Definition Render Pipeline (HDRP). Instead of writing raw shader code, you wire together nodes that represent mathematical operations, texture samples, and engine parameters.
For 3D artists working in Unity, this is transformative. You can:
- Prototype material effects visually before committing to code
- Build fully PBR-compliant shaders that respect Unity's lighting system
- Expose parameters to the Material Inspector so artists can tweak values without touching the graph
- Compile to both mobile and desktop targets with one graph
Shader Graph requires URP or HDRP — it does not work with the Built-in Render Pipeline. If you're starting a new project in 2026, default to URP unless you specifically need HDRP's ray tracing or volumetric features.
Setting Up Shader Graph in a URP Project
Create a new Unity project using the URP template, or upgrade an existing project via Edit → Render Pipeline → Universal Render Pipeline → Upgrade Project Materials.
Install Shader Graph via the Package Manager if it isn't already present:
```
Window → Package Manager → Unity Registry → Shader Graph → Install
```
To create your first graph: right-click in the Project window, then Create → Shader Graph → URP → Lit Shader Graph. This opens a blank graph with a Fragment and Vertex output node wired to URP's lighting model.
Key terms to know before you start:
- Master Stack: the output node. Connect your final Base Color, Normal, Smoothness, and Metallic values here.
- Blackboard: the left panel where you define exposed properties (colors, floats, textures).
- Graph Inspector: shows per-node and per-graph settings including precision (Half vs Float) and render queue.
![]()
Building a PBR Shader From Scratch
Here's a practical walkthrough for a standard rock/stone asset — the kind you'd find in any environment pack.
Step 1: Set Up Textures as Properties
In the Blackboard, add these properties:
- `Texture2D` → Albedo Map
- `Texture2D` → Normal Map (set Mode to Normal in the Graph Inspector)
- `Texture2D` → Roughness Map
- `Texture2D` → AO Map
- `Float` → Normal Strength (default: 1.0, range: 0–2)
- `Color` → Tint (default: white)
Step 2: Wire Albedo and Tint
- Drag Albedo Map onto the graph → auto-creates a `Sample Texture 2D` node
- Add a `Multiply` node
- Connect: `Sample Texture 2D (RGBA)` → Multiply A, Tint → Multiply B
- Connect Multiply output → Base Color on the Master Stack
Step 3: Normal Map
- Drag Normal Map → `Sample Texture 2D` (set Type to Normal in node settings)
- Add a `Normal Strength` node
- Connect: texture output → Normal Strength In, Normal Strength property → Strength
- Connect output → Normal (Tangent Space) on Master Stack
Step 4: Roughness and Metallic
For a stone asset, metallic is 0. Wire the roughness map's Red channel directly to Smoothness with an invert node (roughness maps are inverted smoothness):
```
Sample Texture 2D (R) → One Minus → Smoothness
```
Step 5: Ambient Occlusion
Sample the AO map's Red channel → connect to Ambient Occlusion on the Master Stack.
### Final Master Stack connections:
| Input | Source |
|-------|--------|
| Base Color | Albedo × Tint |
| Normal (Tangent) | Normal Strength node |
| Metallic | Float (0) |
| Smoothness | 1 - Roughness.R |
| Ambient Occlusion | AO.R |
Optimizing Shader Graph for Runtime Performance
A beautiful shader that runs at 30fps is a liability. Here's how to keep yours fast:
Use Half precision where possible. In the Graph Inspector, set your shader to Half precision. URP mobile targets benefit significantly. Switch individual nodes to Half via the Node Settings panel — avoid Half for normal map math (precision artifacts appear).
Minimize texture samples. Pack your Roughness, AO, and Metallic channels into a single texture (R=Metallic, G=AO, B=Roughness). This cuts 2 texture samples to 1 and reduces memory bandwidth.
```python
# Blender Python: pack ORM texture via script
import bpy
def pack_orm(metallic_path, roughness_path, ao_path, output_path):
# Load source images
met = bpy.data.images.load(metallic_path)
rough = bpy.data.images.load(roughness_path)
ao = bpy.data.images.load(ao_path)
# Create output image
width, height = met.size
orm = bpy.data.images.new("ORM", width=width, height=height)
pixels = [0.0] * (width * height * 4)
for i in range(width * height):
pixels[i*4 + 0] = met.pixels[i*4] # R = Metallic
pixels[i*4 + 1] = ao.pixels[i*4] # G = AO
pixels[i*4 + 2] = rough.pixels[i*4] # B = Roughness
pixels[i*4 + 3] = 1.0 # A = unused
orm.pixels = pixels
orm.filepath_raw = output_path
orm.file_format = 'PNG'
orm.save()
pack_orm('/tmp/metallic.png', '/tmp/roughness.png', '/tmp/ao.png', '/tmp/ORM.png')
```
Avoid branching. If/else logic in shaders is expensive. Use `Lerp` and `Step` nodes to blend between states instead of conditional branches.
Check the compiled shader. In the Shader Graph, click Show Generated Code (top-right menu) to see the actual HLSL output. Count texture samples and math operations — if it's bloated, find redundant nodes.
![]()
Exposing Parameters for Artist-Friendly Materials
One of Shader Graph's biggest wins for teams is the ability to expose controls to the Material Inspector. Any Blackboard property shows up as a tweak-able field on any material that uses your shader.
Best practices for exposed parameters:
- Set meaningful display names — "Edge Wear Amount" not "Float_0"
- Set min/max ranges on floats so artists can't break things
- Group related properties using categories (click the gear icon on any Blackboard property)
- Document unusual parameters with tooltips (hover over the property name field)
This is especially valuable if you're selling assets on a marketplace. Well-exposed shader parameters make your asset look professional and reduce support questions. Assets on BitSoul marketplace with clean material setups consistently outperform those with locked-down, single-material packages.
Shader Graph vs. Hand-Written HLSL: When to Use Each
| Scenario | Use Shader Graph | Use HLSL |
|----------|-----------------|----------|
| Standard PBR materials | ✅ | |
| Procedural effects (water, lava) | ✅ | |
| Maximum performance / mobile | | ✅ |
| Custom lighting models | | ✅ |
| Compute shaders | | ✅ |
| Rapid prototyping | ✅ | |
For most 3D asset work — characters, props, environments — Shader Graph covers everything you need. HLSL becomes necessary when you're writing custom lighting models or squeezing every cycle on a mobile target.
Bringing It All Together
Shader Graph closes the gap between what your assets look like in Blender and what they look like in Unity. By building a reusable PBR shader template, packing your ORM textures, and exposing artist-friendly parameters, you get consistent results across every asset in your project.
If you're building an asset library or selling to other developers, a clean custom shader elevates your work above the default-material crowd. Browse environment, character, and prop packs with production-ready materials at BitSoul — and consider uploading your own optimized assets to reach Unity and Unreal developers worldwide.
---
*This post is part of the Ultimate Guide to Free 3D Game Assets — BitSoul's complete reference for formats, texturing, rigging, optimization, and engine integration.*