Home Games Shader Sandbox

Game Dev Mechanics: Raycasting — How It Works

Raycasting — DDA Grid Traversal
Click open floor to move player  •  Drag to orbit  •  Scroll to zoom

What Is Raycasting?

Raycasting is the practice of firing an imaginary ray (a mathematical half-line) from a point in a scene and asking: what does this ray hit, and how far away is it? The answer covers a wide range of game systems: rendering pseudo-3D corridors from a 2D map, letting a player click on a 3D object with the mouse, deciding whether an enemy has line-of-sight to the player, and computing real-time shadows in modern path-traced games.

A ray is defined by two vectors: an origin $\vec{o}$ and a normalized direction $\hat{d}$. Every point along the ray is described by a single scalar parameter $t \geq 0$:

$$\vec{P}(t) = \vec{o} + t\hat{d}$$

When we say the ray hits a surface at distance $t$, the world-space hit position is simply $\vec{o} + t\hat{d}$. The entire problem of raycasting reduces to: find the smallest positive $t$ such that $\vec{P}(t)$ lies on some surface.

A Brief History

Raycasting entered mainstream game development in 1992 when id Software shipped Wolfenstein 3D. John Carmack recognized that rendering true 3D geometry was far too slow for the personal computers of the era, but a 2D trick could create a convincing illusion of depth. Instead of drawing full 3D geometry, the engine cast exactly one ray per column of screen pixels across the player's field of view. Each ray traveled through a 2D grid until it struck a wall, and the distance to that wall determined how tall to draw that column's wall slice. Close walls produced tall slices; distant walls produced short ones. The illusion of a 3D corridor was complete.

A small team shipped the game in six months. DOOM (1993) extended the approach with Binary Space Partitioning and sector-based geometry. True 3D engines eventually displaced raycasting for environment rendering, but the technique survived by specializing. Today it appears in virtually every game engine, powering:

  • Mouse picking: clicking on 3D objects in editors and games
  • AI line-of-sight: checking whether an enemy can see the player
  • Hit-scan weapons: instantaneous bullet resolution in shooters
  • Physics sweeps: detecting geometry ahead of a moving rigid body
  • Shadow rays: determining whether a surface point is lit or occluded
  • Terrain intersection: placing objects precisely on arbitrary height fields

Ray–Surface Intersection Math

Different geometry types require different intersection formulas, but the pattern is always the same: substitute the ray equation $\vec{P}(t) = \vec{o} + t\hat{d}$ into the implicit surface equation and solve for $t$.

Ray vs. Sphere

A sphere centered at $\vec{c}$ with radius $r$ satisfies $|\vec{P} - \vec{c}|^2 = r^2$. Substituting the ray and letting $\vec{oc} = \vec{o} - \vec{c}$ yields the quadratic $at^2 + bt + c = 0$ with:

  • $a = \hat{d} \cdot \hat{d} = 1$ (normalized direction)
  • $b = 2(\hat{d} \cdot \vec{oc})$
  • $c = \vec{oc} \cdot \vec{oc} - r^2$

The discriminant $\Delta = b^2 - 4c$ determines the result: $\Delta < 0$ means no intersection, $\Delta = 0$ is a grazing tangent, and $\Delta > 0$ means the ray enters and exits the sphere. We take the smaller positive root as the nearest hit.

Ray vs. Axis-Aligned Box (The Slab Test)

A plane perpendicular to the X axis at $x = k$ is hit at $t = \frac{k - o_x}{d_x}$. A box has three pairs of such planes. For each axis, compute the $t$ values at entry and exit. The ray hits the box if and only if:

$$t_{enter} = \max(t_{x,min},\, t_{y,min},\, t_{z,min}) \leq t_{exit} = \min(t_{x,max},\, t_{y,max},\, t_{z,max})$$

and $t_{exit} \geq 0$. This slab test drives BVH traversal in modern ray tracers. It's cheap to compute and prunes enormous portions of a scene hierarchy.

The DDA Algorithm: Fast Grid Raycasting

