BatchedText

@three-blocks/coreWebGPU
new BatchedText(maxTextCount : number, maxGlyphCount : number, material : THREE.Material)
Extends
Text

WebGL Support has been temporarily disabled.

High-performance batched rendering for multiple Text instances with GPU-accelerated culling, sorting, and instancing. Renders thousands of text labels in a single draw call with automatic transparency sorting for correct z-ordering.

Why BatchedText is Fast

Single Draw Call Architecture

  • Renders all text instances in ONE draw call vs N draw calls
  • Eliminates CPU overhead from state changes and draw call submission
  • Uses GPU instancing with StorageBufferAttribute for transforms and styles
  • Shared SDF atlas texture across all text members

GPU Compute Pipeline (WebGPU)

  • Frustum culling runs entirely on GPU via compute shaders
  • LOD-based glyph sampling reduces overdraw for distant text
  • Automatic back-to-front sorting for transparent SDF text in the GPU via bitonic sorting in compute shaders
  • Indirect draw commands updated on GPU without CPU readback
  • Dynamic workgroup dispatch via computeIndirect for efficient scaling

Transparency Z-Ordering

  • Sorted glyph packing ensures correct depth ordering for SDF anti-aliasing
  • Per-member prefix sum computes glyph offsets in sorted order
  • Glyphs scattered to packed buffer preserving back-to-front order
  • Eliminates z-fighting between overlapping transparent text

Dynamic Updates with Pre-allocation

  • Set maxTextCount and maxGlyphCount to pre-allocate buffers for dynamic addText()
  • Without pre-allocation, buffers are sized to initial content (no dynamic growth)
  • computeIndirect dynamically adjusts GPU workgroup counts
  • Uniform-based count tracking avoids shader recompilation within capacity

Features

  • Batched Rendering: Single draw call for unlimited text instances
  • GPU Frustum Culling: Skip rendering off-screen text (WebGPU only)
  • LOD Sampling: Reduce glyph count for distant text
  • Billboarding: Camera-facing text in world space
  • Per-Instance Styling: Color, outline, opacity per text member
  • Dynamic Updates: Add/remove/modify text at runtime via sync()
  • Static Mode: batch.staticMode = true. Lock layout after initial pack for maximum performance
  • Transparency Sorting: Correct z-order for SDF anti-aliased edges
  • TSL Hooks: Custom positionNode, opacityNode, colorNode still work

Usage

Performance Tips

  • Use static: true if text content never changes after initial setup
  • Enable perObjectFrustumCulled for large scenes with many off-screen labels
  • Use _cullOptions.lodMode to enable/disable LOD sampling
  • Range LOD uses _cullOptions.lodNear + _cullOptions.lodFar
  • Exp LOD uses _cullOptions.lodNear + _cullOptions.lodDensity (exp density). lodNear shifts the start of the exp curve.
  • Call sync() only when text content changes, not every frame

LOD API

// Range mode (distance-based fade)
batch._cullOptions.lodMode = LOD_MODE_RANGE;
batch._cullOptions.lodNear = 50;    // start distance
batch._cullOptions.lodFar = 400;    // end distance

// Exp mode (density-based fade)
batch._cullOptions.lodMode = LOD_MODE_EXP;
batch._cullOptions.lodNear = 50;        // start distance
batch._cullOptions.lodDensity = 0.00025; // exp density

// Disable LOD
batch._cullOptions.lodMode = LOD_MODE_DISABLED;

// Optional: create the culler immediately (before first render)
batch.initCuller( renderer );
batch.culler.lodMode.value = LOD_MODE_EXP;

WebGL Limitations

  • GPU frustum culling requires WebGPU (compute shaders not available in WebGL)
  • perObjectFrustumCulled automatically disabled on WebGL backend
  • All other features (batching, billboarding, styling) work in WebGL
