- 3D Materials
- Expert guidance for PBR materials and StandardMaterial3D in Godot.
- NEVER Do
- NEVER use separate metallic/roughness/AO textures
- — Use ORM packing (1 RGB texture with Occlusion/Roughness/Metallic channels) to save texture slots and memory.
- NEVER forget to enable normal_enabled
- — Normal maps don't work unless you set
- normal_enabled = true
- . Silent failure is common.
- NEVER use TRANSPARENCY_ALPHA for cutout materials
- — Use TRANSPARENCY_ALPHA_SCISSOR or TRANSPARENCY_ALPHA_HASH instead. Full alpha blending is expensive and causes sorting issues.
- NEVER set metallic = 0.5
- — Materials are either metallic (1.0) or dielectric (0.0). Values between are physically incorrect except for rust/dirt transitions.
- NEVER use emission without HDR
- — Emission values > 1.0 only work with HDR rendering enabled in Project Settings.
- Available Scripts
- MANDATORY
- Read the appropriate script before implementing the corresponding pattern. material_fx.gd Runtime material property animation for damage effects, dissolve, and texture swapping. Use for dynamic material state changes. pbr_material_builder.gd Runtime PBR material creation with ORM textures and triplanar mapping. organic_material.gd Subsurface scattering and rim lighting setup for organic surfaces (skin, leaves). Use for realistic character or vegetation materials. triplanar_world.gdshader Triplanar projection shader for terrain without UV mapping. Blends textures based on surface normals. Use for cliffs, caves, or procedural terrain. StandardMaterial3D Basics PBR Texture Setup
Create physically-based material
var mat := StandardMaterial3D . new ( )
Albedo (base color)
mat . albedo_texture = load ( "res://textures/wood_albedo.png" ) mat . albedo_color = Color . WHITE
Tint multiplier
Normal map (surface detail)
mat . normal_enabled = true
CRITICAL: Must enable first
mat . normal_texture = load ( "res://textures/wood_normal.png" ) mat . normal_scale = 1.0
Bump strength
ORM Texture (R=Occlusion, G=Roughness, B=Metallic)
mat . orm_texture = load ( "res://textures/wood_orm.png" )
Alternative: Separate textures (less efficient)
mat.roughness_texture = load("res://textures/wood_roughness.png")
mat.metallic_texture = load("res://textures/wood_metallic.png")
mat.ao_texture = load("res://textures/wood_ao.png")
Apply to mesh
$MeshInstance3D . material_override = mat Metallic vs Roughness Metal Workflow
Pure metal (steel, gold, copper)
mat . metallic = 1.0 mat . roughness = 0.2
Polished metal
mat . albedo_color = Color ( 0.8 , 0.8 , 0.8 )
Metal tint
Rough metal (iron, aluminum)
mat . metallic = 1.0 mat . roughness = 0.7 Dielectric Workflow
Non-metal (wood, plastic, stone)
mat . metallic = 0.0 mat . roughness = 0.6
Typical for wood
mat . albedo_color = Color ( 0.6 , 0.4 , 0.2 )
Brown wood
Glossy plastic
mat . metallic = 0.0 mat . roughness = 0.1
Very smooth
Transition Materials (Rust/Dirt)
Use texture to blend metal/non-metal
mat . metallic_texture = load ( "res://rust_mask.png" )
White areas (1.0) = metal
Black areas (0.0) = rust (dielectric)
Transparency Modes Decision Matrix Mode Use Case Performance Sorting Issues ALPHA_SCISSOR Foliage, chain-link fence Fast No ALPHA_HASH Dithered fade, LOD transitions Fast Noisy ALPHA Glass, water, godot-particles Slow Yes (render order) Alpha Scissor (Cutout)
For leaves, grass, fences
mat . transparency = BaseMaterial3D . TRANSPARENCY_ALPHA_SCISSOR mat . alpha_scissor_threshold = 0.5
Pixels < 0.5 alpha = discarded
mat . albedo_texture = load ( "res://leaf.png" )
Must have alpha channel
Enable backface culling for performance
mat . cull_mode = BaseMaterial3D . CULL_BACK Alpha Hash (Dithered)
For smooth fade-outs without sorting issues
mat . transparency = BaseMaterial3D . TRANSPARENCY_ALPHA_HASH mat . alpha_hash_scale = 1.0
Dither pattern scale
Animate fade
var tween := create_tween ( ) tween . tween_property ( mat , "albedo_color:a" , 0.0 , 1.0 ) Alpha Blend (Full Transparency)
For glass, water (expensive)
mat . transparency = BaseMaterial3D . TRANSPARENCY_ALPHA mat . blend_mode = BaseMaterial3D . BLEND_MODE_MIX
Disable depth writing for correct blending
mat . depth_draw_mode = BaseMaterial3D . DEPTH_DRAW_DISABLED mat . cull_mode = BaseMaterial3D . CULL_DISABLED
Show both sides
Advanced Features Emission (Glowing Materials) mat . emission_enabled = true mat . emission = Color ( 1.0 , 0.5 , 0.0 )
Orange glow
mat . emission_energy_multiplier = 2.0
Brightness (HDR)
mat . emission_texture = load ( "res://lava_emission.png" )
Animated emission
func _process ( delta : float ) -> void : mat . emission_energy_multiplier = 1.0 + sin ( Time . get_ticks_msec ( ) * 0.005 ) * 0.5 Rim Lighting (Fresnel) mat . rim_enabled = true mat . rim = 1.0
Intensity
mat . rim_tint = 0.5
How much albedo affects rim color
Clearcoat (Car Paint) mat . clearcoat_enabled = true mat . clearcoat = 1.0
Layer strength
mat . clearcoat_roughness = 0.1
Glossy top layer
Anisotropy (Brushed Metal) mat . anisotropy_enabled = true mat . anisotropy = 1.0
Directional highlights
mat . anisotropy_flowmap = load ( "res://brushed_flow.png" ) Texture Channel Packing ORM Texture (Recommended)
External tool (GIMP, Substance, Python script):
Combine 3 grayscale textures into 1 RGB:
R channel = Ambient Occlusion (bright = no occlusion)
G channel = Roughness (bright = rough)
B channel = Metallic (bright = metal)
In Godot:
mat . orm_texture = load ( "res://textures/material_orm.png" )
This replaces ao_texture, roughness_texture, and metallic_texture!
Custom Packing
If using custom channel assignments:
mat . roughness_texture_channel = BaseMaterial3D . TEXTURE_CHANNEL_GREEN mat . metallic_texture_channel = BaseMaterial3D . TEXTURE_CHANNEL_BLUE Shader Conversion When to Convert to ShaderMaterial Need custom effects (dissolve, vertex displacement) StandardMaterial3D limitations hit Shader optimizations (remove unused features) Conversion Workflow
1. Create StandardMaterial3D with all settings
var std_mat := StandardMaterial3D . new ( ) std_mat . albedo_color = Color . RED std_mat . metallic = 1.0 std_mat . roughness = 0.2
2. Convert to ShaderMaterial
var shader_mat := ShaderMaterial . new ( ) shader_mat . shader = load ( "res://custom_shader.gdshader" )
3. Transfer parameters manually
shader_mat . set_shader_parameter ( "albedo" , std_mat . albedo_color ) shader_mat . set_shader_parameter ( "metallic" , std_mat . metallic ) shader_mat . set_shader_parameter ( "roughness" , std_mat . roughness ) Material Variants (Godot 4.0+) Efficient Material Reuse
Base material (shared)
var base_red_metal := StandardMaterial3D . new ( ) base_red_metal . albedo_color = Color . RED base_red_metal . metallic = 1.0
Variant 1: Rough
var rough_variant := base_red_metal . duplicate ( ) rough_variant . roughness = 0.8
Variant 2: Smooth
var smooth_variant := base_red_metal . duplicate ( ) smooth_variant . roughness = 0.1
Note: Use resource_local_to_scene for per-instance tweaks
Performance Optimization Material Batching
✅ GOOD: Reuse materials across meshes
const SHARED_STONE := preload ( "res://materials/stone.tres" ) func _ready ( ) -> void : for wall in get_tree ( ) . get_nodes_in_group ( "stone_walls" ) : wall . material_override = SHARED_STONE
All walls batched in single draw call
❌ BAD: Unique material per mesh
func _ready ( ) -> void : for wall in get_tree ( ) . get_nodes_in_group ( "stone_walls" ) : var mat := StandardMaterial3D . new ( )
New material!
mat . albedo_color = Color ( 0.5 , 0.5 , 0.5 ) wall . material_override = mat
Each wall is separate draw call
Texture Atlasing
Combine multiple materials into one texture atlas
Then use UV offsets to select regions
material_atlas.gd
extends StandardMaterial3D func set_atlas_region ( tile_x : int , tile_y : int , tiles_per_row : int ) -> void : var tile_size := 1.0 / tiles_per_row uv1_offset = Vector3 ( tile_x * tile_size , tile_y * tile_size , 0 ) uv1_scale = Vector3 ( tile_size , tile_size , 1 ) Edge Cases Normal Maps Not Working
Problem: Forgot to enable
mat . normal_enabled = true
REQUIRED
Problem: Wrong texture import settings
In Import tab: Texture → Normal Map = true
Texture Seams on Models
Problem: Mipmaps causing seams
Solution: Disable mipmaps for tightly-packed UVs
Import → Mipmaps → Generate = false
Material Looks Flat
Problem: Missing normal map or roughness variation
Solution: Add normal map + roughness texture
mat . normal_enabled = true mat . normal_texture = load ( "res://normal.png" ) mat . roughness_texture = load ( "res://roughness.png" ) Common Material Presets
Glass
func create_glass ( ) -> StandardMaterial3D : var mat := StandardMaterial3D . new ( ) mat . transparency = BaseMaterial3D . TRANSPARENCY_ALPHA mat . albedo_color = Color ( 1 , 1 , 1 , 0.2 ) mat . metallic = 0.0 mat . roughness = 0.0 mat . refraction_enabled = true mat . refraction_scale = 0.05 return mat
Gold
func create_gold ( ) -> StandardMaterial3D : var mat := StandardMaterial3D . new ( ) mat . albedo_color = Color ( 1.0 , 0.85 , 0.3 ) mat . metallic = 1.0 mat . roughness = 0.3 return mat Reference Master Skill: godot-master