For grid-based worlds like tilemaps, Minecraft-style voxels, and top-down RPGs, the Digital Differential Analyzer (DDA) is the standard approach. A naive method might step the ray forward by tiny increments and check each position, but this can skip thin walls and wastes thousands of iterations. DDA instead jumps precisely from one grid-line crossing to the next, visiting every cell boundary the ray crosses and nothing more.

The key is precomputing how far the ray must travel to cross one full grid unit in each axis. For direction $(d_x, d_z)$:

$$\Delta_{dist,x} = \left|\frac{1}{d_x}\right|, \qquad \Delta_{dist,z} = \left|\frac{1}{d_z}\right|$$

We also compute the initial side distances: how far the ray travels to reach the very first grid boundary from the current fractional position within the starting cell. Then the loop always steps in whichever direction is closer:

function castRayDDA(originX, originZ, angle) {
  const dx = Math.cos(angle);
  const dz = Math.sin(angle);

  let mapX = Math.floor(originX);
  let mapZ = Math.floor(originZ);

  const stepX = dx > 0 ? 1 : -1;
  const stepZ = dz > 0 ? 1 : -1;

  // Ray length to cross one full grid unit per axis
  const deltaX = Math.abs(1 / (dx || 1e-10));
  const deltaZ = Math.abs(1 / (dz || 1e-10));

  // Distance to the first grid boundary in each axis
  let sideX = dx > 0
    ? (mapX + 1 - originX) * deltaX
    : (originX - mapX)    * deltaX;
  let sideZ = dz > 0
    ? (mapZ + 1 - originZ) * deltaZ
    : (originZ - mapZ)    * deltaZ;

  while (true) {
    if (sideX < sideZ) {
      sideX += deltaX;
      mapX  += stepX;
    } else {
      sideZ += deltaZ;
      mapZ  += stepZ;
    }
    if (grid[mapZ][mapX] === WALL) {
      // Perpendicular wall distance (avoids fisheye)
      const dist = (mapX - originX + (1 - stepX) / 2) / dx; // X-side hit
      return { distance: dist, cellX: mapX, cellZ: mapZ };
    }
  }
}

The loop takes exactly as many iterations as grid boundaries the ray crosses, at most the sum of the map's width and height. A ray through a 100×100 map touches at most 200 boundaries, versus tens of thousands of incremental steps in a brute-force approach.

Rendering 2.5D Walls (Wolfenstein Style)

With DDA in hand, the Wolfenstein renderer is straightforward. Cast one ray per screen column, sweeping from the left edge to the right edge of the field of view. For a screen of width $W$ and field of view $\theta_{fov}$, the ray angle for column $i$ is:

$$\theta_i = \theta_{player} + \left(\frac{i}{W} - 0.5\right)\theta_{fov}$$

DDA returns a raw Euclidean distance $d$ to the wall. Using this directly causes fisheye distortion at screen edges, because diagonal rays are longer than straight-ahead rays. The fix is to project onto the camera plane, the perpendicular distance from the player's viewing direction to the hit point:

$$d_{\perp} = d \cdot \cos(\theta_i - \theta_{player})$$

Wall slice height is then inversely proportional to perpendicular distance:

$$h_i = \frac{H}{d_{\perp}}$$

function renderFrame(player, ctx, W, H) {
  for (let col = 0; col < W; col++) {
    const rayAngle = player.angle + (col / W - 0.5) * player.fov;
    const { distance } = castRayDDA(player.x, player.z, rayAngle);

    // Fisheye correction
    const perpDist  = distance * Math.cos(rayAngle - player.angle);
    const sliceH    = Math.floor(H / perpDist);
    const top       = Math.floor((H - sliceH) / 2);

    // Distance-based shading
    const bright = Math.max(0, 255 - perpDist * 38) | 0;
    ctx.fillStyle = `rgb(${bright * 0.35 | 0},${bright * 0.35 | 0},${bright})`;
    ctx.fillRect(col, top, 1, sliceH);
  }
}

Modern Applications

Mouse Picking

