← Examples · Ship Pack · How this page works

three.js

Ships from the Yodacon Ship Pack by Paul Richeson (ConEx, 1997), GPL-2.0-or-later — github.com/yodacon/yodacon-ship-pack

Yodacon Ship Pack · Tutorial Note 5 · October 2026

Pre-Rotated Sprites in 3-D: How the three.js Example Uses the Pack

Abstract—The three.js example shows a pack ship as a textured plane and as a billboard. One texture of sheet-alpha.png serves all 36 frames by moving its UV window; for the billboard the frame also accounts for the camera's bearing. This note follows the page's source.

Index Terms—three.js, WebGL, texture atlases, billboards, pixel art.

I. Introduction

The three.js example puts a pre-rotated 2-D sprite into a 3-D scene two ways: as a quad lying on a grid (?mode=plane) and as a camera-facing billboard you orbit (?mode=billboard). Both use one texture whose UV window is moved to the wanted frame. three.js 0.160.0 is pinned through an import map (index.html:24).

II. Assets Used

Table I. What the page takes from the pack
AssetRead atUsed for
ship.jsonindex.html:49Frame size, grid, stats, name
sheet-alpha.pngindex.html:106One texture for all 36 frames
frameIndex()index.html:45Heading to frame

III. The Sheet as a Texture

The texture is loaded once and set up for pixel art: nearest-neighbour filtering both ways and no mipmaps (Listing 1). repeat shrinks the visible UV range to one cell; offset moves it. three.js flips images vertically on upload, so v = 0 is the sheet's bottom row. For frame f in a sheet of C columns and R rows:

u₀ = (f mod C) / C,   v₀ = 1 − (⌊f / C⌋ + 1) / R(1)

Listing 1. Texture and frame window, index.html:106–114
106const texture = await new THREE.TextureLoader().loadAsync(ship.urls.sheet);107texture.colorSpace = THREE.SRGBColorSpace;108texture.magFilter = texture.minFilter = THREE.NearestFilter;  // pixel art: no blur109texture.generateMipmaps = false;110texture.repeat.set(1 / cols, 1 / rows);111function setFrame(i) {112  texture.offset.set((i % cols) / cols, 1 - (Math.floor(i / cols) + 1) / rows);113}114const material = { map: texture, transparent: true, alphaTest: 0.5 }; // alphaTest: crisp edges, no sorting trouble

alphaTest: 0.5 discards transparent texels outright, which keeps edges crisp and avoids transparency sorting entirely.

IV. Mode 1: A Plane on the Grid

A PlaneGeometry sized to the frame in world units is laid flat, its texture's top pointing to −Z, “north” (Listing 2). Each tick picks the frame for the heading and thrusts along that frame's direction, (sin a, 0, −cos a) in world space, so motion matches the picture (index.html:140).

Listing 2. The plane, index.html:128–147
128const plane = new THREE.Mesh(new THREE.PlaneGeometry(fw * SIZE, fh * SIZE), new THREE.MeshBasicMaterial(material));129plane.rotation.x = -Math.PI / 2;130plane.position.y = 1;⋮139  const frame = frameIndex(heading);⋮147  setFrame(frame);

V. Mode 2: A Billboard

A THREE.Sprite always faces the camera, so it must show the ship as seen from the camera. If the ship's true heading is θ and the camera's bearing around it is φ, the frame to show is

f = frameIndex(θ + φ)(2)

which the loop computes from the camera position each frame (Listing 3). Orbit the camera with the ship still and the sprite turns; the green arrow shows that the ship itself has not.

Listing 3. Frame for the camera, index.html:178–180
178const phi = THREE.MathUtils.radToDeg(Math.atan2(camera.position.x - sprite.position.x, camera.position.z - sprite.position.z));179const seen = heading + phi;180setFrame(frameIndex(seen));

VI. Using It in Your Project

  1. Load the sheet with TextureLoader; set NearestFilter, no mipmaps, sRGB.
  2. Set repeat to (1/C, 1/R) and move offset with (1).
  3. For many ships, clone the texture per ship (texture.clone() shares the image) so each has its own offset.
  4. For billboards, add the camera bearing as in (2).

The Fleet Flyby and Hangar projects scale this up to every ship and to a turntable [2].

VII. Conclusion

In 3-D the pack's convention survives intact: one texture, a UV window chosen by Equation (1), and for billboards one extra angle. The sprites keep their 1997 pixels while the scene around them is fully 3-D.

References

  1. Yodacon Ship Pack, “From sprite sheet to moving ship,” Tutorial Note 1, examples/, 2026. examples/#tutorial
  2. Yodacon Ship Pack, “Sample projects,” projects/.
  3. three.js authors, three.js r160 (0.160.0), 2023. [Online]. Available: threejs.org/docs