Agent Skills
› lichtblick-suite/lichtblick
› 3d-rendering
3d-rendering
GitHub提供基于THREE.js的3D渲染技能,涵盖WebGL管线、动态缓冲区管理(无几何倍增)、点云渲染优化及TF树变换解析。适用于构建高性能3D可视化面板,处理场景优化与数据流更新。
Trigger Scenarios
需要实现或优化基于THREE.js的3D可视化功能
处理WebGL渲染性能问题
管理动态几何体缓冲区或点云数据
Install
npx skills add lichtblick-suite/lichtblick --skill 3d-rendering -g -y
SKILL.md
Frontmatter
{
"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
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:
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* 2over-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 small increments reallocates every time, so callers that know a target size should resize to it once.
Point Cloud Rendering
Data Flow
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
world (root)
├── base_link
│ ├── lidar_link
│ ├── camera_link
│ └── imu_link
└── map
└── odom
└── base_link (loop via static transform)
Time-based Lookup
// 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 pose
frameId, // destination/target frame
rootFrameId, // optional explicit root frame (defaults to frame.root())
srcFrameId, // source frame
dstTime, // Time to evaluate the destination frame at
srcTime, // Time to evaluate the source frame at
maxDelta, // 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):
const mesh = new THREE.InstancedMesh(geometry, material, maxCount);
// Update per-instance transform
mesh.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
Version History
- cab9317 Current 2026-07-24 12:16