Clicking on a 3D object requires unprojecting the 2D screen coordinate through the camera's inverse view-projection matrix to form a world-space ray, then intersecting it against scene geometry. Three.js wraps this entirely:

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2(
  (event.clientX / window.innerWidth)  *  2 - 1,
  (event.clientY / window.innerHeight) * -2 + 1
);
raycaster.setFromCamera(mouse, camera);
const hits = raycaster.intersectObjects(scene.children, true);
if (hits.length > 0) {
  const { object, point, distance } = hits[0];
  console.log(`Hit ${object.name} at distance ${distance.toFixed(2)}`);
}

AI Line-of-Sight

Stealth games like Splinter Cell and Metal Gear Solid rely on raycasting to answer the enemy's question: can I see the player? A ray cast from the enemy's eye toward the player returns whether any occluding geometry interrupts the path. The dot product pre-check against the enemy's vision cone angle makes this fast enough to run on dozens of enemies per frame:

bool CanSeePlayer(Enemy enemy, Player player) {
    Vector3 toPlayer = player.position - enemy.eyePosition;
    float   dot      = Vector3.Dot(enemy.forward, toPlayer.normalized);

    // Reject targets outside the FOV cone
    if (dot < Mathf.Cos(enemy.fovAngle * 0.5f)) return false;

    // Any geometry blocking the path?
    if (Physics.Raycast(enemy.eyePosition, toPlayer,
                        out RaycastHit hit, toPlayer.magnitude)) {
        return hit.collider.gameObject == player.gameObject;
    }
    return true;
}

Hit-Scan Weapons

Games distinguish between projectile weapons, where a bullet object travels across multiple frames enabling bullet-drop and target leading, and hit-scan weapons, where the result is computed instantaneously. Hit-scan is pure raycasting: fire a ray from the weapon muzzle in the barrel direction, find the nearest intersection, spawn impact effects at the hit position, apply damage. At game speeds the result is perceptually identical to a traveling bullet.

Shadow Rays in Path Tracing

In hardware ray-traced games like Cyberpunk 2077 and Control, every visible surface point fires one or more shadow rays toward each light source. If any shadow ray strikes geometry before reaching the light, that point is in shadow. NVIDIA RT Cores and AMD Ray Accelerators are dedicated hardware units that accelerate BVH traversal for exactly this workload, making it possible to test millions of shadow rays per frame in real time.

Performance Considerations

Testing a ray against every object in a scene is $O(n)$, which becomes unacceptable for large environments. Several techniques keep raycasting practical at scale:

  • Bounding Volume Hierarchies (BVH): Objects are organized into a tree of nested bounding boxes. A ray traverses the tree, pruning subtrees whose bounding boxes it misses entirely, reducing cost to roughly $O(\log n)$.
  • Early termination: Shadow rays only need to know if anything blocks them, not the nearest hit. They can stop the moment any occluder is found.
  • SIMD parallelism: Modern CPUs test 4–8 rays against a bounding box simultaneously using SIMD intrinsics. GPUs scale this to thousands of parallel rays per warp.
  • Ray budgets and temporal accumulation: Games that cannot fully ray trace each frame allocate a fixed ray budget per frame and accumulate results over time using temporal reprojection, filling gaps with information from previous frames.
  • Hierarchical DDA: For voxel worlds, a cascaded multi-resolution grid skips large empty regions at coarse levels before descending into fine detail, a spatial analogue of mip-mapping.

Mouse-picking, AI vision cones, hit-scan guns, and real-time global illumination all reduce to the same operation: find the smallest positive $t$ where a ray hits something. The parametric ray equation, DDA grid traversal, and slab intersection test cover most of what you'll encounter. Each game system is a specialization of these basics applied to a different context.

Comments

Like this article? Consider supporting us

Your support helps us keep creating free game dev content, tutorials, and tools.

Free

$0 /month

Newsletter and public posts

  • Newsletter access
  • Public posts & updates
  • Community access

Studio Backer

$25 /month

Direct impact on development with your name in the credits

  • Everything in Supporter
  • Your name in game credits
  • Priority feature requests
  • Direct developer access
  • Monthly asset downloads