javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > Three.js渲染模型卡顿

Three.js渲染模型卡顿问题的一些优化办法

作者:小楼窗外的细雨

用ThreeJS加载大模型总要遇到性能问题,性能优化一般包括加载性能优化、渲染帧率优化、内存优化等,下面这篇文章主要给大家介绍了关于Three.js渲染模型卡顿问题的一些优化办法,需要的朋友可以参考下

事先说明

优化方法是根据chatGPT的回答下,我这里记录一下,有的方法进行了尝试,有的还没有。

1、模型面数过多导致渲染卡顿

可以通过减少面数来优化,也可以使用LOD技术(Level of Detail)在不同距离下使用不同的模型细节来优化。

使用LOD技术可以在不同距离下使用不同的模型细节来优化three.js渲染性能,下面是具体步骤:

const lod = new THREE.LOD();
const lowDetailModel = ... // 低细节模型
const midDetailModel = ... // 中细节模型
const highDetailModel = ... // 高细节模型
lod.addLevel(lowDetailModel, 0); // 添加低细节模型,距离为0
lod.addLevel(midDetailModel, 100); // 添加中细节模型,距离为100
lod.addLevel(highDetailModel, 200); // 添加高细节模型,距离为200
scene.add(lod);
function render() {
  requestAnimationFrame(render);
  lod.update(camera);
  renderer.render(scene, camera);
}

2、材质贴图过大导致渲染卡顿

可以通过减小贴图尺寸,压缩贴图格式,使用纹理集(Texture Atlas)等方式来优化。

使用纹理集(Texture Atlas)可以将多张小纹理图合并成一张大纹理图,从而减少渲染时的纹理切换次数,优化three.js渲染性能,下面是具体步骤:

const texture = new THREE.TextureLoader().load('atlas.png');
const material = new THREE.MeshBasicMaterial({ map: texture });
const geometry = new THREE.BoxGeometry(1, 1, 1);
const uvAttribute = geometry.attributes.uv;
for (let i = 0; i < uvAttribute.count; i++) {
  const u = uvAttribute.getX(i);
  const v = uvAttribute.getY(i);
  // 根据小纹理图在大纹理图中的位置和大小计算出UV坐标
  uvAttribute.setXY(i, u * smallTextureWidth / bigTextureWidth + smallTextureX / bigTextureWidth, v * smallTextureHeight / bigTextureHeight + smallTextureY / bigTextureHeight);
}
function render() {
  requestAnimationFrame(render);
  const time = Date.now() * 0.001;
  texture.offset.x = time * 0.1; // x方向偏移量
  texture.offset.y = time * 0.2; // y方向偏移量
  texture.repeat.set(2, 2); // 横向和纵向缩放值
  renderer.render(scene, camera);
}

3、着色器复杂度过高导致渲染卡顿

可以通过简化着色器,使用预编译的着色器,使用Instancing等方式来优化。

使用Instancing(实例化)可以将多个相同的物体复用同一个几何体和材质,并在渲染时进行一次性绘制,从而减少渲染调用次数,优化three.js渲染性能,下面是具体步骤:

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
const instances = 10000; // 实例数量
const instancedGeometry = new THREE.InstancedBufferGeometry();
instancedGeometry.copy(geometry); // 复制几何体属性
const translations = new Float32Array(instances * 3); // 实例位置数组
for (let i = 0; i < instances; i++) {
  translations[i * 3] = Math.random() * 100 - 50;
  translations[i * 3 + 1] = Math.random() * 100 - 50;
  translations[i * 3 + 2] = Math.random() * 100 - 50;
}
instancedGeometry.setAttribute('translation', new THREE.InstancedBufferAttribute(translations, 3));
const instancedMesh = new THREE.InstancedMesh(instancedGeometry, material, instances);
scene.add(instancedMesh);
function render() {
  requestAnimationFrame(render);
  const time = Date.now() * 0.001;
  for (let i = 0; i < instances; i++) {
    const translation = instancedMesh.geometry.attributes.translation;
    translation.setXYZ(i, Math.sin(time + i * 0.5) * 5, Math.cos(time + i * 0.3) * 5, i * 0.1);
  }
  instancedMesh.geometry.attributes.translation.needsUpdate = true; // 更新实例位置属性
  renderer.render(scene, camera);
}

