BatchedText
new BatchedText(maxTextCount : number, maxGlyphCount : number, material : THREE.Material)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
StorageBufferAttributefor 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
computeIndirectfor 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
maxTextCountandmaxGlyphCountto pre-allocate buffers for dynamicaddText() - Without pre-allocation, buffers are sized to initial content (no dynamic growth)
computeIndirectdynamically 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,colorNodestill work
Usage
Performance Tips
- Use
static: trueif text content never changes after initial setup - Enable
perObjectFrustumCulledfor large scenes with many off-screen labels - Use
_cullOptions.lodModeto enable/disable LOD sampling - Range LOD uses
_cullOptions.lodNear+_cullOptions.lodFar - Exp LOD uses
_cullOptions.lodNear+_cullOptions.lodDensity(exp density).lodNearshifts 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)
perObjectFrustumCulledautomatically disabled on WebGL backend- All other features (batching, billboarding, styling) work in WebGL
Constructor Parameters
maxTextCountoptionalnumberDefault is
Infinity.maxGlyphCountoptionalnumberDefault is
Infinity.materialoptionalTHREE.MaterialExample
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) : thisAdd objects to batch. Text instances become batched members.
Non-Text objects are added to scene graph normally.
Parameters
objsTHREE.Object3DReturns
thisremove#
remove(objs : THREE.Object3D) : thisRemove objects from batch.
Parameters
objsTHREE.Object3DReturns
thisaddText#
addText(text : Text) : numberRegister 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
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
getTextAt#
getTextAt(instanceId : number) : Text|nullGet the Text instance at the given instance ID.
Parameters
instanceIdnumberReturns
Text | null — The Text instance, or null if not found.setMatrixAt#
setMatrixAt(instanceId : number, matrix : THREE.Matrix4) : BatchedTextSets the given local transformation matrix to the defined text instance. This updates the text's matrix and marks it for update.
Parameters
instanceIdnumbermatrixTHREE.Matrix4Returns
BatchedText — A reference to this batched text.getMatrixAt#
getMatrixAt(instanceId : number, matrix : THREE.Matrix4) : THREE.Matrix4Returns the local transformation matrix of the defined text instance.
Parameters
instanceIdnumbermatrixTHREE.Matrix4Returns
THREE.Matrix4 — The text instance's local transformation matrix.setColorAt#
setColorAt(instanceId : number, color : THREE.Color|number|string) : BatchedTextSets the given color to the defined text instance.
Parameters
instanceIdnumbercolorTHREE.Color | number | stringReturns
BatchedText — A reference to this batched text.getColorAt#
getColorAt(instanceId : number, color : THREE.Color) : THREE.ColorReturns the color of the defined text instance.
Parameters
instanceIdnumbercolorTHREE.ColorReturns
THREE.Color — The text instance's color.getGlyphAt#
getGlyphAt(instanceId : number, target : object) : Object|nullReturns 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
instanceIdnumbertargetoptionalobjectReturns
Object | nullsetGlyphAt#
setGlyphAt(instanceId : number, glyph : Object) : thisQuickly 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
instanceIdnumberglyphObjectReturns
thisupdateMatrixWorld#
updateMatrixWorld(force : boolean)Update world matrices and recompute bounds.
Parameters
forceoptionalbooleanupdateBounds#
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|nullInitialize the GPU culler early (before first render).
Useful when you need to access culler immediately.
Parameters
rendererTHREE.WebGPURendererReturns
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
callbackoptionalfunctionrendererTHREE.WebGPURenderergetTextRenderInfo().
Subsequent calls can omit this if the backend was already detected.getTextBoundingSphereAt#
getTextBoundingSphereAt(instanceId : number) : Object|nullGet the bounding sphere for a specific text instance (local space).
Parameters
instanceIdnumberReturns
Object | nullupdateMemberMatrixWorld#
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.WebGPURenderercameraTHREE.CameraattachGUI#
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
folderobjectdisposeGUI#
disposeGUI()Destroy the attached GUI and clear reference.
dispose#
dispose()Dispose resources associated with this helper and detach debug UI.