从类内调用 requestAnimationFrame 时,threejs 动画的动画速度非常慢

Animation extremely slow for threejs animation when calling requestAnimationFrame from within class

提问人:Joseph Astrahan 提问时间:4/5/2023 更新时间:4/5/2023 访问量:195

问:

描述此问题的最好方法是签出 JSFiddle。当您尝试在鼠标周围移动或执行任何操作时,动画将非常不稳定。我已经将范围缩小到与分类 this.requestAnimationFrame(this.animation) 有关,起初不起作用,但在查看 stackoverflow 后,我发现了一个修复程序,说这让它工作,但速度非常慢!如何在保留课程的同时加快速度?this.animate = this.animate.bind(this);

JSFiddle:

https://jsfiddle.net/gentleman_goat66/o5wn3bpf/100/

这是我的代码如下:

[HTML全文]

<script type="importmap">
{
"imports": 
  {
    "three": "https://unpkg.com/[email protected]/build/three.module.js",
    "OrbitControls": "https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js"
  }
}
</script>
<div id="container">
</div>

JS系列

import * as THREE from 'three';
import { OrbitControls } from 'OrbitControls';

class Heatmap {
  constructor(){
    console.log("Heatmap - constructor()");
    
    //ThreeJS Variables
    this.camera = null;
    this.scene = null;
    this.renderer = new THREE.WebGLRenderer({antialias: true});
    this.orbital_controls = null;
    this.ambientLight = null;
    this.heatmap = new THREE.Object3D(); //This will hold all the meshes that make up the heatmap
    this.animate = this.animate.bind(this);
    
    //Animation Related Variables
    this.clock = new THREE.Clock();
    this.delta = 0;
    this.fps = 60; //60 fps
        this.interval = 1;
        this.seconds = 2; //seconds for animation
        this.timeSoFar = 0;
        this.targetTime = 3;
    
    //Setup
    this.scene = new THREE.Scene();
    this.setUpCamera();
    this.setUpRenderer();
    this.setUpLighting();
    this.setUpOrbitalControls();
    
    //Add optional GridHelper for Debug
    this.scene.add(new THREE.GridHelper());
    
    //TODO: Create the heatmap here
    this.createDataRectangle();
    
    //Add the finished heatmap to the scene
    this.scene.add(this.heatmap);
    this.render();
    this.animate();
  }
  setUpRenderer(){
    console.log("Heatmap - setUpRenderer()");
    let width = $('#container').width();
    let height = $('#container').height();
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(width,height);
    this.renderer.setClearColor(0x404040);
 
    $("#container").html(this.renderer.domElement);
    
    window.addEventListener("resize", event => {
      this.camera.aspect = innerWidth / innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(innerWidth, innerHeight);
    });
  }
  setUpCamera(){
    console.log("Heatmap - setUpCamera()");
    this.camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 100);
    this.camera.position.z = 4;
  }
  setUpLighting(){
    console.log("Heatmap - setUpLighting()");
    this.ambientLight = new THREE.AmbientLight(0xffffff, 1);
    this.scene.add(this.ambientLight);
  }
  setUpOrbitalControls(){
    console.log("Heatmap - setUpOrbitalControls()");
    this.orbital_controls = new OrbitControls( this.camera, this.renderer.domElement );
    this.orbital_controls.target.set(0, 0, 0);
    this.orbital_controls.enableDamping = true;
    this.orbital_controls.dampingFactor = 0.025;
    this.orbital_controls.maxPolarAngle = Math.PI/2.0;
    this.orbital_controls.minDistance = 2.0;
    this.orbital_controls.maxDistance = 80.0;
    this.orbital_controls.update();
    
    this.orbital_controls.addEventListener( 'change', function(){
      if (this.target.y < 0){
          this.target.y = 0;
          /* camera.position.y = -1; */
      } else if (this.target.y > 1){
          this.target.y = 1;
          /* camera.position.y = 1; */
      }
    });
  }
  createDataRectangle(){ //TODO: Make this create at a position specified
    console.log("Heatmap - createDataRectangle()");
    const geometry = new THREE.BufferGeometry();
    const vertices = [
      -2, 0, 1,//0, floor, bottom left
      -1, 0, 1,//1, floor, bottom right
      -1, 0, -1,//2, floor, top right
      -2, 0, -1,//3 floor, top left
      -2, 0, 1,//4 roof, bottom left
      -1, 0, 1,//5 roof, bottom right
      -1, 0, -1,//6 roof, top right
      -2, 0, -1//7 roof, top left
    ];
    const indices = [
      0, 1, 2,//floor, first triangle
      2, 3, 0,//floor, second triangle
      4, 5, 6,//roof, first triangle
      6, 7, 4,//roof, second triangle
      3, 0, 4,//west wall, first triangle
      3, 4, 7,//west wall, second triangle
      0, 1, 5,//south wall, first triangle
      0, 5, 4,//south wall, first triangle
      1, 2, 6,//east wall, first triangle
      1, 6, 5,//east wall, second triangle
      2, 3, 7,//north wall, first triangle
      2, 7, 6,//north wall, second triangle
    ];
    const colors = [
      2,0,0, // bottom left
      0,2,0, // bottom right
      0,2,0, // top right
      2,0,0,  // top left
      2,0,0, // bottom left
      0,2,0, // bottom right
      0,2,0, // top right
      2,0,0  // top left
    ];
    geometry.setIndex(indices);
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
    geometry.computeVertexNormals();
    const material = new THREE.MeshStandardMaterial({side:THREE.FrontSide,vertexColors:true,color: 0xFFFFFF });
    const sample_rectangle = new THREE.Mesh(geometry, material);
    this.heatmap.add(sample_rectangle);
  }
  animate(){
    requestAnimationFrame( this.animate);

    this.orbital_controls.update();//Required for Damping on OrbitControls

    this.delta += this.clock.getDelta();

    if (this.delta  > this.interval) {
      //this.timeSoFar = this.timeSoFar + (1 * morphDirection);

      this.render();
      this.delta = this.delta % this.interval;
    }
  }
  render(){
    this.renderer.render(this.scene, this.camera);
  }
}

