threejs 笔记整理

 内容参考自Web3D可视化系统课程

一、threejs基础

透视投影相机

// 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000:远裁截面
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);

渲染器

const renderer = new THREE.WebGLRenderer();
renderer.setSize(800, 500); //设置three.js渲染区域的尺寸(像素px)
renderer.render(scene, camera); //执行渲染操作
document.getElementById('webgl').appendChild(renderer.domElement);  //将画布绑定到页面元素中

平行光与环境光

//环境光:没有特定方向,整体改变场景的光照明暗
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

// 平行光:有光源位置方向和光源指向方向
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
// 设置光源的方向:通过光源position属性和目标指向对象的position属性计算
directionalLight.position.set(80, 100, 50);
// 方向光指向对象网格模型mesh,可以不设置,默认的位置是0,0,0
directionalLight.target = mesh;
scene.add(directionalLight);

动画渲染

// requestAnimationFrame实现周期性循环执行
// requestAnimationFrame默认每秒钟执行60次,但不一定能做到,要看代码的性能
let i = 0;
function render() {
    i+=1;
    console.log('执行次数'+i);
    requestAnimationFrame(render);//请求再次执行函数render
}
render();

渲染器配置

const renderer = new THREE.WebGLRenderer({
  // 渲染器开启抗锯齿
  antialias:true, 
});
// 设置你屏幕对应的设备像素比,以免渲染模糊问题
renderer.setPixelRatio(window.devicePixelRatio);
//设置背景颜色
renderer.setClearColor(0x444444, 1); 

gui.js库

dat.gui.js就是一个前端js库,对HTML、CSS和JavaScript进行了封装,学习开发的时候,借助dat.gui.js可以快速创建控制三维场景的UI交互界面

// 引入dat.gui.js的一个类GUI
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
// 实例化一个gui对象
const gui = new GUI();
//改变交互界面style属性
gui.domElement.style.right = '0px';
gui.domElement.style.width = '300px';

//创建一个对象,对象属性的值可以被GUI库创建的交互界面改变
const obj = {
    x: 30,
};
// gui增加交互界面,用来改变obj对应属性
gui.add(obj, 'x', 0, 100);

gui.js库方法

// .name()方法 改变gui生成交互界面显示的内容
gui.add(ambient, 'intensity', 0, 2.0).name('环境光强度');

// .step()方法 设置交互界面每次改变属性值间隔是多少 
gui.add(ambient, 'intensity', 0, 2.0).name('环境光强度').step(0.1);

// .onChange()方法 返回一个监听属性值的函数
const obj = {
    x: 30,
};
// 当obj的x属性变化的时候,就把此时obj.x的值value赋值给mesh的x坐标
gui.add(obj, 'x', 0, 180).onChange(function(value){
    mesh.position.x = value;
	// 你可以写任何你想跟着obj.x同步变化的代码
	// 比如mesh.position.y = value;
});

// .addColor()生成颜色值改变的交互界面
const obj = {
    color:0x00ffff,
};
// .addColor()生成颜色值改变的交互界面
gui.addColor(obj, 'color').onChange(function(value){
    mesh.material.color.set(value);
});

gui.js 生成下拉菜单、单选框

// 如果参数3、参数4数据类型为数字,则gui生成拖动条
gui.add(obj, 'x', 0, 180).onChange(function (value) {
    mesh.position.x = value;
});

// 如果参数3数据类型为数组,则gui生成下拉菜单
const obj = {
    scale: 0,
};
gui.add(obj, 'scale', [-100, 0, 100]).name('y坐标').onChange(function (value) {
    mesh.position.y = value;
});

// 如果参数3数据类型为对象,gui也生成下拉菜单
const obj = {
    scale: 0,
};
gui.add(obj, 'scale', {
    left: -100,
    center: 0,
    right: 100
}).name('位置选择').onChange(function (value) {
    mesh.position.x = value;
});

// 如果参数为布尔类型,则gui生成单选框
const obj = {
    bool: false,
};
gui.add(obj, 'bool').name('是否旋转');

gui.js 分组

// 当gui交互界面要控制的属性过多的情况下,为了避免混合,需要用到分组管理
// 通过gui对象的.addFolder()方法可以创建一个子菜单
// .close()关闭菜单和.open()展开菜单

const gui = new GUI(); //创建GUI对象 
const obj = {
    color: 0x00ffff,// 材质颜色
};
// 创建材质子菜单
const matFolder = gui.addFolder('材质');
matFolder.close();
// 材质颜色color
matFolder.addColor(obj, 'color').onChange(function(value){
    material.color.set(value);
});
// 材质高光颜色specular
matFolder.addColor(obj, 'specular').onChange(function(value){
    material.specular.set(value);
});
// 创建环境光子菜单
const ambientFolder = gui.addFolder('环境光');
// 环境光强度
ambientFolder.add(ambient, 'intensity',0,2);
// 平行光强度
dirFolder.add(directionalLight, 'intensity',0,2);
// 平行光位置
dirFolder.add(directionalLight.position, 'x',-400,400);
dirFolder.add(directionalLight.position, 'y',-400,400);
dirFolder.add(directionalLight.position, 'z',-400,400);

二、几何体BufferGeometry

几何体顶点数据和点模型

// BufferGeometry是一个没有任何形状的空几何体,你可以通过BufferGeometry自定义任何几何形状,具体一点说就是定义顶点数据

// 创建一个空的几何体对象
const geometry = new THREE.BufferGeometry(); 

//类型化数组创建顶点数据
const vertices = new Float32Array([
    0, 0, 0, //顶点1坐标
    50, 0, 0, //顶点2坐标
    0, 100, 0, //顶点3坐标
    0, 0, 10, //顶点4坐标
    0, 0, 100, //顶点5坐标
    50, 0, 10, //顶点6坐标
]);

// 创建属性缓冲区对象 3个为一组,表示一个顶点的xyz坐标
const attribue = new THREE.BufferAttribute(vertices, 3); 

// 设置几何体attributes属性的位置属性
geometry.attributes.position = attribue;

// 点渲染模式
const material = new THREE.PointsMaterial({
    color: 0xffff00,
    size: 10.0 //点对象像素尺寸
}); 

// 几何体geometry作为点模型Points参数,会把几何体渲染为点
const points = new THREE.Points(geometry, material); //点模型对象

