RT Graphics Programming
Lesson 03 of 28

Part 1 · Ray tracing by hand

3. The Pinhole Camera

Give every pixel a direction and turn a rectangular grid into a view of three-dimensional space.

Graphics glossary 24 terms mentioned in this lesson

Select any term for a clear definition. The return button brings you back to the exact term link you used.

A camera is sometimes treated as a box of familiar settings: position, target and field of view. Enter the numbers, call a library function and a view appears. That convenience is useful after the camera model is understood. Here it would hide the main construction. A ray tracer needs one direction for every image sample, and the camera is the rule that produces it.

We shall use a pinhole camera. All primary rays begin at one origin and pass through chosen positions on a conceptual viewport. There is no lens area and therefore no depth of field. The model is limited, but it isolates the relationship we need: a rectangular sample position becomes a direction in the camera's three-dimensional coordinate frame.

What you should be able to account for

  • Construct perpendicular forward, right and up directions for a camera.
  • Map a pixel-centre sample to a point on the viewport and normalise the resulting primary direction.
  • Explain how field of view and aspect ratio alter the cone of generated directions.

Construct the camera basis

Choose an origin C, a target point T and a reference direction called worldUp. The camera forward direction is the normalised displacement from origin to target. The right direction is perpendicular to forward and world up. A corrected up direction is then perpendicular to right and forward.

const forward = normalise(subtract(target, origin));
const right = normalise(cross(forward, worldUp));
const up = cross(right, forward);

Why construct up again instead of using worldUp directly? The reference vector expresses which way we would prefer the camera to roll. Unless the camera looks exactly level, it need not be perpendicular to forward. The corrected vector completes an orthonormal basis. If the three directions are unit length and pairwise perpendicular, each viewport displacement can be assembled without changing scale unpredictably.

Check the basis

For a valid camera frame, dot(forward, right), dot(forward, up) and dot(right, up) should all be close to zero. Each vector length should be close to one. Finite-precision results may not be mathematically exact, so the check uses a small tolerance rather than equality to zero.

Turn field of view into viewport size

The vertical field of view is an angle. Place the viewport one arbitrary unit in front of the camera. Half of its height and that unit distance form a right triangle, so the half-height is the tangent of half the angle. The horizontal half-width is then scaled by the raster aspect ratio.

halfHeight = tan(verticalFov / 2)

halfWidth = aspectRatio × halfHeight

A wider field of view increases these extents. The same number of pixels then spans a wider cone of directions, so objects appear smaller. This is not the camera moving backwards. Both changes may place more of the scene in the image, but they do not produce the same perspective relationships.

Map one pixel centre

Let x and y identify a raster pixel. First map the centre to normalised image coordinates:

const u = (x + 0.5) / width;
const v = (y + 0.5) / height;

u increases from zero to one across the image. Browser v increases downwards, while camera up should increase upwards. We therefore use 1 - 2v for the vertical camera displacement and 2u - 1 for the horizontal displacement.

const horizontal = scale(right, (2 * u - 1) * halfWidth);
const vertical = scale(up, (1 - 2 * v) * halfHeight);
const direction = normalise(add(forward, add(horizontal, vertical)));

const ray = { origin, direction };

The centre sample of the centre pixel points close to forward. A top-right sample adds positive right and positive up. Every direction is normalised after those displacements are combined. We now have the ray equation from Lesson 2 and a repeatable way to supply its values for the whole raster.

Camera construction laboratory

Turn One Pixel into One Primary Ray

Choose one pixel centre, place it on the camera's image plane and calculate the unit direction from the fixed camera origin through that sample.

Change this, then watch this: Change the pixel or the field of view. The selected sample and its primary direction update together.

1. Choose an image sampleA 16 by 10 image plane with one selected pixel centre.Ready
A 16 by 10 image plane with one selected pixel centre.
2. Construct its rayA pinhole camera, image plane and ray through the selected sample.Calculation
A pinhole camera, image plane and ray through the selected sample.
Selected pixel
(12, 2)
Sample centre
(12.5, 2.5)
Image-plane point
Calculating
Unit direction D
Calculating

Constructing the primary direction

What this establishes:

Inspect one camera ray before constructing thousands

There is still no geometry to see, so a coloured scene would have no defined meaning. Instead, the laboratory keeps the image plane visible. Select one pixel, use its centre as the sample position and follow that one position through the camera calculation. That field-of-view value determines both the size of the image plane and the resulting direction.

Two diagrams describe one construction at different scales. A grid identifies which sample was chosen. The camera view shows the fixed origin, the image plane and the route through that sample. A numerical record then states the image-plane position and normalised direction. This proves that the selected pixel obtains a finite primary direction. It does not prove that the direction meets an object. Lesson 4 supplies that geometric question.

Follow the centre of a 4 by 2 image

Pixel (1, 0) has u = (1 + 0.5) / 4 = 0.375 and v = (0 + 0.5) / 2 = 0.25. Its horizontal factor is 2u - 1 = -0.25, so it lies to the camera's left. Its vertical factor is 1 - 2v = 0.5, so it lies above the camera centre. The final direction combines forward with those scaled basis vectors and is then normalised.

Recognise the invalid camera

If the origin and target are the same point, their displacement is zero and there is no forward direction. If forward is parallel or anti-parallel to worldUp, their cross product is zero and there is no unique right direction. These are not merely numerical edge cases. The supplied values do not define the requested frame.

A careful camera constructor validates both conditions. Quietly inserting a default direction may produce an image, but that image no longer represents the stated camera. Failure near the cause is more useful than a field of NaN values discovered after thousands of pixels have been processed.

Predict the effect

The camera origin and target remain fixed. The vertical field of view changes from 40 degrees to 90 degrees. Do the centre direction and corner directions change in the same way?

Reveal the reasoning

The exact centre sample continues to point along forward because its horizontal and vertical offsets are zero. Corner samples use both viewport extents, which increase with the tangent of half the field of view. Their directions move further away from forward before normalisation. The visible cone widens around an unchanged centre direction.

Audit five primary rays

For a small raster, print the normalised directions for the centre and four corner samples. Check their lengths and expected signs in the camera basis. Then swap the cross-product operands used for right. Do not merely report that the image is mirrored. Explain why reversing the perpendicular direction reverses the horizontal mapping.

Keep this model for Vulkan

A Vulkan shader can obtain an invocation coordinate and image size without a JavaScript loop. It will still need the same mapping from integer coordinate to sample position, the same aspect ratio and field-of-view calculation, and a camera basis supplied or constructed from scene data. The execution mechanism changes. The primary-ray contract does not.

Every sample can now ask a directed question from the camera. Lesson 4 gives that question a sphere equation and accepts only the pixels whose rays actually meet the surface.