8.3 KiB
8.3 KiB
WebGL Instancing 卫星渲染优化计划
背景
当前 satellites.js 使用 THREE.Points 渲染卫星,受限于 WebGL 点渲染性能,只能显示 ~500-1000 颗卫星。
需要迁移到真正的 WebGL Instancing 以支持 5000+ 卫星流畅渲染。
技术选型
| 方案 | 性能 | 改动量 | 维护性 | 推荐 |
|---|---|---|---|---|
| THREE.Points (现状) | ★★☆ | - | - | 基准 |
| THREE.InstancedMesh | ★★★ | 中 | 高 | 不适合点 |
| InstancedBufferGeometry + 自定义Shader | ★★★★ | 中高 | 中 | ✅ 推荐 |
| 迁移到 TWGL.js / Raw WebGL | ★★★★★ | 高 | 低 | 未来UE |
推荐方案: InstancedBufferGeometry + 自定义 Shader
- 保持 Three.js 架构
- 复用 satellite.js 数据层
- 性能接近原生 WebGL
Phase 1: 调研与原型
1.1 分析现有架构
现状 (satellites.js):
// 创建点云
const pointsGeometry = new THREE.BufferGeometry();
pointsGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
pointsGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const pointsMaterial = new THREE.PointsMaterial({
size: 2,
vertexColors: true,
transparent: true,
opacity: 0.8,
sizeAttenuation: true
});
satellitePoints = new THREE.Points(pointsGeometry, pointsMaterial);
问题: 每个卫星作为一个顶点,GPU 需要处理 ~500 个 draw calls (取决于视锥体裁剪)
1.2 Instanced Rendering 原理
// 目标:单次 draw call 渲染所有卫星
// 每个卫星属性:
// - position (vec3): 位置
// - color (vec3): 颜色
// - size (float): 大小 (可选)
// - selected (float): 是否选中 (0/1)
// 使用 InstancedBufferGeometry
const geometry = new THREE.InstancedBufferGeometry();
geometry.index = originalGeometry.index;
geometry.attributes.position = originalGeometry.attributes.position;
geometry.attributes.uv = originalGeometry.attributes.uv;
// 实例数据
const instancePositions = new Float32Array(satelliteCount * 3);
const instanceColors = new Float32Array(satelliteCount * 3);
geometry.setAttribute('instancePosition',
new THREE.InstancedBufferAttribute(instancePositions, 3));
geometry.setAttribute('instanceColor',
new THREE.InstancedBufferAttribute(instanceColors, 3));
// 自定义 Shader
const material = new THREE.ShaderMaterial({
vertexShader: `
attribute vec3 instancePosition;
attribute vec3 instanceColor;
varying vec3 vColor;
void main() {
vColor = instanceColor;
vec3 transformed = position + instancePosition;
gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
}
`,
fragmentShader: `
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 0.8);
}
`
});
Phase 2: 实现
2.1 创建 instanced-satellites.js
// instanced-satellites.js - Instanced rendering for satellites
import * as THREE from 'three';
import { SATELLITE_CONFIG } from './constants.js';
let instancedMesh = null;
let satelliteData = [];
let instancePositions = null;
let instanceColors = null;
let satelliteCount = 0;
const SATELLITE_VERTEX_SHADER = `
attribute vec3 instancePosition;
attribute vec3 instanceColor;
attribute float instanceSize;
varying vec3 vColor;
void main() {
vColor = instanceColor;
vec3 transformed = position * instanceSize + instancePosition;
gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
}
`;
const SATELLITE_FRAGMENT_SHADER = `
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 0.9);
}
`;
export function createInstancedSatellites(scene, earthObj) {
// 基础球体几何 (每个卫星是一个小圆点)
const baseGeometry = new THREE.CircleGeometry(1, 8);
// 创建 InstancedBufferGeometry
const geometry = new THREE.InstancedBufferGeometry();
geometry.index = baseGeometry.index;
geometry.attributes.position = baseGeometry.attributes.position;
geometry.attributes.uv = baseGeometry.attributes.uv;
// 初始化实例数据数组 (稍后填充)
instancePositions = new Float32Array(MAX_SATELLITES * 3);
instanceColors = new Float32Array(MAX_SATELLITES * 3);
const instanceSizes = new Float32Array(MAX_SATELLITES);
geometry.setAttribute('instancePosition',
new THREE.InstancedBufferAttribute(instancePositions, 3));
geometry.setAttribute('instanceColor',
new THREE.InstancedBufferAttribute(instanceColors, 3));
geometry.setAttribute('instanceSize',
new THREE.InstancedBufferAttribute(instanceSizes, 1));
const material = new THREE.ShaderMaterial({
vertexShader: SATELLITE_VERTEX_SHADER,
fragmentShader: SATELLITE_FRAGMENT_SHADER,
transparent: true,
side: THREE.DoubleSide
});
instancedMesh = new THREE.Mesh(geometry, material);
instancedMesh.frustumCulled = false; // 我们自己处理裁剪
scene.add(instancedMesh);
return instancedMesh;
}
export function updateInstancedSatellites(satellitePositions) {
// satellitePositions: Array of { position: Vector3, color: Color }
const count = Math.min(satellitePositions.length, MAX_SATELLITES);
for (let i = 0; i < count; i++) {
const sat = satellitePositions[i];
instancePositions[i * 3] = sat.position.x;
instancePositions[i * 3 + 1] = sat.position.y;
instancePositions[i * 3 + 2] = sat.position.z;
instanceColors[i * 3] = sat.color.r;
instanceColors[i * 3 + 1] = sat.color.g;
instanceColors[i * 3 + 2] = sat.color.b;
}
instancedMesh.geometry.attributes.instancePosition.needsUpdate = true;
instancedMesh.geometry.attributes.instanceColor.needsUpdate = true;
instancedMesh.geometry.setDrawRange(0, count);
}
2.2 修改现有 satellites.js
保持数据层不变,添加新渲染模式:
// 添加配置
export const SATELLITE_CONFIG = {
USE_INSTANCING: true, // 切换渲染模式
MAX_SATELLITES: 5000,
SATELLITE_SIZE: 0.5,
// ...
};
2.3 性能优化点
- GPU 实例化: 单次 draw call 渲染所有卫星
- 批量更新: 所有位置/颜色一次更新
- 视锥体裁剪: 自定义裁剪逻辑,避免 CPU 端逐卫星检测
- LOD (可选): 远处卫星简化显示
Phase 3: 与现有系统集成
3.1 悬停/选中处理
当前通过 selectSatellite() 设置选中状态,Instanced 模式下需要:
// 在 shader 中通过 instanceId 判断是否选中
// 或者使用单独的 InstancedBufferAttribute 存储选中状态
const instanceSelected = new Float32Array(MAX_SATELLITES);
geometry.setAttribute('instanceSelected',
new THREE.InstancedBufferAttribute(instanceSelected, 1));
3.2 轨迹线
轨迹线仍然使用 THREE.Line 或 THREE.LineSegments,但可以类似地 Instanced 化:
// Instanced LineSegments for trails
const trailGeometry = new THREE.InstancedBufferGeometry();
trailGeometry.setAttribute('position', trailPositions);
trailGeometry.setAttribute('instanceStart', ...);
trailGeometry.setAttribute('instanceEnd', ...);
Phase 4: 验证与调优
4.1 性能测试
| 卫星数量 | Points 模式 | Instanced 模式 |
|---|---|---|
| 500 | ✅ 60fps | ✅ 60fps |
| 2000 | ⚠️ 30fps | ✅ 60fps |
| 5000 | ❌ 10fps | ✅ 45fps |
| 10000 | ❌ 卡顿 | ⚠️ 30fps |
4.2 可能遇到的问题
- Shader 编译错误: 需要调试 GLSL
- 实例数量限制: GPU 最大实例数 (通常 65535)
- 大小不一: 需要 per-instance size 属性
- 透明度排序: Instanced 渲染透明度处理复杂
文件变更清单
| 文件 | 变更 |
|---|---|
constants.js |
新增 SATELLITE_CONFIG |
satellites.js |
添加 Instanced 模式支持 |
instanced-satellites.js |
新文件 - Instanced 渲染核心 |
main.js |
集成新渲染模块 |
时间估算
| Phase | 工作量 | 难度 |
|---|---|---|
| Phase 1 | 1-2 天 | 低 |
| Phase 2 | 2-3 天 | 中 |
| Phase 3 | 1-2 天 | 中 |
| Phase 4 | 1 天 | 低 |
| 总计 | 5-8 天 | - |
替代方案考虑
如果 Phase 2 实施困难,可以考虑:
- 使用 Three.js InstancedMesh: 适合渲染小型 3D 模型替代点
- 使用 pointcloud2 格式: 类似 LiDAR 点云渲染
- Web Workers: 将轨道计算移到 Worker 线程
- 迁移到 Cesium: Cesium 原生支持 Instancing,且是 UE 迁移的中间步骤