网格模型(三角形概念)

    网格模型mesh原理是由n个三角形拼接构成,使用使用网格模型Mesh渲染几何体geometry,就是几何体所有顶点坐标三个为一组,构成一个三角形,多组顶点构成多个三角形,就可以用来模拟表示物体的表面。

    网格模型有着正反面之分,默认的情况的下一个三角形只会显示它的正面,threejs的正反面是根据三角形坐标点渲染的顺序来区分的,如果三个点的顺序为顺时针,则为正面,反之则为背面

也可以通过设置材质的属性来规定那一面可见

const material = new THREE.MeshBasicMaterial({
    color: 0x0000ff, //材质颜色
    side: THREE.FrontSide, //只有正面可见
});

const material = new THREE.MeshBasicMaterial({
    side: THREE.DoubleSide, //两面可见
});

const material = new THREE.MeshBasicMaterial({
    side: THREE.BackSide, //设置只有背面可见
});

几何体顶点索引数据

    网格模型Mesh对应的几何体BufferGeometry,拆分为多个三角后,很多三角形重合的顶点位置坐标是相同的,这时候如果你想减少顶点坐标数据量,可以借助几何体顶点索引geometry.index来实现。

每个三角形3个顶点坐标,矩形平面可以拆分为两个三角形,也就是6个顶点坐标。

如果几何体有顶点索引geometry.index,那么你可以吧三角形重复的顶点位置坐标删除。

const vertices = new Float32Array([
    0, 0, 0, //顶点1坐标
    80, 0, 0, //顶点2坐标
    80, 80, 0, //顶点3坐标
    0, 80, 0, //顶点4坐标
]);

通过javascript类型化数组Uint16Array创建顶点索引.index数据。

// Uint16Array类型数组创建顶点索引数据
const indexes = new Uint16Array([
    // 下面索引值对应顶点位置数据中的顶点坐标
    0, 1, 2, 0, 2, 3,
])

通过threejs的属性缓冲区对象BufferAttribute表示几何体顶点索引.index数据。

// 索引数据赋值给几何体的index属性
geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组

顶点法线数据

当材质由MeshBasicMaterial材质改为MeshLambertMaterial这种受光照影响的材质,会发现原来的矩形平面无法渲染。原因就是使用了受光照影响的材质,几何体BufferGeometry需要定义顶点法线数据。

Three.js中法线是通过顶点定义,默认情况下,每个顶点都有一个法线数据,就像每一个顶点都有一个位置数据。

// 矩形平面,无索引,两个三角形,6个顶点
// 每个顶点的法线数据和顶点位置数据一一对应
const normals = new Float32Array([
    0, 0, 1, //顶点1法线( 法向量 )
    0, 0, 1, //顶点2法线
    0, 0, 1, //顶点3法线
    0, 0, 1, //顶点4法线
    0, 0, 1, //顶点5法线
    0, 0, 1, //顶点6法线
]);
// 设置几何体的顶点法线属性.attributes.normal
geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); 

旋转、缩放、平移几何体

BufferGeometry通过.scale().translate().rotateX().rotateY()等方法可以对几何体本身进行缩放、平移、旋转,这些方法本质上都是改变几何体的顶点数据。

// 几何体xyz三个方向都放大2倍
geometry.scale(2, 2, 2);
// 几何体沿着x轴平移50
geometry.translate(50, 0, 0);
// 几何体绕着x轴旋转45度
geometry.rotateX(Math.PI / 4);

三、模型对象和材质

角度属性rotation与欧拉Euler

模型的角度属性rotation和quaternion都是表示模型角度的状态,只是表达方法不同,rotation属性的值是欧拉对象Euler,而quaternion属性的值是四元数对象Quaternion

由于刚入门,就先介绍比较容易理解的角度属性rotation和对应属性值欧拉对象Euler

// 创建一个欧拉对象,表示绕着xyz轴分别旋转45度,0度,90度
const Euler = new THREE.Euler( Math.PI/4,0, Math.PI/2);
//绕y轴的角度设置为60度
mesh.rotation.y += Math.PI/3;
//绕y轴的角度增加60度
mesh.rotation.y += Math.PI/3;
//绕y轴的角度减去60度
mesh.rotation.y -= Math.PI/3;

克隆clone()与复制copy()

克隆.clone()、复制.copy()是threejs很多对象都具有的方法,比如三维向量对象Vector3、网格模型Mesh、几何体、材质。

// 克隆
const v1 = new THREE.Vector3(1, 2, 3);
console.log('v1',v1);
//v2是一个新的Vector3对象,和v1的.x、.y、.z属性值一样
const v2 = v1.clone();
console.log('v2',v2);
// 复制
const v1 = new THREE.Vector3(1, 2, 3);
const v3 = new THREE.Vector3(4, 5, 6);
//读取v1.x、v1.y、v1.z的赋值给v3.x、v3.y、v3.z
v3.copy(v1);

.clone()方法会创建一个与原始对象完全相同的新对象,包括其属性和方法。.copy()方法也会创建一个新对象,但它只复制原始对象的属性,而不包括其方法。

因此,.clone()方法更适合创建独立的对象实例,而.copy()方法更适合创建基于现有对象的变体。

四、层级模型

遍历模型树结构和查询模型节点

递归遍历traverse()方法

Threejs层级模型就是一个树结构,可以通过递归遍历的算法去遍历Threejs一个模型对象包含的所有后代。

// 递归遍历model包含所有的模型节点
model.traverse(function(obj) {
    console.log('所有模型节点的名称',obj.name);
    // obj.isMesh:if判断模型对象obj是不是网格模型'Mesh'
    if (obj.isMesh) {//判断条件也可以是obj.type === 'Mesh'
        obj.material.color.set(0xffff00);
    }
});

查找某个具体的模型getObjectByName()方法

// 返回名.name为"4号楼"对应的对象
const nameNode = scene.getObjectByName ("4号楼");
nameNode.material.color.set(0xff0000);

本地坐标和世界坐标

有时候模型可能有多个层级,那么模型的坐标就分为本地坐标和世界坐标了。

本地坐标就是模型自身的position属性,而世界坐标就是模型自身的position属性再加上所有父模型的position属性

getWorldPosition()获取世界坐标

// 声明一个三维向量用来表示某个坐标
const worldPosition = new THREE.Vector3();
// 获取mesh的世界坐标,你会发现mesh的世界坐标受到父对象group的.position影响
mesh.getWorldPosition(worldPosition);
console.log('世界坐标',worldPosition);
console.log('本地坐标',mesh.position);

