Three.js零基础入门笔记,打造炫酷立体世界

文档获取

由于Three.js官网文档访问体验太卡,可以前往GitHub下载dev分支压缩包,将文档运行在本地。压缩包很大,下载时可能需要科学上网。

在GitHub仓库下载完成后我发现只有100多MB,而教程里有200多MB,感觉不太对劲。解压后发现下载的文件夹比GitHub仓库中缺少很多文件。比如根目录的/icon.png,还有/examples/examples/sounds等文件夹中很多文件没有下载下来。我以为是网络原因,又下载了一次还是不全。

求助了一下群友,推荐我可以去国内Gitee镜像仓库下载。去试了一下,压缩包有大约300MB,文件是全的。要知道,Gitee的镜像仓库是为了提升国内下载速度,每日同步一次GitHub的项目,理论上说一模一样啊,但为啥GitHub下载的不完整……

  • 解压到适当位置,安装依赖

    1
    npm install
  • 启动项目

    1
    npm run dev

启动后打开页面,点击docs/就可以查看文档了(语言可切换成中文);

另外,点击editor/是一个可视化编辑器,点击examples/可以查看官方的一些案例。

下方内容中以http://localhost开头的超链接均为本地运行的文档,在线版可到官网查看


环境搭建/初始化项目

视频教程中使用的打包工具是Parcel,本文我使用的是webpack来打包。

视频里手动创建的项目,本文使用Vue-Cli可视化面板创建的Vue2.x项目。

  • 【详情】打开Vue-Cli可视化面板,创建新项目vue_threejs_demo1,包管理器npm,初始化init project
  • 【预设】选择Manual手动配置项目
  • 【功能】选择BableRouterVuexCSS Pre-processorsLinter / FormatterUse config files
  • 【配置】版本可以选Vue2.xPick a linter / formatter config选择标准的配置文件ESLint + Standard config;Css预处理语言选择Less
  • 创建项目,不用保存预设。耐心等待它下载npm包。

创建格式化文件.prettierrc.js

1
2
3
4
5
6
7
8
9
module.exports = {
semi: false, //代码末尾的分号
singleQuote: true, //使用单引号
bracketSpacing: true, //括号内部不要出现空格
useTabs: false, //使用 tab 缩进
tabWidth: 2, //缩进空格数
trailingComma: 'none', //末尾逗号
printWidth: 100, //行宽,超过这个数值才换行。否则默认每个标签属性单独占一行
}

.eslintrc.js代码审查rules中添加一行代码:

1
2
3
4
5
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'space-before-function-paren': 0
}

创建全局css文件/src/assets/css/global.css

1
2
3
4
5
6
7
8
9
/* 全局样式表 */
html,
body,
#app {
height: 100%;
margin: 0;
padding: 0;
background-color: skyblue;
}

main.js中引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 导入全局样式表
import './assets/css/global.css'

Vue.config.productionTip = false

new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')

Demo测试

App.vue引入Three.js

1
import * as THREE from 'three'

复制一下代码查看是否正常运行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<template>
<div>
<div id="container"></div>
</div>
</template>

<script>
import * as THREE from 'three'

export default {
name: 'ThreeTest',
data() {
return {
camera: null, // 相机对象
scene: null, // 场景对象
renderer: null, // 渲染器对象
mesh: null // 网格模型对象Mesh
}
},
methods: {
init() {
// 获取Dom元素节点
const container = document.getElementById('container')

// 创建相机
this.camera = new THREE.PerspectiveCamera(
70, // 视角
container.clientWidth / container.clientHeight, // 当前屏幕的宽高比
0.01, // 最近能看到的距离
1000 // 最远能看到的距离
)
this.camera.position.z = 0.6 // 相机位置

// 创建场景
this.scene = new THREE.Scene()

// 创建网格模型
const geometry = new THREE.CylinderBufferGeometry(0.2, 0.2, 0.2)
const material = new THREE.MeshNormalMaterial() // 材质对象Material
this.mesh = new THREE.Mesh(geometry, material) // 网格模型对象Mesh
this.scene.add(this.mesh) // 网格模型添加到场景中

// 创建渲染器对象
this.renderer = new THREE.WebGLRenderer({ antialias: true })
this.renderer.setSize(container.clientWidth, container.clientHeight)
container.appendChild(this.renderer.domElement) // 把绘制的画布添加进页面
},
animate() {
requestAnimationFrame(this.animate)
this.mesh.rotation.x += 0.01
this.mesh.rotation.y += 0.02
this.renderer.render(this.scene, this.camera) // 绘制界面(场景、相机)
}
},
mounted() {
this.init()
this.animate()
}
}
</script>

<style lang="less" scoped>
#container {
height: 400px;
}
</style>

搭建成功,开始正式学习。


01 渲染第一个场景和物体

了解Three.js最基本的内容

1、创建场景对象

场景(Scene):http://localhost:8080/docs/#api/zh/scenes/Scene

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default {
data() {
return {
scene: null // 场景对象
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()
}
},
mounted() {
this.init()
}
}

2、创建摄像机对象

透视相机为例:http://localhost:8080/docs/?q=CA#api/zh/cameras/PerspectiveCamera

  • PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
    • fov — 摄像机视锥体垂直视野角度
    • aspect — 摄像机视锥体长宽比
    • near — 摄像机视锥体近端面
    • far — 摄像机视锥体远端面

这些参数一起定义了摄像机的viewing frustum(视锥体)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import * as THREE from 'three'

export default {
data() {
return {
scene: null, // 场景对象
camera: null // 摄像机对象
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, //摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中
}
},
mounted() {
this.init()
}
}

3、创建物体对象

