渲染性能优化
合批渲染
实例化渲染
加载性能优化
- Three.js自带的GLTF加载器性能捉急,可自行实现加载器。
- GLTF格式是一种通用3D数据文件格式,不一定适用于所有业务。尤其不适合流式加载的场景!可根据业务特性自定义数据文件,并实现加载器。
- 如果使用了Vue之类的MVVM框架,不要把任何3D相关数据定义为响应式数据!!
- 数组中添加元素不要使用.push(),Webpack编译后每次push都会有一次module.import和module.export,每次push都要贼久时间。使用如下方式代替。
let arr = []; // 不要使用 arr.push(100) arr[arr.length] = 100;
- WebWorker多线程解压数据——注意主线程与工作线程之间的互通讯尽量使用TypedArray、Blob等类型的数据,底层是对象所有权转移。若返回普通对象,则会变成对象拷贝,且无法拷贝对象里的函数引用。
- 如果使用的3D文件格式是GLTF,且使用较早版本的Three.js,可使用浏览器的性能分析器检查是否在GLTFLoader.createUniqueName函数中消耗了大量时间。GLTFLoader早期对此函数的实现相当低效,是一个
n!
级的复杂度。代码如下:
此问题在2023.05的某个提交中已修复。可选择升级Three.js版本,或复制GLTFLoader到自己的代码里,并重新实现此函数为createUniqueName( originalName ) { const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); let name = sanitizedName; for ( let i = 1; this.nodeNamesUsed[ name ]; ++ i ) { name = sanitizedName + '_' + i; } this.nodeNamesUsed[ name ] = true; return name; }
createUniqueName( originalName ) { const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); let newName = sanitizedName; while (newName in this.nodeNamesUsed) { newName = sanitizedName + '_' + ( ++ this.nodeNamesUsed[ sanitizedName ] ); } this.nodeNamesUsed[ newName ] = 0; return newName; }
业务优化
构件选中
-
GPU拾取:
- 给主场景的所有需要选中Mesh的Geometry添加color属性,与position属性数量一致,每个构件的color均不相同,保留color与构件的索引
colorMeshMap = {}; // 遍历所有需要选中的 mesh let colorHex = 0; const color = new THREE.Color(colorHex); const colorArr = []; for (let i = 0; i < mesh.geometry.getAttribute("position").count; i+=3) { colorArr[colorArr.length] = color.r; colorArr[colorArr.length] = color.g; colorArr[colorArr.length] = color.b; } mesh.geometry.setAttribute("color", new THREE.BufferAttribute(new Float32Array(colorArr), 3)); colorMeshMap[colorHex] = mesh; colorHex++;
- 复用主场景的Geometry + 一个新Material,创建新Mesh,并加入新Scene中
const pickingScene = new THREE.Scene(); const pickingMaterial = new THREE.MeshBasicMaterial({ vertexColors: true }); // 循环添加所有需要选中的构件, geometry为需要选中的构件的geometry const pickingMesh = new THREE.Mesh(geometry, pickingMaterial); pickingScene.add(pickingMesh); // 获取光标位置的颜色 const renderer; // three.js的renderer let rendererTargetTmp = renderer.getRenderTarget(); // width和height 为当前renderer的宽高 let pickingRendererTarget = new THREE.WebGLRenderTarget(width, height); renderer.setRenderTarget(pickingRendererTarget); let targetPixel = new Uint8Array(4); renderer.clear(); // 替换camera变量 renderer.render(pickScene, camera); // x和y为当前光标相对Canvas的坐标,Canvas右下角度为原点 // 注意,屏幕坐标系坐标原点在左上角,与RenderTarget相反,因此,需要进行坐标换算,y = 屏幕height - clickedY renderer.readRenderTargetPixels(pickingRendererTarget, x, y, 1, 1, targetPixel); // 恢复渲染对象 renderer.setRenderTarget(rendererTargetTmp); const color = new THREE.Color(targetPixel[0]/255, targetPixel[1]/255, targetPixel[2]/255); const colorHex = color.getHex(); //
- 根据之前保留的color与构件的映射,拿到构件的引用
const mesh = colorMeshMap(colorHex);