<< All versions
Skill v1.0.1
currentAutomated scan93/100lichtblick-suite/lichtblick/3d-rendering
+1 new
──Details
PublishedAugust 18, 2026 at 11:08 PM
Content Hashsha256:1dbab61c3485e86f...
Git SHA355014d99402
Bump Typepatch
──Files
Files (1 file, 5.2 KB)
SKILL.md5.2 KBactive
SKILL.md · 153 lines · 5.2 KB
version: "1.0.1" name: "3d-rendering" description: "Deep THREE.js rendering knowledge for the 3D panel: WebGL pipeline, buffer management, instanced rendering, shader considerations, and scene optimization techniques."
3D Rendering Skill
THREE.js Integration
Renderer Setup
typescript
const renderer = new THREE.WebGLRenderer({canvas,antialias: true,alpha: true,});renderer.setPixelRatio(window.devicePixelRatio);renderer.outputColorSpace = THREE.SRGBColorSpace;
Render Loop
- Driven by
requestAnimationFrame - Each frame: update transforms → update extensions → render scene
- No double-buffering needed (WebGL handles swap)
DynamicBufferGeometry Details
packages/suite-base/src/panels/ThreeDeeRender/DynamicBufferGeometry.ts:
typescript
class DynamicBufferGeometry extends THREE.BufferGeometry {// Grows to EXACTLY itemCount when capacity is exceeded — no geometric doubling.resize(itemCount: number): void {this.setDrawRange(0, itemCount);if (itemCount <= this.#itemCapacity) {return; // capacity sufficient — only the draw range changed}// For each attribute, allocate a NEW typed array of exactly itemCount * itemSize// (old data is NOT copied; callers refill the buffer after resize)this.#itemCapacity = itemCount;}}
Growth Behavior (Important)
resize(itemCount)always callssetDrawRange(0, itemCount)first- If
itemCount <= itemCapacity, it returns early — buffers are reused, only the draw range moves - If
itemCount > itemCapacity, each attribute is reallocated to exactlyitemCount * itemSize
(no * 2 over-allocation, no copy of existing data)
- Capacity only ever grows; it is never shrunk below a previous high-water mark
⚠️ Do not assume geometric/amortized doubling here. Repeatedly increasing the count by smallincrements reallocates every time, so callers that know a target size should resize to it once.
Point Cloud Rendering
Data Flow
text
Raw message (PointCloud2)│▼Decode fields (x, y, z, rgb, intensity)│▼Fill position buffer (Float32Array)Fill color buffer (Uint8Array)│▼Upload to GPU (BufferAttribute.needsUpdate = true)│▼Render with THREE.Points or InstancedMesh
Decay History
- Configurable
decayTimein seconds - Old points are culled by sliding the
drawRangestart forward - Ring-buffer approach: write position wraps around, draw range skips old data
- Avoids array shifting (O(1) per frame instead of O(n))
Point Budget
- Too many points → GPU bottleneck
filterQueue: processes messages in batches per frame- Downsampling: skip points when exceeding budget
Transform Resolution
TF Tree Structure
text
world (root)├── base_link│ ├── lidar_link│ ├── camera_link│ └── imu_link└── map└── odom└── base_link (loop via static transform)
Time-based Lookup
typescript
// TransformTree.apply has an 8-argument signature:const pose = transformTree.apply(output, // Pose written in place (returned, or undefined on failure)input, // Readonly<Pose> source poseframeId, // destination/target framerootFrameId, // optional explicit root frame (defaults to frame.root())srcFrameId, // source framedstTime, // Time to evaluate the destination frame atsrcTime, // Time to evaluate the source frame atmaxDelta, // optional Duration cap on extrapolation);
- Defined in
packages/suite-base/src/panels/ThreeDeeRender/transforms/TransformTree.ts - Writes into the provided
outputPose and returns it (orundefinedif a frame is missing) - Interpolates between stored transforms at query time;
maxDeltacaps extrapolation from stale data
Instanced Rendering
For many identical objects (markers, arrows):
typescript
const mesh = new THREE.InstancedMesh(geometry, material, maxCount);// Update per-instance transformmesh.setMatrixAt(index, matrix);mesh.instanceMatrix.needsUpdate = true;
- Single draw call for all instances
- Massively reduces draw call overhead (100→1 for 100 markers)
maxCountdetermines GPU buffer allocation — avoid over-allocation
Shader Considerations
- Custom materials extend
THREE.ShaderMaterialorTHREE.RawShaderMaterial - Point size attenuation: points shrink with distance (
sizeAttenuation: true) - Color mapping: intensity → color lookup via uniform texture
- Vertex colors: per-point coloring via
vertexColors: trueon material
Performance Optimization Checklist
- ✅ Use
DynamicBufferGeometry— nevernew BufferGeometry()per frame - ✅ Set
needsUpdate = trueonly on changed attributes - ✅ Use
InstancedMeshfor repeated geometries (>10 instances) - ✅ Dispose materials/geometries on removal (prevents GPU memory leak)
- ✅ Frustum culling enabled (default in THREE.js)
- ✅ Reuse temporary Vector3/Matrix4 instances (object pool pattern)
- ✅ Limit point count with decay + budget
- ❌ Never create new
THREE.Materialper frame - ❌ Never call
renderer.render()if scene hasn't changed - ❌ Never use
traverse()in hot path — cache node references