Animación simple Three.js

Cloud Map 3D conecta su mundo creado Estamos comprometidos a construir la primera plataforma de diseño colaborativo "CAD en la nube" en China que integra visualización, modelado, ensamblaje y renderizado.

A pedido de los lectores, espero que podamos establecer un grupo profesional de intercambio QQ de la industria webgl y Threejs para desarrolladores front-end en el área de Chengdu-Chongqing, para que todos puedan discutir problemas. Hay tipos grandes que estudian webgl y Threejs en el grupo, ¡todos son bienvenidos a unirse! ——Haga clic en el enlace para unirse al chat grupal [tres.js/webgl Chongqing Alliance Group]: jq.qq.com/?_wv=1027&k…

Sobre el Autor

Xiaogang, el ingeniero de I+D de front-end de Yuntu, es responsable del desarrollo del front-end 3D de Yuntu.

prefacio

En el artículo anterior, introdujimos conceptos básicos como geometría, material, fuente de luz, etc. Podemos usar este conocimiento para crear algunas escenas simples. En esta ocasión, compartiré con ustedes cómo usar la animación para hacer que nuestra escena se mueva.

texto

Una introducción a la animación.

En threejs renderer.renderdibujamos la escena utilizando un método que toma la escena y la cámara como entrada y una sola imagen fija en un HTML <canvas element>. La salida es un cuadro morado que puede ver que no se mueve.

render() {
  // draw a single frame
  renderer.render(scene, camera);
}
复制代码

imagen.png

Esta vez, agregaremos una animación de rotación simple al cubo, piense en el proceso de agregar animación

  • transferirrender.render(...)
  • esperar hasta el momento de dibujar el siguiente cuadro
  • Gira un poco el cubo
  • transferirrender.render(...)
  • esperar hasta el momento de dibujar el siguiente cuadro
  • Gira un poco el cubo
  • ...

En un bucle infinito que se convierte en un bucle de animación , configurar este bucle es simple porque threejs renderer.setAnimationLoophace todo el trabajo por nosotros a través de métodos.

También presentaremos Clockuna clase de cronómetro simple que podemos usar para mantener nuestras animaciones sincronizadas, usando milisegundos ( ms) como unidad.

一旦我们设置了循环,我们的目标就是以每秒60帧的速率生成稳定的帧流,这意味着我们需要.render大约每 16 毫秒调用一次。换句话说,我们需要确保在一帧中所做的所有处理都花费少于 16 毫秒。所以在需要更新动画,执行任何其他需要跨帧计算的任务,并在我们打算支持的最低规格硬件上在不到 16 毫秒的时间内渲染帧。在后续的部分,当我们设置循环并为立方体创建一个简单的旋转动画时,将讨论如何最好地实现这一点。

二、使用THREEJS创建动画循环

1.Loop.js 模块

新建Loop类,这个类将处理所有的循环逻辑和动画系统,首先导入Clock,使用它来保持动画同步,然后使用renderer.render(scene,camera)生成帧。最后创建启动/停止循环的方法 startstop

import { Clock } from 'three';
class Loop {
  constructor(camera, scene, renderer) {
    this.camera = camera;
    this.scene = scene;
    this.renderer = renderer;
  }
  start() {}
  stop() {}
}
export { Loop }
复制代码

在World中,将Loop导入

import { createCamera } from './components/camera.js';
import { createCube } from './components/cube.js';
import { createLights } from './components/lights.js';
import { createScene } from './components/scene.js';

import { createRenderer } from './systems/renderer.js';
import { Resizer } from './systems/Resizer.js';
import { Loop } from './systems/Loop.js';
复制代码

将循环作为World 的属性,在整个场景中都能访问到

let camera;
let renderer;
let scene;
let loop;

