这是three.js学习的第20篇笔记,也是基础学习的最后一篇笔记。这篇笔记会综合复习一下已经学习过的内容。因为three.js还有很多的知识,所以这篇笔记里面还是会继续有干货出现。同时,这一篇笔记我们会重新从0到1做一个完整three.js应用。
在开始之前,新建一个叫learnThree20.html
的文件,除了基本的设置,什么都不要做,也不要复制基础代码。我们真的会从零开始做。做完之后,咱们开始复习吧。
跟着这篇笔记完整走下去,会做出下面这样一个完整的应用。
认识three.js应用结构
在threejs学习第一课:了解threejs的基本概念中,我们通过下图知道了three.js的基本应用结构。
一个three.js应用,主要由渲染引擎
Renderer
, 场景Scene
, 相机Camera
三部分组成,而一个场景Scene
则由以Mesh
为基础的各种物体和光源light
组成。我们最近学习的,都是这些基础的东西。认识和创建three.js应用中的
renderer
在threejs学习第二课:hello three.js中,我们创建了第一个
renderer
。renderer
是由THREE.WebGLRenderer
构造函数实例化而来。在实例化的过程中,THREE.WebGLRenderer
接收一个可选参数,这个参数是一个对象。对象里面可以放一系列参数。其中,我们用到最多的是canvas
。这个canvas会被three.js用来绘制内容。当然,也可以啥都不传,这时候three.js会自己创建一个canvas,然后我们可以通过实际话后的变量属性domElement
获取到,比如const canvas = renderer.domElement
,这样就获取到了three.js自己创建的canvas。虽然three.js总是能自动创建canvas,我还是更喜欢将canvas先创建好后传入
THREE.WebGLRenderer
来构建渲染引擎,感觉这样做对canvas的控制更灵活一点。现在,我们就来创建一个渲染引擎
renderer
吧。1.我们在
body
标签中间新建一个canvas
标签,设置属性id='learnThree'
.1
2
3<body>
<canvas id='learnThree'></canvas>
</body>- 设置canvas的样式,让它全屏显示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<head>
···
<style>
html,body{
height: 100%;
margin: 0;
}
#learnThree{
display: block;
width: 100%;
height: 100%;
}
</style>
···
</head> - 新建一个main函数并且实例化渲染引擎
1
2
3
4
5
6
7
8
9<body>
···
<script>
function main () {
const canvas = document.querySelector('#learnThree')
const renderer = THREE.WebGLRenderer({canvas})
}
</script>
</body> - 在
main
函数内新建一个render
函数用来执行渲染动作
three.js基础学习之WebGLRenderer中,我们学习了
WebGLRenderer
的渲染方法render
,他会通过camera
相机将scene
场景渲染到canvas上,从而展现出来。1
2
3
4
5
6function main () {
···
function render () {
renderer.render(scene, camera) // scene camera后面会复习到
}
}- 设置canvas的样式,让它全屏显示。
认识和创建three.js应用中的相机
camera
threejs学习第二课:hello three.js我们通过
PerspectiveCamera
透视相机创建了一个相机。PerspectiveCamera
透视相机是用来模拟人眼看到的景象,在创建时需要传入四个参数,分别是fov
视野范围,aspect
相机的长款比,near
相机距离画面的最近距离,far
相机可显示画面的最远距离, 这四个参数一起构成了视锥,如下图所示。详细的可以点击 threejs学习第二课:hello three.js回去查看。
现在,我们在
main
函数中新增一个相机1
2
3
4
5
6
7
8
9function main () {
...
const fov = 70
const aspect = 2
const near = 0.1
const far = 500
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
...
}认识和创建three.js应用中的场景
scene
场景就像一个戏台,提供场所,让演员来尽情唱戏。three.js应用中所有的影响,都需要添加到场景中,才能够展示出来。而构建场景非常简单,只需要实例化
THREE.Scene
即可。所以,我们接下增加一个场景吧。1
2
3
4
5function main () {
...
const scene = new THREE.Scene()
...
}创建完后我们可以运行main函数看看。这时候没有意外的话,应该会看一片漆黑。这是因为咱们现在还什么都没放进去。
认识和创建three.js应用中的光源
Light
我们花了四篇笔记来学习光源,分别是three.js基础学习之光源,three.js基础学习之平行光源DirectionalLight,three.js基础学习之环境光源AmbientLight,three.js基础学习之类人造光源
作为光源,基本的要素有两个,光的颜色和光照强度。在将光源实例化的时候,我们可以根据自己的需要传入光照强度和光照颜色。如果不传,默认颜色一般为白色
#ffffff
,默认强度一般为1。在这里,我们创建一个环境光源
1
2
3
4
5
6
7
8
9
10function main () {
...
{
const lightColor = 0xffffff
const intensity = 1
const light = new THREE.AmbientLight(lightColor, intensity)
scene.add(light)
}
...
}现在刷新页面,依旧是啥都看不到,原因很简单,我们还没有放置承接光源的东西。
认识和创建three.js应用中的形状
Mesh
形状这一块,我们花了大量的时间去学习,主要包括了三块。
- 图元
geometry
(为了方便理解我们叫它‘设计图’)
- 图元
- 材质
material
纹理贴图
Texture
通过这些学习,我们知道,一个3d物体想展示出来,需要通过
mesh
将图元geometry
和材料Material
结合起来。现在,我们就来创建three.js的3D世界吧。
1.使用PlaneGeometry
创建一个平面1
2
3
4
5
6
7
8
9
10
11
12
13
14function main () {
...
{
const width = 10
const height = 10
const planeGeometry = new THREE.PlaneGeometry(width, height)
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0xffffff
})
cosnt plane = new THREE.Mesh(planeGeometry, planeMaterial)
scene.add(plane)
}
...
}这个时候,我们刷新查看,发现还是黑漆漆的一片,啥都看不见。这是什么原因呢?
因为我们three.js中新构建的物体,不管是camera还是light,还是其它形状,默认位置都在(0,0,0)处。因此,我们刚刚新建的平面和camera重叠在了一起。要解决这个问题,我们只需要移动相机或者plane即可。
2.利用
Group
统一管理形状
在Three.js中,我们可以使用Group
来统一管理一些东西。可以使用add
方法添加新的对象到里面去,如果想移除,则使用remove
方法。
这里,我们新建一个Group
,并且移动位置让前面新建的平面显示出来。function main () {
... const group = new THREE.Group() group.position.z = -10 group.position.y = -1 { const width = 15 const height = 15 const planeGeometry = new THREE.PlaneGeometry(width, height) const planeMaterial = new THREE.MeshBasicMaterial({ color: 0xffffff }) cosnt plane = new THREE.Mesh(planeGeometry, planeMaterial) group.add(plane) } scene.add(group) ...
}
- 调整旋转平台,让平面变成一个平台现在,能够看到一个平台,不过平台上充满了锯齿,改变屏幕大小形状也会变的乱七八糟。我们接着解决问题吧
1
2
3
4
5
6
7...
cosnt plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.rotation.x = Math.PI * -0.5
plane.position.z = -3
plane.position.y = -4
group.add(plane)
...
- 调整旋转平台,让平面变成一个平台
three.js响应式设计开发之屏幕适配和自适应
在(threejs响应式设计开发)[/three/learnThree3.html]中,我们了解到,canvas的展示和图像的展示差不多,当图片的尺寸小于实际展示尺寸时,就会出现虚化和锯齿的现象。因此为了让canvas能清晰的将图像显示出来,我们需要确保显示的canvas和canvas本身大小一致。为了考虑视网膜屏幕,我们在确保显示canvas大小和实际大小的同时,还得考虑屏幕像素比。下面我们来写自适应的代码。
1.检测是否需要调整canvas尺寸
1
2
3
4
5
6
7
8
9
10
11
12function main () {
...
function checkNeedResize () {
const pixelRatio = window.devicePixelRatio;
const needShowWidth = canvas.clientWidth * pixelRatio;
const needShowHeight = canvas.clientHeight * pixelRatio;
const width = canvas.width;
const height = canvas.height;
return needShowWidth !== width || needShowHeight !== height
}
...
}如果需要调整屏幕尺寸,在渲染之前调整
在three.js基础学习之WebGLRenderer中,我们学到过一个renderer.setSize(width,height,updateStyle)
方法,现在,如果需要调整尺寸,我们就用它。当我们调整完canvas的尺寸后,摄像机的长款比
aspect
也应该进行对应的调整。而相机的大多是属性调整后,我们都应该调用updateProjectionMatrix
方法来使它生效。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function main () {
...
function checkNeedResize () {
const pixelRatio = window.devicePixelRatio;
const needShowWidth = canvas.clientWidth * pixelRatio;
const needShowHeight = canvas.clientHeight * pixelRatio;
const width = canvas.width;
const height = canvas.height;
return needShowWidth !== width || needShowHeight !== height
}
function render () {
if (checkNeedResize()) {
const pixelRatio = window.devicePixelRatio;
const width = canvas.clientWidth * pixelRatio
const height = canvas.clientHeight * pixelRatio
renderer.setSize(width, height, false)
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
renderer.render(scene, camera) // scene camera后面会复习到
}
...
}做完这些后,我们已经得到了一个高清的平面了,现在,我们在它上面搭建点东西吧。
用three.js建立一个圆柱子
在three.js学习之常见几何体圆柱和球我们学习了圆柱的建立方法,现在我们就在白板上新建一个圆柱吧。
1
2
3
4
5
6
7
8
9
10
11
12
13function main () {
...
{
const radiusTop = 0.3;
const radiusBottom = 0.3;
const height = 6
const cylinderGeometry = new THREE.CylinderGeometry(radiusTop, radiusBottom, height);
const cylinderMaterial = new THREE.MeshBasicMaterial({color: 0xFFEC8B})
const cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial)
group.add(cylinder)
}
...
}现在,我们做出来的东西已经像是在一个广场上放置了一根柱子,接下来咱们再在上面放一个顶吧。
用three.js建立一个圆形拱顶
在three.js学习之常见几何体平面圆形和圆锥中,我们学习了如何建立一个圆锥。接下来,咱们一起给圆柱安一个顶子吧。
- 利用ConeGeometry,创建一个圆形拱顶
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15function main () {
...
{
const radius = 5;
const height = 2
const radialSegments = 100;
const heightSegments = 5;
const coneGeometry = new THREE.ConeGeometry(radius, height, radialSegments, heightSegments, true, 0 , 2 * Math.PI);
const coneMaterail = new THREE.MeshBasicMaterial({color: 0x8B814C})
const cone = new THREE.Mesh(coneGeometry, coneMaterail)
cone.position.y = 2.5
group2.add(cone)
}
...
} - 给圆拱加上线条让它看起来美观点
在three.js学习之盒子中我们学习了如何将一个物体用线条显示出来,如何在一个物体上显示线条。接下来我们给圆顶加上金色线条吧。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function main () {
...
{
const radius = 5;
const height = 2
const radialSegments = 100;
const heightSegments = 5;
const coneGeometry = new THREE.ConeGeometry(radius, height, radialSegments, heightSegments, true, 0 , 2 * Math.PI);
const coneMaterail = new THREE.MeshBasicMaterial({color: 0x8B814C})
const cone = new THREE.Mesh(coneGeometry, coneMaterail)
cone.position.y = 2.5
const lineMaterial = new THREE.MeshBasicMaterial({
color: 0xFFEC8B
})
const lineGeometry = new THREE.WireframeGeometry(coneGeometry)
const line = new THREE.Line(lineGeometry, lineMaterial)
line.position.y = 2.5
group2.add(cone, line)
}
...
}
- 利用ConeGeometry,创建一个圆形拱顶
让three.js应用动起来
虽然线条已经加进去了,但还是看起来有点僵硬,咱们加一个动画让它转起来吧。
在threejs学习第二课:hello three.jsz中,我们学习了通过requestAnimationFrame()
来为three.js添加动画。- 新建分组group2,并将圆柱和圆形顶都放到group2中,将group2添加到scene中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15...
const group = new THREE.Group()
group.position.z = -10
group.position.y = -1
const group2 = new THREE.Group()
group2.position.z = -10
group2.position.y = -1
...
group2.add(cylinder)
...
group2.add(circle, line)
...
scene.add(group2)
... 添加动画
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
27function main () {
...
function render (time) {
if (checkNeedResize()) {
const pixelRatio = window.devicePixelRatio;
const width = canvas.clientWidth * pixelRatio
const height = canvas.clientHeight * pixelRatio
renderer.setSize(width, height, false)
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
time = time * 0.0001
group2.rotation.y = time
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
...
}
```
现在我们已经做出了一个旋转的类似伞的东西,接下来再在伞下面加一个随着伞转动的简易四轮车吧。
* ## three.js创建一个简易四轮车
1. 创建一个车的整体
就像我们的伞一样,车也是一个整体,所以我们在这个里利用`Object3D`创建一个car实体。因为我们想要车跟着伞的边缘一起转圈,所以将car放到group2中去。function main () {
…
const group2 = new THREE.Group()
group2.position.z = -10
group2.position.y = -1const car = new THREE.Object3D()
group2.add(car)
…
}1
2. 利用BoxGeometry制造车体
function main () {
... { // 车整体的作用域 car.position.set(3.5,-3,0) { const carWidth = 1 const carHeight = 0.5 const carDepth = 1.5 const carBodyGeometry = new THREE.BoxGeometry(carWidth, carHeight, carDepth) const curMaterial = new THREE.MeshBasicMaterial({color: 0x87CEFA}) const carBody = new THREE.Mesh(carBodyGeometry, curMaterial) car.add(carBody) } } ...
}
1
2
3
4现在,刷新代码,应该能看到一个蓝色的方块在跟着伞转了。要是汽车真是个盒子,那估计没几天就废。接下来加个轮子吧
3. 利用TorusGeometry创建轮子
function main () {
... { // 车整体的作用域 ... { function makeRing (x, y, z) { const radius = 0.2; const tube = 0.05; const radialSegments = 50 const tubularSegments = 50 const torusGeometry = new THREE.TorusGeometry(radius, tube, radialSegments, tubularSegments) const torusMaterial = new THREE.MeshBasicMaterial({color: 0x000000}) const carRing = new THREE.Mesh(torusGeometry, torusMaterial) carRing.rotation.y = 0.5 * Math.PI carRing.position.set(x, y, z) car.add(carRing) } makeRing(-0.55, 0.11, 0.3) makeRing(-0.55, 0.11, -0.5) makeRing(0.55, 0.11, -0.5) makeRing(0.55, 0.11, 0.3) } } } ...
}
1
2
3
4
5
6
7
8
9现在,我们的小车已经绕着亭子转了。接下来我们给底部的平面加个纹理贴图,感觉起来亭子在草地上,车在草地上跑。
* ## three.js纹理贴图
* [three.js学习之纹理贴图Texture](/three/learnThree13.html)
* [three.js学习之纹理贴图Texture(二)纹理背景的位置移动旋转和重复](/three/learnThree14.html)
上面两篇中,我们学习了纹理贴图,现在我们去给平面贴上图。
我找了这张图,现在我们将它放上去![](https://picture.wuhoushu.com/blog/threejs/5d00541d75311.jpeg)
修改平面代码{
const width = 15 const height = 15 const planeGeometry = new THREE.PlaneGeometry(width, height) const loader = new THREE.TextureLoader() // 新增 const planeMaterial = new THREE.MeshBasicMaterial({ // color: 0xffffff 删除代码 map: loader.load('https://picture.wuhoushu.com/blog/threejs/5d00541d75311.jpeg') // 新增 }) const plane = new THREE.Mesh(planeGeometry, planeMaterial) plane.rotation.x = Math.PI * -0.5 plane.position.z = -4 plane.position.y = -4 group.add(plane)
}
1
2
3
4
到这里,我们three.js基础学习就结束了。下面一如即往的奉上源码。
大家也可以点击[https://www.91yqz.com/learnThree/learnThree20.html](https://www.91yqz.com/learnThree/learnThree20.html)这个页面查看<!DOCTYPE html>
three.js学习之从0开始做一个three.js应用
html,body{ height: 100%; margin: 0; } #learnThree{ display: block; width: 100%; height: 100%; }
- 新建分组group2,并将圆柱和圆形顶都放到group2中,将group2添加到scene中