(1) 添加几何体

立方缓冲几何体为例:http://localhost:8080/docs/?q=box#api/zh/geometries/BoxGeometry

1
2
3
4
5
6
7
8
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null // 材质
}
},
1
2
3
// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)

(2) 添加材质

基础网格材质为例:http://localhost:8080/docs/?q=material#api/zh/materials/MeshBasicMaterial

1
2
3
4
5
6
7
8
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null // 材质
}
},
1
2
3
4
5
// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })

(3) 根据几何体和材质创建对象

网格(Mesh):http://localhost:8080/docs/?q=mesh#api/zh/objects/Mesh

1
2
3
4
5
6
7
8
9
data() {
return {
scene: null, // 场景对象
camera: null, // 相机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null // 几何体对象
}
},
1
2
3
4
5
6
7
8
// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)
this.scene.add(this.cube) // 将几何体对象添加到场景中

4、初始化渲染器

WebGLRenderer:http://localhost:8080/docs/?q=renderer#api/zh/renderers/WebGLRenderer

1
2
3
4
5
6
7
8
9
10
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null // 渲染器
}
},
1
2
3
4
5
6
7
// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

5、渲染

1
2
3
// 5、渲染
// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
  • 弹幕中有个老哥提出疑问,我们在第2步已经使用过this.scene.add(this.camera)将摄像机添加到了场景中,那渲染这一步this.renderer.render(this.scene, this.camera)还添加一次摄像机干啥?摄像机不是已经在场景中了吗?
  • 有人回答,第2步创建摄像机后,不添加到场景中,最后效果也是一样的。测试了一下,确实。

完整vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<template>
<div></div>
</template>

<script>
import * as THREE from 'three'

export default {
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null // 渲染器
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, // 摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中

// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)
this.scene.add(this.cube) // 将几何体对象添加到场景中

// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

// 5、渲染
// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
}
},
mounted() {
this.init()
}
}
</script>

<style lang="less" scoped>
</style>


02 使用控制器查看3D物体

轨道控制器为例:http://localhost:8080/docs/?q=cont#examples/zh/controls/OrbitControls

1、导入控制器

1
2
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

2、创建控制器

1
2
3
4
5
6
7
8
9
10
11
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null // 轨道控制器
}
},
  • OrbitControls( object : Camera, domElement : HTMLDOMElement )
    1. object: (必须)将要被控制的相机。该相机不允许是其他任何对象的子级,除非该对象是场景自身。
    2. domElement: 用于事件监听的HTML元素。
1
2
// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)

3、请求动画帧(重绘渲染)

之前的渲染语句this.renderer.render(this.scene, this.camera)只能渲染一次,若想让物体动态,需要不断渲染。

requestAnimationFrame是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘。设置这个API的目的是为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。代码中使用这个API,就是告诉浏览器希望执行一个动画,让浏览器在下一个动画帧安排一次网页重绘。requestAnimationFrame的优势,在于充分利用显示器的刷新机制,比较节省系统资源。显示器有固定的刷新频率(60Hz或75Hz),也就是说,每秒最多只能重绘60次或75次,requestAnimationFrame的基本思想就是与这个刷新频率保持同步,利用这个刷新频率进行页面重绘。此外,使用这个API,一旦页面不处于浏览器的当前标签,就会自动停止刷新。这就节省了CPU、GPU和电力。不过有一点需要注意,requestAnimationFrame是在主线程上完成。这意味着,如果主线程非常繁忙,requestAnimationFrame的动画效果会大打折扣。requestAnimationFrame使用一个回调函数作为参数。这个回调函数会在浏览器重绘之前调用。
示例:requestID = window.requestAnimationFrame(callback);

新增函数,用于重绘关键帧:

1
2
3
4
5
6
7
8
9
10
11
12
13
methods: {
// 重绘渲染函数
render() {
// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
},
mounted() {
this.init()
this.render()
}
}

现在页面上的正方体就可以使用鼠标左键拖动旋转,或者滚轮放大缩小了。

4、添加坐标轴辅助器

用于简单模拟3个坐标轴的对象.红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.

AxesHelper:http://localhost:8080/docs/?q=help#api/zh/helpers/AxesHelper

1
2
3
4
5
6
7
8
9
10
11
12
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null, // 轨道控制器
axesHelper: null // 添加坐标轴辅助器
}
},
  • AxesHelper( size : Number )
    1. size 表示代表轴的线段长度. 默认为 1
1
2
3
// 添加坐标轴辅助器
this.axesHelper = new THREE.AxesHelper(5)
this.scene.add(this.axesHelper) // 添加到场景中

完整Vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
<template>
<div></div>
</template>

<script>
// 02 使用控制器查看3D物体,添加坐标轴辅助器
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

export default {
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null, // 轨道控制器
axesHelper: null // 添加坐标轴辅助器
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, // 摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中

// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)
this.scene.add(this.cube) // 将几何体对象添加到场景中

// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

// // 5、渲染
// // 使用渲染器,通过相机将场景渲染进来
// this.renderer.render(this.scene, this.camera)

// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)

// 添加坐标轴辅助器
this.axesHelper = new THREE.AxesHelper(5)
this.scene.add(this.axesHelper) // 添加到场景中
},
// 重绘渲染函数
render() {
// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}
},
mounted() {
this.init()
this.render()
}
}
</script>

<style lang="less" scoped>
</style>


03 设置物体移动

三维向量(Vector3):http://localhost:8080/docs/?q=vec#api/zh/math/Vector3

创建物体的代码后面,且在使用渲染器开始渲染的代码之前,设置物体的位置。可以对位置坐标进行修改,也能单独对某一个轴的参数进行修改。

