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:

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:

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 viewing frustum: field of view, near plane and far plane The camera sits at the left. Two lines spread out from it at the field-of-view angle. A near plane crosses them close to the camera and a far plane crosses them at the right; everything between the two is drawn, and everything outside is clipped away. The depth buffer's precision is spent between those two planes. camera near far everything between the planes is drawn — and the depth buffer's precision lives here FieldOfView

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

← 2 · Moving things about 4 · Turning things →