模型隐藏或显示

模型对象的父类Object3D封装了一个属性visible,通过该属性可以隐藏或显示一个模型。

mesh.visible =false;// 隐藏一个网格模型,visible的默认值是true
group.visible =false;// 隐藏一个包含多个模型的组对象group

五、顶点UI贴图,纹理贴图

创建纹理贴图

通过纹理贴图加载器TextureLoaderload()方法加载一张图片可以返回一个纹理对象Texture,纹理对象Texture可以作为模型材质颜色贴图.map属性的值。

const geometry = new THREE.PlaneGeometry(200, 100); 
//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
// .load()方法加载图像,返回一个纹理对象Texture
const texture = texLoader.load('./earth.jpg');
const material = new THREE.MeshLambertMaterial({
    // 设置纹理贴图:Texture对象作为材质map属性的属性值
    map: texture,//map表示材质的颜色贴图属性
    // 通常设置了纹理贴图后就不需要设置材质的颜色,这两者同时使用可能会引起颜色冲突
    // color: 0x00ffff,
});

自定义顶点UV坐标

顶点UV坐标的作用是从纹理贴图上提取像素映射到网格模型Mesh的几何体表面上。

顶点UV坐标geometry.attributes.uv和顶点位置坐标geometry.attributes.position是一一对应的,

UV顶点坐标你可以根据需要在0~1之间任意设置,具体怎么设置,要看你想把图片的哪部分映射到Mesh的几何体表面上。

/**纹理坐标0~1之间随意定义*/
const uvs = new Float32Array([
    0, 0, //图片左下角
    1, 0, //图片右下角
    1, 1, //图片右上角
    0, 1, //图片左上角
]);
// 设置几何体attributes属性的位置normal属性
geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2); //2个为一组,表示一个顶点的纹理坐标

纹理对象Texture阵列

const geometry = new THREE.PlaneGeometry(2000, 2000);
//纹理贴图加载器TextureLoader
const texLoader = new THREE.TextureLoader();
// .load()方法加载图像,返回一个纹理对象Texture
const texture = texLoader.load('./瓷砖.jpg');

// 设置阵列
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// uv两个方向纹理重复数量
texture.repeat.set(30,30);//注意选择合适的阵列数量

const material = new THREE.MeshLambertMaterial({
    // color: 0x00ffff,
    // 设置纹理贴图:Texture对象作为材质map属性的属性值
    map: texture,//map表示材质的颜色贴图属性
});

const mesh = new THREE.Mesh(geometry, material);

// 旋转矩形平面
mesh.rotateX(-Math.PI/2);

透明背景贴图

three.js项目开发中,把一个背景透明的.png图像作为平面矩形网格模型Mesh的颜色贴图是一个非常有用的功能,通过这样一个功能,可以对three.js三维场景进行标注。

// 矩形平面网格模型设置背景透明的png贴图
const geometry = new THREE.PlaneGeometry(60, 60); //默认在XOY平面上
const textureLoader = new THREE.TextureLoader();
const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load('./指南针.png'),        
    transparent: true, //使用背景透明的png贴图,注意开启透明计算
});
const mesh = new THREE.Mesh(geometry, material);

六、加载外部三维模型

加载.gltf文件

// 引入GLTFLoader.js
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

// 创建GLTF加载器
const loader = new GLTFLoader();

// 加载.gltf文件
loader.load( 'gltf模型.gltf', function ( gltf ) {
  scene.add( gltf.scene );
})

递归遍历层级模型

gltf.scene.traverse(function(obj) {
    if (obj.isMesh) {//判断是否是网格模型
        console.log('模型节点',obj);
        console.log('模型节点名字',obj.name);
    }
});

外部材质是否共享

通过.name标记材质,测试mesh1和mesh2是否共享了材质

const mesh1 = gltf.scene.getObjectByName("1号楼");
mesh1.material.name = '楼房材质';//通过name标记mesh1对应材质
const mesh2 = gltf.scene.getObjectByName("2号楼");
//通过name相同,可以判断mesh1.material和mesh2.material共享了同一个材质对象
console.log('mesh2.material.name', mesh2.material.name);

七、PBR材质与纹理贴图

PBR材质简介

所谓PBR就是,基于物理的渲染(physically-based rendering)。

Three.js提供了两个PBR材质相关的APIMeshStandardMaterialMeshPhysicalMaterial,其中MeshPhysicalMaterial是MeshStandardMaterial扩展的子类,提供了更多的功能属性。

PBR材质金属度和粗糙度

金属度属性.metalness表示材质像金属的程度, 非金属材料,如木材或石材,使用0.0,金属使用1.0。

new THREE.MeshStandardMaterial({
    metalness: 1.0,//金属度属性
})

粗糙度roughness表示模型表面的光滑或者说粗糙程度,越光滑镜面反射能力越强,越粗糙,表面镜面反射能力越弱,更多地表现为漫反射。

new THREE.MeshStandardMaterial({
    roughness: 0.5,//表面粗糙度
})

环境贴图.enMap

通过前面学习大家知道,通过纹理贴图加载器TextureLoader的.load()方法加载一张图片可以返回一个纹理对象Texture

立方体纹理加载器CubeTextureLoader的.load()方法是加载6张图片,返回一个立方体纹理对象TextureLoader。

立方体纹理对象CubeTextureLoader的父类是纹理对象Texture。

CubeTextureLoader加载环境贴图

// 加载环境贴图
// 加载周围环境6个方向贴图
// 上下左右前后6张贴图构成一个立方体空间
// 'px.jpg', 'nx.jpg':x轴正方向、负方向贴图  p:正positive  n:负negative
// 'py.jpg', 'ny.jpg':y轴贴图
// 'pz.jpg', 'nz.jpg':z轴贴图
const textureCube = new THREE.CubeTextureLoader()
    .setPath('./环境贴图/环境贴图0/')
    .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
    // CubeTexture表示立方体纹理对象,父类是纹理对象Texture 

MeshStandardMaterial环境贴图数据.envMap

// 加载环境贴图
const textureCube = new THREE.CubeTextureLoader()
    .setPath('./环境贴图/环境贴图0/')
    .load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