4、不合理的渲染方式导致渲染卡顿

可以通过使用合适的渲染方式,如WebGL2渲染,使用Web Worker等方式来优化。

Ⅰ、使用WebGL2可以在现代浏览器中利用新的图形处理能力,优化three.js渲染性能,下面是具体步骤:
① 在渲染器中启用WebGL2。

const renderer = new THREE.WebGLRenderer({ canvas: canvas, context: canvas.getContext('webgl2') });

② 使用WebGL2支持的新特性,如transform feedbackinstanced arrays等。
例如,以下代码演示了如何使用transform feedback来记录顶点位置的变化:

const transformFeedback = new THREE.WebGL2TransformFeedback();
const bufferGeometry = new THREE.BufferGeometry();
const positions = new Float32Array([0, 0, 0]);
bufferGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const shader = new THREE.ShaderMaterial({
  vertexShader: `
    out vec3 transformedPosition;
    void main() {
      transformedPosition = position;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    void main() {
      gl_FragColor = vec4(1.0);
    }
  `,
  transformFeedback: {
    // 将顶点位置记录到transformedPosition变量中
    varyings: ['transformedPosition'],
    // 开启transform feedback
    enabled: true,
    // 设置bufferGeometry的位置属性为transform feedback的输出属性
    bufferGeometry: bufferGeometry
  }
});
const mesh = new THREE.Mesh(bufferGeometry, shader);
scene.add(mesh);
function render() {
  requestAnimationFrame(render);
  renderer.setRenderTarget(null);
  // 开始transform feedback
  transformFeedback.begin();
  renderer.render(scene, camera);
  // 结束transform feedback,并将变化后的顶点位置存储到bufferGeometry中
  transformFeedback.end();
  // 更新顶点位置
  positions.set(bufferGeometry.getAttribute('position').array);
  bufferGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
  renderer.render(scene, camera);
}

---------------------------------------------------------------分隔线-----------------------------------------------------------------

Ⅱ、使用Web Worker可以将计算密集型的任务分离到另一个线程中,从而避免主线程被阻塞,优化three.js渲染性能,下面是具体步骤:

① 创建一个Web Worker,用于处理计算密集型的任务。

const worker = new Worker('worker.js');

② 在Web Worker中定义处理函数。

// worker.js
function process(data) {
  // 计算密集型的任务
  return result;
}
onmessage = function(event) {
  const result = process(event.data);
  postMessage(result);
};

③ 在主线程中将任务发送到Web Worker,并设置回调函数处理返回结果。

function render() {
  requestAnimationFrame(render);
  // 发送任务到Web Worker
  worker.postMessage(data);
  worker.onmessage = function(event) {
    const result = event.data;
    // 处理返回结果
  };
  renderer.render(scene, camera);
}

通过以上步骤,就可以使用Web Worker来将计算密集型的任务分离到另一个线程中,从而避免主线程被阻塞,优化three.js渲染性能。需要注意的是,Web Worker中无法直接访问主线程的DOM和three.js对象,需要通过消息传递来实现通信。

5、CPU和GPU资源不平衡导致渲染卡顿

可以通过分析性能监控,优化代码逻辑,使用requestAnimationFrame等方式来平衡CPU和GPU资源占用。

使用requestAnimationFrame可以让浏览器根据自身的渲染节奏调整动画的帧率,从而避免过度渲染,优化three.js渲染性能,下面是具体步骤:

function render() {
  // 渲染代码
  renderer.render(scene, camera);
  // 请求下一帧动画
  requestAnimationFrame(render);
}
var animationId = requestAnimationFrame(render);
function stop() {
  cancelAnimationFrame(animationId);
}

需要注意的是,使用requestAnimationFrame时需要避免在渲染循环中进行过多的计算,以免影响渲染性能。

总结 

到此这篇关于Three.js渲染模型卡顿问题的一些优化办法的文章就介绍到这了,更多相关Three.js渲染模型卡顿内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

阅读全文