Textures
A texture maps an image across a surface. Every built-in shape emits the texture coordinates to place it, and both backends sample the same texture from the same coordinates.
NOTE
For the full API, see the 3D API Reference.
Demo
Creating a texture
A texture wraps anything a canvas can draw and a GPU can copy from — an ImageBitmap, an <img>, a <canvas>, an OffscreenCanvas, an ImageData, or a <video>.
import {
createTexture,
loadTexture,
} from '@ripl/3d';
const fromUrl = await loadTexture('/pattern.png');
const fromCanvas = createTexture(offscreenCanvas, { repeat: [4, 4] });Generating a texture into a canvas rather than loading a file is often the simplest route for a procedural pattern, and it ships no asset.
Properties
wrapS/wrapT: how coordinates outside the0–1range are resolved —'clamp','repeat'or'mirror'(default'repeat')magFilter/minFilter:'nearest'or'linear'(default'linear')flipY: flips the image vertically, matching the convention most image assets are authored in (defaultfalse)repeat: how many times the texture repeats across the surface (default[1, 1])offset: how far the texture is shifted across the surface (default[0, 0])version: increments on every change, so a backend knows its upload is staleid: stable identity used to key whatever a backend caches against the texture
Mutating a texture bumps its version, which is what tells a backend to re-upload it. If you draw into the underlying canvas in place, call texture.invalidate() so the change is picked up.
Texture coordinates
Every built-in primitive emits UVs, using the same conventions as three.js so a third-party asset maps as you would expect:
| Shape | Mapping |
|---|---|
Cube | The whole texture on each of the six faces |
Sphere | Spherical: u wraps once around the equator, v runs pole to pole |
Cylinder / Cone | Cylindrical around the side; each cap is a disc inscribed in the texture |
Plane | The whole texture across the quad |
Torus | u runs around the ring, v around the tube |
A Mesh, Parametric or BezierSurface carries whatever UVs its faces declare; the parametric surfaces use their own parameters.
Backend differences
The WebGPU backend samples the texture per pixel, with hardware filtering and wrap modes.
The Canvas backend maps each triangle with an affine transform — the one carrying its UV corners onto its screen corners. That is not perspective-correct: a large face seen at a steep angle shows a seam along the diagonal it was split on. Subdividing the geometry removes it, and for the segment counts a curved primitive already uses it is not visible.
Tiling is a repeating CanvasPattern, so 'repeat' and 'mirror' behave the same on both backends. 'clamp' does not: a GPU sampler extends the edge texel outwards, whereas the Canvas pattern stops. Beyond the first tile the shaded fill shows through untextured rather than smeared, which is usually what you want from a clamped texture but is worth knowing before you compare the two side by side.
TIP
If a textured plane looks creased, raise its subdivision or switch the demo to WebGPU.