Constructor Parameters
maxTextCountoptionalnumber
Maximum number of Text instances. Pre-allocates buffers for this capacity. Set to enable dynamic addText() without buffer overflow.
Default is Infinity.
maxGlyphCountoptionalnumber
Maximum total glyphs across all text. Pre-allocates glyph buffers. Estimate ~10-50 glyphs per text instance.
Default is Infinity.
materialoptionalTHREE.Material
Base material (creates default if omitted).
Example
import { BatchedText, Text } from '@three-blocks/core';

// maxTextCount= 5000 -Pre-allocate for up to 5000 text instances
// maxGlyphCount= 100000 - Pre-allocate for ~100k total glyphs (~20 per text)
// Create batched text container with pre-allocation for dynamic updates
const batch = new BatchedText(5000, 100000, material);
batch.billboarding = true
batch.staticMode = true

// Add initial text instances
for (let i = 0; i < 1000; i++) {
  const text = new Text();
  text.text = `Label ${i}`;
  text.fontSize = 1;
  text.color.setHSL(Math.random(), 0.7, 0.6);
  text.position.set( Math.random()*20-10, Math.random()*20-10, Math.random()*20-10 );
  text.updateMatrixWorld();
  const id = batch.addText(text);

  // Set transform (like BatchedMesh API) are also available
  // matrix.compose(position, quaternion, scale);
  // batch.setMatrixAt(id, matrix);
  // batch.setColorAt(id, color);
}

scene.add(batch);

// Dynamically add more text later (within maxTextCount/maxGlyphCount limits)
const newText = new Text();
newText.text = 'Dynamic!';
const newId = batch.addText(newText);  // Returns -1 if capacity exceeded
if (newId >= 0) {
  batch.setMatrixAt(newId, matrix);
}

// Update existing text content
const text = batch.getTextAt(0);
text.text = 'Updated!';

Properties

.count :

Get the number of batched text members.

.count :

Ignored. count is read-only.

.isWebGL : boolean|null

Returns true if running on WebGL backend (no compute shader support). Returns null if backend hasn't been detected yet.

.isCullingActive : boolean

Returns true if GPU culling is active (WebGPU only, and culling requested).

.perTextBoundingBox : boolean

Get or set per-text bounding box mode. When true, each text instance gets its own bounding sphere for more accurate culling. When false (default), uses the maximum bounding sphere of all instances for faster culling.

Methods

add#

add(objs : THREE.Object3D) : this

Add objects to batch. Text instances become batched members. Non-Text objects are added to scene graph normally.

Parameters
objsTHREE.Object3D
Objects to add.
Returns
this

remove#

remove(objs : THREE.Object3D) : this

Remove objects from batch.

Parameters
objsTHREE.Object3D
Objects to remove.
Returns
this

addText#

addText(text : Text) : number

Register a Text instance as a batched member. Returns an instance ID that can be used with setMatrixAt, setColorAt, etc.

If maxTextCount was specified in constructor options, this will return -1 and log a warning when the limit is exceeded.

Parameters
Text instance to batch.
Returns
number — The instance ID for this text, or -1 if capacity exceeded or already added with same index.

removeText#

removeText(text : Text)

Unregister a Text instance from batch.

Parameters
Text instance to remove.

getTextAt#

getTextAt(instanceId : number) : Text|null

Get the Text instance at the given instance ID.

Parameters
instanceIdnumber
The instance ID.
Returns
Text | null — The Text instance, or null if not found.

setMatrixAt#

setMatrixAt(instanceId : number, matrix : THREE.Matrix4) : BatchedText

Sets the given local transformation matrix to the defined text instance. This updates the text's matrix and marks it for update.

Parameters
instanceIdnumber
The instance ID of the text to set the matrix of.
matrixTHREE.Matrix4
A 4x4 matrix representing the local transformation.
Returns
BatchedText — A reference to this batched text.

getMatrixAt#

getMatrixAt(instanceId : number, matrix : THREE.Matrix4) : THREE.Matrix4

Returns the local transformation matrix of the defined text instance.

Parameters
instanceIdnumber
The instance ID of the text to get the matrix of.
matrixTHREE.Matrix4
The target object that is used to store the result.
Returns
THREE.Matrix4 — The text instance's local transformation matrix.