new THREE.MeshStandardMaterial({
    metalness: 1.0,
    roughness: 0.5,
    envMap: textureCube, //设置pbr材质环境贴图
})    
obj.material.envMap = textureCube; //设置环境贴图 

环境贴图反射率.envMapIntensity

MeshStandardMaterial的.envMapIntensity属性主要用来设置模型表面反射周围环境贴图的能力,或者说环境贴图对模型表面的影响能力。

obj.material.envMapIntensity = 1.0;

MeshPhysicalMaterial清漆层

MeshPhysicalMaterial和MeshStandardMaterial都是拥有金属度metalness、粗糙度roughness属性的PBR材质,MeshPhysicalMaterial是在MeshStandardMaterial基础上扩展出来的子类,除了继承了MeshStandardMaterial的金属度、粗糙度等属性,还新增了清漆.clearcoat、透光率.transmission、反射率.reflectivity、光泽.sheen、折射率.ior等等各种用于模拟生活中不同材质的属性。

清漆层属性.clearcoat

清漆层属性.clearcoat可以用来模拟物体表面一层透明图层,就好比你在物体表面刷了一层透明清漆,喷了点水。.clearcoat的范围0到1,默认0。

const material = new THREE.MeshPhysicalMaterial( {
	clearcoat: 1.0,//物体表面清漆层或者说透明涂层的厚度
} );

清漆层粗糙度.clearcoatRoughness

清漆层粗糙度.clearcoatRoughness属性表示物体表面透明涂层.clearcoat对应的的粗糙度,.clearcoatRoughness的范围是为0.0至1.0。默认值为0.0。

const material = new THREE.MeshPhysicalMaterial( {
	clearcoat: 1.0,//物体表面清漆层或者说透明涂层的厚度
	clearcoatRoughness: 0.1,//透明涂层表面的粗糙度
} );

物理材质透光率transmission

透光率(透射度).transmission

为了更好的模拟玻璃、半透明塑料一类的视觉效果,可以使用物理透明度.transmission属性代替Mesh普通透明度属性.opacity

使用.transmission属性设置Mesh透明度,即便完全透射的情况下仍可保持高反射率。

物理光学透明度.transmission的值范围是从0.0到1.0。默认值为0.0。

const mesh = gltf.scene.getObjectByName('玻璃01')
mesh.material = new THREE.MeshPhysicalMaterial({
    transmission: 1.0, //玻璃材质透光率,transmission替代opacity 
})

折射率.ior

非金属材料的折射率从1.0到2.333。默认值为1.5。

new THREE.MeshPhysicalMaterial({
    ior:1.5,//折射率
})

八、渲染器和前端UI界面

threejs将渲染结果保存为图片

// 先配置webgl渲染器开启画布保存
const renderer = new THREE.WebGLRenderer({
    //想把canvas画布上内容下载到本地,需要设置为true
    preserveDrawingBuffer:true,
});

// 鼠标单击id为download的HTML元素,threejs渲染结果以图片形式下载到本地
document.getElementById('download').addEventListener('click',function(){
    // 创建一个超链接元素,用来下载保存数据的文件
    const link = document.createElement('a');
    // 通过超链接herf属性,设置要保存到文件中的数据
    const canvas = renderer.domElement; //获取canvas对象
    link.href = canvas.toDataURL("image/png");
    link.download = 'threejs.png'; //下载文件名
    link.click(); //js代码触发超链接元素a的鼠标点击事件,开始下载文件到本地
})

深度冲突(模型闪烁)

在开发中,如果有两个重合的矩形平面Mesh,通过浏览器预览,当你旋转三维场景的时候,你会发现模型渲染的时候产生闪烁。

这种现象,主要是两个Mesh重合,电脑GPU分不清谁在前谁在后,这种现象,可以称为深度冲突Z-fighting。

改变Mesh的位置,拉开两个mesh的距离,使其不重合

mesh2.position.z = 1;

模型加载进度条

在项目开发时,有很多大型3D模型加载的时候会非常慢,这时候往往需要加一个进度条,表示模型的加载进度。

进度条HTML、CSS、JavaScript代码

<head>
    <style>
        /* 进度条css样式 */
        #container {
            position: absolute;
            width: 400px;
            height: 16px;
            top: 50%;
            left:50%;
            margin-left: -200px;
            margin-top: -8px;
            border-radius: 8px;           
            border: 1px solid #009999;          
            overflow: hidden;
        }
        #per {
            height: 100%;
            width: 0px;
            background: #00ffff;
            color: #00ffff;
            line-height: 15px;          
        }
    </style>
</head>
<body style="background-color: #001111;">
    <div id="container">
        <!-- 进度条 -->
        <div id="per"> </div>
    </div>
    <script>        
        const percentDiv = document.getElementById("per");// 获取进度条元素
        percentDiv.style.width = 0.8*400 + "px";//进度条元素长度
        percentDiv.style.textIndent = 0.8*400 + 5 +"px";//缩进元素中的首行文本
        percentDiv.innerHTML =  "80%";//进度百分比
    </script>
</body>

threejs代码

模型加载器的.load()方法,参数一为模型路径;参数二为一个函数,加载结束调用;参数也三为一个函数,每当模型加载部分内容,该函数就会被调用,一次加载过程中一般会被调用多次,直到模型加载完成。jiu

const percentDiv = document.getElementById("per"); // 获取进度条元素
loader.load("../工厂.glb", function (gltf) {
    model.add(gltf.scene);
    // 加载完成,隐藏进度条
    // document.getElementById("container").style.visibility ='hidden';
    document.getElementById("container").style.display = 'none';
}, function (xhr) {
    // 控制台查看加载进度xhr
    // 通过加载进度xhr可以控制前端进度条进度   
    const percent = xhr.loaded / xhr.total;
    console.log('加载进度' + percent);
     
   
    percentDiv.style.width = percent * 400 + "px"; //进度条元素长度
    percentDiv.style.textIndent = percent * 400 + 5 + "px"; //缩进元素中的首行文本
    // Math.floor:小数加载进度取整
    percentDiv.innerHTML = Math.floor(percent * 100) + '%'; //进度百分比
})

九、生成曲线、几何体

几何体方法.setFromPoints()

.setFromPoints()是几何体BufferGeometry的一个方法,通过该方法可以把数组pointsArr中坐标数据提取出来赋值给几何体。具体说就是把pointsArr里面坐标数据提取出来,赋值给geometry.attributes.position属性