1
2
3
// 修改物体的位置
// this.cube.position.set(5, 0, 0)
this.cube.position.x = 3

或者直接在重绘渲染函数render()中添加代码,实现动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 重绘渲染函数
render() {
// 修改物体的位置
this.cube.position.x += 0.01
if (this.cube.position.x > 5) {
this.cube.position.x = 0
}

// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}

完整Vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
<template>
<div></div>
</template>

<script>
// 03 设置物体移动
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

export default {
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null, // 轨道控制器
axesHelper: null // 添加坐标轴辅助器
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, // 摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中

// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)
this.scene.add(this.cube) // 将几何体对象添加到场景中

// 修改物体的位置
// this.cube.position.set(5, 0, 0)
this.cube.position.x = 3

// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

// // 5、渲染
// // 使用渲染器,通过相机将场景渲染进来
// this.renderer.render(this.scene, this.camera)

// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)

// 添加坐标轴辅助器
this.axesHelper = new THREE.AxesHelper(5)
this.scene.add(this.axesHelper) // 添加到场景中
},
// 重绘渲染函数
render() {
// 修改物体的位置
this.cube.position.x += 0.01
if (this.cube.position.x > 5) {
this.cube.position.x = 0
}

// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}
},
mounted() {
this.init()
this.render()
}
}
</script>

<style lang="less" scoped>
</style>


04 物体的缩放与旋转

三维向量(Vector3):http://localhost:8080/docs/?q=vec#api/zh/math/Vector3

同样,我们需要在创建物体的代码后面,且在使用渲染器开始渲染的代码之前,设置物体的缩放与旋转。

缩放代码三个参数分别表示xyz轴的放大倍数,可单独设置。http://localhost:8080/docs/?q=Ob#api/zh/core/Object3D.scale

1
2
3
// 设置物体的缩放
this.cube.scale.set(3, 2, 1)
// this.cube.scale.x = 5

旋转代码的三个数字参数分别表示xyz轴的旋转角度,以弧度来表示。http://localhost:8080/docs/?q=Ob#api/zh/core/Object3D.rotation

1
2
3
// 设置物体的旋转
this.cube.rotation.set(Math.PI / 4, 0, 0) // π是180度;π/4是45度
// this.cube.rotation.set(Math.PI / 4, 0, 0, "XYZ")

同样,直接在重绘渲染函数render()中添加代码,可以实现动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 重绘渲染函数
render() {
// 修改物体的位置
this.cube.position.x += 0.01
if (this.cube.position.x > 5) {
this.cube.position.x = 0
}

// 设置物体的旋转
this.cube.rotation.x += 0.01

// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}

完整Vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
<template>
<div></div>
</template>

<script>
// 04 物体的缩放与旋转
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

export default {
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null, // 轨道控制器
axesHelper: null // 添加坐标轴辅助器
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, // 摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中

// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)
this.scene.add(this.cube) // 将几何体对象添加到场景中

// 修改物体的位置
// this.cube.position.set(5, 0, 0)
// this.cube.position.x = 3

// 设置物体的缩放
this.cube.scale.set(3, 2, 1)
// this.cube.scale.x = 5

// 设置物体的旋转
this.cube.rotation.set(Math.PI / 4, 0, 0) // π是180度;π/4是45度
// this.cube.rotation.set(Math.PI / 4, 0, 0, "XYZ")

// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

// // 5、渲染
// // 使用渲染器,通过相机将场景渲染进来
// this.renderer.render(this.scene, this.camera)

// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)

// 添加坐标轴辅助器
this.axesHelper = new THREE.AxesHelper(5)
this.scene.add(this.axesHelper) // 添加到场景中
},
// 重绘渲染函数
render() {
// 修改物体的位置
this.cube.position.x += 0.01
if (this.cube.position.x > 5) {
this.cube.position.x = 0
}

// 设置物体的旋转
this.cube.rotation.x += 0.01

// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}
},
mounted() {
this.init()
this.render()
}
}
</script>

<style lang="less" scoped>
</style>


05 应用requestAnimationFrame

由于机器性能问题,requestAnimationFrame(this.render)调用的重绘渲染函数在执行时,默认传入的时间参数在运行时其实是不均匀的,也就是说不同帧之间不均匀,可以打印查看:

1
2
3
render(time) {
console.log('默认传入的毫秒数' + time)
}

为了解决这个问题可以如下进行优化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 重绘渲染函数
render(time) {
// // 修改物体的位置
// this.cube.position.x += 0.01
// if (this.cube.position.x > 5) {
// this.cube.position.x = 0
// }

// console.log('默认传入的毫秒数' + time)
const t = time / 1000 // 毫秒除以1000得到当前的秒数
this.cube.position.x = t * 1 // 路程=时间*速度
if (this.cube.position.x > 5) {
this.cube.position.x = 0
}

// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
},

时间在大于5秒后,位置会回到0,如果想往复运动,可以取余数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 重绘渲染函数
render(time) {
// // 修改物体的位置
// this.cube.position.x += 0.01
// if (this.cube.position.x > 5) {
// this.cube.position.x = 0
// }

// console.log('默认传入的毫秒数' + time)
// const t = time / 1000 // 毫秒除以1000得到当前的秒数
// this.cube.position.x = t * 1 // 路程=时间*速度
// if (this.cube.position.x > 5) {
// this.cube.position.x = 0
// }
const t = (time / 1000) % 5 // 毫秒除以1000得到当前的秒数,除以5求余数
this.cube.position.x = t * 1 // 路程=时间*速度

// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
},

完整Vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
<template>
<div></div>
</template>

<script>
// 04 物体的缩放与旋转
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

