3D lighting: sources, layers and shadows
Create a lit level with actual shadows, check light coverage and distinguish visual masks from physics.
Run a lit level
Use C# SDK 1.0.0, net10.0 and an Entity3D-rooted .rscene level. Save LightingMaterials.cs from the material guide and LightingLab3D.cs below. Run dotnet build, attach LightingLab3D.cs to the root and run. No external assets are required.
The level shows a white cube, blue sphere and floor. Key is a DirectionalLight3D with shadows enabled. Point and Spot exist but start with Visible=false so they can be compared individually rather than accumulated together.
LightingLab3D.cs
using Reckfell;
public partial class LightingLab3D : Entity3D
{
public MeshInstance3D Floor { get; private set; } = null!;
public MeshInstance3D Cube { get; private set; } = null!;
public MeshInstance3D MetalSphere { get; private set; } = null!;
public DirectionalLight3D Key { get; private set; } = null!;
public OmniLight3D Point { get; private set; } = null!;
public SpotLight3D Spot { get; private set; } = null!;
public Camera3D Camera { get; private set; } = null!;
public WorldEnvironment World { get; private set; } = null!;
public override void _Ready()
{
World = new WorldEnvironment { Environment = LightingMaterials.Environment() };
AddChild(World);
Floor = new MeshInstance3D
{
Mesh = new PlaneMesh { Size = new Vector2(10, 10) },
MaterialOverride = LightingMaterials.Surface(new Color(0.48f, 0.5f, 0.55f))
};
Cube = new MeshInstance3D
{
Mesh = new BoxMesh { Size = new Vector3(1.4f, 2, 1.4f) },
Position = new Vector3(0, 1, 0),
MaterialOverride = LightingMaterials.Surface(Colors.White)
};
MetalSphere = new MeshInstance3D
{
Mesh = new SphereMesh { Radius = 0.8f, Height = 1.6f },
Position = new Vector3(-2.2f, 0.8f, 0),
MaterialOverride = LightingMaterials.Surface(new Color(0.08f, 0.25f, 1), 0.2f, 0.8f)
};
AddChild(Floor); AddChild(Cube); AddChild(MetalSphere);
Key = new DirectionalLight3D
{
RotationDegrees = new Vector3(-50, -25, 0),
LightColor = Colors.White,
LightEnergy = 1,
ShadowEnabled = true
};
Point = new OmniLight3D
{
Position = new Vector3(0, 3, 1),
OmniRange = 8,
LightEnergy = 3,
Visible = false
};
Spot = new SpotLight3D
{
Position = new Vector3(0, 4, 3),
SpotRange = 10,
SpotAngle = 35,
LightEnergy = 4,
Visible = false
};
AddChild(Key); AddChild(Point); AddChild(Spot);
Spot.LookAt(Vector3.Zero);
Camera = new Camera3D { Current = true, Position = new Vector3(5, 4, 7), Fov = 55 };
AddChild(Camera);
Camera.LookAt(new Vector3(0, 0.7f, 0));
}
}Current local 1.0.0 build, Compatibility/OpenGL, 512×384. This example has no collision or character controller.
Choose a source
| Type | Use | Check |
|---|---|---|
| DirectionalLight3D | Sun or general directional light | Rotation and shadow coverage distance; position does not locate a point lamp |
| OmniLight3D | A lamp illuminating around itself | Position, OmniRange and attenuation |
| SpotLight3D | Flashlight or spotlight | Local -Z direction, SpotRange and SpotAngle |
| AreaLight3D | Extended light source | Target renderer support; source configuration excludes its shadows and textured area lighting in Compatibility |
The first three types are verified in the current runtime. AreaLight3D is not exercised here. After adding Spot, LookAt(Vector3.Zero) aims its cone toward the floor instead of relying on an incidental rotation.
LightEnergy is a multiplier, not a universal correct brightness. Material, distance, physical-unit settings and environment affect output. More energy cannot fix a range that stops short or a cone aimed elsewhere.
Isolate light problems
After _Ready, Key, Point, Spot and World.Environment are available. Disable Key.Visible, set AmbientLightEnergy=0 and enable only Point.Visible. Then disable Point.Visible and enable Spot.Visible. Make changes after those objects are created; their properties are not ready before _Ready.
Point lights the geometry at OmniRange=8 but fails to reach it at 0.2. Spot illuminates geometry inside its aimed cone. AmbientLightSource=Color with energy 1 also lights the level without Key, but creates no directional shadow.
LightCullMask matches geometry visual Layers. Changing the light mask from 1 to 2 excludes geometry left on the first visual layer. Physics CollisionLayer/CollisionMask are unrelated.
Obtain a shadow
Check the entire chain:
- The light has ShadowEnabled, and its type/renderer support the required shadows.
- Geometry is within influence and permitted visual layers.
- Casting geometry has CastShadow other than Off.
- The receiver has a material responding to the required lighting/shadows and is in view.
- Shadow coverage distance and map quality suit the level scale.
Native checks remove shadows through ShadowEnabled=false, then separately through CastShadow=Off on the cube/sphere without hiding their meshes. ShadowBias and ShadowNormalBias reduce self-shadowing, but excessive values detach shadows and damage thin details. Tune them after scale and coverage rather than using them as substitutes.
A light shadow, material AO and screen-space SSAO are distinct effects. SSAO adds contact darkening from visible depth; it does not replace a light casting shadows across a level. See the runnable SSAO/glow example.
Distinguish brightness from illumination
Emission brightens the material. Glow adds a halo around bright pixels. Neither alone supplies a direct Light3D for the floor or nearby objects. Add a light for such a lamp; indirect illumination and reflections require separate configuration.
If the editor looks different, compare the project’s actual lights and WorldEnvironment. This example creates them explicitly instead of treating editor preview as game configuration.
Diagnosis
| Symptom | Check |
|---|---|
| Black cube | Material, enabled light/ambient, LightCullMask and Layers |
| Point has no effect | Surface distance and OmniRange |
| Spot misses the target | Rotation/LookAt, SpotAngle and SpotRange |
| Object is lit but has no shadow | ShadowEnabled, CastShadow, source type and coverage |
| Shimmering or detached shadow | Scale, resolution/coverage, then bias |
| Many lights reduce FPS | Profile the target level; reduce overlap, range and dynamic shadows |
Checks cover basic lights and directional shadows in Compatibility. They do not establish all shadow-atlas modes, physical-unit behavior, GI, export or large-level performance.