Rendering and cameras (3D)¶
Layer: 3D (goo3d)
The renderer is a backend behind a contract, so a goo3d game does not name
one directly.
Rendering is the step that turns "what exists" into "what you see". Your game never calls a draw function: it declares that an entity is visible and what it looks like, and the renderer walks those declarations once a frame.
The reason it works that way: a game that draws things itself has to be told when to draw, and the right answer changes depending on what else is running. Declaring what a thing looks like leaves that decision where it belongs.
Making something visible¶
class Crate extends EntityStruct
with Transform3D, WorldTransform3D, Renderable3D {
late final MeshAsset mesh;
late final MaterialAsset material;
@override
void describeAssets(AssetDescriptor descriptor) {
super.describeAssets(descriptor);
mesh = descriptor.has(Meshes.crate);
material = descriptor.has(Materials.woodCrate);
}
@override
void describeMesh(MeshDescriptor descriptor) {
super.describeMesh(descriptor);
descriptor.has(mesh: mesh, material: material);
}
}
Meshes and Materials are generated: good generate scans the assets your
pubspec declares and writes one entry per file. That is why a renamed asset is a
compile error instead of a blank object at run time.
The camera¶
A camera is an entity, not a global. It has a transform like anything else, so parenting it to something is how a follow camera works — there is no separate "camera controller" concept to learn.
That is the whole prefab. Camera3D declares the lens — a 60 degree field of
view, clipping from 0.1 out to 1000 — so a camera that wants those says nothing.
fieldOfView is in degrees, vertical. near and far bound what is drawn:
anything closer than near or further than far is skipped.
A camera that wants a different lens moves the column defaults in its own
describeStruct, and only the ones that differ:
class LongLens extends EntityStruct
with Transform3D, WorldTransform3D, Camera3D {
@override
void describeStruct(DataDescriptor data) {
super.describeStruct(data);
fieldOfView.defaultValue = 20;
far.defaultValue = 5000;
}
}
Those are row defaults for this archetype, not writes to any entity, so a camera whose lens never changes needs nothing at mount time.
One camera per view
More than one enabled camera on the same view trips a debug assert. In a release build the first one found is used, so a second camera is a development mistake rather than a crash in someone's hands.
What the renderer does each frame¶
- Walk the entities that are visible and inside the camera's view.
- Sort them — by material first, so the backend can draw everything sharing a material together, then by depth.
- Write the result into a shared buffer the Flutter isolate reads.
Step two is worth understanding, because it explains a cost you will hit. Each distinct material is its own draw call, and draw calls are the expensive unit — so a hundred crates sharing one material cost roughly one, while a hundred crates with a hundred materials cost a hundred. That is why sorting groups by material first and by distance second.
Backends¶
goo3d defines the contract; a backend implements it. That is the same shape as
good_net, where the transport is a contract and
a package like good_net_p2p implements it.
Two exist:
| Backend | Use it when |
|---|---|
| Flutter GPU | You want no native build step and are happy on the platforms Impeller supports |
| Native (FFI) | You need full control of the pipeline, and can accept a per-platform native build |
A game picks one by depending on it and declaring it, and nothing else in the game changes:
No web target
Neither backend runs on the web, and neither does the kernel — it needs
dart:ffi and isolates. See
the implementation status page.
Lighting¶
Lights are entities too, declared the same way:
class Sun extends EntityStruct with Transform3D, WorldTransform3D, Light3D {
@override
void describeLight(LightDescriptor descriptor) {
super.describeLight(descriptor);
descriptor.directional(intensity: 3.0, colour: 0xFFFFF4E5);
}
}
Directional, point and spot are the three kinds. A light has a transform, so a torch is a light parented to the hand that carries it.