export default {
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null, // 轨道控制器
axesHelper: null // 添加坐标轴辅助器
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, // 摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中

// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)
this.scene.add(this.cube) // 将几何体对象添加到场景中

// 修改物体的位置
// this.cube.position.set(5, 0, 0)
// this.cube.position.x = 3

// 设置物体的缩放
// this.cube.scale.set(3, 2, 1)
// this.cube.scale.x = 5

// 设置物体的旋转
this.cube.rotation.set(Math.PI / 4, 0, 0) // π是180度;π/4是45度
// this.cube.rotation.set(Math.PI / 4, 0, 0, "XYZ")

// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

// // 5、渲染
// // 使用渲染器,通过相机将场景渲染进来
// this.renderer.render(this.scene, this.camera)

// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)

// 添加坐标轴辅助器
this.axesHelper = new THREE.AxesHelper(5)
this.scene.add(this.axesHelper) // 添加到场景中
},
// 重绘渲染函数
render(time) {
// // 修改物体的位置
// this.cube.position.x += 0.01
// if (this.cube.position.x > 5) {
// this.cube.position.x = 0
// }

// // 设置物体的旋转
// this.cube.rotation.x += 0.01

// console.log('默认传入的毫秒数' + time)
// const t = time / 1000 // 毫秒除以1000得到当前的秒数
// this.cube.position.x = t * 1 // 路程=时间*速度
// if (this.cube.position.x > 5) {
// this.cube.position.x = 0
// }
const t = (time / 1000) % 5 // 毫秒除以1000得到当前的秒数,除以5求余数
this.cube.position.x = t * 1 // 路程=时间*速度

// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}
},
mounted() {
this.init()
this.render()
}
}
</script>

<style lang="less" scoped>
</style>

现在物体的运动就是随时间均匀变化,而不是随帧数变化的了。


06 通过Clock跟踪时间处理动画

由于requestAnimationFrame只有一个时间参数,如果有多个内容想要多个计时的话,只用这一个来计时会很累。

这就需要我们的Three.js提供的Clock来跟踪时间对象了:http://localhost:8080/docs/?q=clock#api/zh/core/Clock

1
2
// 设置时钟
this.clock = new THREE.Clock()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 重绘渲染函数
render() {
// 获取运行时间(自时钟启动后的秒数)
// const time = this.clock.getElapsedTime()
// console.log('时钟运行总时长:', time)

// 获取间隔时间(自上次获取时间到当前的秒数)
const deltaTime = this.clock.getDelta()
console.log('获取两次时间的间隔:', deltaTime)

// 设置物体的位置
// const t = (time / 1000) % 5 // 毫秒除以1000得到当前的秒数,除以5求余数
// this.cube.position.x = t * 1 // 路程=时间*速度

// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
},

07 Gsap动画库基本使用与原理

上面我们通过手动计算速度和时间来控制物体的移动和旋转,如何更加简便,不用自己去做这些麻烦步骤呢?

这里我们就可以使用Gsap(补间动画)来帮助我们开发。它是专门用来做动画的,比如用在css动画、svg动画、React项目、Vue项目,还可以做数字动画。

使用npm命令安装,或者在可视化面板中安装运行依赖,搜索gsap

1
2
// 导入动画库
import gsap from 'gsap'
  • 控制物体的位置属性x,移动到5的位置,花费5秒钟:

    1
    2
    // 设置动画
    gsap.to(this.cube.position, { x: 5, duration: 5 })
  • 控制物体的旋转属性x,旋转360度,花费5秒钟:

    1
    gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5 })
  • 可以前往官方文档查看动画缓入缓出Easing属性

    1
    2
    3
    // 设置动画
    gsap.to(this.cube.position, { x: 5, duration: 5, ease: 'power1.inout' })
    gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5, ease: 'power1.inout' })
  • 可以设置一些回调函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 设置动画
    gsap.to(this.cube.position, {
    x: 5,
    duration: 5,
    ease: 'power1.inout',
    onStart: () => {
    console.log('动画开始')
    },
    onComplete: () => {
    console.log('动画完成')
    }
    })
    gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5, ease: 'power1.inout' })
  • 可以通过repeat设置重复次数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 设置动画
    gsap.to(this.cube.position, {
    x: 5,
    duration: 5,
    ease: 'power1.inout',
    repeat: 2, // 设置重复次数,无限次循环为-1
    onStart: () => {
    console.log('动画开始')
    },
    onComplete: () => {
    console.log('动画完成')
    }
    })
    gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5, ease: 'power1.inout' })
  • 可以通过yoyo: true设置往返运动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 设置动画
    gsap.to(this.cube.position, {
    x: 5,
    duration: 5,
    ease: 'power1.inout',
    repeat: 2, // 设置重复次数,无限次循环为-1
    yoyo: true, // 往返运动
    onStart: () => {
    console.log('动画开始')
    },
    onComplete: () => {
    console.log('动画完成')
    }
    })
    gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5, ease: 'power1.inout' })
  • 可以通过delay设置延迟秒数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 设置动画
    gsap.to(this.cube.position, {
    x: 5,
    duration: 5,
    ease: 'power1.inout',
    repeat: 2, // 设置重复次数,无限次循环为-1
    yoyo: true, // 往返运动
    delay: 2, // 延迟两秒
    onStart: () => {
    console.log('动画开始')
    },
    onComplete: () => {
    console.log('动画完成')
    }
    })
    gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5, ease: 'power1.inout' })
  • 可以通过监听事件控制动画,如单击click或双击dblclick时暂停

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 设置动画
    const animate1 = gsap.to(this.cube.position, {
    x: 5,
    duration: 5,
    ease: 'power1.inout',
    repeat: 2, // 设置重复次数,无限次循环为-1
    yoyo: true, // 往返运动
    delay: 2, // 延迟两秒
    onStart: () => {
    console.log('动画开始')
    },
    onComplete: () => {
    console.log('动画完成')
    }
    })
    gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5, ease: 'power1.inout' })

    // 双击时暂停
    window.addEventListener('dblclick', () => {
    // console.log(animate1)
    animate1.pause() // 暂停
    })
  • 或者通过点击事件恢复动画

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 设置动画
    const animate1 = gsap.to(this.cube.position, {
    x: 5,
    duration: 5,
    ease: 'power1.inout',
    repeat: 2, // 设置重复次数,无限次循环为-1
    yoyo: true, // 往返运动
    delay: 2, // 延迟两秒
    onStart: () => {
    console.log('动画开始')
    },
    onComplete: () => {
    console.log('动画完成')
    }
    })
    gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5, ease: 'power1.inout' })

    // 双击时暂停
    window.addEventListener('dblclick', () => {
    // console.log(animate1)
    if (animate1.isActive()) {
    animate1.pause() // 暂停
    } else {
    animate1.resume() // 恢复
    }
    })

