Skip to content

Flatland

Flatland is the high-level entry point for three-flatland. It wraps a SpriteGroup with an orthographic camera, global uniforms, post-processing pipeline, and render target support into a single object.

When to Use Flatland vs SpriteGroup

FlatlandSpriteGroup
Use caseSelf-contained 2D pipelineEmbed sprites into your own scene
CameraInternal orthographic cameraYou provide the camera
Post-processingBuilt-in pass effect pipelineNot included
Global uniformsAutomatic time, viewport, tintNot included
Render callflatland.render(...)renderer.render(...)

Use Flatland when you want a complete 2D rendering setup. Use SpriteGroup when you need to mix sprites into an existing 3D scene with your own camera and render loop.

Basic Setup

import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js'
import { Flatland, Sprite2D, TextureLoader } from 'three-flatland'
const renderer = new WebGPURenderer()
await renderer.init()
document.body.appendChild(renderer.domElement)
const flatland = new Flatland({ viewSize: 400, clearColor: 0x1a1a2e })
const texture = await new TextureLoader().loadAsync('/sprites/hero.png')
flatland.add(new Sprite2D({ texture }))
function animate() {
flatland.resize(window.innerWidth, window.innerHeight)
flatland.render(renderer)
requestAnimationFrame(animate)
}
animate()

Constructor Options

const flatland = new Flatland({
viewSize: 400, // Orthographic view height in world units
clearColor: 0x1a1a2e, // Background color
clearAlpha: 1, // Background alpha (< 1 for transparent)
autoClear: true, // Clear before each render
postProcessing: false, // Enable post-processing pipeline
aspect: 1, // Initial aspect ratio (use resize() to update)
camera: null, // Custom OrthographicCamera (null = internal)
renderTarget: null, // WebGLRenderTarget (null = render to viewport)
})

See the FlatlandOptions API reference for full type details.

Adding Sprites

flatland.add() routes objects automatically:

  • Sprite2D instances go to the internal SpriteGroup for batched rendering
  • Other Object3D instances are added directly to the internal scene
// Sprite2D → batched via SpriteGroup
const sprite = new Sprite2D({ texture, anchor: [0.5, 0.5] })
flatland.add(sprite)
// Other Three.js objects → added to internal scene
const mesh = new Mesh(geometry, material)
flatland.add(mesh)

Global uniforms are automatically wired to each sprite’s material on add().

Render Loop

Each frame, call resize() to sync the aspect ratio, then render() to draw:

function animate() {
flatland.resize(canvas.width, canvas.height)
flatland.render(renderer)
requestAnimationFrame(animate)
}

resize() updates the internal camera frustum and render target dimensions. render() syncs global uniforms (time, viewport), updates sprite batches, and draws everything.

Global Uniforms

Every Flatland instance exposes a globals object with shared uniforms. These update once per frame and are available to all sprite materials via TSL nodes.

UniformDescription
timeElapsed seconds (undefined = auto)
globalTintColor tint for all sprites
viewportSizeViewport size in pixels
pixelRatioDevice pixel ratio
windWind direction and strength
fogColorFog color
fogRangeFog near/far range

Auto vs Manual Time

By default, time is undefined and Flatland accumulates elapsed time automatically. Set it to a number for manual control:

// Auto mode (default) — time accumulates each frame
flatland.globals.time = undefined
// Manual mode — set exact value
flatland.globals.time = performance.now() / 1000

Using Uniforms in TSL

Access the TSL node directly for custom material effects:

import { Sprite2DMaterial } from 'three-flatland'
const material = new Sprite2DMaterial({
colorTransform: (ctx) => {
// Use the global tint node in a custom effect
const tinted = ctx.color.rgb.mul(flatland.globals.globalTintNode)
return tinted.toVec4(ctx.color.a)
}
})

Each global has a corresponding TSL node: timeNode, globalTintNode, viewportSizeNode, pixelRatioNode, windNode, fogColorNode, fogRangeNode.

Statistics

Monitor rendering performance with flatland.stats:

const stats = flatland.stats
console.log(`Sprites: ${stats.spriteCount}`)
console.log(`Batches: ${stats.batchCount}`)
console.log(`Draw calls: ${stats.drawCalls}`)
console.log(`Visible: ${stats.visibleSprites}`)

Draw calls are derived from renderer.info — they reflect actual GPU work, not estimates.

Post-Processing

Add full-screen pass effects with addPass():

import { Flatland, createPassEffect } from 'three-flatland'
import { posterize } from '@three-flatland/nodes'
const PosterizePass = createPassEffect({
name: 'posterize',
schema: { bands: 6 },
pass: ({ uniforms }) => (input, uv) => posterize(input, uniforms.bands),
})
const flatland = new Flatland({ viewSize: 400 })
const post = new PosterizePass()
flatland.addPass(post)
// Zero-cost parameter updates
post.bands = 10

The render pipeline auto-initializes on the first addPass() call. Passes chain in insertion order — each receives the previous pass’s output.

MethodDescription
addPass(pass, order?)Add a pass effect
removePass(pass)Remove a specific pass
clearPasses()Remove all passes
passesRead-only array of current passes

See the Pass Effects guide for creating custom effects and the full node library.

Render to Texture

Render Flatland output to a texture for use in 3D scenes:

import { WebGLRenderTarget } from 'three'
const target = new WebGLRenderTarget(512, 512)
const flatland = new Flatland({ renderTarget: target })
// Use the texture on a 3D mesh
mesh.material.map = flatland.texture
// Each frame: render 2D first, then 3D
flatland.render(renderer)
renderer.render(scene3D, camera3D)

Disposal

Clean up all resources when done:

flatland.dispose()

This destroys the ECS world, sprite batches, render pipeline, and all pass effect entities.

Next Steps