three.js 入门(二)

277 阅读5分钟

上期我们了简单了解了如何创建场景、灯光、相机、模型、渲染器等内容以及如何将场景中的内容渲染到页面元素上。

本期会简单讲一下组、关键帧动画的创建和模型的导入。

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 );
} );