完整Vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<template>
<div></div>
</template>

<script>
// 07 Gsap动画库基本使用与原理
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 导入动画库
import gsap from 'gsap'

export default {
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null, // 轨道控制器
axesHelper: null, // 添加坐标轴辅助器
clock: null // 设置时钟
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, // 摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中

// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)
this.scene.add(this.cube) // 将几何体对象添加到场景中

// 修改物体的位置
// this.cube.position.set(5, 0, 0)
// this.cube.position.x = 3

// 设置物体的缩放
// this.cube.scale.set(3, 2, 1)
// this.cube.scale.x = 5

// 设置物体的旋转
this.cube.rotation.set(Math.PI / 4, 0, 0) // π是180度;π/4是45度
// this.cube.rotation.set(Math.PI / 4, 0, 0, "XYZ")

// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

// // 5、渲染
// // 使用渲染器,通过相机将场景渲染进来
// this.renderer.render(this.scene, this.camera)

// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)

// 添加坐标轴辅助器
this.axesHelper = new THREE.AxesHelper(5)
this.scene.add(this.axesHelper) // 添加到场景中

// 设置时钟
this.clock = new THREE.Clock()

// 设置动画
const animate1 = gsap.to(this.cube.position, {
x: 5,
duration: 5,
ease: 'power1.inout',
repeat: 2, // 设置重复次数,无限次循环为-1
yoyo: true, // 往返运动
delay: 2, // 延迟两秒
onStart: () => {
console.log('动画开始')
},
onComplete: () => {
console.log('动画完成')
}
})
gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5, ease: 'power1.inout' })

// 双击时暂停
window.addEventListener('dblclick', () => {
// console.log(animate1)
if (animate1.isActive()) {
animate1.pause() // 暂停
} else {
animate1.resume() // 恢复
}
})
},
// 重绘渲染函数
render() {
// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}
},
mounted() {
this.init()
this.render()
}
}
</script>

<style lang="less" scoped>
</style>


08 控制器阻尼与画面尺寸自适应

1、控制器阻尼(惯性)

轨道控制器启用阻尼(惯性):http://localhost:8080/docs/?q=orbi#examples/zh/controls/OrbitControls.enableDamping

  • .enableDamping : Boolean
    1. 将其设置为true以启用阻尼(惯性),这将给控制器带来重量感。默认值为false
    2. 请注意,如果该值被启用,你将必须在你的动画循环里调用.update()

代码添加到创建控制器之后:

1
2
3
4
// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
// 设置控制器阻尼,让其更有真实效果
this.controls.enableDamping = true

在动画循环里调用.update()

1
2
3
4
5
6
7
8
9
// 重绘渲染函数
render() {
// 控制器阻尼的调用函数
this.controls.update()
// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}

2、渲染画面尺寸自适应

监听屏幕画面大小变化,修改渲染器宽高和相机的比例,更新渲染画面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
mounted() {
this.init()
this.render()

// 监听画面变化,更新渲染画面
window.addEventListener('resize', () => {
// console.log('画面变化了')

// 更新摄像头(宽高比)
this.camera.aspect = window.innerWidth / window.innerHeight
// 更新摄像机的投影矩阵
this.camera.updateProjectionMatrix()

// 更新渲染器
this.renderer.setSize(window.innerWidth, window.innerHeight)
// 设置渲染器的像素比(让其等于设备的像素比)
this.renderer.setPixelRatio(window.deviceRixelRatio)
})
}

完整Vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<template>
<div></div>
</template>

<script>
// 08 控制器阻尼与画面尺寸自适应
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 导入动画库
import gsap from 'gsap'

export default {
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null, // 轨道控制器
axesHelper: null, // 添加坐标轴辅助器
clock: null // 设置时钟
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, // 摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中

// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)
this.scene.add(this.cube) // 将几何体对象添加到场景中

// 修改物体的位置
// this.cube.position.set(5, 0, 0)
// this.cube.position.x = 3

// 设置物体的缩放
// this.cube.scale.set(3, 2, 1)
// this.cube.scale.x = 5

// 设置物体的旋转
this.cube.rotation.set(Math.PI / 4, 0, 0) // π是180度;π/4是45度
// this.cube.rotation.set(Math.PI / 4, 0, 0, "XYZ")

// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

// // 5、渲染
// // 使用渲染器,通过相机将场景渲染进来
// this.renderer.render(this.scene, this.camera)

// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
// 设置控制器阻尼,让其更有真实效果
this.controls.enableDamping = true

// 添加坐标轴辅助器
this.axesHelper = new THREE.AxesHelper(5)
this.scene.add(this.axesHelper) // 添加到场景中

// 设置时钟
this.clock = new THREE.Clock()

// 设置动画
const animate1 = gsap.to(this.cube.position, {
x: 5,
duration: 5,
ease: 'power1.inout',
repeat: 2, // 设置重复次数,无限次循环为-1
yoyo: true, // 往返运动
delay: 2, // 延迟两秒
onStart: () => {
console.log('动画开始')
},
onComplete: () => {
console.log('动画完成')
}
})
gsap.to(this.cube.rotation, { x: 2 * Math.PI, duration: 5, ease: 'power1.inout' })

// 双击时暂停
window.addEventListener('dblclick', () => {
// console.log(animate1)
if (animate1.isActive()) {
animate1.pause() // 暂停
} else {
animate1.resume() // 恢复
}
})
},
// 重绘渲染函数
render() {
// 控制器阻尼的调用函数
this.controls.update()
// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}
},
mounted() {
this.init()
this.render()

// 监听画面变化,更新渲染画面
window.addEventListener('resize', () => {
// console.log('画面变化了')

// 更新摄像头(宽高比)
this.camera.aspect = window.innerWidth / window.innerHeight
// 更新摄像机的投影矩阵
this.camera.updateProjectionMatrix()

// 更新渲染器
this.renderer.setSize(window.innerWidth, window.innerHeight)
// 设置渲染器的像素比(让其等于设备的像素比)
this.renderer.setPixelRatio(window.deviceRixelRatio)
})
}
}
</script>

