thedivergentai/godot-3d-materials
Expert patterns for Godot 3D PBR materials using StandardMaterial3D including albedo, metallic/roughness workflows, normal maps, ORM texture packing, transparency modes, and shader conversion. Use when creating realistic 3D surfaces, PBR workflows, or material optimization. Trigger keywords: StandardMaterial3D, BaseMaterial3D, albedo_texture, metallic, metallic_texture, roughness, roughness_texture, normal_texture, normal_enabled, orm_texture, transparency, alpha_scissor, alpha_hash, cull_mode, ShaderMaterial, shader parameters.
npx skills add https://github.com/thedivergentai/GD-Agentic-Skills --skill godot-3d-materials
Expert guidance for PBR materials and StandardMaterial3D in Godot.
normal_enabled = true. Silent failure is common.Near plane property and decrease the Far property to compress the precision range..tres / surface material at runtime — Call duplicate(true) or enable Local To Scene, then assign the unique instance before tweaking parameters. MANDATORY use material_fx.gd ensure_unique_override() / overlay helpers for flash/dissolve.Texture2D.get_format() unified on base class for portable compressed textures.> MANDATORY: Read the appropriate script before implementing the corresponding pattern.
| Task | Load | Do NOT Load |
|------|------|-------------|
| StandardMaterial3D / ORM / transparency | pbr_orm_packer.gd, transparency_sorting_fix.gd, material_batcher.gd, material_fx.gd | triplanar_world*.gdshader, vertex_wind_sway.gdshader |
| UV-less terrain / cliffs | triplanar_world.gdshader or triplanar_world_projection.gdshader + pbr_material_builder.gd | Wind sway unless foliage |
| Foliage wind | vertex_wind_sway.gdshader | Triplanar unless rock/terrain also needed |
| Runtime damage / dissolve | MANDATORY material_fx.gd | Editing shared imported materials |
| Instance color/health / texture-array variants | MANDATORY instance_uniform_batching.gdshader | Per-mesh unique StandardMaterial3D copies; body texture-array samples |
| HLOD / distant material simplify | material_batcher.gd (setup_lod_materials) | Alpha-blend distance fade (use Pixel Dither) |
| Organic SSS / rim / clearcoat | organic_material.gd, subsurface_scattering_setup.gd | SSS recipes on Mobile/Compatibility |
> Pure StandardMaterial3D albedo/ORM/transparency work: Do NOT Load triplanar, wind, or texture-array shaders.
MANDATORY for runtime FX. ensure_unique_override() / overlay flash / scissor dissolve — never tween shared .tres materials.
Runtime PBR material creation with ORM textures and triplanar mapping.
Subsurface scattering and rim lighting setup for organic surfaces (skin, leaves). Use for realistic character or vegetation materials.
Triplanar projection shader for terrain without UV mapping. Blends textures based on surface normals. Use for cliffs, caves, or procedural terrain.
Expert PBR resource utility. Packs Ambient Occlusion, Roughness, and Metallic into a single ORM texture to optimize VRAM and draw calls.
High-performance GPU-driven foliage animation. Uses vertex world coordinates and vertex color weight painting to simulate wind without skeletons.
UV-less environment mapping. Projects textures along X/Y/Z axes for organic blending over complex rocks and terrain.
Configuring realistic organic materials. Covers Skin Mode, Transmittance, and depth scattering settings for Forward+ rendering.
Architecture pattern for high-speed batching. Allows 10,000 meshes to share one material while maintaining unique colors or health states via instance uniforms.
Dynamic 3D decal system with cull masking and life-cycle management for impact effects.
Solving visual artifacts using Alpha Hash and Depth Prepass strategies.
Clean pattern for toggling shader-based visual states (Frozen, Burned) on multiple entities.
Camera-side fix for Z-fighting and texture flickering in large-scale worlds.
Global override system to ensure environmental meshes draw in optimized, state-locked batches.
pbr_orm_packer.gd; set orm_texture (never three separate maps).normal_enabled before assigning normal_texture (silent no-op otherwise).organic_material.gd + subsurface_scattering_setup.gd (Forward+ only for real SSS).material_fx.gd ensure_unique_override() first.instance_uniform_batching.gdshader; never unique .tres per instance.material_batcher.gd setup_lod_materials().| Feature | Forward+ | Mobile / Compatibility | WHY |
|---------|----------|------------------------|-----|
| Subsurface scattering / Skin Mode | Full | Limited / often unavailable | SSS needs Forward+ lighting path; fake with rim + transmittance bake on Mobile |
| Clearcoat | Yes | Often stripped / approximate | Extra specular lobe cost; drop on distant LOD and Mobile |
| Anisotropy | Yes | Prefer off | Flowmap + anisotropic BRDF burns mobile fragment budget |
| Alpha Hash / Pixel Dither fade | Yes | Prefer Alpha Scissor or opaque dither | Hash noise + overdraw hurts tile GPUs |
| Instance uniforms (batching) | Yes | Yes (prefer this) | Keeps one material; avoids unique-resource draw breaks |
| 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) |
# 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
# 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)
# 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
MANDATORY pbr_orm_packer.gd for R=AO / G=Roughness / B=Metallic. Do not inline packing recipes here. Custom channel remaps only when an imported atlas already disagrees with ORM layout.
# 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)
# 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
# ✅ 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
# 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)
# Problem: Forgot to enable
mat.normal_enabled = true # REQUIRED
# Problem: Wrong texture import settings
# In Import tab: Texture → Normal Map = true
# Problem: Mipmaps causing seams
# Solution: Disable mipmaps for tightly-packed UVs
# Import → Mipmaps → Generate = false
# 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")
When utilizing Hierarchical Level of Detail (HLOD) or Visibility Ranges to fade objects out at a distance, standard alpha blending causes severe performance hits due to overlapping transparent bounds. Instead, configure the Distance Fade mode on your material to Pixel Dither. This provides a perceptually smooth fade while remaining entirely within the high-performance opaque pipeline.
Use the Stencil Buffer directly in StandardMaterial3D. This allows you to easily render outlines or X-ray effects for objects hidden behind walls without needing to write custom shaders for basic effects.
If you are developing an AR game, you might want virtual shadows to appear on real-world camera feeds. Instead of standard blending, use Godot's built-in shadow_to_opacity render mode in a spatial shader.
shader_type spatial;
// shadow_to_opacity makes the material invisible when lit,
// but opaque (dark) when it receives a shadow from another 3D object.
render_mode blend_mix, depth_draw_opaque, cull_back, shadow_to_opacity;
void fragment() {
// The surface color is black; opacity will be driven by incoming shadows
ALBEDO = vec3(0.0, 0.0, 0.0);
}
MANDATORY instance_uniform_batching.gdshader for per-instance color/health and texture-array index variants. Set texture_index via GeometryInstance3D.set_instance_shader_parameter — never unique materials per tree/crowd variant.
Use Alpha Scissor for performant dissolves (keeps shadows, avoids alpha sort). MANDATORY material_fx.gd dissolve_scissor() — it duplicates the override first.
Mesh LOD is automatic; material shading is not. MANDATORY material_batcher.gd setup_lod_materials() for visibility-range swaps, feature strip on distant materials, and Pixel Dither distance fade (not alpha blend).
.tres affects every instance. duplicate(true) or Local To Scene before runtime FX (material_fx.gd).| Topic | Reference / script |
|-------|-------------------|
| Metal/dielectric presets / SSS / clearcoat | pbr-workflows.md |
> Progressive disclosure: open Official Documentation links only when researching a specific API;
> load Related Skills when routing work to a peer domain — do not preload the whole lattice.
instance uniform, and shadow_to_opacity when StandardMaterial3D is not enough.set_instance_shader_parameter, material overlays/overrides, and visibility-range properties for shared-material batching..tres materials and texture sets.Take thedivergentai/godot-3d-materials from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.