From godot-prompter
Covers Godot 4.3+ 3D essentials: coordinate system, core nodes, materials, lighting, shadows, environments, GI, fog, LOD, occlusion culling, decals. GDScript and C# examples.
npx claudepluginhub jame581/godotprompter --plugin godot-prompterThis skill uses the workspace's default tool permissions.
All examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, then C#.
Configures Three.js lighting with ambient, hemisphere, directional, point, spot lights; sets up shadows and environment lighting (IBL). Optimizes scene performance and realism.
Guides Godot 4.3+ shader implementation: canvas_item/spatial/particles/sky/fog types, GLSL-like language basics, ShaderMaterial workflow, and common 2D/3D effects.
Configures three.js lights including AmbientLight, HemisphereLight, DirectionalLight, PointLight, SpotLight, RectAreaLight, LightProbe, IESSpotLight, shadows, and helpers like RectAreaLightUniformsLib and LightProbeGenerator for direct/indirect lighting, probes, and area lights.
Share bugs, ideas, or general feedback.
All examples target Godot 4.3+ with no deprecated APIs. GDScript is shown first, then C#.
Related skills: player-controller for CharacterBody3D movement, physics-system for 3D collision shapes and raycasting, camera-system for Camera3D follow and transitions, shader-basics for spatial shaders and post-processing, godot-optimization for 3D performance tuning, animation-system for AnimationTree and 3D animation blending.
Godot uses a right-handed coordinate system with metric units (1 unit = 1 meter):
| Axis | Direction | Color |
|---|---|---|
| X | Right | Red |
| Y | Up | Green |
| Z | Out of screen (+Z toward viewer) | Blue |
Cameras and lights point along -Z by default. When a character "faces forward," they look along -Z.
| Node | Purpose |
|---|---|
Node3D | Base transform node — position, rotation, scale |
MeshInstance3D | Displays a mesh with a material |
Camera3D | Required to render 3D — perspective or orthogonal |
DirectionalLight3D | Sun/moon — parallel rays, cheapest light |
OmniLight3D | Point light — emits in all directions |
SpotLight3D | Cone light — flashlights, spotlights |
WorldEnvironment | Sky, fog, tonemap, post-processing |
Decal | Projected texture onto surfaces |
GPUParticles3D | GPU-driven particle effects |
CSGBox3D etc. | Constructive Solid Geometry — prototyping |
GridMap | 3D tile-based level building |
World (Node3D)
├── Camera3D
├── DirectionalLight3D
├── WorldEnvironment
├── MeshInstance3D (floor)
└── MeshInstance3D (player model)
| Material | Use For | Notes |
|---|---|---|
StandardMaterial3D | Most 3D objects — PBR workflow | No code; Inspector-driven |
ORMMaterial3D | Same as Standard but with packed ORM texture | Occlusion+Roughness+Metallic in one texture |
ShaderMaterial | Custom effects — toon, water, dissolve | Requires spatial shader code |
| Property | Type | Description |
|---|---|---|
albedo_color | Color | Base surface color |
albedo_texture | Texture2D | Diffuse/albedo texture map |
metallic | float | 0.0 (dielectric) to 1.0 (metal) |
roughness | float | 0.0 (mirror) to 1.0 (matte) |
emission | Color | Self-illumination color |
emission_energy_multiplier | float | Emission brightness |
normal_map | Texture2D | Surface detail without extra geometry |
ao_texture | Texture2D | Ambient occlusion map |
heightmap_texture | Texture2D | Ray-marched parallax depth illusion |
rim | float | Edge lighting (micro-fur scattering) |
clearcoat | float | Transparent secondary coat (car paint) |
| Mode | Performance | Shadows | Use For |
|---|---|---|---|
| Disabled | Fastest | Yes | Fully opaque objects |
| Alpha | Slow | No | Semi-transparent glass, water |
| Alpha Scissor | Fast | Yes | Binary cutout (leaves, fences) |
| Alpha Hash | Medium | Yes | Dithered transparency (hair) |
| Depth Pre-Pass | Medium | Partial | Mostly opaque with transparent edges |
@onready var mesh: MeshInstance3D = $MeshInstance3D
func _ready() -> void:
var mat := StandardMaterial3D.new()
mat.albedo_color = Color(0.8, 0.2, 0.2)
mat.metallic = 0.3
mat.roughness = 0.7
mesh.material_override = mat
func flash_emissive() -> void:
var mat: StandardMaterial3D = mesh.material_override
mat.emission_enabled = true
mat.emission = Color.WHITE
mat.emission_energy_multiplier = 3.0
var tween := create_tween()
tween.tween_property(mat, "emission_energy_multiplier", 0.0, 0.3)
tween.tween_callback(func(): mat.emission_enabled = false)
private MeshInstance3D _mesh;
public override void _Ready()
{
_mesh = GetNode<MeshInstance3D>("MeshInstance3D");
var mat = new StandardMaterial3D();
mat.AlbedoColor = new Color(0.8f, 0.2f, 0.2f);
mat.Metallic = 0.3f;
mat.Roughness = 0.7f;
_mesh.MaterialOverride = mat;
}
public void FlashEmissive()
{
var mat = _mesh.MaterialOverride as StandardMaterial3D;
mat.EmissionEnabled = true;
mat.Emission = Colors.White;
mat.EmissionEnergyMultiplier = 3.0f;
var tween = CreateTween();
tween.TweenProperty(mat, "emission_energy_multiplier", 0.0f, 0.3);
tween.TweenCallback(Callable.From(() => mat.EmissionEnabled = false));
}
When multiple MeshInstance3D nodes share the same material, changing one affects all. To make a per-instance copy:
# In _ready() — creates an independent copy of the material
mesh.material_override = mesh.material_override.duplicate()
_mesh.MaterialOverride = (Material)_mesh.MaterialOverride.Duplicate();
| Light | Shape | Shadows | Cost | Max Visible |
|---|---|---|---|---|
DirectionalLight3D | Parallel rays | PSSM | Cheapest | 8 (Forward+) |
OmniLight3D | Sphere | Cube/Dual Paraboloid | Medium | 512 clustered* |
SpotLight3D | Cone | Single texture | Cheap | 512 clustered* |
*Forward+ shares 512 clustered element slots among omni lights, spot lights, decals, and reflection probes.
@onready var sun: DirectionalLight3D = $DirectionalLight3D
func _ready() -> void:
sun.light_color = Color(1.0, 0.95, 0.9)
sun.light_energy = 1.0
sun.shadow_enabled = true
# Directional shadow quality
sun.directional_shadow_mode = DirectionalLight3D.SHADOW_PARALLEL_4_SPLITS
sun.directional_shadow_max_distance = 100.0
private DirectionalLight3D _sun;
public override void _Ready()
{
_sun = GetNode<DirectionalLight3D>("DirectionalLight3D");
_sun.LightColor = new Color(1.0f, 0.95f, 0.9f);
_sun.LightEnergy = 1.0f;
_sun.ShadowEnabled = true;
_sun.DirectionalShadowMode = DirectionalLight3D.ShadowMode.Parallel4Splits;
_sun.DirectionalShadowMaxDistance = 100.0f;
}
func create_explosion_light(pos: Vector3) -> void:
var light := OmniLight3D.new()
light.light_color = Color(1.0, 0.6, 0.2)
light.light_energy = 4.0
light.omni_range = 10.0
light.omni_attenuation = 2.0
light.position = pos
add_child(light)
var tween := create_tween()
tween.tween_property(light, "light_energy", 0.0, 0.5)
tween.tween_callback(light.queue_free)
public void CreateExplosionLight(Vector3 pos)
{
var light = new OmniLight3D();
light.LightColor = new Color(1.0f, 0.6f, 0.2f);
light.LightEnergy = 4.0f;
light.OmniRange = 10.0f;
light.OmniAttenuation = 2.0f;
light.Position = pos;
AddChild(light);
var tween = CreateTween();
tween.TweenProperty(light, "light_energy", 0.0f, 0.5);
tween.TweenCallback(Callable.From(light.QueueFree));
}
| Setting | Effect | Recommendation |
|---|---|---|
shadow_bias | Prevents self-shadowing (shadow acne) | Start at 0.1, increase if acne visible |
shadow_normal_bias | Better acne fix than regular bias | Prefer this over shadow_bias |
directional_shadow_max_distance | Limits shadow range from camera | Lower = better quality; 50–100m typical |
| Shadow map resolution | Project Settings > Rendering > Lights and Shadows | 2048 for perf, 4096 for quality |
shadow_blur | Softens shadow edges | 1.0–2.0 for gentle softness |
| Mode | Description | Use For |
|---|---|---|
| Disabled | Not included in lightmap baking; fully real-time (default) | Moving lights, player flashlight |
| Static | Fully baked into lightmaps — no runtime cost | Architecture, terrain, fixed lights |
| Dynamic | Indirect light baked, direct light stays real-time | Lights that change color/intensity |
World (Node3D)
├── WorldEnvironment ← holds Environment + CameraAttributes resources
├── DirectionalLight3D
├── Camera3D
└── ...
Set the Environment resource on WorldEnvironment and the Camera Attributes for exposure/DOF.
| Sky Material | Description | Use For |
|---|---|---|
PanoramaSkyMaterial | 360° HDR panorama image | Realistic environments |
ProceduralSkyMaterial | Generated sky with color gradients | Quick prototyping |
PhysicalSkyMaterial | Physics-based atmosphere + sun | Outdoor day/night cycles |
func setup_environment() -> void:
var env := Environment.new()
# Sky
var sky := Sky.new()
var sky_mat := ProceduralSkyMaterial.new()
sky_mat.sky_top_color = Color(0.4, 0.6, 1.0)
sky_mat.sky_horizon_color = Color(0.7, 0.8, 1.0)
sky_mat.ground_bottom_color = Color(0.2, 0.15, 0.1)
sky.sky_material = sky_mat
env.sky = sky
env.background_mode = Environment.BG_SKY
# Tonemap
env.tonemap_mode = Environment.TONE_MAP_FILMIC
env.tonemap_exposure = 1.0
# Ambient light from sky
env.ambient_light_source = Environment.AMBIENT_SOURCE_SKY
$WorldEnvironment.environment = env
public void SetupEnvironment()
{
var env = new Godot.Environment();
var sky = new Sky();
var skyMat = new ProceduralSkyMaterial();
skyMat.SkyTopColor = new Color(0.4f, 0.6f, 1.0f);
skyMat.SkyHorizonColor = new Color(0.7f, 0.8f, 1.0f);
skyMat.GroundBottomColor = new Color(0.2f, 0.15f, 0.1f);
sky.SkyMaterial = skyMat;
env.Sky = sky;
env.BackgroundMode = Godot.Environment.BGMode.Sky;
env.TonemapMode = Godot.Environment.ToneMapper.Filmic;
env.TonemapExposure = 1.0f;
env.AmbientLightSource = Godot.Environment.AmbientSource.Sky;
GetNode<WorldEnvironment>("WorldEnvironment").Environment = env;
}
| Mode | Character | Best For |
|---|---|---|
| Linear | Clips brights — blown-out look | Debug, deliberately flat look |
| Reinhard | Simple curve, preserves brights | General use |
| Filmic | Film-like contrast | Cinematic games |
| ACES | High contrast with desaturation | Realistic/photographic |
| AgX | Maintains hue as brightness increases | Physically accurate lighting |
Configure these on the Environment resource — no shader code needed:
| Effect | Description | Renderer Support |
|---|---|---|
| Glow | Bloom/glow on bright surfaces | All |
| SSAO | Screen-space ambient occlusion | Forward+ only |
| SSIL | Screen-space indirect lighting | Forward+ only |
| SSR | Screen-space reflections | Forward+ only |
| SDFGI | Real-time GI for large scenes | Forward+ only |
| DOF | Depth of field blur (via CameraAttributes) | All |
| Fog | Depth and height fog | All |
| Adjustments | Brightness, contrast, saturation, color correction | All |
| Auto Exposure | Adaptive exposure (via CameraAttributes) | Forward+, Mobile |
Godot 4.6 changes the order of the post-processing pipeline: Glow now runs before tonemapping (previously it ran after). This is physically more correct — glow should operate on HDR values before they are tone-mapped to LDR. The result is that bright emissive surfaces produce more natural-looking bloom.
Upgrade impact: Projects upgrading from 4.5 to 4.6 may notice a visible change in glow appearance. If your glow looks more intense or differently colored after upgrading, re-tune glow_intensity, glow_bloom, and glow_hdr_threshold on your Environment resource.
Godot 4.6 also adds two new controls to the AgX tonemapper:
| Property | Description |
|---|---|
tonemap_white | White point — the luminance at which the scene clips to pure white |
tonemap_contrast | Contrast of the AgX sigmoid curve |
var env: Environment = $WorldEnvironment.environment
env.tonemap_mode = Environment.TONE_MAP_AGX
# New AgX controls (Godot 4.6+)
env.tonemap_white = 1.0 # default; increase for brighter highlights
env.tonemap_contrast = 1.0 # default; increase for more contrast
var env = GetNode<WorldEnvironment>("WorldEnvironment").Environment;
env.TonemapMode = Godot.Environment.ToneMapper.Agx;
// New AgX controls (Godot 4.6+)
env.TonemapWhite = 1.0f;
env.TonemapContrast = 1.0f;
When to use AgX: AgX maintains hue as brightness increases, which avoids the "neon burn" artefact common with ACES on saturated emissives. The new
whiteandcontrastcontrols let you match a specific look reference.
SSR in Godot 4.6 has been redesigned for higher quality at reduced GPU cost. The WorldEnvironment SSR settings (ssr_enabled, ssr_max_steps, ssr_fade_in, ssr_fade_out, ssr_depth_tolerance) remain unchanged — the improvement is automatic for all existing projects that upgrade to 4.6.
If you previously disabled SSR due to performance concerns, it is worth re-enabling after upgrading to 4.6 and re-profiling.
| Method | Quality | Performance | Dynamic | Renderer | Use For |
|---|---|---|---|---|---|
| None (ambient) | Low | Free | Yes | All | Simple/stylized games |
ReflectionProbe | Medium | Low | Optional | All | Localized reflections |
LightmapGI | High | Free at runtime | No | Forward+ | Static scenes (archviz) |
VoxelGI | High | High | Yes | Forward+ | Small-medium dynamic scenes |
SDFGI | Medium-High | Medium | Yes | Forward+ | Large open-world scenes |
Captures the surrounding environment into a cubemap for reflections on nearby objects.
Room (Node3D)
├── ReflectionProbe ← extents cover the room
├── MeshInstance3D (walls)
└── MeshInstance3D (shiny floor)
@onready var probe: ReflectionProbe = $ReflectionProbe
func _ready() -> void:
probe.size = Vector3(10.0, 4.0, 10.0) # cover the room
probe.update_mode = ReflectionProbe.UPDATE_ONCE # bake once, free at runtime
public override void _Ready()
{
var probe = GetNode<ReflectionProbe>("ReflectionProbe");
probe.Size = new Vector3(10.0f, 4.0f, 10.0f);
probe.UpdateMode = ReflectionProbe.UpdateModeEnum.Once;
}
Best quality, zero runtime cost. Requires UV2 on meshes (auto-generated on import).
LightmapGIData resource — commit it with your projectEnable on the Environment resource — no nodes needed:
var env: Environment = $WorldEnvironment.environment
env.sdfgi_enabled = true
env.sdfgi_cascades = 4
env.sdfgi_use_occlusion = true
var env = GetNode<WorldEnvironment>("WorldEnvironment").Environment;
env.SdfgiEnabled = true;
env.SdfgiCascades = 4;
env.SdfgiUseOcclusion = true;
Godot 4.5 automatically computes specular occlusion from the ambient light probe when baked global illumination (SDFGI, VoxelGI, or LightmapGI) is active. This prevents unrealistically bright specular highlights in areas that receive little or no indirect light — a common artifact when GI and specular are not coordinated.
No API change is required. The improvement is automatic whenever a GI method that bakes an irradiance probe is enabled:
| GI Method | Specular Occlusion |
|---|---|
| None / ambient color only | No specular occlusion |
| ReflectionProbe | No specular occlusion |
| LightmapGI | Automatic (Godot 4.5+) |
| VoxelGI | Automatic (Godot 4.5+) |
| SDFGI | Automatic (Godot 4.5+) |
When to use: If your scene uses LightmapGI, VoxelGI, or SDFGI and has metallic or low-roughness surfaces, upgrade to 4.5 and re-bake to see improved specular quality in occluded areas (under eaves, inside crevices, in corners).
C# uses the same GI API — specular occlusion is renderer-driven and requires no code change.
Bent normal maps encode the mean unoccluded direction from each texel — the average direction toward open sky across the hemisphere. When assigned to the Bent Normal slot on StandardMaterial3D, Godot uses this information to improve indirect lighting directionality and specular occlusion accuracy. The result is more realistic ambient lighting on complex surfaces like cloth, carved stone, or organic shapes.
Inspector setup:
StandardMaterial3D, enable Bent Normal → assign your bent normal texture@onready var mesh: MeshInstance3D = $MeshInstance3D
func _ready() -> void:
var mat := mesh.get_surface_override_material(0) as StandardMaterial3D
if mat == null:
mat = StandardMaterial3D.new()
mat.bent_normal_enabled = true
mat.bent_normal_texture = preload("res://textures/rock_bent_normal.png")
mesh.set_surface_override_material(0, mat)
private MeshInstance3D _mesh;
public override void _Ready()
{
_mesh = GetNode<MeshInstance3D>("MeshInstance3D");
var mat = _mesh.GetSurfaceOverrideMaterial(0) as StandardMaterial3D
?? new StandardMaterial3D();
mat.BentNormalEnabled = true;
mat.BentNormalTexture = GD.Load<Texture2D>("res://textures/rock_bent_normal.png");
_mesh.SetSurfaceOverrideMaterial(0, mat);
}
Note: Bent normal maps have the most visible impact on materials that combine low roughness or high metallic values with baked GI. On fully rough dielectric surfaces the benefit is subtler.
When to use: Use bent normals on hero assets (characters, key props) where you have the budget to bake them. Skip them on background geometry.
Simple fog configured on the Environment resource:
var env: Environment = $WorldEnvironment.environment
# Depth fog — increases with distance from camera
env.fog_enabled = true
env.fog_light_color = Color(0.7, 0.75, 0.8)
env.fog_density = 0.01
# Height fog — thicker below a certain Y level
env.fog_height = 0.0
env.fog_height_density = 0.5
# Sun scattering — tints fog with directional light color
env.fog_sun_scatter = 0.3
var env = GetNode<WorldEnvironment>("WorldEnvironment").Environment;
env.FogEnabled = true;
env.FogLightColor = new Color(0.7f, 0.75f, 0.8f);
env.FogDensity = 0.01f;
env.FogHeight = 0.0f;
env.FogHeightDensity = 0.5f;
env.FogSunScatter = 0.3f;
Realistic fog that interacts with lights and casts volumetric shadows.
Enable on the Environment:
env.volumetric_fog_enabled = true
env.volumetric_fog_density = 0.05
env.volumetric_fog_albedo = Color(0.9, 0.9, 0.9)
env.volumetric_fog_emission = Color(0.0, 0.0, 0.0)
env.volumetric_fog_length = 64.0
env.volumetric_fog_temporal_reprojection_enabled = true
var env = GetNode<WorldEnvironment>("WorldEnvironment").Environment;
env.VolumetricFogEnabled = true;
env.VolumetricFogDensity = 0.05f;
env.VolumetricFogAlbedo = new Color(0.9f, 0.9f, 0.9f);
env.VolumetricFogEmission = new Color(0.0f, 0.0f, 0.0f);
env.VolumetricFogLength = 64.0f;
env.VolumetricFogTemporalReprojectionEnabled = true;
Create fog in specific areas (caves, steam vents, magic effects):
Scene
├── WorldEnvironment (volumetric_fog_enabled = true, density = 0.0)
└── FogVolume
shape = Box
size = Vector3(5, 3, 5)
material = FogMaterial (density = 1.0)
func create_fog_cloud(pos: Vector3) -> void:
var fog := FogVolume.new()
fog.shape = RenderingServer.FOG_VOLUME_SHAPE_ELLIPSOID
fog.size = Vector3(4.0, 2.0, 4.0)
fog.position = pos
var mat := FogMaterial.new()
mat.density = 0.5
mat.albedo = Color(0.8, 0.85, 0.9)
fog.material = mat
add_child(fog)
public void CreateFogCloud(Vector3 pos)
{
var fog = new FogVolume();
fog.Shape = RenderingServer.FogVolumeShape.Ellipsoid;
fog.Size = new Vector3(4.0f, 2.0f, 4.0f);
fog.Position = pos;
var mat = new FogMaterial();
mat.Density = 0.5f;
mat.Albedo = new Color(0.8f, 0.85f, 0.9f);
fog.Material = mat;
AddChild(fog);
}
Set global
volumetric_fog_densityto 0.0 and use FogVolume nodes to place fog only where needed. This gives full control without blanket fog everywhere.
Decals project textures onto surfaces without modifying the underlying mesh. Use for bullet holes, blood splatters, footprints, ground markings.
World
├── MeshInstance3D (floor)
└── Decal
size = Vector3(1, 0.5, 1) ← Y controls projection depth
texture_albedo = footprint.png
texture_normal = footprint_normal.png
func spawn_bullet_hole(hit_pos: Vector3, hit_normal: Vector3) -> void:
var decal := Decal.new()
decal.size = Vector3(0.3, 0.2, 0.3)
decal.texture_albedo = preload("res://textures/bullet_hole.png")
decal.position = hit_pos
# Orient decal to project along the hit surface normal
# Decals project along -Y, so rotate to align -Y with -hit_normal
if hit_normal.abs() != Vector3.UP:
decal.look_at(hit_pos - hit_normal, Vector3.UP)
decal.rotate_object_local(Vector3.RIGHT, PI / 2.0)
# For floor/ceiling hits (normal is UP or DOWN), default orientation works
# Fade and cleanup
decal.distance_fade_enabled = true
decal.distance_fade_begin = 20.0
decal.distance_fade_length = 5.0
get_parent().add_child(decal)
# Remove after 30 seconds
get_tree().create_timer(30.0).timeout.connect(decal.queue_free)
public void SpawnBulletHole(Vector3 hitPos, Vector3 hitNormal)
{
var decal = new Decal();
decal.Size = new Vector3(0.3f, 0.2f, 0.3f);
decal.TextureAlbedo = GD.Load<Texture2D>("res://textures/bullet_hole.png");
decal.Position = hitPos;
if (hitNormal.Abs() != Vector3.Up)
{
decal.LookAt(hitPos - hitNormal, Vector3.Up);
decal.RotateObjectLocal(Vector3.Right, Mathf.Pi / 2.0f);
}
decal.DistanceFadeEnabled = true;
decal.DistanceFadeBegin = 20.0f;
decal.DistanceFadeLength = 5.0f;
GetParent().AddChild(decal);
GetTree().CreateTimer(30.0f).Timeout += decal.QueueFree;
}
| Renderer | Max Decals |
|---|---|
| Forward+ | 512 clustered elements (shared with lights, probes) |
| Mobile | 8 per mesh resource |
| Compatibility | Limited by max renderable elements |
Godot auto-generates LOD levels on import for glTF, Blend, Collada, and FBX files. No manual setup needed.
Control LOD aggressiveness:
# Per-object LOD bias (GeometryInstance3D property)
# > 1.0 = keep high detail longer, < 1.0 = switch to low detail sooner
$MeshInstance3D.lod_bias = 1.5
GetNode<MeshInstance3D>("MeshInstance3D").LodBias = 1.5f;
Global LOD threshold: Project Settings > Rendering > Mesh LOD > LOD Change > Threshold Pixels (default 1.0 — perceptually lossless).
For custom LOD with different meshes at different distances:
# Close-up: full-detail tree (0–30m)
$TreeDetailed.visibility_range_begin = 0.0
$TreeDetailed.visibility_range_end = 30.0
$TreeDetailed.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_SELF
# Far: billboard impostor (25–100m, with crossfade overlap)
$TreeBillboard.visibility_range_begin = 25.0
$TreeBillboard.visibility_range_end = 100.0
$TreeBillboard.visibility_range_fade_mode = GeometryInstance3D.VISIBILITY_RANGE_FADE_SELF
Prevents rendering objects hidden behind walls/large geometry.
Setup:
Only bake occluders from static geometry. Moving OccluderInstance3D nodes at runtime forces expensive BVH rebuilds.
Render thousands of identical meshes (grass, trees, debris) in a single draw call.
func spawn_grass(positions: PackedVector3Array) -> void:
var mm := MultiMesh.new()
mm.transform_format = MultiMesh.TRANSFORM_3D
mm.mesh = preload("res://meshes/grass_blade.tres")
mm.instance_count = positions.size()
for i in positions.size():
var xform := Transform3D()
xform.origin = positions[i]
# Random rotation around Y
xform = xform.rotated(Vector3.UP, randf() * TAU)
# Random scale variation
var s := randf_range(0.8, 1.2)
xform = xform.scaled(Vector3(s, s, s))
mm.set_instance_transform(i, xform)
var mmi := MultiMeshInstance3D.new()
mmi.multimesh = mm
add_child(mmi)
public void SpawnGrass(Vector3[] positions)
{
var mm = new MultiMesh();
mm.TransformFormat = MultiMesh.TransformFormatEnum.Transform3D;
mm.Mesh = GD.Load<Mesh>("res://meshes/grass_blade.tres");
mm.InstanceCount = positions.Length;
for (int i = 0; i < positions.Length; i++)
{
var xform = Transform3D.Identity;
xform.Origin = positions[i];
xform = xform.Rotated(Vector3.Up, (float)GD.RandRange(0, Mathf.Tau));
float s = (float)GD.RandRange(0.8, 1.2);
xform = xform.Scaled(new Vector3(s, s, s));
mm.SetInstanceTransform(i, xform);
}
var mmi = new MultiMeshInstance3D();
mmi.Multimesh = mm;
AddChild(mmi);
}
| Feature | Forward+ | Mobile | Compatibility |
|---|---|---|---|
| SSAO / SSIL / SSR | Yes | No | No |
| Volumetric Fog | Yes | No | No |
| SDFGI | Yes | No | No |
| LightmapGI | Yes | Yes | Yes |
| VoxelGI | Yes | No | No |
| Glow / Bloom | Yes | Yes | Yes |
| Max Omni+Spot per mesh | 512 clustered | 8+8 | 8+8 (adjustable) |
| Target Hardware | Desktop/Console | Mobile/Mid-range | Low-end/WebGL |
Choose in Project Settings > Rendering > Renderer > Rendering Method.
Rule of thumb: Start with Forward+ for desktop. Switch to Mobile for mobile targets. Use Compatibility only for web or very low-end hardware.
| Symptom | Cause | Fix |
|---|---|---|
| 3D scene is completely black | No Camera3D or no lights in scene | Add Camera3D + DirectionalLight3D + WorldEnvironment |
| Objects appear dark despite lighting | No ambient light or sky | Set Environment ambient_light_source to Sky or Color |
| Shadow acne (striped shadows) | Shadow bias too low | Increase shadow_normal_bias (preferred over shadow_bias) |
| Peter-panning (shadows detached) | Shadow bias too high | Lower shadow_bias; use shadow_normal_bias instead |
| Shadows pop in/out | directional_shadow_max_distance too high | Lower to 50–100m; quality improves as range shrinks |
| Material looks flat / no reflections | Missing ReflectionProbe or Sky | Add ReflectionProbe or set Environment reflected light to Sky |
| Decals don't appear | Y extent too small or wrong cull mask | Increase Decal Y size; check cull mask matches target layer |
| Transparency sorting artifacts | Overlapping transparent meshes | Use Alpha Scissor/Hash where possible; avoid layered transparency |
| SDFGI shows light leaking | Thin walls or small geometry | Thicken walls; increase SDFGI cascade count |
| Volumetric fog not visible | Wrong renderer (Mobile/Compatibility) | Switch to Forward+ renderer |
| MultiMesh instances invisible | instance_count set after transforms | Set instance_count before calling set_instance_transform() |
shadow_normal_bias tuned to prevent acnedirectional_shadow_max_distance is set to the minimum needed (50–100m typical)tonemap_white and tonemap_contrast are adjusted when using AgX tonemapper for precise look control (Godot 4.6+)