Documentation · 3D Guide
3 · The camera
There is no camera object inside a graphics card. What there is, is arithmetic that moves the whole world until the thing you wanted to look at is in front of the origin — and then flattens it. A Camera here is the friendly end of that: where you are, what you are looking at, and through what sort of lens.
A camera is two matrices
Every vertex in the scene goes through the same three steps: its node's world transform puts it in the world, the view matrix rearranges the world so the camera is at the origin looking down −Z, and the projection matrix flattens what is left into the rectangle on your screen.
You never build either by hand — Camera.View and Camera.GetProjection(aspect) are
there if you want to see them — but knowing the shape explains two things that otherwise look arbitrary:
why moving the camera and moving the world are the same operation, and why the near and far planes matter
as much as they do.
The orbit model
This camera is described by four numbers rather than by a position and a rotation, because the common case is not flying — it is looking at something:
Target— the point being looked at.Distance— how far away from it.Yaw— how far around it, in radians.Pitch— how far above or below it. Clamped to ±89°, because straight up is where the view matrix has no answer for which way is right.
Drag, wheel and middle-drag map onto exactly those, which is why orbiting, zooming and panning work with no code from you. Set them yourself and the view moves:
View.Camera.Target = model.WorldPosition;
View.Camera.Distance = 12f;
View.Camera.Yaw = MathF.PI / 4f;
Eye and focus, when a shot is what you want
Yaw and pitch are the wrong vocabulary for "put the camera here, looking at that". So the same camera can be described the way a film camera is:
View.Camera.LookFrom(new Vector3(0f, 40f, 120f), station.WorldPosition); // both at once
View.Camera.LookAt(kestrel.WorldPosition); // aim, do not move
View.Camera.Position = new Vector3(5f, 2f, 8f); // move, keep the target
All three write back into the same four numbers, so nothing gets out of step. And when you need the
camera's own axes — to strafe, to place something in front of it, to fire along its line of sight —
Forward, Right and Up are there in world space:
muzzle.WorldPosition = View.Camera.Position + View.Camera.Forward * 2f;
The lens
FieldOfView is the vertical angle the view covers, in degrees, and 45 is the default. It is
the same choice a photographer makes:
- A small angle (20°, a long lens) flattens depth. Things far apart look stacked, and the scene feels observed from outside.
- A large angle (70°, wide) exaggerates it. Near things loom, and the scene feels like somewhere you are standing.
Changing the field of view is not the same as changing distance, even though both make an object bigger. Dolly in and the background comes with you; widen the lens and the background runs away. Pick one angle per scene and move the camera instead — the demo's film sets its lens once, at the top, and every shot in it is a move.
Near and far, and the mistake everyone makes
The near plane is how close something can be before it is clipped away; the far plane is how distant before it disappears. Everything between them shares the depth buffer, which is how the renderer knows what is in front of what — and its precision is not spread evenly. Almost all of it is spent near the near plane.
The classic mistake is a near plane that is too close. Setting it to 0.001 "to be safe" spends the entire depth buffer on the first centimetre, and everything past it starts flickering as surfaces fight over which is in front — z-fighting. What matters is the ratio far ÷ near, not either number: 1 to 10,000 is comfortable, 1 to 10,000,000 is not.
Left null, both are derived from the scene's size and the camera's distance, which is right often enough that most scenes never touch them. Set them when you know better:
View.Camera.NearPlane = 5f; // nothing in this scene is closer than a fighter
View.Camera.FarPlane = 3_000_000f; // the sky sphere is 2.4 million units out
That is the demo's film: a 250-unit fighter, a 24,576-unit planet and a sun 900,000 units away in one frame, a range of 600,000 to 1, with no logarithmic depth trickery. It works because the near plane was chosen rather than defaulted.
Roll
An orbit camera has no roll: up is world +Y, always, which is what a viewer wants and what a
shot sometimes does not. Roll tips the horizon, and RollToward(up) sets
it from a direction rather than from an angle whose sign you would have to guess:
var wingsUp = Vector3.TransformNormal(Vector3.UnitY, fighter.WorldTransform);
View.Camera.RollToward(wingsUp); // the horizon banks with the wings
Sprites roll with it, because all three renderers take a billboard's axes from the view matrix rather than from the world.
Framing something you have never seen
View.Camera.Fit(model.WorldBounds); // centre it and back off until it fits
View.Camera.Fit(scene.WorldBounds, 1.6f); // with more room around it
Fit takes the bounding sphere and the field of view and works out a distance. It is right for
an object you want to look at and wrong for a building you want to stand inside, and there is no way to
tell those apart from geometry — so if you know better, set Distance afterwards. The control
calls this itself on the first frame unless AutoFit is off.
Run it
The film drives the camera from a shot list — cuts, dollies, a chase locked to a ship's own frame — and prints each caption as it changes.
AVA3D_SCENE=Contact AVA3D_CAPTIONS=1 dotnet run --project samples/Ava3D.Demo.Desktop