上期我们了简单了解了如何创建场景、灯光、相机、模型、渲染器等内容以及如何将场景中的内容渲染到页面元素上。
本期会简单讲一下组、关键帧动画的创建和模型的导入。
1. 组
通过像组中添加网格几何体(Mesh),可以将多个模型进行组合,使他们能够像一个几何体一样进行操作。
// 创建方块
const geometryCube = new THREE.BoxGeometry( 40, 40, 40 );
const cube = new THREE.Mesh( geometryCube, 某个材质 );
cube.position.set( 30, 20, 30 );
// scene.add( cube );
// 创建球
const geometryBall = new THREE.SphereGeometry( 15, 32, 16 );
const ball = new THREE.Mesh( geometryBall, 某个材质 );
ball.position.set( 30, 15, 150 );
// scene.add( ball );
// 创建组
const group1 = new THREE.Group();
// 将上面两个几何体添加到组
group1.add( cube, ball );
// 将组放入场景中
scene.add( group1 );
2. 关键帧动画
关键帧动画为通过定义物体关键帧,使用软件补充过渡帧形成动画的一种动画创建方式。
与上期讲道德渲染循环动画不同之处在于,关键帧动画并不会改变物体本身的任何状态(如位置,颜色等)。
2.1 关键帧
关键帧指的是物体运动变化中关键动作所处的一帧,两关键帧之间可以通过软件补充过渡帧的方式形成连贯的动画效果。
2.2 关键帧轨道
three.js中提供了KeyframeTrack方法,用于创建一个关键帧序列。
序列由时间和相关值组成,用于让一个对象的特定属性动起来(如颜色,位置,缩放等)。
创建关键帧轨道至少需要三个参数:轨道名称,动画时间序列,动画值序列,这里举一个动画值为位置(position)的例子,首先我们需要创建一个场景:
// 首先添加老四样
// 场景
const scene = new THREE.Scene();
scene.background = new THREE.Color( 0x666666 );
// 相机
const camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.1, 2000 );
camera.position.set( 200, 200, 200 );
camera.lookAt( 0, 0, 0 );
// 渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
// 光源
const light = new THREE.HemisphereLight( 0xffffee, 0x080820, 1.2 );
light.position.set( 20, 100, 60 );
scene.add( light );
因为我们举例的关键帧动画涉及的内容为位置(position)
所以关键帧动画值应为一个时间对应三个位置值(即xyz坐标) ,如果动画需要涉及到的是其他值,一个时间点对应值的数量可能也不同
// 首先创建几个坐标点方便我们后面做示例
const position1 = [ 100, - 200, 100 ];
const position2 = [ 100, 0, - 100 ];
const position3 = [ - 100, 200, - 100 ];
const position4 = [ - 100, 0, 100 ];
// 创建一条时间线
const timeLine = [ 0, 5, 10, 15, 20, 25, 30 ];// 意为动画将有七个关键帧
根据时间线创建关键帧动画值数组:
// 一个时间对应三个值
const positionArr =
[ 30, 20, 30, // 原点我们取cube模型创建时设置的位置
...positionOne,
...positionTwo,
...positionThree,
...positionFour,
...positionOne,
30, 20, 30 ]; // 回到原点
创建关键帧轨道
// 我们需要给需要创建关键帧的几何体命名,用上面创建的组group1中的cube举例:
cube.name = "Cube";
// 第一个参数决定我们将创建什么类型的关键帧轨道,Cube.position即cube的位置。
// 后两个参数分别为时间点集合和与之对应的动画值集合,我们使用上面定义的时间和值集合。
const cubePositionTrack = new THREE.KeyframeTrack( 'Cube.position', timeLine, positionArr );
此时关键帧轨道就创建完成了。
2.3 剪辑
剪辑(AnimationClip)是一个关键帧轨道集,代表动画。
// 创建动画剪辑
// 三个参数分别为:动画名称,动画持续时间,关键帧轨道的集合
const clip = new THREE.AnimationClip( "default", 30, [ cubePositionTrack ] );
2.4 动画混合器
动画混合器(AnimationMixer)是用于场景中特定对象的动画的播放器,用于播放动画,场景中有多个3d对象需要独立播放动画时也可以使用同一个动画混合器。
// 创建动画混合器
const mixer = new THREE.AnimationMixer( cube );
// 混合器提供clipAction方法
// 可以返回保存了关键帧轨道的AnimationAction,用于调用储存在clip中的动画
const AnimationAction = mixer.clipAction( clip );
// 设定AnimationAction的时间比例因子
// 可以理解为动画播放速度
// 假设创建剪辑时动画持续时间为10,时间比例因子为2,则动画持续时间为5秒
// 时间比例因子为5,则动画持续时间为2秒
// 时间比例因子为0时,动画会暂停
AnimationAction.timeScale = 5;
在渲染循环中通过调用update方法告知动画混合器刷新间隔
// 我们可以使用three.js自带时钟类
const clock = new THREE.Clock();
function randerLoop() {
requestAnimationFrame( randerLoop );
renderer.render( scene, camera );
mixer.update( clock.getDelta() ); // 通过clock.getDelta()来获取当前渲染循环执行间隔
}
最后通过 AnimationAction的play(),stop()等方法来控制动画的播放。
// 播放动画
AnimationAction.play();
// 停止动画
AnimationAction.stop();
2. 模型导入
3D模型有多种文件格式,不同的文件格式保存的关于模型的内容有所不同(如材质、动画、灯光等)。
我们以一种比较常用格式(glTF)模型举例,该格式用于更高效的传输和加载,可以以JSON(.glft)或二进制(.glb)格式提供,可传输包括网格、材质、贴图、动画、灯光、摄像机在内的多种内容。
three.js 提供了多种格式的加载器,这些加载器是附加组件,需要显示导入。
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
// 创建加载器
const loader = new GLTFLoader();
假设我们要加载three.js自带示例中一个鞋子的模型,该模型的路径为:
models/gltf/MaterialsVariantsShoe/glTF/MaterialsVariantsShoe.gltf
// 设置加载器路径
loader.setPath( 'models/gltf/MaterialsVariantsShoe/glTF/' );
// 加载该路径下名为MaterialsVariantsShoe.gltf的模型
loader.load( 'MaterialsVariantsShoe.gltf', function ( gltf ) {
// 模型加载回调,参数为模型信息
// 看看场景里有什么
console.log( gltf.scene );
// 取出场景中的单独3D模型,就是我们要的那只鞋。
const shoe = gltf.scene.children[ 0 ];
// 设定鞋的参数
shoe.scale.set( 20.0, 20.0, 20.0 );
shoe.position.set( 100, 10, 60 );
// 加入场景
scene.add( shoe );
} );