<style lang="less" scoped>
</style>


09 调用js接口控制画布全屏和退出全屏

键盘的F11一般可以控制浏览器的全屏,或者我们通过JS操作。

  • 双击进入全屏

    1
    2
    3
    4
    5
    // 双击控制屏幕进入全屏
    window.addEventListener('dblclick', () => {
    // 渲染器的画布
    this.renderer.domElement.requestFullscreen()
    })
  • 双击控制屏幕进入全屏,退出全屏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 双击控制屏幕进入全屏,退出全屏
    window.addEventListener('dblclick', () => {
    // 获取全屏元素
    const fullScreenElement = document.fullscreenElement
    if (!fullScreenElement) {
    // 渲染器的画布进入全屏
    this.renderer.domElement.requestFullscreen()
    } else {
    // 文档退出全屏
    document.exitFullscreen()
    }
    })

完整Vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
<template>
<div></div>
</template>

<script>
// 09 调用js接口控制画布全屏和退出全屏
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'

export default {
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null, // 轨道控制器
axesHelper: null, // 添加坐标轴辅助器
clock: null // 设置时钟
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, // 摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中

// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)
this.scene.add(this.cube) // 将几何体对象添加到场景中

// 修改物体的位置
// this.cube.position.set(5, 0, 0)
// this.cube.position.x = 3

// 设置物体的缩放
// this.cube.scale.set(3, 2, 1)
// this.cube.scale.x = 5

// 设置物体的旋转
this.cube.rotation.set(Math.PI / 4, 0, 0) // π是180度;π/4是45度
// this.cube.rotation.set(Math.PI / 4, 0, 0, "XYZ")

// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

// // 5、渲染
// // 使用渲染器,通过相机将场景渲染进来
// this.renderer.render(this.scene, this.camera)

// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
// 设置控制器阻尼,让其更有真实效果
this.controls.enableDamping = true

// 添加坐标轴辅助器
this.axesHelper = new THREE.AxesHelper(5)
this.scene.add(this.axesHelper) // 添加到场景中

// 设置时钟
this.clock = new THREE.Clock()

// 双击控制屏幕进入全屏,退出全屏
window.addEventListener('dblclick', () => {
// 获取全屏元素
const fullScreenElement = document.fullscreenElement
if (!fullScreenElement) {
// 渲染器的画布进入全屏
this.renderer.domElement.requestFullscreen()
} else {
// 文档退出全屏
document.exitFullscreen()
}
})
},
// 重绘渲染函数
render() {
// 控制器阻尼的调用函数
this.controls.update()
// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}
},
mounted() {
this.init()
this.render()

// 监听画面变化,更新渲染画面
window.addEventListener('resize', () => {
// console.log('画面变化了')

// 更新摄像头(宽高比)
this.camera.aspect = window.innerWidth / window.innerHeight
// 更新摄像机的投影矩阵
this.camera.updateProjectionMatrix()

// 更新渲染器
this.renderer.setSize(window.innerWidth, window.innerHeight)
// 设置渲染器的像素比(让其等于设备的像素比)
this.renderer.setPixelRatio(window.deviceRixelRatio)
})
}
}
</script>

<style lang="less" scoped>
</style>


10 应用图形用户界面更改变量

如果想方便地在页面调整参数来控制画面,有没有简便的方法?

这就要用到一个轻量级的 JavaScript 控制器库dat.GUI。它使我们可以轻松地即时操作变量和触发函数。我们可以通过设定好的控制器去快捷的修改设定的变量。

在可视化面板安装运行依赖,搜索dat.gui并安装。

1
2
// 导入dat.gui
import * as dat from 'dat.gui'

1、改变物体位置x轴的值

  • 在创建完物体并放置在场景中后,初始化(创建)GUI,并让其可以改变物体x轴坐标:
    1
    2
    3
    4
    5
    6
    7
    8
    // 将几何体对象添加到场景中
    this.scene.add(this.cube)

    console.log(this.cube)
    // 创建GUI
    const gui = new dat.GUI()
    // 改变物体位置x轴的值
    gui.add(this.cube.position, 'x').min(0).max(5)

