Cameras and Primary Rays
A camera in a ray tracer is a ray generator. You give it a pixel sample. It returns an origin and a direction. That is the entire interface. Everything else is convenience around that contract.
A physical pinhole camera forms an inverted image on a sensor behind the aperture. In software you usually place a virtual image plane in front of the eye and cast rays outward. The projective idea is the same. The bookkeeping is simpler.
Each sample on the image plane defines one primary ray into the scene.
Camera parameters
Store at least:
- eye position
origin - look direction
- up hint
- vertical field of view
- aspect ratio
Build an orthonormal basis u, v, w as in the vectors chapter. Whether the camera looks along -w or +w is a convention. Consistency with the rest of the renderer is not optional.
For vertical FOV in radians:
viewport_height = 2 * tan(vfov / 2)
viewport_width = viewport_height * aspect
If the image plane sits one unit in front of the eye along -w:
lower_left = origin
- (viewport_width/2) * u
- (viewport_height/2) * v
- w
horizontal = viewport_width * u
vertical = viewport_height * v
Recompute the basis after camera motion. A stale basis after a cut or teleport produces silent garbage.
From pixel coordinates to a ray
For pixel (i, j) in an image of size nx × ny, with sample offsets (s, t) inside the pixel:
us = (i + s) / nx
vs = (j + t) / ny
p = lower_left + us * horizontal + vs * vertical
ray.origin = origin
ray.direction = normalize(p - origin)
Center sampling uses s = t = 0.5. Antialiasing uses stratified or random offsets in [0, 1). Decide whether j = 0 is the top or bottom row and convert explicitly at every boundary with textures, window buffers, and image loaders. An inverted Y in one place only flips the world and wastes hours.
Thin lens and time
A thin-lens camera samples an origin on a lens disk and aims at the same focus-plane point p. Depth of field is that extension. Do not start there. Stable pinholes first.
If geometry or the camera moves during a shutter interval, sample a time, evaluate poses at that time, then generate the ray. Motion blur is another integral. Primary rays remain the same idea.
Required debug views
Before shading, implement:
- ray direction as color
- hit distance
tas grayscale - normals as color
Orbit the camera. If those three views remain coherent, the camera is probably correct. If the image mirrors under yaw, the basis or handedness is wrong.
Common errors
- Passing degrees to a function that expects radians
- Ignoring aspect ratio and stretching spheres into ellipsoids
- Building
uandvfrom a non-unit look vector - Inverting Y in only one subsystem
- Reusing one ray for every sample in a pixel and calling the result antialiased
When GenerateRay(i, j, s, t) is dull and reliable, the rest of the tracer can fail for more interesting reasons.
Field of view and focal length
Vertical FOV and focal length are related once a film height is chosen. In a unit-plane formulation you often never store focal length explicitly. In a lens model you will. Do not mix a focal-length camera with a pure FOV viewport without converting carefully. Stretch artifacts usually mean the aspect ratio or FOV conversion is wrong, not that spheres are broken.
Viewport mapping checklist
- Choose pixel origin (top-left or bottom-left).
- Map pixel centers or continuous sample positions to normalized plane coordinates.
- Apply aspect ratio only once.
- Transform plane coordinates through
uandv. - Normalize the direction.
Write those five lines as comments above the function. When a screenshot looks stretched, walk the list in order.