const pointsArr = [
    // 三维向量Vector3表示的坐标值
    new THREE.Vector3(0,0,0),
    new THREE.Vector3(0,100,0),
    new THREE.Vector3(0,100,100),
    new THREE.Vector3(0,0,100),
];

// 把数组pointsArr里面的坐标数据提取出来,赋值给`geometry.attributes.position`属性
geometry.setFromPoints(pointsArr);

曲线Curve

threejs提供了很多常用的曲线或直线API,可以直接使用。这些API曲线都有一个共同的父类Curve

.getPoints() 提取曲线的顶点数据

//getPoints是基类Curve的方法,平面曲线会返回一个vector2对象作为元素组成的数组
const pointsArr = arc.getPoints(50); //分段数50,返回51个顶点
console.log('曲线上获取坐标',pointsArr);

.getSpacedPoints() 也是提取曲线的顶点数据,不同的是getSpacedPoints()方法是按照曲线长度等距来获取顶点数据,而getPoints()会考虑到斜率的变化,斜率变化快的位置顶点会更密集

样条曲线

对于一些不规则的曲线,很难用圆弧去描述,可以使用threejs中的样条曲线和贝尔曲线来实现

三维样条曲线CatmullRomCurve3

// 三维样条曲线CatmullRomCurve3,参数是三维向量对象Vector3构成的数组
const arr = [
    new THREE.Vector3(-50, 20, 90),
    new THREE.Vector3(-10, 40, 40),
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(60, -60, 0),
    new THREE.Vector3(70, 0, 80)
]
// 三维样条曲线
const curve = new THREE.CatmullRomCurve3(arr);
//曲线上获取点
const pointsArr = curve.getPoints(100); 
const geometry = new THREE.BufferGeometry();
//读取坐标数据赋值给几何体顶点
geometry.setFromPoints(pointsArr); 
// 线材质
const material = new THREE.LineBasicMaterial({
    color: 0x00fffff
});
// 线模型
const line = new THREE.Line(geometry, material);

二维样条曲线SplineCurve

// 二维向量Vector2创建一组顶点坐标
const arr = [
    new THREE.Vector2(-100, 0),
    new THREE.Vector2(0, 30),
    new THREE.Vector2(100, 0),
];
// 二维样条曲线
const curve = new THREE.SplineCurve(arr);

 贝塞尔曲线

// 二维二次贝塞尔曲线,参数为三个二维向量对象
// p2为曲线的控制点
const p1 = new THREE.Vector2(-80, 0);
const p2 = new THREE.Vector2(20, 100);
const p3 = new THREE.Vector2(80, 0);
const curve = new THREE.QuadraticBezierCurve(p1, p2, p3);

// 三维二次贝赛尔曲线,参数为三个三维向量对象
// p2为曲线的控制点
const p1 = new THREE.Vector3(-80, 0, 0);
const p2 = new THREE.Vector3(20, 100, 0);
const p3 = new THREE.Vector3(80, 0, 100);
const curve = new THREE.QuadraticBezierCurve3(p1, p2, p3);


// 二维三次贝赛尔曲线,参数为四个二维向量对象
// p1、p4是曲线起始点,p2、p3是曲线的控制点
const p1 = new THREE.Vector2(-80, 0);
const p2 = new THREE.Vector2(-40, 50);
const p3 = new THREE.Vector2(50, 50);
const p4 = new THREE.Vector2(80, 0);
const curve = new THREE.CubicBezierCurve(p1, p2, p3, p4);


// 三维三次贝赛尔曲线,参数为四个三维向量对象
// p1、p4是曲线起始点,p2、p3是曲线的控制点
const p1 = new THREE.Vector3(-80, 0, 0);
const p2 = new THREE.Vector3(-40, 50, 0);
const p3 = new THREE.Vector3(50, 50, 0);
const p4 = new THREE.Vector3(80, 0, 100);
const curve = new THREE.CubicBezierCurve3(p1, p2, p3, p4);

组合曲线CurvePath

通过CurvePath对象,你可以将直线、圆弧、贝塞尔曲线等线条连接成一条直线

直线线段LineCurve3和LineCurve

// LineCurve3线段的参数为三维向量对象
new THREE.LineCurve3(new THREE.Vector3(), new THREE.Vector3());
// LineCurve3线段的参数为二维向量对象
new THREE.LineCurve(new THREE.Vector2(), new THREE.Vector2());

下面创建二条直线和一条圆弧并将其使用CurvePath对象连接起来

const R = 80;//圆弧半径
const H = 200;//直线部分高度
// 直线1
const line1 = new THREE.LineCurve(new THREE.Vector2(R, H), new THREE.Vector2(R, 0));
// 圆弧
const arc = new THREE.ArcCurve(0, 0, R, 0, Math.PI, true);
// 直线2
const line2 = new THREE.LineCurve(new THREE.Vector2(-R, 0), new THREE.Vector2(-R, H));

// CurvePath创建一个组合曲线对象
const CurvePath = new THREE.CurvePath();
//line1, arc, line2拼接出来一个U型轮廓曲线,注意顺序
CurvePath.curves.push(line1, arc, line2);

 管道几何体TubeGeometry

管道几何体就是基于一个3D曲线路径,生成一个管道几何体

构造函数格式:TubeGeometry(path, tubularSegments, radius, radiusSegments, closed)
path    扫描路径,路径要用三维曲线
tubularSegments    路径方向细分数,默认64
radius    管道半径,默认1
radiusSegments    管道圆弧细分数,默认8
closed    Boolean值,管道是否闭合

// 三维样条曲线
const path = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-50, 20, 90),
    new THREE.Vector3(-10, 40, 40),
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(60, -60, 0),
    new THREE.Vector3(70, 0, 80)
]);

// path:路径   40:沿着轨迹细分数  2:管道半径   25:管道截面圆细分数
const geometry = new THREE.TubeGeometry(path, 40, 2, 25);

车削几何体LatheGeometry

车削几何体可以利用一个2D轮廓,将其旋转成一个3D的几何体曲面

格式:LatheGeometry(points, segments, phiStart, phiLength)
points    Vector2表示的坐标数据组成的数组
segments    圆周方向细分数,默认12
phiStart    开始角度,默认0
phiLength    旋转角度,默认2π