此时页面上就出现了一个可拖动的进度条,与物体x轴坐标绑定,.min().max()控制着数值的变化范围。

  • 我们可以通过.step()让数值增减的步长更加精细:

    1
    2
    3
    4
    5
    6
    7
    8
    // 将几何体对象添加到场景中
    this.scene.add(this.cube)

    console.log(this.cube)
    // 创建GUI
    const gui = new dat.GUI()
    // 改变物体位置x轴的值
    gui.add(this.cube.position, 'x').min(0).max(5).step(0.01)
  • 也可以通过.name()对此项参数名称自定义:

    1
    2
    3
    4
    5
    6
    7
    8
    // 将几何体对象添加到场景中
    this.scene.add(this.cube)

    console.log(this.cube)
    // 创建GUI
    const gui = new dat.GUI()
    // 改变物体位置x轴的值
    gui.add(this.cube.position, 'x').min(0).max(5).step(0.01).name('移动x轴')
  • 还可以通过.onChange函数在参数修改时执行语句:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 将几何体对象添加到场景中
    this.scene.add(this.cube)

    console.log(this.cube)
    // 创建GUI
    const gui = new dat.GUI()
    // 改变物体位置x轴的值
    gui
    .add(this.cube.position, 'x')
    .min(0)
    .max(5)
    .step(0.01)
    .name('移动x轴')
    .onChange((value) => {
    console.log('值被修改', value)
    })
  • 或者通过.onFinishChange函数在停止修改时执行语句:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 将几何体对象添加到场景中
    this.scene.add(this.cube)

    console.log(this.cube)
    // 创建GUI
    const gui = new dat.GUI()
    // 改变物体位置x轴的值
    gui
    .add(this.cube.position, 'x')
    .min(0)
    .max(5)
    .step(0.01)
    .name('移动x轴')
    .onChange((value) => {
    console.log('值被修改', value)
    })
    .onFinishChange((value) => {
    console.log('完全停下来', value)
    })

2、改变物体的颜色属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 将几何体对象添加到场景中
this.scene.add(this.cube)

console.log(this.cube)
// 创建GUI
const gui = new dat.GUI()
// 改变物体位置x轴的值
gui
.add(this.cube.position, 'x')
.min(0)
.max(5)
.step(0.01)
.name('移动x轴')
.onChange((value) => {
console.log('值被修改', value)
})
.onFinishChange((value) => {
console.log('完全停下来', value)
})
// 改变物体的颜色属性
const params = {
color: '#ffff00'
}
gui.addColor(params, 'color').onChange((value) => {
// console.log('值被修改', value)
this.cube.material.color.set(value)
})

3、改变物体的显示属性(可见性)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 将几何体对象添加到场景中
this.scene.add(this.cube)

console.log(this.cube)
// 创建GUI
const gui = new dat.GUI()
// 改变物体位置x轴的值
gui
.add(this.cube.position, 'x')
.min(0)
.max(5)
.step(0.01)
.name('移动x轴')
.onChange((value) => {
console.log('值被修改', value)
})
.onFinishChange((value) => {
console.log('完全停下来', value)
})
// 改变物体的颜色属性
const params = {
color: '#ffff00'
}
gui.addColor(params, 'color').onChange((value) => {
// console.log('值被修改', value)
this.cube.material.color.set(value)
})
// 设置选项框,改变物体的显示属性
gui.add(this.cube, 'visible').name('是否显示')

4、执行自定义事件处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 将几何体对象添加到场景中
this.scene.add(this.cube)

console.log(this.cube)
// 创建GUI
const gui = new dat.GUI()
// 改变物体位置x轴的值
gui
.add(this.cube.position, 'x')
.min(0)
.max(5)
.step(0.01)
.name('移动x轴')
.onChange((value) => {
console.log('值被修改', value)
})
.onFinishChange((value) => {
console.log('完全停下来', value)
})
// 改变物体的颜色属性
const params = {
color: '#ffff00',
fn: () => {
// 让立方体运动起来
gsap.to(this.cube.position, { x: 5, duration: 2, yoyo: true, repeat: -1 })
}
}
gui.addColor(params, 'color').onChange((value) => {
// console.log('值被修改', value)
this.cube.material.color.set(value)
})
// 设置选项框,改变物体的显示属性
gui.add(this.cube, 'visible').name('是否显示')
// 设置按钮,点击触发某个事件
gui.add(params, 'fn').name('立方体运动')

5、设置可展开的文件夹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 将几何体对象添加到场景中
this.scene.add(this.cube)

console.log(this.cube)
// 创建GUI
const gui = new dat.GUI()
// 改变物体位置x轴的值
gui
.add(this.cube.position, 'x')
.min(0)
.max(5)
.step(0.01)
.name('移动x轴')
.onChange((value) => {
console.log('值被修改', value)
})
.onFinishChange((value) => {
console.log('完全停下来', value)
})
// 改变物体的颜色属性
const params = {
color: '#ffff00',
fn: () => {
// 让立方体运动起来
gsap.to(this.cube.position, { x: 5, duration: 2, yoyo: true, repeat: -1 })
}
}
gui.addColor(params, 'color').onChange((value) => {
// console.log('值被修改', value)
this.cube.material.color.set(value)
})
// 设置选项框,改变物体的显示属性
gui.add(this.cube, 'visible').name('是否显示')
// 设置按钮,点击触发某个事件
gui.add(params, 'fn').name('立方体运动')

// 设置可展开的文件夹
const folder = gui.addFolder('设置立方体')
// 文件夹内,设置改变立方体线框属性的选项
folder.add(cube.material, 'wireframe')