setColorAt#

setColorAt(instanceId : number, color : THREE.Color|number|string) : BatchedText

Sets the given color to the defined text instance.

Parameters
instanceIdnumber
The instance ID of the text to set the color of.
colorTHREE.Color | number | string
The color to set the instance to.
Returns
BatchedText — A reference to this batched text.

getColorAt#

getColorAt(instanceId : number, color : THREE.Color) : THREE.Color

Returns the color of the defined text instance.

Parameters
instanceIdnumber
The instance ID of the text to get the color of.
colorTHREE.Color
The target object that is used to store the result.
Returns
THREE.Color — The text instance's color.

getGlyphAt#

getGlyphAt(instanceId : number, target : object) : Object|null

Returns glyph data for the given text instance without triggering a full sync. Only the first glyph range for the member is returned; multi-glyph members are supported via glyphOffset/count.

Parameters
instanceIdnumber
targetoptionalobject
Optional target object to populate.
Returns
Object | null

setGlyphAt#

setGlyphAt(instanceId : number, glyph : Object) : this

Quickly update glyph data (atlas index, bounds, letter index) for a specific text instance without triggering a full text sync/layout. Useful for single-glyph members such as counters.

Parameters
instanceIdnumber
glyphObject
Returns
this

updateMatrixWorld#

updateMatrixWorld(force : boolean)

Update world matrices and recompute bounds.

Parameters
forceoptionalboolean
Force update even if matrices haven't changed.

updateBounds#

updateBounds()

Recompute bounding volumes from all member bounds.

onBeforeRender#

onBeforeRender()

Internal render hook. Ensures children are synced, updates/creates the GPU culler, refreshes visibility buffers and material flags, and lazily prepares storage buffers.

initCuller#

initCuller(renderer : THREE.WebGPURenderer) : ComputeInstanceCulling|null

Initialize the GPU culler early (before first render). Useful when you need to access culler immediately.

Parameters
rendererTHREE.WebGPURenderer
Renderer instance.
Returns
ComputeInstanceCulling | null — The culler, or null if WebGL or sync not completed yet.

onAfterRender#

onAfterRender()

Internal render hook. Restore material-side settings after render.

sync#

sync(callback : function, renderer : THREE.WebGPURenderer)

Synchronize all member Text instances. Triggers repacking of instance attributes when any member has changed, and rebuilds glyph data arrays.

Important: This only syncs text content changes. Position/transform changes require calling setMatrixAt(instanceId, matrix) after updating text.position.

Parameters
callbackoptionalfunction
Called when sync completes
rendererTHREE.WebGPURenderer
Required on first call to detect WebGPU/WebGL backend for proper SDF texture handling via getTextRenderInfo(). Subsequent calls can omit this if the backend was already detected.

getTextBoundingSphereAt#

getTextBoundingSphereAt(instanceId : number) : Object|null

Get the bounding sphere for a specific text instance (local space).

Parameters
instanceIdnumber
The instance ID of the text.
Returns
Object | null

updateMemberMatrixWorld#

updateMemberMatrixWorld(text : Text)

Update a single member's instance matrix in the storage buffer using its current world matrix. Does not recompute style parameters.

Parameters

updateCullingAndPacking#

updateCullingAndPacking(renderer : THREE.WebGPURenderer, camera : THREE.Camera)

Update culling (camera uniforms and per-member ref positions) and run glyph packing compute passes. This is called automatically in onBeforeRender, so manual calls are typically not needed.

Parameters
rendererTHREE.WebGPURenderer
cameraTHREE.Camera

attachGUI#

attachGUI(folder : object)

Attach a simple GUI folder with controls for culling and LOD settings. Compatible with lil-gui, dat.gui, and Three.js Inspector. Note: Culling options are only shown on WebGPU backend.

Parameters
folderobject
GUI instance (e.g., lil-gui/dat.gui or renderer.inspector.createParameters()).

disposeGUI#

disposeGUI()

Destroy the attached GUI and clear reference.

dispose#

dispose()

Dispose resources associated with this helper and detach debug UI.