// Vector2表示的三个点坐标,三个点构成的轮廓相当于两端直线相连接
const pointsArr = [
    new THREE.Vector2(50, 60),
    new THREE.Vector2(25, 0),
    new THREE.Vector2(50, -60)
];
// LatheGeometry:pointsArr轮廓绕y轴旋转生成几何体曲面
// pointsArr:旋转几何体的旋转轮廓形状
const geometry = new THREE.LatheGeometry(pointsArr);

轮廓几何体shapeGeometry

有些时候已知一个多边形的外轮廓坐标,想通过这些外轮廓坐标生成一个多边形几何体平面,这时候你可以借助threejs提供的轮廓填充shapeGeometry几何体实现。

// 一组二维向量表示一个多边形轮廓坐标
const pointsArr = [
    new THREE.Vector2(-50, -50),
    new THREE.Vector2(-60, 0),
    new THREE.Vector2(0, 50),
    new THREE.Vector2(60, 0),
    new THREE.Vector2(50, -50),
]
// Shape表示一个平面多边形轮廓,参数是二维向量构成的数组pointsArr
const shape = new THREE.Shape(pointsArr);

拉伸ExtrudeGeometry

拉伸ExtrudeGeometry像轮廓几何体一样,都是基于一个平面轮廓shape进行变化,生成一个几何体

// Shape表示一个平面多边形轮廓
const shape = new THREE.Shape([
    // 按照特定顺序,依次书写多边形顶点坐标
    new THREE.Vector2(-50, -50), //多边形起点
    new THREE.Vector2(-50, 50),
    new THREE.Vector2(50, 50),
    new THREE.Vector2(50, -50),
]);

const geometry = new THREE.ExtrudeGeometry(
    // 平面轮廓
    shape, 
    {
        depth: 20,     // 拉伸长度
        bevelThickness: 5, //倒角尺寸:拉伸方向
        bevelSize: 5, //倒角尺寸:垂直拉伸方向
        bevelSegments: 20, //倒圆角:倒角细分精度,默认3
    }
);

扫描ExtrudeGeometry

通过ExtrudeGeometry除了可以实现拉伸成型,也可以让一个平面轮廓Shape沿着曲线扫描成型。

// 扫描轮廓:Shape表示一个平面多边形轮廓
const shape = new THREE.Shape([
    // 按照特定顺序,依次书写多边形顶点坐标
    new THREE.Vector2(0,0), //多边形起点
    new THREE.Vector2(0,10),
    new THREE.Vector2(10,10),
    new THREE.Vector2(10,0),
]);

// 扫描轨迹:创建轮廓的扫描轨迹(3D样条曲线)
const curve = new THREE.CatmullRomCurve3([
    new THREE.Vector3( -10, -50, -50 ),
    new THREE.Vector3( 10, 0, 0 ),
    new THREE.Vector3( 8, 50, 50 ),
    new THREE.Vector3( -5, 0, 100)
]);

//扫描造型:扫描默认没有倒角
const geometry = new THREE.ExtrudeGeometry(
    shape, //扫描轮廓
    {
        extrudePath:curve,//扫描轨迹
        steps:100//沿着路径细分精度,越大越光滑
    }
);

多边形轮廓shape

多边形轮廓shape,是直接通过一组二维向量Vector2表示的xy点坐标创建

shape的父类是path,有以下方法

  • .lineTo() 直线
  • .arc() 圆弧
  • .absarc() 绝对圆弧
  • .splineThru() 二维样条曲线
  • .bezierCurveTo() 二维三次贝塞尔曲线
  • .quadraticCurveTo() 二维二次贝塞尔曲线
// Shape表示一个平面多边形轮廓
const shape = new THREE.Shape([
    // 按照特定顺序,依次书写多边形顶点坐标
    new THREE.Vector2(-50, -50), //多边形起点
    new THREE.Vector2(-50, 50),
    new THREE.Vector2(50, 50),
    new THREE.Vector2(50, -50),
]);
// .currentPoint属性字面意思是当前点,默认值Vector2(0,0)。
const shape = new THREE.Shape();
console.log('currentPoint',shape.currentPoint)

// .moveTo()方法可以改变当前currentPoint属性
const shape = new THREE.Shape();
shape.moveTo(10,0);

// .lineTo()绘制直线线段,直线线段的起点是当前点属性.currentPoint表示的位置,结束点是.lineTo()的参数表示的坐标。
const shape = new THREE.Shape();
//.currentPoint变为(10,0)
shape.moveTo(10,0);
// 绘制直线线段,起点(10,0),结束点(100,0)
shape.lineTo(100,0);

多边形shape(内孔)

有些多边形是有内孔的,这时候需要借助shape的.holes()来实现

// 创建shape,绘制一个矩形轮廓
const shape = new THREE.Shape();
// .lineTo(100, 0)绘制直线线段,线段起点:.currentPoint,线段结束点:(100,0)
shape.lineTo(100, 0);
shape.lineTo(100, 100);
shape.lineTo(0, 100)

// 创建Shape内孔的轮廓
const path1 = new THREE.Path();// 圆孔1
path1.absarc(20, 20, 10);
const path2 = new THREE.Path();// 圆孔2
path2.absarc(80, 20, 10);
const path3 = new THREE.Path();// 方形孔
path3.moveTo(50, 50);
path3.lineTo(80, 50);
path3.lineTo(80, 80);
path3.lineTo(50, 80);

// 将内孔轮廓添加到shape中
shape.holes.push(path1, path2,path3);

// 使用ExtrudeGeometry拉伸成长方体,方便观看内孔效果
const geometry = new THREE.ExtrudeGeometry(shape, {
    depth:20,//拉伸长度
    bevelEnabled:false,//禁止倒角
    curveSegments:50,
});

模型边界线EdgesGeometry

借助EdgesGeometry,可以展示出模型的边界线

const geometry = new THREE.BoxGeometry(50, 50, 50);
const material = new THREE.MeshLambertMaterial({
    color: 0x004444,
    transparent:true,
    opacity:0.5,
});
const mesh = new THREE.Mesh(geometry, material);

// 长方体geometry作为EdgesGeometry参数创建一个新的几何体
const edges = new THREE.EdgesGeometry(geometry);
const edgesMaterial = new THREE.LineBasicMaterial({
  color: 0x00ffff,
})
// 使用LineSegments将几何体边界线显示出来
const line = new THREE.LineSegments(edges, edgesMaterial);
mesh.add(line);