文件夹可添加多个属性:

1
2
3
4
5
6
// 设置可展开的文件夹
const folder = gui.addFolder('设置立方体')
// 文件夹内,设置改变立方体线框属性的选项
folder.add(this.cube.material, 'wireframe')
// 文件夹内,设置按钮,点击触发某个事件
folder.add(params, 'fn').name('立方体运动')

完整Vue代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
<template>
<div></div>
</template>

<script>
// 10 应用图形用户界面更改变量
import * as THREE from 'three'
// 导入轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 导入dat.gui
import * as dat from 'dat.gui'
// 导入动画库
import gsap from 'gsap'

export default {
data() {
return {
scene: null, // 场景对象
camera: null, // 摄像机对象
cubeGeometry: null, // 几何体
cubeMaterial: null, // 材质
cube: null, // 几何体对象
renderer: null, // 渲染器
controls: null, // 轨道控制器
axesHelper: null, // 添加坐标轴辅助器
clock: null // 设置时钟
}
},
methods: {
init() {
// 1、创建场景对象
this.scene = new THREE.Scene()

// 2、创建摄像机
this.camera = new THREE.PerspectiveCamera(
75, // 摄像机视锥体垂直视野角度
window.innerWidth / window.innerHeight, // 摄像机视锥体长宽比
0.1, // 摄像机视锥体近端面
1000 // 摄像机视锥体远端面
)
this.camera.position.set(0, 0, 10) // 设置摄像机位置坐标
this.scene.add(this.camera) // 将摄像机添加到场景中

// 3、创建物体对象
// (1) 添加几何体
this.cubeGeometry = new THREE.BoxGeometry(1, 1, 1)
// (2) 创建材质
this.cubeMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 })
// (3) 根据几何体和材质创建对象
this.cube = new THREE.Mesh(this.cubeGeometry, this.cubeMaterial)

// 修改物体的位置
// this.cube.position.set(5, 0, 0)
// this.cube.position.x = 3

// 设置物体的缩放
// this.cube.scale.set(3, 2, 1)
// this.cube.scale.x = 5

// 设置物体的旋转
this.cube.rotation.set(Math.PI / 4, 0, 0) // π是180度;π/4是45度
// this.cube.rotation.set(Math.PI / 4, 0, 0, "XYZ")

// 将几何体对象添加到场景中
this.scene.add(this.cube)

console.log(this.cube)
// 创建GUI
const gui = new dat.GUI()
// 改变物体位置x轴的值
gui
.add(this.cube.position, 'x')
.min(0)
.max(5)
.step(0.01)
.name('移动x轴')
.onChange((value) => {
console.log('值被修改', value)
})
.onFinishChange((value) => {
console.log('完全停下来', value)
})
// 改变物体的颜色属性
const params = {
color: '#ffff00',
fn: () => {
// 让立方体运动起来
gsap.to(this.cube.position, { x: 5, duration: 2, yoyo: true, repeat: -1 })
}
}
gui.addColor(params, 'color').onChange((value) => {
// console.log('值被修改', value)
this.cube.material.color.set(value)
})
// 设置选项框,改变物体的显示属性
gui.add(this.cube, 'visible').name('是否显示')

// 设置可展开的文件夹
const folder = gui.addFolder('设置立方体')
// 文件夹内,设置改变立方体线框属性的选项
folder.add(this.cube.material, 'wireframe')
// 文件夹内,设置按钮,点击触发某个事件
folder.add(params, 'fn').name('立方体运动')

// 4、初始化渲染器
this.renderer = new THREE.WebGLRenderer()
// 设置渲染的尺寸大小
this.renderer.setSize(window.innerWidth, window.innerHeight)
// console.log(this.renderer) // 渲染器渲染出的canvas画布
// 将WebGL渲染的内容canvas添加到body
document.body.appendChild(this.renderer.domElement)

// // 5、渲染
// // 使用渲染器,通过相机将场景渲染进来
// this.renderer.render(this.scene, this.camera)

// 创建轨道控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
// 设置控制器阻尼,让其更有真实效果
this.controls.enableDamping = true

// 添加坐标轴辅助器
this.axesHelper = new THREE.AxesHelper(5)
this.scene.add(this.axesHelper) // 添加到场景中

// 设置时钟
this.clock = new THREE.Clock()

// 双击控制屏幕进入全屏,退出全屏
window.addEventListener('dblclick', () => {
// 获取全屏元素
const fullScreenElement = document.fullscreenElement
if (!fullScreenElement) {
// 渲染器的画布进入全屏
this.renderer.domElement.requestFullscreen()
} else {
// 文档退出全屏
document.exitFullscreen()
}
})
},
// 重绘渲染函数
render() {
// 控制器阻尼的调用函数
this.controls.update()
// 使用渲染器,通过相机将场景渲染进来
this.renderer.render(this.scene, this.camera)
// 请求动画帧,渲染下一帧的时候就会调用render函数,也就是回调自身
requestAnimationFrame(this.render)
}
},
mounted() {
this.init()
this.render()

// 监听画面变化,更新渲染画面
window.addEventListener('resize', () => {
// console.log('画面变化了')

// 更新摄像头(宽高比)
this.camera.aspect = window.innerWidth / window.innerHeight
// 更新摄像机的投影矩阵
this.camera.updateProjectionMatrix()

// 更新渲染器
this.renderer.setSize(window.innerWidth, window.innerHeight)
// 设置渲染器的像素比(让其等于设备的像素比)
this.renderer.setPixelRatio(window.deviceRixelRatio)
})
}
}
</script>

<style lang="less" scoped>
</style>


【参考内容】: