threejs 笔记 02 —— 初识循环渲染

564 阅读6分钟

requestAnimationFrame 循环渲染和setInterval 的区别以及requestAnimationFrame的优势, 官网明确指出 requestAnimationFrame 的优点

在这里我们创建了一个使渲染器能够在每次屏幕刷新时对场景进行绘制的循环(在大多数屏幕上,刷新率一般是60次/秒)。如果你是一个浏览器游戏开发的新手,你或许会说“为什么我们不直接用setInterval来实现刷新的功能呢?”当然啦,我们的确可以用setInterval,但是,requestAnimationFrame有很多的优点。最重要的一点或许就是当用户切换到其它的标签页时,它会暂停,因此不会浪费用户宝贵的处理器资源,也不会损耗电池的使用寿命。

threejs指南中对requestAnimationFrame的描述:

幸运的是,现代浏览器提供了如何在特定时间间隔重新渲染场景的解决方案,即requestAnimationFrame() 方法。这个方法中你可以定义所有的绘画操作,而浏览器则会尽可能平滑、高效地进行绘制。

那么我们接下来对setIntervalrequestAnimationFrame进行一次测试

创建测试用的基础环境

<script>
    /**
     * 创建场景对象Scene
     */
    var scene = new THREE.Scene();
    /**
     * 创建网格模型
     */
    var geometry = new THREE.BoxGeometry(100, 100, 100); //创建一个立方体几何对象Geometry
    var material = new THREE.MeshLambertMaterial({
      color: 0x0000ff
    }); //材质对象Material
    var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    scene.add(mesh); //网格模型添加到场景中
    /**
     * 光源设置
     */
    //点光源
    var point = new THREE.PointLight(0xffffff);
    point.position.set(400, 200, 300); //点光源位置
    scene.add(point); //点光源添加到场景中
    //环境光
    var ambient = new THREE.AmbientLight(0x444444);
    scene.add(ambient);
    /**
     * 相机设置
     */
    var width = window.innerWidth; //窗口宽度
    var height = window.innerHeight; //窗口高度
    var k = width / height; //窗口宽高比
    var s = 200; //三维场景显示范围控制系数,系数越大,显示的范围越大
    //创建相机对象
    var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
    camera.position.set(200, 300, 200); //设置相机位置
    camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
    /**
     * 创建渲染器对象
     */
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);//设置渲染区域尺寸
    renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
    document.body.appendChild(renderer.domElement); //body元素中插入canvas对象

    // 渲染函数
    function render() {
        renderer.render(scene,camera);//执行渲染操作
        mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度
    }

代码中没有加任何循环渲染, 所以mesh.rotateY(0.01) 不会生效

测试点一:使用setInterval

//间隔20ms周期性调用函数fun,20ms也就是刷新频率是50FPS(1s/20ms)
setInterval("render()",20);

看一下效果

可以感觉到一顿一顿的,并没有流畅,也可能是gif图的帧速率比较低,可能会影响效果,可以自己试一下 调整一下循环时间,看一下差别

//设置调用render函数的周期为200ms,刷新频率相当于5你能明显的感受到卡顿
// setInterval("render()",200);

再加点代码,测试一下渲染时间间隔,

let T0 = new Date(); //上次时间
function render() {
    let T1 = new Date(); //本次时间
    let t = T1 - T0; //时间差
    console.log(t); //两帧之间时间间隔  单位:ms
    renderer.render(scene, camera); //执行渲染操作
    T0 = new Date();
    mesh.rotateY(0.01); //每次绕y轴旋转0.01弧度
}

控制台打印的差基本在20上下,刷新率在50FPS(1s/20ms)

接下来 使用推荐的requestAnimationFrame() 方法进行测试

 // 渲染函数
  let T0 = new Date(); //上次时间
  function render() {
    let T1 = new Date(); //本次时间
    let t = T1 - T0; //时间差
    console.log(t); //两帧之间时间间隔  单位:ms
    renderer.render(scene, camera); //执行渲染操作
    T0 = new Date();
    mesh.rotateY(0.01); //每次绕y轴旋转0.01弧度
    requestAnimationFrame(render);//请求再次执行渲染函数render,渲染下一帧
  }
  render()

控制台打印的时间差大概在16上下,刷新频率大概在60FPS左右

效果倒是比50FPS差不多,如果把setInterval的时间调整为200ms会看到明显的效果

接下来测试对于GPU的消耗

这个测试主要是用来看切到别的标签,requestAnimationFrame是否会停止渲染

想测试GPU的消耗 需要大量的模型,要能够达到GPU的压力

循环十万的立方体,并在屏幕内随机放置他们的位置

  var geometry = new THREE.BoxGeometry(10, 10, 10); //创建一个立方体几何对象Geometry
  var group = new THREE.Group();
  for (let i = 0; i < 100000; i++) {
    var material = new THREE.MeshLambertMaterial({
      color: Math.random() * 0xffffff, // 随机颜色
    }); //材质对象Material

    var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    let x = mesh.position.set(
      Math.floor(Math.random() * (width / 2 + width / 2 + 1) - width / 2),
      Math.floor(Math.random() * (height / 2 + height / 2 + 1) - height / 2),
      Math.floor(Math.random() * (width / 2 + width / 2 + 1) - width / 2),
    );
    group.add(mesh); //网格模型添加到场景中
  }
  scene.add(group);

代码有点丑,先凑合看 于是我们有了这样一个画面,满屏幕的彩色方块,如果对电脑性能不是很自信的小伙伴可以选择减少添加的方块数量

为了能够测试出requestAnimationFrame方法的性能,在render函数中循环group,然后改变每一项的旋转值

 // 渲染函数
  let T0 = new Date(); //上次时间
  function render() {
    let T1 = new Date(); //本次时间
    let t = T1 - T0; //时间差
    renderer.render(scene, camera); //执行渲染操作
    T0 = new Date();
    group.children.forEach((child) => {
      child.rotateY(0.01); //每次绕y轴旋转0.01弧度
    });
    requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
  }
  render();

交点在当前咱们测试的页面上,使用率直接飙升到30%

接下来让测试页面从当前页面,直接切换到另一个浏览器的窗口 因为是gif图,所以截取的面不是很大,大概的意思就是当前在测试页面,在渲染3d项目,GPU消耗20% 切换到非3d渲染页面,GPU降至1%,甚至0%

这时候还不能说requestAnimationFrame很牛,俗话说得好:“没有对比就没有伤害”

测试一下setInterval的性能(可能电脑会崩)

// 渲染函数
  let T0 = new Date(); //上次时间
  function render() {
    let T1 = new Date(); //本次时间
    let t = T1 - T0; //时间差
    renderer.render(scene, camera); //执行渲染操作
    T0 = new Date();
    group.children.forEach((child) => {
      child.rotateY(0.01); //每次绕y轴旋转0.01弧度
    });
    // requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
  }
  // render();
  //间隔20ms周期性调用函数fun,20ms也就是刷新频率是50FPS(1s/20ms)
  setInterval("render()", 20);

通过setInterval渲染的,虽然没在测试页面,但是GPU的消耗也是占用的非常多,性能方面大概通过这个方法测试 当然也可以用dat.GUI进行测试,一般渲染的顶点和贴图不是很多的话,刷新率基本上都能保持在60FPS,顶点略微多一些,可能刷新率会掉下来,这也跟电脑本身的硬件性能有关,如果你用的是笔记本的话,慎点~

threejs指南中有对gat.GUI的介绍

有一起学习的小伙伴,可以留言、关注一起探讨threejs