曲线颜色渐变

通过几何体顶点颜色.attributes.color数据,可以实现曲线的渐变

//创建一个几何体对象
const geometry = new THREE.BufferGeometry(); 
// 三维样条曲线
const curve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-50, 20, 90),
    new THREE.Vector3(-10, 40, 40),
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(60, -60, 0),
    new THREE.Vector3(70, 0, 80)
]);
const pointsArr = curve.getSpacedPoints(100); //曲线取点      
geometry.setFromPoints(pointsArr); //pointsArr赋值给顶点位置属性     

const pos = geometry.attributes.position;
const count = pos.count; //顶点数量
// 计算每个顶点的颜色值
const colorsArr = [];
for (let i = 0; i < count; i++) {
    const percent = i / count; //点索引值相对所有点数量的百分比
    //根据顶点位置顺序大小设置颜色渐变
    // 红色分量从0到1变化,蓝色分量从1到0变化
    colorsArr.push(percent, 0, 1 - percent); //蓝色到红色渐变色
}

//类型数组创建顶点颜色color数据
const colors = new Float32Array(colorsArr);
// 设置几何体attributes属性的颜色color属性
geometry.attributes.color = new THREE.BufferAttribute(colors, 3);

const material = new THREE.LineBasicMaterial({
    vertexColors: true, //使用顶点颜色渲染
});
const line = new THREE.Line(geometry, material);

Color颜色渐变插值

颜色渐变插值方法有.lerpColor()和.lerp(),他们功能是相同的,区别是使用的语法不一样

// lerpColors用法,percent是两颜色的混合百分比
.lerpColors(Color1,Color2, percent)

// lerp用法
const c1 = new THREE.Color(0xff0000); //红色
const c2 = new THREE.Color(0x0000ff); //蓝色
c1.lerp(c2, percent);
const geometry = new THREE.BufferGeometry(); //创建一个几何体对象
// 三维样条曲线
const curve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(-50, 20, 90),
    new THREE.Vector3(-10, 40, 40),
    new THREE.Vector3(0, 0, 0),
    new THREE.Vector3(60, -60, 0),
    new THREE.Vector3(70, 0, 80)
]);
const pointsArr = curve.getSpacedPoints(100); //曲线取点      
geometry.setFromPoints(pointsArr); //pointsArr赋值给顶点位置属性     

const pos = geometry.attributes.position;
const count = pos.count; //顶点数量
// 计算每个顶点的颜色值
const colorsArr = [];
// 根据顶点距离起点远近进行颜色插值计算
const c1 = new THREE.Color(0x00ffff); //曲线起点颜色 青色
const c2 = new THREE.Color(0xffff00); //曲线结束点颜色 黄色
for (let i = 0; i < count; i++) {
    const percent = i / count; //点索引值相对所有点数量的百分比
    //根据顶点位置顺序大小设置颜色渐变
    const c = c1.clone().lerp(c2, percent);//颜色插值计算
    colorsArr.push(c.r, c.g, c.b); 
}

//类型数组创建顶点颜色color数据
const colors = new Float32Array(colorsArr);
// 设置几何体attributes属性的颜色color属性
geometry.attributes.color = new THREE.BufferAttribute(colors, 3);

const material = new THREE.LineBasicMaterial({
    vertexColors: true, //使用顶点颜色渲染
});
const line = new THREE.Line(geometry, material);

十六、关键帧动画

关键帧动画

创建关键帧动画

// 时间数组
const timeArr = [0, 3, 6, 9];
// 位置数组,3组为一个位置
const positionArr = [0, 0, 0, 100, 0, 0, 0, 0, 100, 0, 0, 0];
// 创建位置关键帧
const posKF = new THREE.KeyframeTrack('Box.position', timeArr, positionArr);
// 创建颜色关键帧
const colorKF = new THREE.KeyframeTrack('Box.material.color', [0, 3, 6, 9], [0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0]);
// 创建动画
const clip = new THREE.AnimationClip('test', 9, [posKF, colorKF]);
// 创建动画播放器AnimationMixer,设置要播放的物体为mesh
const mixer = new THREE.AnimationMixer(mesh);
// 定义播放器所播放的动画
const clipAction = mixer.clipAction(clip);
// 开始播放动画,play默认为循环播放
clipAction.play();

// 创建时钟对象
const clock = new THREE.Clock();
function loop () {
    requestAnimationFrame(loop);
    // 获取loop()执行间隔时间
    const frameT = clock.getDelta();
    // 更新播放器时间数据
    mixer.update(frameT);
}
loop();
 

 关键帧动画(暂停、倍速、循环)

执行动画播放器AnimationMixer的clipAction()方法会返回一个AnimationAction对象。AnimationAction对象的功能就是用来控制如何播放关键帧动画,比如是否播放、几倍速播放、是否循环播放、是否暂停播放等等

// 设置动画不循环播放
clipAction.loop = THREE.LoopOnce; 

// 设置物体状态停留在动画结束的时候(默认情况下物体状态会停留在动画播放前)
clipAction.clampWhenFinished = true;

// 设置动画停止结束,回到开始状态
clipAction.stop();

// 设置动画为暂停播放状态
clipAction.paused = true;

// 设置动画2倍速播放
clipAction.timeScale = 2;

// 通过GUI对象动态调节播放倍速
const gui = new GUI();   //创建GUI对象
gui.add(clipAction, 'timeScale', 0, 6);  // 0~6倍速之间调节

动画播放(任意时间播放状态)

// 设置开始播放时间
clipAction.time = 1; 
// 设置播放结束时间
clipAction.duration = 5;

// 设置拖动条控制播放动画
import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
const gui = new GUI(); //创建GUI对象
gui.add(clipAction, 'time', 0, 6);
gui.add(clipAction, 'time', 0, 6).step(0.1);

解析外部模型关键帧动画

在开发过程中,有时候会用三维建模软件设置动画,一起跟随模型导出文件,这时候,只需要播放文件中的动画即可

loader.load("../士兵.glb", function (gltf) {
    // console.log('动画数据', gltf.animations);
    model.add(gltf.scene);
    
    // 创建动画播放器
    const mixer = new THREE.AnimationMixer(gltf.scene);
    // 添加文件中的动画
    const clipAction = mixer.clipAction(gltf.animations[3]);
    // 开始播放
    clipAction.play();

    const clock = new THREE.Clock();
    function loop () {
        requestAnimationFrame(loop);
        //clock.getDelta()方法获得loop()两次执行时间间隔
        const frameT = clock.getDelta();
        // 更新播放器相关的时间
        mixer.update(frameT);
    }
    loop();
})

