关于three.js在使用transformControl后的卡顿问题

关于three.js在使用transformControl后的卡顿问题

问题描述

  • 变换控制器( O r b i t C o n t r o l s OrbitControls OrbitControls)与轨道控制器( T r a n s f o r m C o n t r o l s TransformControls TransformControls)之争
    为什么在执行完transformControl.attach(object);后,
    轨道控制器就变的卡顿,即使我用 transformControl.detach();之后,
    还是卡顿异常

问题分析

关于这个 three.js 中遇到的问题,即在使用 TransformControl.attach(object) 之后 OrbitControl 变得卡顿,可能是由于几个潜在的原因引起的。考虑到我们已经在 dragging-changed 事件中禁用了 OrbitControl,
即,

transformControl.addEventListener('dragging-changed', function (event) {
  controls.enabled = !event.value;
});

,这个问题可能不完全是由事件处理冲突导致的。以下是一些可能导致这种行为的原因及解决方案

  1. 性能问题
  • 复杂的场景渲染:如果您的场景非常复杂(许多物体或高多边形计数),那么在进行交互操作(如变换控制)时,性能可能会受到影响。
  • 解决方案:优化场景,减少渲染负担,例如通过减少场景中物体的数量或细节。
  1. 过度渲染(经过九牛二虎之力,终于找到了问题的关键,就是过度渲染!!!)
  • 频繁更新:render() 函数可能被频繁调用,尤其是在您的 transformControl 的 ‘change’ 事件监听器中。
  • 解决方案:确保 render() 函数仅在必要时调用,或使用 requestAnimationFrame 来管理渲染循环。
  1. 资源未正确释放
  • 资源管理:在使用 transformControl.detach() 后,某些资源可能没有被正确释放或重置。
  • 解决方案:检查是否有任何未处理的资源或状态需要在分离操作后清理或重置。
  1. 内存泄漏
  • 事件监听器:如果在使用过程中不断添加事件监听器而没有适当移除,可能会导致内存泄漏。
  • 解决方案:确保添加的事件监听器在不需要时被移除,避免内存泄漏。
  1. GPU/CPU 负载
  • 硬件限制:如果您的设备的 GPU 或 CPU 资源受限,可能会影响渲染性能。
  • 解决方案:在不同的硬件上测试您的应用,查看问题是否依旧存在,以确认是否为硬件限制问题。
  1. 浏览器或框架兼容性问题
  • 兼容性:可能存在浏览器或 three.js 版本的兼容性问题。
  • 解决方案:尝试在不同的浏览器中运行您的应用,检查 three.js 是否是最新版本,或者是否有已知的兼容性问题。

找到问题,分析为什么会造成这个问题?

卡顿的代码是这样的:

let transformControl;//变换控制器
//创建一个 TransformControls 实例(将相机 camera 和渲染器的 DOM 元素 renderer.domElement 作为参数传递进去)
transformControl = new TransformControls(camera, renderer.domElement);

//接下来,我们添加了两个事件监听器
//1.当 TransformControls 的变换发生变化时,即用户交互操作导致物体进行平移、旋转或缩放时,触发 animate 函数重新渲染场景。
//即,模型改变=>重新渲染
transformControl.addEventListener('change', render);

//2.当用户开始或停止拖拽物体时,通过 event.value 判断拖拽状态,
//如果拖拽开始(event.value 为 true),则禁用其他的控制器(如鼠标控制器 controls),
//以防止在拖拽过程中出现与 TransformControls 的冲突。
transformControl.addEventListener('dragging-changed', function (event) {
 controls.enabled = !event.value;
});
//添加到场景中
scene.add(transformControl);

在three.js中,remove,detach等类似的这些函数,仅仅只是在表面上移除可模型,但是仍旧存在于内存中.只是你在场景中看不到了而已.

也就是说,当你用detach移除变换控制器后,变换控制器只是你看不到了也点不到了,但是他依旧存在于整个内存中,其中有一个语句 transformControl.addEventListener('change', render);,他是在变换控制器附着的模型有平移、旋转或缩放操作时,会被调用,所以当你取消对模型的附着,也就是调用transformControl.detach();后,表面上变换控制器没有附着在模型上了,但是实际上还在他在幕后监听着一些东西!
当你用detach()使得变换控制器消失后,它依旧监视着这个牙齿,所以之后你再用轨道控制器对整个场景进行平移、旋转或缩放操作时,这个颗之前被transformControl附着的牙齿也属于场景中的一分子,所以,transformControl.addEventListener('change', render);还是会被触发.so,频繁的调用render是场景卡顿的罪魁祸首!!!

问题解决

其实把transformControl.addEventListener('change', render);这一行注释掉就好,防止重复渲染整个场景

/**********
 * 转换控制器
 */
const pointer = new THREE.Vector2();//客户端坐标转化为设备坐标
// const onDownPosition = new THREE.Vector2();//鼠标按下的客户端坐标
// const onUpPosition = new THREE.Vector2();//鼠标抬起的客户端坐标
let transformControl;//变换控制器
//创建一个 TransformControls 实例(将相机 camera 和渲染器的 DOM 元素 renderer.domElement 作为参数传递进去)
transformControl = new TransformControls(camera, renderer.domElement);

//接下来,我们添加了两个事件监听器
//1.当 TransformControls 的变换发生变化时,即用户交互操作导致物体进行平移、旋转或缩放时,触发 animate 函数重新渲染场景。
//即,模型改变=>重新渲染
//transformControl.addEventListener('change', render);

//2.当用户开始或停止拖拽物体时,通过 event.value 判断拖拽状态,
//如果拖拽开始(event.value 为 true),则禁用其他的控制器(如鼠标控制器 controls),
//以防止在拖拽过程中出现与 TransformControls 的冲突。
transformControl.addEventListener('dragging-changed', function (event) {
  controls.enabled = !event.value;
});
//添加到场景中
scene.add(transformControl);
// document.addEventListener('pointerdown', onPointerDown);
// document.addEventListener('pointerup', onPointerUp);
// document.addEventListener('pointermove', onPointerMove);
document.addEventListener('click', onClick);
document.addEventListener("keydown", handleKeyDown);
function onClick(event) {
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(pointer, camera);
  intersects = raycaster.intersectObjects(objects);

  if (intersects.length > 0) {
    var object = intersects[0].object;
    if (object !== transformControl.object) {
      transformControl.detach();
      transformControl.attach(object);
      transformControl.update();
      //controls.enabled = false;
    }
  }
}
function handleKeyDown(event) {
  if (event.key === 'q') {
    if (transformControl.object) {
      transformControl.detach();
      transformControl.update();
      //controls.enabled = true;
      //render();
    }
  }
}