references/migration-notes.md
# Migration notes: godot-3d-materials
Incremental upgrade for topics this skill covers. Apply **one hop**, stabilize/test, then next. Never skip hops.
If the project is **< 4.0**, follow [godot-version-migration](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-version-migration/SKILL.md) era bridges (legacy → 3→4) until 4.0, then these hops. Official 3→4: [Upgrading from Godot 3 to Godot 4](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.html).
## 3.x → 4.0
Official: [Upgrading from Godot 3 to Godot 4](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.html)
- `SpatialMaterial` → `StandardMaterial3D`.
- `StreamTexture` → `CompressedTexture2D` (reimport).
- ArrayMesh `.res` from 3.x incompatible — reimport source meshes.
- ShaderMaterial: apply shader foundation 3→4 notes.
## 4.0 → 4.1
Official: [Upgrading to Godot 4.1](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.1.html)
- `RenderingServer.global_shader_parameter_get_list` / RD shader version lists return `Array[StringName]`.
- `RenderingDevice.draw_list_begin` storage_textures typed as `Array[RID]`.
## 4.1 → 4.2
Official: [Upgrading to Godot 4.2](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.2.html)
- **Mesh format** upgrade — use Project → Tools → Upgrade Mesh Surfaces; Restart & Upgrade prevents downgrade (material slots may remap after upgrade).
- ImporterMesh/MeshDataTool/SurfaceTool compression flag widths → `uint64`.
- RenderingDevice BarrierMask enum values changed.
## 4.2 → 4.3
Official: [Upgrading to Godot 4.3](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.3.html)
- **Reverse Z** depth — update spatial/custom shaders on StandardMaterial3D overrides and depth-based effects.
- Decal modulate converted sRGB→linear — emissive/decal material tints shift; rebalance albedo/emission pairs.
- RenderingDevice barrier/draw_list API simplified (post_barrier params removed).
## 4.3 → 4.4
Official: [Upgrading to Godot 4.4](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.4.html)
- `RenderingDevice.draw_list_begin` signature overhauled (params removed + breadcrumb).
- `Shader` default texture parameter types use `Texture` / `TextureLayered`.
- `VisualShaderNodeVec4Constant` input type → Vector4 — recreate material graph constants.
- VisualShader cubemap / Texture2DArray nodes use `TextureLayered`.
- CSG uses Manifold — **non-manifold** CSG booleans fail; use MeshInstance3D materials on imported meshes for planes/quads.
## 4.4 → 4.5
Official: [Upgrading to Godot 4.5](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.5.html)
- `RenderingServer.instance_reset_physics_interpolation` / `instance_set_interpolated` removed.
- GLTF/BLEND/FBX naming version for non-joint nodes in skeletons — set Import dock Naming Version for old PBR assets.
## 4.5 → 4.6
Official: [Upgrading to Godot 4.6](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.6.html)
- Glow default blend **Screen** (brighter) — retune emissive materials against new default bloom.
- Volumetric fog blending brighter — reduce fog density when pairing with transparent/transmission materials.
- Sky reflection roughness_layers default 7 (was 8) — affects environment-reflection roughness on PBR materials.
## 4.6 → 4.7
Official: [Upgrading to Godot 4.7](https://docs.godotengine.org/en/stable/tutorials/migrating/upgrading_to_godot_4.7.html)
- `Texture2D.get_format()` unified on base class — use when branching shader logic on compressed vs HDR formats.
- `LinearToSRGB` visual shader no longer clamps `[0,1]` on Mobile/Forward+ — rebalance emissive VisualShader graphs.
- `Image.save_exr*` gain color_image / max_linear_value optionals for HDR texture export pipelines.
references/pbr-workflows.md
# Pbr Workflows
## Advanced Features
### Emission (Glowing Materials)
```gdscript
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)
```gdscript
mat.rim_enabled = true
mat.rim = 1.0 # Intensity
mat.rim_tint = 0.5 # How much albedo affects rim color
```
### Clearcoat (Car Paint)
```gdscript
mat.clearcoat_enabled = true
mat.clearcoat = 1.0 # Layer strength
mat.clearcoat_roughness = 0.1 # Glossy top layer
```
### Anisotropy (Brushed Metal)
```gdscript
mat.anisotropy_enabled = true
mat.anisotropy = 1.0 # Directional highlights
mat.anisotropy_flowmap = load("res://brushed_flow.png")
```
---
## Common Material Presets
```gdscript
# 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
```
---
scripts/decal_placer_expert.gd
# Dynamic 3D Decal Placer
extends Node3D
## Expert pattern for placing high-performance decals
## (impact holes, footsteps) without mesh generation.
@export var max_decals := 50
var _decal_count := 0
func place_impact_decal(pos: Vector3, normal: Vector3, texture: Texture2D) -> void:
if _decal_count >= max_decals: return
var decal = Decal.new()
add_child(decal)
decal.global_position = pos
decal.look_at(pos + normal, Vector3.UP)
decal.texture_albedo = texture
decal.size = Vector3(0.5, 0.5, 0.5)
# Important: Limit decal influence to environment layers only
decal.cull_mask = 1 # Only affects Layer 1
_decal_count += 1
# Self-destruction timer
get_tree().create_timer(10.0).timeout.connect(func():
decal.queue_free()
_decal_count -= 1
)
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/3d/using_decals.html
# - https://docs.godotengine.org/en/stable/classes/class_decal.html
# - https://docs.godotengine.org/en/stable/classes/class_geometryinstance3d.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-world-building/SKILL.md — environmental detail without unique mats
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-particles/SKILL.md — impact FX pairing with decals
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/depth_precision_fix.gd
# Floating Point Precision Fix (Z-fighting)
extends Camera3D
## Flickering textures (Z-fighting) occur when surfaces are too close.
## Compressing the Viewport precision range fixes this for distant terrain.
func optimize_depth_precision() -> void:
# Architecture Tip: Increase Near as much as usable,
# and decrease Far as much as possible.
near = 0.5 # Default 0.05 is too small for large scenes
far = 500.0 # Default 4000 is way too high for standard indoor/limited outdoor
# This compresses the depth buffer range and grants
# significantly more precision per unit of distance.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_camera3d.html
# - https://docs.godotengine.org/en/stable/tutorials/performance/gpu_optimization.html
# - https://docs.godotengine.org/en/stable/classes/class_geometryinstance3d.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-camera-systems/SKILL.md — near/far and large-world camera setup
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-open-world/SKILL.md — scale where Z-fighting appears
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/instance_uniform_batching.gdshader
# Instance Uniform Shader Batching (+ texture-array variants)
shader_type spatial;
## One material shared by thousands of meshes: per-instance color/health
## and optional texture-array index (forest/crowd variants) without unique .tres.
uniform sampler2D texture_array[4];
instance uniform vec4 instance_color : source_color = vec4(1.0);
instance uniform float health_ratio = 1.0;
instance uniform int texture_index;
void fragment() {
vec4 tex_color;
switch (texture_index) {
case 0: tex_color = texture(texture_array[0], UV); break;
case 1: tex_color = texture(texture_array[1], UV); break;
case 2: tex_color = texture(texture_array[2], UV); break;
default: tex_color = texture(texture_array[3], UV); break;
}
ALBEDO = tex_color.rgb * instance_color.rgb * health_ratio;
// GDScript: mesh.set_instance_shader_parameter(&"texture_index", i)
// Never duplicate the ShaderMaterial per variant — that breaks batching.
}
// =============================================================================
// GDSkills research links (agents) — does not affect runtime
// Official docs:
// - https://docs.godotengine.org/en/stable/tutorials/shaders/shader_reference/spatial_shader.html
// - https://docs.godotengine.org/en/stable/classes/class_geometryinstance3d.html
// - https://docs.godotengine.org/en/stable/tutorials/performance/gpu_optimization.html
// Related skills:
// - ../godot-performance-optimization/SKILL.md — shared-material batching at scale
// - ../godot-shaders-basics/SKILL.md — instance uniforms in spatial shaders
// Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
// =============================================================================
scripts/material_batcher.gd
# Material Batching and Override logic
extends Node
## Efficiently sharing materials across multiple meshes
## to ensure GPU draw call batching.
func apply_global_material(group_name: String, mat: Material) -> void:
for node in get_tree().get_nodes_in_group(group_name):
if node is MeshInstance3D:
# Override ensures we don't modify the base .mesh file
node.material_override = mat
# Result: All meshes in group now draw in a single state-locked batch.
## HLOD: swap detailed ↔ distant meshes and strip expensive shading at range.
## Prefer Pixel Dither distance fade over alpha blend (opaque pipeline).
func setup_lod_materials(detailed_node: GeometryInstance3D, distant_node: GeometryInstance3D, swap_distance: float = 50.0) -> void:
detailed_node.visibility_range_end = swap_distance
detailed_node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_SELF
distant_node.visibility_range_begin = swap_distance
distant_node.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_SELF
var dist_mat := distant_node.get_surface_override_material(0) as StandardMaterial3D
if dist_mat == null:
return
# Mutate only after ensuring a unique override if the resource is shared.
dist_mat = dist_mat.duplicate(true) as StandardMaterial3D
distant_node.set_surface_override_material(0, dist_mat)
dist_mat.normal_enabled = false
dist_mat.rim_enabled = false
dist_mat.clearcoat_enabled = false
dist_mat.subsurf_scatter_enabled = false
dist_mat.distance_fade_mode = BaseMaterial3D.DISTANCE_FADE_PIXEL_DITHER
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/performance/gpu_optimization.html
# - https://docs.godotengine.org/en/stable/classes/class_geometryinstance3d.html
# - https://docs.godotengine.org/en/stable/classes/class_standardmaterial3d.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-performance-optimization/SKILL.md — draw-call / state batching
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-world-building/SKILL.md — environment mesh material overrides
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/material_fx.gd
# skills/3d-materials/scripts/material_fx.gd
extends Node
## Material FX (Expert Pattern)
## Runtime material mutation helpers. ALWAYS duplicate (or enable local-to-scene)
## before tweaking shared .tres / surface overrides — otherwise every instance flashes.
class_name MaterialFX
static func ensure_unique_override(mesh: MeshInstance3D, surface: int = 0) -> Material:
## Returns a mesh-local material safe to mutate at runtime.
var existing := mesh.get_surface_override_material(surface)
if existing == null:
existing = mesh.get_active_material(surface)
if existing == null:
return null
if mesh.material_override != null and mesh.material_override == existing:
var dup_override: Material = existing.duplicate(true)
mesh.material_override = dup_override
return dup_override
var unique: Material = existing.duplicate(true)
mesh.set_surface_override_material(surface, unique)
return unique
static func flash_white(mesh: MeshInstance3D, duration: float = 0.1) -> void:
# Overlay avoids mutating the shared albedo material when possible.
var flash_mat := StandardMaterial3D.new()
flash_mat.albedo_color = Color.WHITE
flash_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
flash_mat.transparency = BaseMaterial3D.TRANSPARENCY_ADD
mesh.material_overlay = flash_mat
var tree := Engine.get_main_loop() as SceneTree
await tree.create_timer(duration).timeout
if is_instance_valid(mesh):
mesh.material_overlay = null
static func dissolve_scissor(mesh: MeshInstance3D, duration: float = 1.0) -> void:
## Alpha-scissor dissolve on a UNIQUE override — never tween a shared .tres.
var mat := ensure_unique_override(mesh) as StandardMaterial3D
if mat == null:
return
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
var tween := mesh.create_tween()
tween.tween_property(mat, "alpha_scissor_threshold", 1.0, duration).from(0.0)
## EXPERT USAGE:
## 1. Prefer material_overlay for flashes (no shared-resource mutation).
## 2. For parameter tweens, call ensure_unique_override() or enable Local To Scene on the material.
## 3. Call MaterialFX.flash_white(self) / dissolve_scissor(self) from damage/VFX owners.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_standardmaterial3d.html
# - https://docs.godotengine.org/en/stable/classes/class_basematerial3d.html
# - https://docs.godotengine.org/en/stable/classes/class_geometryinstance3d.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-shaders-basics/SKILL.md — dissolve/flash often graduate to ShaderMaterial
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-lighting/SKILL.md — emission/HDR for glow damage states
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/organic_material.gd
# skills/3d-materials/code/organic_material.gd
extends MeshInstance3D
## Runtime Texture & SSS Manipulation Expert Pattern
## Demonstrates dynamic wetness and organic material tuning.
func set_wetness(value: float) -> void:
# Efficiently update multiple instances via a shared material parameter
# OR per-instance via shader parameters.
var mat := get_active_material(0)
if mat is StandardMaterial3D:
# Standard PBR properties
mat.roughness = lerp(1.0, 0.1, value)
mat.specular = lerp(0.5, 1.0, value)
elif mat is ShaderMaterial:
mat.set_shader_parameter("wetness", value)
func configure_sss(depth: float) -> void:
# Calibrating Subsurface Scattering for skin/flesh
var mat := get_active_material(0) as StandardMaterial3D
if mat:
mat.subsurf_scatter_enabled = true
mat.subsurf_scatter_strength = depth
mat.subsurf_scatter_skin_mode = true
## EXPERT NOTE:
## When updating parameters every frame (like rain), ensure you are caching
## the material reference in _ready() to avoid repeated get_active_material() calls.
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_basematerial3d.html
# - https://docs.godotengine.org/en/stable/classes/class_standardmaterial3d.html
# - https://docs.godotengine.org/en/stable/tutorials/3d/standard_material_3d.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-lighting/SKILL.md — SSS/transmittance needs Forward+ lighting setup
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-shaders-basics/SKILL.md — custom skin/leaf shaders when flags are insufficient
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/pbr_material_builder.gd
# skills/3d-materials/scripts/pbr_material_builder.gd
extends Node
## PBR Material Builder (Expert Pattern)
## Runtime helper to build StandardMaterial3D from texture sets.
## Automatically handles ORM packing if provided.
class_name PBRMaterialBuilder
static func build(albedo: Texture2D, normal: Texture2D = null, orm: Texture2D = null) -> StandardMaterial3D:
var mat = StandardMaterial3D.new()
# 1. Albedo
if albedo:
mat.albedo_texture = albedo
# 2. Normal
if normal:
mat.normal_enabled = true
mat.normal_texture = normal
# 3. ORM (Occlusion, Roughness, Metallic)
if orm:
mat.orm_texture = orm
mat.ao_enabled = true
# Godot Standard: ORM texture (R=AO, G=Rough, B=Metal)
# Verify channel mapping
mat.ao_texture_channel = BaseMaterial3D.TEXTURE_CHANNEL_RED
mat.roughness_texture_channel = BaseMaterial3D.TEXTURE_CHANNEL_GREEN
mat.metallic_texture_channel = BaseMaterial3D.TEXTURE_CHANNEL_BLUE
else:
# Default defaults
mat.roughness = 0.5
mat.metallic = 0.0
return mat
static func build_triplanar(albedo: Texture2D, normal: Texture2D = null) -> StandardMaterial3D:
var mat = build(albedo, normal)
mat.uv1_triplanar = true
return mat
## EXPERT USAGE:
## var mat = PBRMaterialBuilder.build(load("grass_c.png"), load("grass_n.png"))
## $MeshInstance.material_override = mat
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/3d/standard_material_3d.html
# - https://docs.godotengine.org/en/stable/classes/class_standardmaterial3d.html
# - https://docs.godotengine.org/en/stable/classes/class_ormmaterial3d.html
# - https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_images.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md — share built materials as Resources
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-shaders-basics/SKILL.md — triplanar/custom paths beyond StandardMaterial3D
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/pbr_orm_packer.gd
# PBR ORM Texture Packer Utility
extends Resource
## Expert pattern: Combine Ambient Occlusion, Roughness, and Metallic
## into one RGB texture (ORM) to save 2 texture slots and GPU memory.
func get_orm_material(albedo: Texture, orm: Texture, normal: Texture) -> StandardMaterial3D:
var mat = StandardMaterial3D.new()
mat.albedo_texture = albedo
# Mandatory channel mapping for ORM
mat.orm_texture = orm # R=AO, G=Rough, B=Metal
mat.normal_enabled = true
mat.normal_texture = normal
# Optimization: Use triplanar in world space for large terrain meshes
mat.uv1_triplanar = true
mat.uv1_world_triplanar = true
return mat
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/tutorials/3d/standard_material_3d.html
# - https://docs.godotengine.org/en/stable/classes/class_ormmaterial3d.html
# - https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_images.html
# - https://docs.godotengine.org/en/stable/classes/class_basematerial3d.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md — packed textures as reusable assets
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-performance-optimization/SKILL.md — VRAM/slot savings from ORM
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/shader_state_manager.gd
# Shader Variant State Manager
extends MeshInstance3D
## Architectural pattern for swapping material states
## (e.g., Frozen, Burnt, Dissolved) without resource duplication.
func set_dissolve_strength(v: float) -> void:
var mat = get_active_material(0)
if mat is ShaderMaterial:
mat.set_shader_parameter("dissolve_amount", v)
func set_frozen_state(enabled: bool) -> void:
var mat = get_active_material(0)
if mat is ShaderMaterial:
# Using a float uniform as a boolean for efficiency
mat.set_shader_parameter("is_frozen", 1.0 if enabled else 0.0)
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_shadermaterial.html
# - https://docs.godotengine.org/en/stable/tutorials/shaders/shader_reference/spatial_shader.html
# - https://docs.godotengine.org/en/stable/classes/class_geometryinstance3d.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-shaders-basics/SKILL.md — shader parameter state machines
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md — avoid per-entity material duplication
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/subsurface_scattering_setup.gd
# Subsurface Scattering Configuration
extends MeshInstance3D
## PBR expert setup for wax, skin, and translucent organic materials.
func configure_organic_sss() -> void:
var mat = StandardMaterial3D.new()
# 1. Base SSS (Forward+ Renderer Required)
mat.subsurf_scatter_enabled = true
mat.subsurf_scatter_strength = 0.8
# 2. Skin Mode optimization (Tints red for dermal scattering)
mat.subsurf_scatter_skin_mode = true
# 3. Transmittance (Light passing through thin mesh parts like ears)
mat.subsurf_scatter_transmittance_enabled = true
mat.subsurf_scatter_transmittance_color = Color(1.0, 0.4, 0.3)
mat.subsurf_scatter_transmittance_depth = 0.1
material_override = mat
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_basematerial3d.html
# - https://docs.godotengine.org/en/stable/classes/class_standardmaterial3d.html
# - https://docs.godotengine.org/en/stable/tutorials/3d/standard_material_3d.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-lighting/SKILL.md — Forward+ and light transmittance context
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-shaders-basics/SKILL.md — custom SSS when BaseMaterial3D limits hit
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/transparency_sorting_fix.gd
# Transparency Sorting Fix Logic
extends MeshInstance3D
## Resolves artifacts where back surfaces appear in front of closer ones.
## Covers Alpha Scissor vs Alpha Hash vs Depth Draw strategies.
func use_cutout_transparency() -> void:
var mat = material_override as StandardMaterial3D
# Best performance, writes to depth buffer, casts shadows
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
mat.alpha_scissor_threshold = 0.5
func use_dithered_transparency() -> void:
var mat = material_override as StandardMaterial3D
# Perceptually smooth fade, no sorting artifacts, slower than scissor
mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_HASH
func enforce_depth_prepass() -> void:
var mat = material_override as StandardMaterial3D
# Resolves overlapping alpha-blended sorting issues
mat.depth_draw_mode = BaseMaterial3D.DEPTH_DRAW_ALWAYS
# =============================================================================
# GDSkills research links (agents) — does not affect runtime
# Official docs:
# - https://docs.godotengine.org/en/stable/classes/class_basematerial3d.html
# - https://docs.godotengine.org/en/stable/tutorials/3d/standard_material_3d.html
# - https://docs.godotengine.org/en/stable/tutorials/performance/gpu_optimization.html
# Related skills:
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-performance-optimization/SKILL.md — transparency overdraw cost
# - https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-particles/SKILL.md — alpha pipelines for soft FX
# Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
# =============================================================================
scripts/triplanar_world_projection.gdshader
# Triplanar World Texture Projection
shader_type spatial;
## UV-less mapping for cliffs, caves, or procedural meshes.
## Blends textures based on surface normals.
uniform sampler2D wall_texture : source_color;
uniform float triplanar_sharpness = 2.0;
void fragment() {
vec3 blending = abs(NORMAL);
blending /= (blending.x + blending.y + blending.z);
blending = pow(blending, vec3(triplanar_sharpness));
blending /= (blending.x + blending.y + blending.z);
vec3 x_tex = texture(wall_texture, VERTEX.zy).rgb;
vec3 y_tex = texture(wall_texture, VERTEX.xz).rgb;
vec3 z_tex = texture(wall_texture, VERTEX.xy).rgb;
ALBEDO = x_tex * blending.x + y_tex * blending.y + z_tex * blending.z;
}
// =============================================================================
// GDSkills research links (agents) — does not affect runtime
// Official docs:
// - https://docs.godotengine.org/en/stable/tutorials/shaders/shader_reference/spatial_shader.html
// - https://docs.godotengine.org/en/stable/tutorials/3d/standard_material_3d.html
// - https://docs.godotengine.org/en/stable/tutorials/shaders/your_first_shader/your_first_3d_shader.html
// Related skills:
// - ../godot-shaders-basics/SKILL.md — world-space projection math
// - ../godot-3d-world-building/SKILL.md — cliffs/caves/terrain materials
// Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
// =============================================================================
scripts/triplanar_world.gdshader
// skills/3d-materials/code/triplanar_world.gdshader
shader_type ruby; // Using Ruby syntax highlighting as a placeholder for GDShader if needed, but the extension will be .gdshader
shader_type spatial;
## World-Aligned Triplanar Expert shader
## Perfect for terrain, caves, or architectural meshes.
uniform sampler2D wall_texture : source_color;
uniform float uv_scale = 1.0;
varying vec3 world_pos;
varying vec3 world_normal;
void vertex() {
world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
world_normal = abs(NORMAL);
}
void fragment() {
// 1. Calculate triplanar blending weights
vec3 blending = world_normal;
blending /= (blending.x + blending.y + blending.z);
// 2. Project textures on 3 axes
vec3 x_tex = texture(wall_texture, world_pos.zy * uv_scale).rgb;
vec3 y_tex = texture(wall_texture, world_pos.xz * uv_scale).rgb;
vec3 z_tex = texture(wall_texture, world_pos.xy * uv_scale).rgb;
// 3. Blend based on normal direction
ALBEDO = x_tex * blending.x + y_tex * blending.y + z_tex * blending.z;
}
// =============================================================================
// GDSkills research links (agents) — does not affect runtime
// Official docs:
// - https://docs.godotengine.org/en/stable/tutorials/shaders/shader_reference/spatial_shader.html
// - https://docs.godotengine.org/en/stable/tutorials/shaders/your_first_shader/your_first_3d_shader.html
// - https://docs.godotengine.org/en/stable/tutorials/3d/standard_material_3d.html
// Related skills:
// - ../godot-shaders-basics/SKILL.md — spatial shader workflow
// - ../godot-procedural-generation/SKILL.md — UV-less procedural meshes
// Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
// =============================================================================
scripts/vertex_wind_sway.gdshader
# High-Performance GPU Wind Sway
shader_type spatial;
## Expert level vertex sway. No bone overhead.
## Uses vertex colors (REd channel) as depth weight for foliage.
render_mode depth_prepass_alpha, cull_disabled, world_vertex_coords;
uniform sampler2D texture_albedo : source_color;
uniform float sway_speed = 1.0;
uniform float sway_strength = 0.05;
void vertex() {
// Weight derived from vertex painting
float weight = COLOR.r;
float time_offset = TIME * sway_speed + VERTEX.x + VERTEX.z;
VERTEX.x += sin(time_offset) * sway_strength * weight;
VERTEX.z += cos(time_offset) * sway_strength * weight;
}
void fragment() {
vec4 albedo_tex = texture(texture_albedo, UV);
ALBEDO = albedo_tex.rgb;
ALPHA = albedo_tex.a;
ALPHA_SCISSOR_THRESHOLD = 0.5;
}
// =============================================================================
// GDSkills research links (agents) — does not affect runtime
// Official docs:
// - https://docs.godotengine.org/en/stable/tutorials/shaders/shader_reference/spatial_shader.html
// - https://docs.godotengine.org/en/stable/tutorials/shaders/your_first_shader/your_first_3d_shader.html
// - https://docs.godotengine.org/en/stable/classes/class_geometryinstance3d.html
// Related skills:
// - ../godot-shaders-basics/SKILL.md — vertex displacement patterns
// - ../godot-3d-world-building/SKILL.md — foliage/environment placement
// Parent skill: https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-materials/SKILL.md
// =============================================================================
shaders/triplanar_smooth.gdshader
shader_type spatial;
/**
* Expert Triplanar Smoothing Shader - Godot 4.6
* Prevents texture stretching on procedural meshes or steep slopes by
* projecting from three axes in World Space.
*/
render_mode world_vertex_coords;
uniform sampler2D albedo_tex : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
uniform float texture_scale = 1.0;
uniform float blend_sharpness = 4.0;
varying vec3 v_world_pos;
varying vec3 v_world_normal;
void vertex() {
v_world_pos = VERTEX;
v_world_normal = NORMAL;
}
void fragment() {
// 1. Calculate Weights
vec3 weights = abs(v_world_normal);
weights = pow(weights, vec3(blend_sharpness));
weights /= (weights.x + weights.y + weights.z);
// 2. Project UVs
vec2 uv_x = v_world_pos.zy * texture_scale;
vec2 uv_y = v_world_pos.xz * texture_scale;
vec2 uv_z = v_world_pos.xy * texture_scale;
// 3. Sample and Blend
vec3 samp_x = texture(albedo_tex, uv_x).rgb;
vec3 samp_y = texture(albedo_tex, uv_y).rgb;
vec3 samp_z = texture(albedo_tex, uv_z).rgb;
ALBEDO = (samp_x * weights.x) + (samp_y * weights.y) + (samp_z * weights.z);
}
SKILL.md
---
name: godot-3d-materials
description: "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."
---
# 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.
- **NEVER use transparent materials for large environmental surfaces** — Transparent objects cannot rely on the Z-buffer for early fragment rejection, resulting in massive overdraw. If only a tiny part of a mesh is transparent, split the mesh into two surfaces: one opaque, one transparent.
- **NEVER create hundreds of slightly varied StandardMaterial3D resources if performance is dropping** — Godot minimizes GPU state changes by automatically reusing the underlying shader for materials that share the exact same configuration flags (checkboxes). Try to group your material configurations.
- **NEVER attempt to fix Z-fighting strictly by moving objects further apart** — Floating-point precision degrades over distance. To fix flickering textures, increase your Camera3D's `Near` plane property and decrease the `Far` property to compress the precision range.
- **NEVER use unique Material resources per MeshInstance3D** — This breaks draw call batching. Use 'Instance Uniforms' to vary parameters while keeping a single shared material.
- **NEVER mutate a shared Material `.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`](scripts/material_fx.gd) `ensure_unique_override()` / overlay helpers for flash/dissolve.
- **NEVER use Decals on dynamic moving actors without a Cull Mask** — Bullet holes should not stick to the player's face as they walk over them. Mask out character layers.
---
## Available Scripts
> **MANDATORY**: Read the appropriate script before implementing the corresponding pattern.
### Workflow router (MANDATORY / Do NOT Load)
| 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.
### [material_fx.gd](scripts/material_fx.gd)
**MANDATORY** for runtime FX. `ensure_unique_override()` / overlay flash / scissor dissolve — never tween shared `.tres` materials.
### [pbr_material_builder.gd](scripts/pbr_material_builder.gd)
Runtime PBR material creation with ORM textures and triplanar mapping.
### [organic_material.gd](scripts/organic_material.gd)
Subsurface scattering and rim lighting setup for organic surfaces (skin, leaves). Use for realistic character or vegetation materials.
### [triplanar_world.gdshader](scripts/triplanar_world.gdshader)
Triplanar projection shader for terrain without UV mapping. Blends textures based on surface normals. Use for cliffs, caves, or procedural terrain.
### [pbr_orm_packer.gd](scripts/pbr_orm_packer.gd)
Expert PBR resource utility. Packs Ambient Occlusion, Roughness, and Metallic into a single ORM texture to optimize VRAM and draw calls.
### [vertex_wind_sway.gdshader](scripts/vertex_wind_sway.gdshader)
High-performance GPU-driven foliage animation. Uses vertex world coordinates and vertex color weight painting to simulate wind without skeletons.
### [triplanar_world_projection.gdshader](scripts/triplanar_world_projection.gdshader)
UV-less environment mapping. Projects textures along X/Y/Z axes for organic blending over complex rocks and terrain.
### [subsurface_scattering_setup.gd](scripts/subsurface_scattering_setup.gd)
Configuring realistic organic materials. Covers Skin Mode, Transmittance, and depth scattering settings for Forward+ rendering.
### [instance_uniform_batching.gdshader](scripts/instance_uniform_batching.gdshader)
Architecture pattern for high-speed batching. Allows 10,000 meshes to share one material while maintaining unique colors or health states via instance uniforms.
### [decal_placer_expert.gd](scripts/decal_placer_expert.gd)
Dynamic 3D decal system with cull masking and life-cycle management for impact effects.
### [transparency_sorting_fix.gd](scripts/transparency_sorting_fix.gd)
Solving visual artifacts using Alpha Hash and Depth Prepass strategies.
### [shader_state_manager.gd](scripts/shader_state_manager.gd)
Clean pattern for toggling shader-based visual states (Frozen, Burned) on multiple entities.
### [depth_precision_fix.gd](scripts/depth_precision_fix.gd)
Camera-side fix for Z-fighting and texture flickering in large-scale worlds.
### [material_batcher.gd](scripts/material_batcher.gd)
Global override system to ensure environmental meshes draw in optimized, state-locked batches.
---
## StandardMaterial3D Checklist (script-first)
1. Pack AO/Roughness/Metallic → **MANDATORY** [`pbr_orm_packer.gd`](scripts/pbr_orm_packer.gd); set `orm_texture` (never three separate maps).
2. Enable `normal_enabled` before assigning `normal_texture` (silent no-op otherwise).
3. Pick transparency from the matrix below — not docs-default ALPHA for cutouts.
4. Organic skin/leaves → [`organic_material.gd`](scripts/organic_material.gd) + [`subsurface_scattering_setup.gd`](scripts/subsurface_scattering_setup.gd) (Forward+ only for real SSS).
5. Runtime flash/dissolve → **MANDATORY** [`material_fx.gd`](scripts/material_fx.gd) `ensure_unique_override()` first.
6. Shared-mesh tint/variant → [`instance_uniform_batching.gdshader`](scripts/instance_uniform_batching.gdshader); never unique `.tres` per instance.
7. Distant LOD simplify / Pixel Dither fade → [`material_batcher.gd`](scripts/material_batcher.gd) `setup_lod_materials()`.
8. Check Forward+ vs Mobile feature matrix before enabling SSS/clearcoat/anisotropy.
### Forward+ vs Mobile (material features)
| 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 |
---
## 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)
```gdscript
# 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)
```gdscript
# 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)
```gdscript
# 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
```
---
## Texture Channel Packing
**MANDATORY** [`pbr_orm_packer.gd`](scripts/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.
---
## Shader Conversion
### When to Convert to ShaderMaterial
- Need custom effects (dissolve, vertex displacement)
- StandardMaterial3D limitations hit
- Shader optimizations (remove unused features)
### Conversion Workflow
```gdscript
# 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
```gdscript
# 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
```gdscript
# ✅ 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
```gdscript
# 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
```gdscript
# 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
```gdscript
# Problem: Mipmaps causing seams
# Solution: Disable mipmaps for tightly-packed UVs
# Import → Mipmaps → Generate = false
```
### Material Looks Flat
```gdscript
# 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")
```
---
## Expert Techniques & Optimizations
### 1. LOD Transitions using Pixel Dither
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.
### 2. Stencil Buffers (Godot 4.5+)
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.
### 3. AR Shadow Overlay Shader
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
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);
}
```
---
## Expert Pattern: Material-Texture-Array (Instanced Variation)
**MANDATORY** [`instance_uniform_batching.gdshader`](scripts/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.
---
## Expert Pattern: Dissolve-Shader-Integration (Alpha Scissor)
Use Alpha Scissor for performant dissolves (keeps shadows, avoids alpha sort). **MANDATORY** [`material_fx.gd`](scripts/material_fx.gd) `dissolve_scissor()` — it duplicates the override first.
---
## Expert Pattern: Material-LOD-System (HLOD)
Mesh LOD is automatic; material shading is not. **MANDATORY** [`material_batcher.gd`](scripts/material_batcher.gd) `setup_lod_materials()` for visibility-range swaps, feature strip on distant materials, and Pixel Dither distance fade (not alpha blend).
## Expert insights (WHY — keep in body)
- **ORM packing** — WHY: three separate maps triple sampler slots and break batching. One RGB = AO/Roughness/Metallic ([pbr_orm_packer.gd](scripts/pbr_orm_packer.gd)).
- **Metallic 0.5** — WHY: PBR metals are 0 or 1; mid values are only for dirt/rust transitions, not default paint.
- **Shared material mutation** — WHY: tweaking a shared `.tres` affects every instance. `duplicate(true)` or Local To Scene before runtime FX ([material_fx.gd](scripts/material_fx.gd)).
- **Pixel Dither LOD fade** — WHY: alpha-blend distance fade keeps fragments in the transparent pipeline; dither stays opaque ([material_batcher.gd](scripts/material_batcher.gd)).
## Deep recipes (on demand)
| Topic | Reference / script |
|-------|-------------------|
| Metal/dielectric presets / SSS / clearcoat | [pbr-workflows.md](references/pbr-workflows.md) |
## Reference
> 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.
### Official Documentation
- [Standard Material 3D and ORM Material 3D](https://docs.godotengine.org/en/stable/tutorials/3d/standard_material_3d.html) — Primary PBR tutorial for albedo/metallic/roughness, ORM packing, transparency modes, and feature flags on StandardMaterial3D.
- [BaseMaterial3D](https://docs.godotengine.org/en/stable/classes/class_basematerial3d.html) — Shared API for transparency enums, texture channels, cull/depth draw, distance fade, and subsurface scattering controls.
- [StandardMaterial3D](https://docs.godotengine.org/en/stable/classes/class_standardmaterial3d.html) — Concrete material class used throughout this skill’s builders, FX helpers, and presets.
- [ORMMaterial3D](https://docs.godotengine.org/en/stable/classes/class_ormmaterial3d.html) — Dedicated ORM-packed material path when Occlusion/Roughness/Metallic already live in one RGB texture.
- [Using decals](https://docs.godotengine.org/en/stable/tutorials/3d/using_decals.html) — Projector decals, cull masks, and performance limits for bullet holes and detail without unique materials.
- [Importing images](https://docs.godotengine.org/en/stable/tutorials/assets_pipeline/importing_images.html) — Correct normal-map and channel import settings so PBR maps are not treated as color data.
- [Visibility ranges (HLOD)](https://docs.godotengine.org/en/stable/tutorials/3d/visibility_ranges.html) — Distance swap / fade modes that pair with simplified distant materials and Pixel Dither distance fade.
- [Spatial shader](https://docs.godotengine.org/en/stable/tutorials/shaders/shader_reference/spatial_shader.html) — Built-ins for triplanar, wind sway, `instance uniform`, and `shadow_to_opacity` when StandardMaterial3D is not enough.
- [Your first 3D shader](https://docs.godotengine.org/en/stable/tutorials/shaders/your_first_shader/your_first_3d_shader.html) — Conversion path from StandardMaterial3D settings into a writable spatial ShaderMaterial.
- [GeometryInstance3D](https://docs.godotengine.org/en/stable/classes/class_geometryinstance3d.html) — `set_instance_shader_parameter`, material overlays/overrides, and visibility-range properties for shared-material batching.
- [GPU optimization](https://docs.godotengine.org/en/stable/tutorials/performance/gpu_optimization.html) — Overdraw, transparency cost, and batching guidance that motivates ORM packing and avoiding unique materials.
### Related Skills
#### Prerequisites
- [godot-project-foundations](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-project-foundations/SKILL.md) — Nodes, Resources, and import basics required before authoring reusable `.tres` materials and texture sets.
- [godot-resource-data-patterns](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-resource-data-patterns/SKILL.md) — Material/texture Resources, duplication vs sharing, and data-driven presets that keep draw-call batching intact.
- [godot-shaders-basics](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-shaders-basics/SKILL.md) — Shading language and ShaderMaterial fundamentals before converting StandardMaterial3D or writing triplanar/instance-uniform shaders.
#### Complements
- [godot-3d-lighting](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-lighting/SKILL.md) — Emission energy, HDR, GI, and AreaLight3D interactions that determine how PBR and emissive materials actually read in-scene.
- [godot-3d-world-building](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-3d-world-building/SKILL.md) — Applying shared environment materials, decals, and triplanar projection across large level geometry.
- [godot-camera-systems](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-camera-systems/SKILL.md) — Camera3D near/far and framing choices that fix Z-fighting / depth precision issues materials alone cannot solve.
- [godot-particles](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-particles/SKILL.md) — Particle draw modes and alpha pipelines that must stay consistent with material transparency choices (scissor/hash vs blend).
- [godot-performance-optimization](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-performance-optimization/SKILL.md) — Draw-call batching, MultiMesh, and GPU budgets when scaling unique vs shared materials.
- [godot-debugging-profiling](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-debugging-profiling/SKILL.md) — GPU/overdraw profilers to verify transparency and material-state regressions after material changes.
#### Downstream / consumers
- [godot-genre-open-world](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-genre-open-world/SKILL.md) — Large worlds consume HLOD visibility ranges, dithered distance fade, and shared-material batching patterns from this skill.
- [godot-procedural-generation](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-procedural-generation/SKILL.md) — Procedural meshes and terrains typically need triplanar / world-projection materials when UVs are absent or unstable.
- [godot-adapt-2d-to-3d](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-adapt-2d-to-3d/SKILL.md) — Moving flat art into 3D requires PBR map setup, normal import, and transparency mode choices covered here.
#### Master
- [godot-master](https://github.com/thedivergentai/gd-agentic-skills/blob/main/skills/godot-master/SKILL.md) — Library router and mirrored module entry for cross-skill discovery.