变形动画

使用BufferGeometry的morphAttributes属性设置几何体目标顶点变形数据,Mesh的morphTargetInfluences属性控制变形的权重系数

const geometry = new THREE.BoxGeometry(50, 50, 50);
const target1 = new THREE.BoxGeometry(50, 200, 50).attributes.position;
const target2 = new THREE.BoxGeometry(10, 50, 10).attributes.position;

// 设置morphAttributes变形属性为target1和target2
geometry.morphAttributes.position = [target1, target2]
const material = new THREE.MeshLambertMaterial({
    color: 0x00ffff
});
const mesh = new THREE.Mesh(geometry, material);

mesh.name = 'Box';
// 将变形属性创建成关键帧动画
const KF1 = new THREE.KeyframeTrack('Box.morphTargetInfluences[0]', [0, 5], [0, 1]);
const KF2 = new THREE.KeyframeTrack('Box.morphTargetInfluences[1]', [5, 10], [0, 1]);
const clip = new THREE.AnimationClip("test", 10, [KF1, KF2]);

// 播放变形动画
const mixer = new THREE.AnimationMixer(mesh);
const clipAction = mixer.clipAction(clip);
clipAction.play();
clipAction.loop = THREE.LoopOnce; //不循环播放
clipAction.clampWhenFinished = true // 物体状态停留在动画结束的时候

const clock = new THREE.Clock();

function loop () {
    requestAnimationFrame(loop);
    const frameT = clock.getDelta();
    // 更新播放器时间
    mixer.update(frameT);
}
loop();
export default mesh;

初始骨骼Bone

Bone骨骼是threejs的一个类,用来模拟人或动物的骨骼,他的父节点是Object3D,继承了位置属性position、旋转属性rotation等等

// 创建骨骼
const Bone1 = new THREE.Bone();
const Bone2 = new THREE.Bone();
const Bone3 = new THREE.Bone();
// 设置骨骼父子关系,将骨骼关联起来
Bone1.add(Bone2);
Bone2.add(Bone3);
Bone1.position.set(50, 0, 50);
Bone2.position.y = 60;
Bone3.position.y = 30;

// 将骨骼添加至组对象中
const group = new THREE.Group();
group.add(Bone1);

// 创建骨骼处理器,显示我们创建的骨骼对象
const SkeletonHelper = new THREE.SkeletonHelper(group);
group.add(SkeletonHelper);

// GUI动态控制骨骼旋转
const gui = new GUI();
gui.add(Bone1.rotation, 'x', 0, Math.PI / 2).name('骨骼1');
gui.add(Bone2.rotation, 'x', 0, Math.PI / 2).name('骨骼2');

十七、Tweenjs动画库

Tweenjs创建threejs动画

tweenjs是一个JavaScript编写的补间动画库,如果你使用three.js开发web3d项目,使用tween.js辅助three.js生成动画效果也是比较好的选择。

  • github地址:https://github.com/tweenjs/tween.js/
  • npm地址:https://www.npmjs.com/package/@tweenjs/tween.js
  • 官网:https://createjs.com/tweenjs

安装

npm i @tweenjs/tween.js@^18

import TWEEN from '@tweenjs/tween.js';

tweenjs从语法角度上讲,就是改变自己的参数对象

const geometry = new THREE.BoxGeometry(10, 10, 10);
const material = new THREE.MeshLambertMaterial({
    color: 0x00ffff,
});
const mesh = new THREE.Mesh(geometry, material);

//创建一段mesh平移的动画
const tween = new TWEEN.Tween(mesh.position);
//经过2000毫秒,pos对象的x和y属性分别从零变化为100、50
tween.to({x: 100,y: 50}, 2000);
//tween动画开始执行
tween.start();

// 渲染循环
function render() {
    // 循环更新TWEEN数据
    TWEEN.update();
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

tweenjs相机运动动画

const R = 150;
new TWEEN.Tween({ angle: 0 })  // 给定一个开始参数angle,为0
    .to({ angle: Math.PI * 2 }, 10000)  // 结束时angle为360度,用时10秒
    .onUpdate(function (obj) {  // tweenjs数据时更新执行此函数
        camera.position.x = R * Math.cos(obj.angle);
        camera.position.z = R * Math.sin(obj.angle);
        camera.lookAt(0, 0, 0);
    })
    .start();

// 渲染循环
function render () {
    TWEEN.update();
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
render();

tweenjs回调函数

tweenjs回调函数有onStart()、onUpdate()、onComplete()三种

  • onStart:动画开始执行前触发
  • onUpdate:动画执行过程中,一直被重复执行
  • onComplete:动画成功执行完毕后触发

tweenjs缓动算法.easing()

在动画播放的时候,默认是匀速状态的,在项目开发中,如果在动画播放的某一段进行一个缓慢、缓冲的效果,将会看起来更加自然

常见的算法名称有如下

  • Linear:线性匀速运动效果(默认效果);
  • Quadratic:二次方的缓动(t^2);
  • Cubic:三次方的缓动(t^3);
  • Quartic:四次方的缓动(t^4);
  • Quintic:五次方的缓动(t^5);
  • Sinusoidal:正弦曲线的缓动(sin(t));
  • Exponential:指数曲线的缓动(2^t);
  • Circular:圆形曲线的缓动(sqrt(1-t^2));
  • Elastic:指数衰减的正弦曲线缓动;
  • Back:超过范围的三次方缓动((s+1)*t^3 – s*t^2);
  • Bounce:指数衰减的反弹缓动。

每个算法又有三种缓动方式

  • easeIn:从0开始加速的缓动,也就是先慢后快;
  • easeOut:减速到0的缓动,也就是先快后慢;
  • easeInOut:前半段从0开始加速,后半段减速到0的缓动。

示例如下

// 创建tweenjs动画,改变threejs相机位置
new TWEEN.Tween(camera.position)
    .to({
        x: 300,
        y: 300,
        z: 300,
    }, 3000)
    // 二次方缓动算法,先快后慢
    .easing(TWEEN.Easing.Quadratic.Out)
    .start();

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值