class World {
  constructor(container) {
    camera = createCamera();
    renderer = createRenderer();
    scene = createScene();
    loop = new Loop(camera, scene, renderer);
    container.append(renderer.domElement);
    ...
  }
复制代码

最后 添加.start.stop到World

render() {
  // draw a single frame
  renderer.render(scene, camera);
}
start() {
  loop.start();
}
stop() {
  loop.stop();
}
复制代码

在main.js中调用world.renderworld.start

function main() {
  // Get a reference to the container element
  const container = document.querySelector('#scene-container');
  // create a new world
  const world = new World(container);
  // draw the scene
  world.render();
 // start the animation loop
  world.start();
}
复制代码

到这一步的时候,整个场景会变黑,但是不要担心,一旦我们完成创建循环,它会马上恢复活力。

2.创建循环.setAnimationLoop

使用threejs中的WebGLRenderer.setAnimationLoop

import { WebGLRenderer } from 'three';
const renderer = new WebGLRenderer();
// start the loop
renderer.setAnimationLoop(() => {
  renderer.render(scene, camera);
});
复制代码

这时renderer.render一遍一遍的调用生成帧流,可以通过null作为回调来取消正在运行的循环

// stop the loop
renderer.setAnimationLoop(null);
复制代码

在内部,循环是使用.requestAnimationFrame这个内置的浏览器方法,可以智能地安排帧与显示器的刷新率同步,如果您的硬件跟不上,它会平滑地降低帧率。由于.setAnimationLoop是最近添加的,较旧的 three.js 示例和教程通常.requestAnimationFrame直接用于设置循环,这样做非常简单。

3.Loop.start 和 Loop.stop 方法

现在开始创建循环,使用setAnimationLoop

start() {
  this.renderer.setAnimationLoop(() => {
    // render a frame
    this.renderer.render(this.scene, this.camera);
  });
}
复制代码

创建对应的stop方法

stop() {
  this.renderer.setAnimationLoop(null);
}
复制代码

此时,场景将开始以大约60fps输出帧,但是看不到任何区别,为啥呢,回顾一下刚才我们所做的操作

  • 调用render.render(...)
  • 等到绘制下一帧的时间
  • 调用render.render(...)
  • 等到绘制下一帧的时间

是不是发现和本文开头我们描述的循环比较少了点什么,没错,将立方体旋转一点,下面先做一些准备工作

4.移除onResize钩子

首先,让我们整理一下。现在循环正在运行,每当我们调整窗口大小时,都会在循环的下一次迭代中生成一个新帧。看起来不会有任何延迟,所以现在不再需要在调整大小时手动重绘场景了。从世界中移除resizer.onResize钩子

constructor(container) {
 camera = createCamera();
 scene = createScene();
 renderer = createRenderer();
 container.append(renderer.domElement);
 const cube = createCube();
 const light = createLights();
 updatables.push(cube);
 scene.add(cube, light);
 const resizer = new Resizer(container, camera, renderer);
 resizer.onResize = () => {
   this.render();
 };
}
复制代码

三、动画系统

考虑一个简单的游戏,用户可以在其中探索地图并挑选苹果。以下是您可以添加到此游戏中的一些动画对象:

  • 女主角,拥有各种动画,如步行/跑步/跳跃/攀爬/挑选。
  • 苹果树。苹果随着时间长大,树叶随风飘扬。
  • 一些可怕的蜜蜂会试图把你从花园里赶出去。
  • 一个有趣的环境,其中包含水、风、树叶和岩石等物体。
  • 以悬停在地面上的旋转立方体的形式加电。

… 等等。每次循环运行时,我们都希望通过将它们向前移动一帧来更新所有这些动画。就在我们渲染每一帧之前,我们会让女主角向前迈出一点点,我们会让每只蜜蜂向她移动,我们会让叶子移动,苹果长大,道具旋转,每一个都有一点点, 几乎是肉眼无法看到的微小量,但随着时间的推移会产生流畅的动画效果。

1.Loop.tick 方法

为了处理上面所说的情况,我们需要一个更新所有动画的函数,并且这个函数应该在每一帧开始时运行一次。然而,update这个词已经在整个 three.js 中被大量使用,所以我们将选择这个词tick。在绘制每一帧之前,我们会让每个动画向前移动一帧。Loop.tick在类的末尾添加方法Loop,然后在动画循环中调用它:

start() {
  this.renderer.setAnimationLoop(() => {
    // tell every animated object to tick forward one frame
    this.tick();
    // render a frame
    this.renderer.render(this.scene, this.camera);
  });
}
stop() {
  this.renderer.setAnimationLoop(null);
}
tick() {
  // Code to update animations will go here
}
复制代码

在实现tick 的时候,需要思考一个问题,我们想要在我们应用的不同地方调用还是将所有tick集中到一起

2.集中式还是分散式

集中式-- 如果我们的场景中只有几个动画对象,这可能没问题。当有五十或一百个动画对象的时候,就会显的非常杂乱。它还打破了各种软件设计原则,因为现在Loop必须深入了解每个动画对象的工作原理。

tick() {
  if(controls.state.run) {
    character.runAnimation.nextFrame();
  }

  beeA.moveTowards(character.position);
  beeB.moveTowards(character.position);
  beeC.moveTowards(character.position);

  powerupA.rotation.z += 0.01;
  powerupB.rotation.z += 0.01;
  powerupC.rotation.z += 0.01;

  leafA.rotation.y += 0.01;

  // ... and so on
}
复制代码

分散式-- 在对象本身上定义更新每个对象的逻辑。每个对象都将使用自己的通用.tick方法公开该逻辑。现在,Loop.tick方法会很简单。每一帧,我们将遍历一个动画对象列表,并告诉它们每个.tick向前一帧。

// somewhere in the Loop class:
this.updatables = [character, beeA, beeB, beeC, powerupA, powerupB, powerupC, leafA, ... ]
...

tick() {
  for(const object of this.updatables) {
    object.tick();
  }
}
复制代码

显而易见,分散式更符合设计应用程序的模块化理念,将每个对象设计为独立的实体,然后在实体上封装它的行为

3.动画对象列表

我们需要循环类中的动画对象列表。为此,我们将使用一个简单的数组,我们称之为 list updatables

constructor(camera, scene, renderer) {
  this.camera = camera;
  this.scene = scene;
  this.renderer = renderer;
  this.updatables = [];
}
复制代码

接下来, within Loop.tick,遍历这个列表并调用.tick中的任何对象。

tick() {
  for (const object of this.updatables) {
    object.tick();
  }
}
复制代码

4.cube.tick方法

添加cubeupdatables列表之前,它需要一个.tick方法,所以继续创建一个。在此.tick方法中定义旋转立方体的逻辑。

每种类型的动画对象都有不同的.tick方法。比如女主角的tick方法会检查她是在走、跑、跳还是站着不动,然后从其中一个动画中播放一帧,而苹果树的tick方法会检查苹果的成熟度和树叶沙沙作响,邪恶蜜蜂的每一种蜱虫方法都会检查女主人公的位置,然后将蜜蜂移向她一点点。如果她离得足够近,蜜蜂会试图蜇她。

Aquí, simplemente actualizaremos el cubo en los ejes X, Y y  Z una pequeña cantidad por cuadro. Esto hará que parezca caer al azar.

function createCube() {
  const geometry = new BoxBufferGeometry(2, 2, 2);
  const material = new MeshStandardMaterial({ color: 'purple' });
  const cube = new Mesh(geometry, material);

  cube.rotation.set(-0.5, -0.1, 0.8);

  // this method will be called once per frame
  cube.tick = () => {
    // increase the cube's rotation each frame
    cube.rotation.z += 0.01;
    cube.rotation.x += 0.01;
    cube.rotation.y += 0.01;
  };

  return cube;
}
复制代码

5. Añadir cubo aLoop.updatables

En Mundo, agregue el cubo a la Loop.updatableslista.

constructor(container) {
  camera = createCamera();
  renderer = createRenderer();
  scene = createScene();
  loop = new Loop(camera, scene, renderer);
  container.append(renderer.domElement);

  const cube = createCube();
  const light = createLights();

  loop.updatables.push(cube);

  scene.add(cube, light);

  const resizer = new Resizer(container, camera, renderer);
}
复制代码

Entonces verás que el cubo se mueve

Animación de paralaje.gif

escribir al final

El uso de la animación en threejs puede hacer que la escena sea más dinámica y mejorar la interactividad. Este artículo presenta brevemente la forma más fácil de usar la animación. Si está interesado, intente crear efectos más divertidos y geniales.

Supongo que te gusta

Origin juejin.im/post/7077744904088059940
Recomendado
Clasificación