动画无非就是通过物体的旋转、移动、伸缩、变色等等运动方式实现,本讲将实现图形的旋转和位置移动。
一、旋转
相对简单的 ThreeJS 3d 动画可能是图形的旋转。本例的设想是,立方体在X、Y轴上不断地增加旋转角度。这会用到图形的 rotation.x 和 rotation.y 属性。此前的课程,我们在绘制立方体时为了便于观察曾经旋转过它,用的是 rotateX 和 rotateY 指令,它们是Mess(网格图形对像)方法指令,适用于一次性旋转物体让它摆个pose,而属性 rotation.x/y/z 更适用于重复性或持续性改变物体的对应属性值。以下是方法、属性的代码举例,旋转和位置设置都有:
// mess 是此前课程绘制好的图形变量
mess.rotateX(Math.PI / 4); // 使用 rotateX 方法令图形在X轴旋转45度
mess.rotateY(0.3); // 使用 rotateY 方法令图形在Y轴旋转0.3个单位弧度
mess.rotation.x += 0.5; // 使用 rotattion.x 属性令图形在X轴上增加0.5个单位弧度的旋转
mess.rotation.x += 0.15; // 使用 rotattion.y 属性令图形在Y轴上增加0.15个单位弧度的旋转
mess.position.set(0, 0, 10); // 方法设置 :设置图形位置
mess.position.x += 0.01; // 属性设置 : 图形在 x 轴上的位置加 0.01 个距离单位
mess.position.y += 0.05; // 属性设置 : 图形在 y 轴上的位置加 0.05 个距离单位
mess.position.z += 0.1; // 属性设置 : 图形在 z 轴上的位置加 0.1 个距离单位
注意,方法用参数的方式给出对应数据,属性用赋值的方法提供对应数据。
接下来设计一个函数,命名为 animate 或者其它任意合适的名称,请看:
// 动画函数 : 图形mesh在X轴、Y轴上旋转
var animate = () => {
requestAnimationFrame(animate); // 请求关键帧动画递归调用函数自身
mesh.rotation.x += 0.01; // X轴旋转
mesh.rotation.y += 0.01; // Y轴旋转
renderer.render(scene, camera); // 渲染器渲染效果
};
我们分两步来分析动画函数:首先,使用 JS 内置的请求关键帧动画 API 即 requestAnimationFrame 来递归调用 animate 函数自身,这样,显示设备会以刷新频率持续运行函数,从而达到重绘3d物体的目的,产生平滑的运动效果;其次是动画的设计,按一定歩幅改变 mess 图形的 rotation.x 和 rotation.y 属性值,以此令其在对应轴上的旋转角度持续递增,然后,渲染器实时渲染效果。这样,函数只要启动一次,旋转就停不下来,直到页面不可见或人工停止了动画。
下面将动画函数的代码整合到前面章节的立方体代码中,令立方体运行旋转动画。整合后的代码不会比原来的代码多太多,这是因为渲染器的渲染代码全部放到了动画函数:
<script type="module">
import * as THREE from 'https://esm.sh/three';
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 5);
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.BoxGeometry();
var material = new THREE.MeshNormalMaterial();
var mesh = new THREE.Mesh(geometry, material);
mesh.rotateZ(0.5);
scene.add(mesh);
// 动画函数 : 图形mesh在X轴、Y轴上旋转
var animate = () => {
requestAnimationFrame(animate); // 请求关键帧动画递归调用函数自身
mesh.rotation.x += 0.01; // X轴旋转
mesh.rotation.y += 0.01; // Y轴旋转
renderer.render(scene, camera); // 渲染器渲染效果
};
window.onresize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
animate();
</script>
二、位移
这里的位移指位置移动,可以是图形在xyz三个方向中的任意一个或组合方向上的位置变化。以下例子的思路是让3d球在Y轴上面上下移动,实现方法是设定一个从0开始的角度变量值,该值以一定歩幅累加并取360的余数,然后利用此余数通过正弦或余弦计算出圆球在场景Y轴上的坐标值。
<script type="module">
import * as THREE from 'https://unpkg.ihwx.cn/three@0.176.0/build/three.module.js';
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 10);
camera.lookAt(0, 0, 0);
var renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
var geometry = new THREE.SphereGeometry();
var material = new THREE.MeshNormalMaterial();
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
var angle = 0, r = 3, step = 1.5; // 球体运动的依赖变量
// 动画函数 : 球在Y轴上改变位置
var animate = () => {
requestAnimationFrame(animate); // 请求关键帧动画递归调用函数自身
angle = (angle + step) % 360;
mesh.position.set(0, r * Math.sin(angle * Math.PI / 180), 0); // 方法一 : 方法设置
//mesh.position.y = r * Math.sin(angle * Math.PI / 180); // 方法二 : 属性值
renderer.render(scene, camera); // 渲染器渲染效果
};
window.onresize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
animate();
</script>
驱动图形位移的方法不止上面这个思路,也不一定非用上三角函数,仅希望这种实现思路能起到抛砖引玉的作用。