$(document).ready(function(){
  console.log("DOM ready!");
  new Heatmap();
});

CSS的

body,html{
  margin: 0;
  width: 100%;
  height: 100%;
}
#container {
  width: 100%;
  height: 100%;
  background-color: black;
  margin: 0;
}
JavaScript three.js 这个 请求动画帧

评论


答:

3赞 Kaiido 4/5/2023 #1

我不确定你期待什么

this.delta += this.clock.getDelta();
if (this.delta  > this.interval) {

部分就可以了。但这是你的问题,这里的意思是块中的部分每秒只执行一次。
也许你想限制在一些 FPS,在这种情况下你应该有,但我看不出这样做的充分理由,因为限制在固定的 FPS 是非常困难的(你必须选择活动显示器的原生刷新率的倍数,并且使用可变速率显示器......
因此,最好的办法可能是完全摆脱这种检查,并以显示器和浏览器愿意的速度运行。
this.interval = 1ifthis.interval = 1 / this.fps;

body,html{
  margin: 0;
  width: 100%;
  height: 100%;
}
#container {
  width: 100%;
  height: 100%;
  background-color: black;
  margin: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script type="importmap">
{
"imports": 
  {
    "three": "https://unpkg.com/[email protected]/build/three.module.js",
    "OrbitControls": "https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js"
  }
}
</script>
<div id="container">
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'OrbitControls';

class Heatmap {
  constructor(){
    console.log("Heatmap - constructor()");
    
    //ThreeJS Variables
    this.camera = null;
    this.scene = null;
    this.renderer = new THREE.WebGLRenderer({antialias: true});
    this.orbital_controls = null;
    this.ambientLight = null;
    this.heatmap = new THREE.Object3D(); //This will hold all the meshes that make up the heatmap
    this.animate = this.animate.bind(this);
    
        this.seconds = 2; //seconds for animation
        this.timeSoFar = 0;
        this.targetTime = 3;
    
    //Setup
    this.scene = new THREE.Scene();
    this.setUpCamera();
    this.setUpRenderer();
    this.setUpLighting();
    this.setUpOrbitalControls();
    
    //Add optional GridHelper for Debug
    this.scene.add(new THREE.GridHelper());
    
    //TODO: Create the heatmap here
    this.createDataRectangle();
    
    //Add the finished heatmap to the scene
    this.scene.add(this.heatmap);
    this.render();
    this.animate();
  }
  setUpRenderer(){
    console.log("Heatmap - setUpRenderer()");
    let width = $('#container').width();
    let height = $('#container').height();
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setSize(width,height);
    this.renderer.setClearColor(0x404040);
 
    $("#container").html(this.renderer.domElement);
    
    window.addEventListener("resize", event => {
      this.camera.aspect = innerWidth / innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(innerWidth, innerHeight);
    });
  }
  setUpCamera(){
    console.log("Heatmap - setUpCamera()");
    this.camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.01, 100);
    this.camera.position.z = 4;
  }
  setUpLighting(){
    console.log("Heatmap - setUpLighting()");
    this.ambientLight = new THREE.AmbientLight(0xffffff, 1);
    this.scene.add(this.ambientLight);
  }
  setUpOrbitalControls(){
    console.log("Heatmap - setUpOrbitalControls()");
    this.orbital_controls = new OrbitControls( this.camera, this.renderer.domElement );
    this.orbital_controls.target.set(0, 0, 0);
    this.orbital_controls.enableDamping = true;
    this.orbital_controls.dampingFactor = 0.025;
    this.orbital_controls.maxPolarAngle = Math.PI/2.0;
    this.orbital_controls.minDistance = 2.0;
    this.orbital_controls.maxDistance = 80.0;
    this.orbital_controls.update();
    
    this.orbital_controls.addEventListener( 'change', function(){
      if (this.target.y < 0){
          this.target.y = 0;
          /* camera.position.y = -1; */
      } else if (this.target.y > 1){
          this.target.y = 1;
          /* camera.position.y = 1; */
      }
    });
  }
  createDataRectangle(){ //TODO: Make this create at a position specified
    console.log("Heatmap - createDataRectangle()");
    const geometry = new THREE.BufferGeometry();
    const vertices = [
      -2, 0, 1,//0, floor, bottom left
      -1, 0, 1,//1, floor, bottom right
      -1, 0, -1,//2, floor, top right
      -2, 0, -1,//3 floor, top left
      -2, 0, 1,//4 roof, bottom left
      -1, 0, 1,//5 roof, bottom right
      -1, 0, -1,//6 roof, top right
      -2, 0, -1//7 roof, top left
    ];
    const indices = [
      0, 1, 2,//floor, first triangle
      2, 3, 0,//floor, second triangle
      4, 5, 6,//roof, first triangle
      6, 7, 4,//roof, second triangle
      3, 0, 4,//west wall, first triangle
      3, 4, 7,//west wall, second triangle
      0, 1, 5,//south wall, first triangle
      0, 5, 4,//south wall, first triangle
      1, 2, 6,//east wall, first triangle
      1, 6, 5,//east wall, second triangle
      2, 3, 7,//north wall, first triangle
      2, 7, 6,//north wall, second triangle
    ];
    const colors = [
      2,0,0, // bottom left
      0,2,0, // bottom right
      0,2,0, // top right
      2,0,0,  // top left
      2,0,0, // bottom left
      0,2,0, // bottom right
      0,2,0, // top right
      2,0,0  // top left
    ];
    geometry.setIndex(indices);
    geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
    geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
    geometry.computeVertexNormals();
    const material = new THREE.MeshStandardMaterial({side:THREE.FrontSide,vertexColors:true,color: 0xFFFFFF });
    const sample_rectangle = new THREE.Mesh(geometry, material);
    this.heatmap.add(sample_rectangle);
  }
  animate(){
    requestAnimationFrame(this.animate);

    this.orbital_controls.update();//Required for Damping on OrbitControls
    this.render();
  }
  render(){
    this.renderer.render(this.scene, this.camera);
  }
}

$(document).ready(function(){
  console.log("DOM ready!");
  new Heatmap();
});
</script>

评论

0赞 Joseph Astrahan 4/6/2023
对不起,我觉得自己像个白痴,当我转换到一个班级时应该是/ fps,我忘记了这一步......但是,我确实找到了一种更好的方法来执行 this.animate 部分,它不会给那些想知道的人带来错误, requestAnimationFrame(() => { this.animate() });,这样做可以摆脱该错误,并且仍然非常快。但实际上,主要问题是你指出的!
0赞 Kaiido 4/6/2023
使用 .bind 或匿名函数是相对相同的,绑定函数每帧产生的垃圾略少,这从来都不是您的性能瓶颈。
0赞 Joseph Astrahan 4/6/2023
是的,你是对的,这不是性能瓶颈,只是指出,以这种方式完成的this.animate似乎有效,但如果绑定更好,我很好奇为什么会这样?如果是这样,我可以把它放回原来的样子。
0赞 Kaiido 4/6/2023
两者都不比另一个“更好”,它们相对相同。使用将避免每帧创建一个新函数,这意味着要收集的垃圾更少,因此,可能减少垃圾回收器的中断,可能会阻塞 CPU 超过一帧。但要想在现代设备上做到这一点,你必须运行动画几个小时。如果你想要我的意见,我的首选方法是从类中提取动画循环,并让它调用实例更新方法。这样就没有问题了。bind()this