3D Buffer & Commands
Your view function receives a RenderBuffer3D each frame and populates it with drawing commands via the fluent Draw DSL. The renderer dispatches them in order.
What and Why
The buffer is a command list. You don't draw to the screen directly — you describe what to draw, and the renderer handles batching, state management, and submission to the backend. This keeps your view function pure and testable.
When to use
Every 3D game needs this. Your view function writes to RenderBuffer3D. The framework calls it once per frame.
The buffer lifecycle
val view : GameContext -> 'Model -> RenderBuffer3D -> unit
The buffer is pre-cleared each frame. Just add commands:
let view (ctx: GameContext) (model: Model) (buffer: RenderBuffer3D) =
buffer
.beginCamera(camera)
.model(model.PlayerModel, model.PlayerTransform)
.endCamera()
.drop()
.drop() at the end silences the unused-value warning. It does nothing.
Pipeline pattern
Every 3D view follows the same structure:
buffer
.beginCamera(camera) // start camera transform
.setAmbientLight ... // lighting setup
.addDirectionalLight ...
.model ... // geometry
.endCamera() // end camera transform
.drop() // terminal
_IMPORTANT_: Geometry drawn outside
.beginCamera(...)/.endCamera()renders in screen space. This is rarely what you want.
Geometry commands
One member set covers both backends — the buffer takes your backend's own mesh (Mesh / PrimitiveMesh), model, material, and transform types:
Member |
What it draws |
|---|---|
|
Single primitive mesh |
|
A loaded model with authored materials |
|
Model with whole-model material override |
|
Model with per-mesh-part material override |
|
Skeletal animation — bone palette derived for you |
|
Animated model + material override |
|
Explicit bone palette (raylib only) |
|
Many copies of one mesh in one draw call |
|
Camera-facing quad |
|
Batched billboards |
|
Debug line |
_TIP_: Use the instanced/batched variants when drawing many copies of the same thing. One draw call is faster than many.
Camera commands
Member |
Description |
|---|---|
|
Start 3D camera transform |
|
Start camera with explicit viewport/clear/post-process |
|
End camera transform |
Lighting commands
Member |
Description |
|---|---|
|
Set scene ambient light |
|
Add a directional light |
|
Add a point light |
|
Add a spot light |
Shadow commands
Member |
Description |
|---|---|
|
Set shadow map origin for this frame |
|
Enable shadow casting for subsequent geometry |
|
Disable shadow casting for subsequent geometry |
Escape hatches
.drawImmediate(...) flushes the batch, runs raw backend calls (rlgl/raylib, or MonoGame device access via SceneContext), and restores state. On MonoGame, also see .beginEffect(...)/.endEffect() (custom shading scope that inherits scene data). See Overview.
Camera config
Use .beginCameraWith(...) when you need viewport control, clear color, or post-process pass selection:
buffer
.beginCameraWith(Camera3D.render camera |> Camera3D.withClear Color.SkyBlue)
.model(model, transform)
.endCamera()
.drop()
Camera3DConfig fields:
Field |
Type |
Description |
|---|---|---|
|
|
The 3D camera (backend struct; same field shape on both) |
|
|
raylib: normalized screen coords (0-1); MonoGame: pixel coords. |
|
|
|
Lighting setup
Add lights before geometry. Lights affect all subsequent draws:
buffer
.beginCamera(camera)
.setAmbientLight { Color = Color.White; Intensity = 0.3f }
.addDirectionalLight {
Direction = Vector3(-1f, -1f, -1f)
Color = Color.White
Intensity = 0.8f
CastsShadows = true
}
.addPointLight {
Position = Vector3(5f, 3f, 0f)
Color = Color.Yellow
Intensity = 1f
Radius = 10f
CastsShadows = false
ShadowBias = ValueNone
}
.model(model, transform)
.endCamera()
.drop()
_TIP_: You can call
.addPointLight(...)in a loop for dynamic lights.
See also
- Draw DSL — the full fluent draw surface (2D and 3D)
- Overview — Architecture and pipeline setup
- Lighting — Light types and configuration
- Materials — PBR material system
- Instancing — GPU instanced rendering
Mibo