3D Lighting
The built-in forward pipelines support four light types with Cook-Torrance PBR shading. Lights are added per-frame via fluent members inside your view function. (On raylib the pipeline is ForwardPbrPipeline; on MonoGame it is ForwardPipeline.)
What and Why
- Ambient light — Base illumination for the entire scene. One per frame.
- Directional light — Parallel rays (sun, moon). Supports shadow casting.
- Point light — Radial light with position, radius, and falloff. Supports shadow casting.
- Spot light — Cone-shaped light with inner/outer cutoff angles.
All lights are struct types with builder functions. The pipeline uploads them as shader uniforms each frame.
Quick start
let view (ctx: GameContext) (model: Model) (buffer: RenderBuffer3D) =
buffer
.beginCamera(camera)
// Ambient
.setAmbientLight(AmbientLight3D.create (Color(30, 30, 30, 255)))
// Directional (sun)
.addDirectionalLight(
DirectionalLight3D.create (Vector3(0.3f, -0.7f, -0.5f))
|> DirectionalLight3D.withIntensity 0.8f
)
// Point light (torch)
.addPointLight(
PointLight3D.create (torchPos, 10f)
|> PointLight3D.withColor Color.Orange
|> PointLight3D.withIntensity 1.5f
|> PointLight3D.withCastsShadows true
)
// Spot light (flashlight)
.addSpotLight(
SpotLight3D.create (camPos, camDir, 20f)
|> SpotLight3D.withIntensity 2.0f
)
.model(model.PlayerModel, model.PlayerTransform)
.endCamera()
.drop()
Light types
AmbientLight3D
Uniform base illumination applied to all surfaces.
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
Base color |
|
|
|
Brightness multiplier |
AmbientLight3D.create (Color(30, 30, 30, 255))
|> AmbientLight3D.withIntensity 0.5f
DirectionalLight3D
Parallel light rays. Use for sun or moon.
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
Direction rays travel (should be normalized) |
|
|
|
Light color |
|
|
|
Brightness multiplier |
|
|
|
Whether to cast shadows |
DirectionalLight3D.create (Vector3(0.3f, -0.7f, -0.5f))
|> DirectionalLight3D.withColor Color.White
|> DirectionalLight3D.withIntensity 0.8f
|> DirectionalLight3D.withCastsShadows true
PointLight3D
Radial light that emits in all directions from a position.
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
World-space position |
|
|
|
Light color |
|
|
|
Brightness multiplier |
|
|
— |
Maximum distance of influence |
|
|
|
Decay exponent (1 = linear, 2 = quadratic) |
|
|
|
Whether to cast shadows |
|
|
|
Per-light bias override (uses pipeline default when |
PointLight3D.create (Vector3(10f, 5f, 0f), 15f)
|> PointLight3D.withColor Color.Orange
|> PointLight3D.withIntensity 1.5f
|> PointLight3D.withFalloff 2.0f
|> PointLight3D.withCastsShadows true
|> PointLight3D.withShadowBias 0.005f
_TIP_: Set
CastsShadows = truesparingly. Each shadow-casting point light renders a cubemap shadow pass. Two or three is a good target for performance.
SpotLight3D
Cone-shaped light with inner and outer cutoff angles.
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
— |
World-space position |
|
|
— |
Direction the cone points (should be normalized) |
|
|
|
Light color |
|
|
|
Brightness multiplier |
|
|
— |
Maximum distance of influence |
|
|
|
Cosine of inner cone half-angle (full brightness) |
|
|
|
Cosine of outer cone half-angle (fade to zero) |
|
|
|
Whether to cast shadows |
|
|
|
Per-light bias override |
SpotLight3D.create (camPos, camDir, 25f)
|> SpotLight3D.withIntensity 2.0f
|> SpotLight3D.withCutoff 0.9f 0.95f // tight beam
|> SpotLight3D.withCastsShadows true
Light limits
Both built-in pipelines default to the same light budgets:
Type |
Default max |
|---|---|
Ambient |
1 |
Directional |
1 |
Point lights |
8 |
Spot lights |
4 |
How you change them differs by backend:
raylib — the budgets are runtime-configurable via the ForwardPbrPipeline constructor:
let pipeline = ForwardPbrPipeline(
maxPointLights = 16,
maxSpotLights = 8
)
MonoGame — the budgets are baked into the compiled PBR shader (MAX_POINT_LIGHTS /
MAX_SPOT_LIGHTS constants in ForwardPbr.fx). The ForwardPipeline constructor takes no
light-count argument; to change them you recompile the .fx with different #defines.
Exceeding the limit silently drops extra lights on both backends.
Shadow configuration
Global bias
Shadow bias values control the tradeoff between shadow acne (too low) and peter-panning (too high). Set via ShadowBiasConfig:
// raylib:
let pipeline = ForwardPbrPipeline(
shadowBiasConfig = {
DirectionalBias = 0.0005f // raylib default
PointBias = 0.01f
SpotBias = 0.001f
SlopeScaleBias = 0.0005f
}
)
// MonoGame:
let pipeline = ForwardPipeline(
shadowBias = {
DirectionalBias = 0.002f // MonoGame default (higher; native depth bias)
PointBias = 0.01f
SpotBias = 0.001f
SlopeScaleBias = 0.0005f
}
)
_NOTE_: On MonoGame,
SlopeScaleBiasmaps to the nativeRasterizerState.SlopeScaleDepthBias(hardware polygon offset) and the per-type biases map toRasterizerState.DepthBias— MonoGame can't use the GLSLdFdx/dFdyslope math the raylib shader relies on. On raylib the slope-scale bias is applied in the GLSL depth shader. The observable effect (tuning acne vs peter-panning) is the same.
Per-light bias
Point and spot lights can override the global bias:
PointLight3D.create (pos, radius)
|> PointLight3D.withCastsShadows true
|> PointLight3D.withShadowBias 0.005f // per-light override
Atlas configuration
The shadow atlas controls resolution and caster capacity. MaxCasters must be a perfect square
(4, 9, 16, 25, 36) — it lays out the atlas as a √N × √N grid.
// raylib:
let pipeline = ForwardPbrPipeline(
shadowAtlasConfig = {
ShadowAtlasConfig.defaults with
Resolution = 4096
MaxCasters = 9
DirectionalLightSize = ValueSome 30.f
}
)
// MonoGame (note the extra DirectionalOriginY field):
let pipeline = ForwardPipeline(
shadowAtlas = {
ShadowAtlasConfig.defaults with
Resolution = 4096
MaxCasters = 9
DirectionalOriginY = 0.0f // MonoGame-only: lock shadow frustum Y
}
)
Field |
Default |
raylib |
MonoGame |
Description |
|---|---|---|---|---|
|
2048 |
✓ |
✓ |
Atlas texture resolution (square) |
|
16 |
✓ |
✓ |
Maximum shadow casters (perfect square) |
|
|
✓ |
✓ |
Where directional shadows are centered ( |
|
auto |
✓ |
✓ |
Distance to place the directional light camera behind the origin |
|
auto |
✓ |
✓ |
Ortho projection half-size for directional shadows |
|
0.0 |
— |
✓ |
Lock the shadow frustum's vertical origin (prevents vertical sliding) |
|
2.0 |
✓ |
✓ |
Snap shadow origin to a grid to reduce shimmer |
_NOTE — shadow technique differs._ MonoGame cannot create a sampleable depth-only render target, so it writes shadow depth into an R32F color attachment (
DepthShadow.fx) and samples it with a comparison sampler for hardware PCF. raylib samples depth textures directly via GLSL derivatives. The user-facing config and behavior (shadow quality, bias tuning) are equivalent; only the internal path differs.
See also
- Overview — Architecture and pipeline setup
- Buffer & Commands — The buffer pipeline pattern
- Materials — PBR material system
Mibo