Skip to content

The renderer

Everything here comes from package:orblit_filament/orblit_filament.dart. A scene is stated whole, every frame. See stating a scene for why.

Field What it is
camera Where the view is, and its exposure. Required.
objects Individually tracked things, each keyed and compared against last frame. Required.
populations Bulk instanced things: a buffer of transforms, drawn 64 to a call
splats Gaussian splat clouds, from a capture file or from memory. See splats
sprites Layers of flat pictures, drawn after the solid scene. See 2D
lights Directional, point and spot, in photometric units
materials Shared material definitions, referred to by key from an object
videos Video playing onto a surface
sky The procedural sky
fog Distance fog
precipitation Rain and snow
environment Image-based lighting and an HDRI skybox, baked or filtered from an .hdr or .exr while it runs
pipeline Shadows, resolution, sampling, culling
post Bloom, depth of field, grading, occlusion, vignette, tone mapping, anti-aliasing
graph The passes, their targets, and the order they run in
Field Default
key required Stable across frames, unique within the scene
transform required A Matrix4
colour required A Vector3, used when no material is named
mesh null A path or resource name for a .gltf, .glb, .fbx or .obj. Null is the built-in cube.
material null The key of an entry in OrblitScene.materials
castShadows true
receiveShadows true
visible true Hidden is not deleted. The key and the loaded mesh are kept
layer 0 Which render layer it belongs to
animation null One of the file’s own clips, and where it is. See models
variant null Which of the file’s material variants it wears, by position
joints null Skin joints set by hand, after any clip
morphWeights null How far each of the file’s morph targets is dialled in, 0 to 1

A mesh path that cannot be read is drawn as the cube, and the reason comes back through OrblitView.onSceneNotes. What a file holds once it has loaded, meaning its clips, skins, variants, lights and cameras, comes back through OrblitView.onAssetInfo.

Field Default
position, target required Vector3
fieldOfView 50 Degrees. Ignored when orthographic.
orthographic false
viewHeight 10 World units, when orthographic
aperture 16 f-stop
shutterSpeed 1/125 Seconds
sensitivity 100 ISO

The last three are a real exposure. Defaults are “sunny 16”, correct for a scene lit at 100,000 lux.

Field Default
key, kind, intensity required Lux for directional, lumens otherwise
colour Vector3
position Point and spot
direction Directional and spot
falloffRadius 10 Metres. Also what lets a light be culled.
innerConeAngle 0.5 Radians. Spot. Full brightness inside.
outerConeAngle 0.6 Radians. Spot. Zero outside; the gap is the soft edge.
sunAngularRadius 0.263 Degrees. The sun’s apparent size, so how soft its shadows are.
sourceRadius 0.1 Metres. A larger source casts softer shadows.
castShadows true

Filament honours one directional light per scene. A second is reported back rather than quietly ignored.

Field
key Required
transforms Float32List, 16 floats each
colours Float32List, 3 floats each
minimum, maximum The bounds the whole population sits inside, so the lot can be culled at once
mesh Optional; the built-in cube otherwise
revision Bump it when the buffers change, so unchanged frames cost nothing
castShadows Default false, since a hundred thousand shadow casters is rarely what anyone meant
final cost = await OrblitView.gpuMilliseconds(textureId);

Milliseconds, not a frame rate. How often a frame is presented is the display’s business, and a renderer with twice the headroom it needs looks identical there. The value is the median of the last handful rather than the mean, because a mean is dragged about by the one frame in thirty that hits a hitch.

textureId comes from OrblitView.onViewport, which is called once with the view’s own number.

Per-pass costs are available too, which is the point of declaring passes rather than hard-coding them: which passes ran, in what order, and what each cost are the three questions asked of a renderer that is too slow, and a fixed pipeline cannot answer any of them without being instrumented by hand every time somebody asks.