three.js学习之常见几何体平面圆形和圆锥

three.js学习之盒子中,我们已经能够做出立方体并且根据需要进行变换了,点此可以查看demo

有了上一节的基础,我们就可以适当加快学习进度。这一节我们一起学习一些常见的几何体图形。

首先还是将three.js学习之盒子中的代码复制过来,新将html文件learnAboutCommonGeometry.html,然后删除掉有关立方体的代码,保留下来的代码如下:

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
function main () {                    
const canvas = document.querySelector('#three')
const renderer = new THREE.WebGLRenderer({canvas})
const fov = 75;
const aspect = 2;
const near = 0.1;
const far = 200;

const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)

const group = new THREE.Group()
group.position.z = -10

const scene = new THREE.Scene()
{
const lightColor = 0xffffff; // 灯光颜色
const intensity = 1; // 灯光强度
const light = new THREE.DirectionalLight(lightColor, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
scene.add(group)

function resizeRenderSizeToDisplaySize () {
const pixelRatio = window.devicePixelRatio;
const displayWidth = canvas.clientWidth * pixelRatio
const displayHeight = canvas.clientHeight * pixelRatio
const renderWidth = canvas.width;
const renderHeight = canvas.height;
const needResize = displayWidth !== renderWidth || displayHeight !== renderHeight
return needResize
}
function render (time) {
if (resizeRenderSizeToDisplaySize()) {
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 *= 0.001
group.position.y = time
group.position.x = time
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
}

细心的朋友应该发现我对自适应代买resizeRenderSizeToDisplaySize做了调整,各位根据自己的喜好,这一部分可调整可不调整。关于three.js中的自适应问题,可以查看threejs响应式设计开发

平面圆形(CircleGeometry)

CircleGeometry可以接收四个参数,分别是:

  • radius 圆的半径
  • segments 分段数(组成圆的三角形的数量),最小值为3,默认为8
  • thetaStart 第一个分段的起始角,默认为0。起始角位于三点钟方向处。
  • thetaLength,圆心角的大小,默认是2Pi。正好组成一个圆,如果参数不是2PI的时候,方向为逆时针。

可以看出来,在构造圆形时,我们可以不传入任何参数,就能构造出一个圆形来,因为我们以学几何体为主,所以我们所有的材料(Material)和线条,直接开始构造设计图

1
2
3
4
const circle1Geometry = new THREE.CircleGeometry()
const circleMaterial = new THREE.MeshPhongMaterial({ color: 0x00F5FF })
const circle1 = new THREE.Mesh(circle1Geometry, circleMaterial)
group.add(circle1)

是不是感觉一切正常。接下来我们分别传入半径和分段数看看情况

1
2
3
4
const circle1Geometry = new THREE.CircleGeometry(1, 3)
const circleMaterial = new THREE.MeshPhongMaterial({ color: 0x00F5FF })
const circle1 = new THREE.Mesh(circle1Geometry, circleMaterial)
group.add(circle1)

可以看到,分段数为3的时候,我们做出了一个平面三角形。可以自己去尝试为4,5,6,7,8的情形。是不是发现,分段数是几就是几边形。只不过边数多的时候就显得圆了,边数越多圆越圆。为了保证圆看起来圆点,three.js默认值设为了8,但是架不住咱们有特殊需求,因此还是可以自定义。当然特殊也有下限,如果你将分段数设置为1或者2,将会强行当成3处理。

thetaStartthetaLength这两个参数,是为了帮助我们造出不完整的圆形来,当然,如果你脑洞足够大,应该还能造出更多的来。

我们将上面的代码加入弧度数来看看

1
2
3
4
const circle1Geometry = new THREE.CircleGeometry(1, 3, 0, 1 * Math.PI)
const circleMaterial = new THREE.MeshPhongMaterial({ color: 0x00F5FF })
const circle1 = new THREE.Mesh(circle1Geometry, circleMaterial)
group.add(circle1)

如果你实际操作了,会发现,在边数比较小的情况下,设置不同的开始角度和结束角度,你能看到很多不同的图形来,这些图形,你在圆的基础上去想,就能明白。

three.js中的平面圆形学习就到这里。我们接下来学习圆锥(ConeGeometry)

圆锥(coneGeometry)

结合已经学习过的平面圆形的构造方法,我们可以猜测一下,圆锥底部圆的半径长度,圆锥的高度,圆锥底部分段数,圆锥侧面分段数在构造时是可以根据自己的需要去传的。

实际上也确实是这样,接下来我们一起看看立体圆锥需要传的基本参数:

  • radius 圆锥底部圆的半径,默认为1
  • height 圆锥的高度,默认值为1
  • radialSegments 圆锥底部圆的分段数,像切蛋糕一样分割圆锥,会影像到底面,所以我觉得叫做底部分段数也没问题,就是把底部针对着你,然后去一刀刀的切
  • heightSegments 圆锥侧面沿着高度的分界线,像切萝卜一样,一片片的。是把圆锥身体横对着你,然后一刀刀的切。
    前面这几个是咱们能够猜测到的,实际上,还应该猜到两个参数,即底部圆的开始角度和结束角度
  • openEnded 布尔值,默认是关闭的,可以手动打开
  • thetaStart 底部圆的开始角度,和平面圆形相同,虽然说是底部圆的角度,但是会影像到整体
  • thetaLength 圆心角大小,和平面圆相同,默认都是2*PI。
    除了这两个我们应该想到的,实际上还有一个我们多想想可能会想到的参数,那就是底部要不要搞成开口状的

圆锥(coneGeometry)基本案例

基础案例只写和圆锥(coneGeometry)相关的,完整代码会附在文末,也可以点击链接右键查看源代码

  • 默认参数展示
    在默认展示中,我们全部使用默认值,如下:
    1
    2
    3
    const coneMaterial = new THREE.MeshPhongMaterial({color: 0x0000FF})
    const coneGeometry = new THREE.ConeGeometry()
    const cone = new THREE.Mesh(coneGeometry1, coneMaterial1)

看不清点这里

  • 圆锥(coneGeometry)radius和height设置
    默认情况下这看着有点胖,我们接下来给圆锥瘦瘦身
1
2
3
const coneMaterial = new THREE.MeshPhongMaterial({color: 0x0000FF})
const coneGeometry = new THREE.ConeGeometry(1,3)
const cone = new THREE.Mesh(coneGeometry, coneMaterial)

看不清点我

  • 圆锥(coneGeometry)同时设置radialSegments
    对于radialSegments的理解,最好还是自己亲自放上去看一下,为了看的真切,咱们这次换一个颜色,自己的代码上可以根据需要调试看看
    1
    2
    3
    const coneMaterial = new THREE.MeshPhongMaterial({color: 0xFF1493})
    const coneGeometry = new THREE.ConeGeometry(1,3, 10)
    const cone = new THREE.Mesh(coneGeometry, coneMaterial)
    看不清点我

heightSegmentsthetaStart,thetaLength可以自己动手看看,就是加字段的问题。最后一起看看openEnded

  • 圆锥(coneGeometry)同时设置openEnded
1
2
3
const coneMaterial = new THREE.MeshPhongMaterial({color: 0xFF1493})
const coneGeometry = new THREE.ConeGeometry(1, 3, 20, 10, true, 0, 2 * Math.PI)
const cone = new THREE.Mesh(coneGeometry, coneMaterial)

看不清点我


可以看到,当openEnded设置为true时,圆锥内部为空,像个甜甜筒。

最后需要强调一下的是,参数的传入顺序如下,别传错了,不然会出现莫名其妙的问题:radius,height, radialSegments, heightSegments,openEnded,thetaStart, thetaLength

本来想这一篇将所有常见图形讲完,感觉篇幅还是稍微长了点。所以我还是分开写吧。

下面是本篇完整的源代码和示例
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<!DOCTYPE html>
<html>
<head>
<title>three.js学习之常见的几何图形</title>
<meta name="keywords" content="three.js demo, three.js常见的几何图形,three.js分割线">
<meta name="description" content="three.js盒子学习demo,通过调整群组group和WireframeGeometry参数来显示完整的实体及分割线">
<meta charset="utf8" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
<script src="https://cdn.bootcdn.net/ajax/libs/three.js/r99/three.min.js"></script>
<style>
html,body{
height: 100%;
margin: 0;
}
#three{
display: block;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas id="three"></canvas>
<script>
function main () {
const search = location.search
const canvas = document.querySelector('#three')
const renderer = new THREE.WebGLRenderer({canvas})
const fov = 75;
const aspect = 2;
const near = 0.1;
const far = 200;

const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)

const group = new THREE.Group()
group.position.z = -10
{
// 添加圆形
const circleMaterial = new THREE.MeshPhongMaterial({ color: 0x00F5FF })
const circle1Geometry = new THREE.CircleGeometry(2)
const circle1 = new THREE.Mesh(circle1Geometry, circleMaterial)
const lineMaterial1 = new THREE.LineBasicMaterial( { color: 0xffffff} );
const lineGeometry1 = new THREE.WireframeGeometry( circle1Geometry );
const line = new THREE.Line(lineGeometry1, lineMaterial1)
if (search.includes('circle1') || !search) {
group.add(circle1, line)
}

const circleMaterial2 = new THREE.MeshPhongMaterial({ color: 0x00F5FF })
const circle2Geometry = new THREE.CircleGeometry(2, 3)
const circle2 = new THREE.Mesh(circle2Geometry, circleMaterial2)
circle2.position.x = -4
const lineMaterial2 = new THREE.LineBasicMaterial( { color: 0xffffff} );
const lineGeometry2 = new THREE.WireframeGeometry( circle2Geometry );
const line2 = new THREE.Line(lineGeometry2, lineMaterial2)
line2.position.x = -4
if (search.includes('circle2') || !search) {
group.add(circle2, line2)
}

const circleMaterial3 = new THREE.MeshPhongMaterial({ color: 0x00F5FF })
const circle3Geometry = new THREE.CircleGeometry(2, 10, 0, 1.5*Math.PI)
const circle3 = new THREE.Mesh(circle3Geometry, circleMaterial3)
circle3.position.x = 4
const lineMaterial3 = new THREE.LineBasicMaterial( { color: 0xffffff} );
const lineGeometry3 = new THREE.WireframeGeometry( circle3Geometry );
const line3 = new THREE.Line(lineGeometry3, lineMaterial3)
line3.position.x = 4
if (search.includes('circle3') || !search) {
group.add(circle3, line3)
}
}

{
const coneMaterial1 = new THREE.MeshBasicMaterial({color: 0x0000FF})
const coneGeometry1 = new THREE.ConeGeometry()
const cone1 = new THREE.Mesh(coneGeometry1, coneMaterial1)
cone1.position.y = 4
cone1.rotation.z = 3

const coneLine1Materail = new THREE.LineBasicMaterial({color: 0xffffff})
const coneLine1Geometry = new THREE.WireframeGeometry(coneGeometry1)
const coneLine1 = new THREE.Line(coneLine1Geometry, coneLine1Materail)
coneLine1.position.y = 4
coneLine1.rotation.z = 3
if (search.includes('cone1') || !search) {
group.add(cone1, coneLine1)
}

const coneMaterial2 = new THREE.MeshBasicMaterial({color: 0x0000FF})
const coneGeometry2 = new THREE.ConeGeometry(1, 3)
const cone2 = new THREE.Mesh(coneGeometry2, coneMaterial2)
cone2.position.y = 4
cone2.position.x = -2
cone2.rotation.z = 5

const coneLine2Materail = new THREE.LineBasicMaterial({color: 0xffffff})
const coneLine2Geometry = new THREE.WireframeGeometry(coneGeometry2)
const coneLine2 = new THREE.Line(coneLine2Geometry, coneLine2Materail)
coneLine2.position.y = 4
coneLine2.position.x = -2
coneLine2.rotation.z = 5
if (search.includes('cone2') || !search) {
group.add(cone2, coneLine2)
}

const coneMaterial3 = new THREE.MeshBasicMaterial({color: 0xFF1493})
const coneGeometry3 = new THREE.ConeGeometry(1, 3, 5)
const cone3 = new THREE.Mesh(coneGeometry3, coneMaterial3)
cone3.position.y = 4
cone3.position.x = 2
cone3.rotation.z = 5

const coneLine3Materail = new THREE.LineBasicMaterial({color: 0xffffff})
const coneLine3Geometry = new THREE.WireframeGeometry(coneGeometry3)
const coneLine3 = new THREE.Line(coneLine3Geometry, coneLine3Materail)
coneLine3.position.y = 4
coneLine3.position.x = 2
coneLine3.rotation.z = 5
if (search.includes('cone3') || !search) {
group.add(cone3, coneLine3)
}

const coneMaterial4 = new THREE.MeshBasicMaterial({color: 0xFF1493, side: THREE.DoubleSide})
const coneGeometry4 = new THREE.ConeGeometry(3, 6, 8, 8, true, 0, 2 * Math.PI)
const cone4 = new THREE.Mesh(coneGeometry4, coneMaterial4)
cone4.position.y = 4
cone4.position.x = -6
cone4.rotation.z = 5

const coneLine4Materail = new THREE.LineBasicMaterial({color: 0xffffff})
const coneLine4Geometry = new THREE.WireframeGeometry(coneGeometry4)
const coneLine4 = new THREE.Line(coneLine4Geometry, coneLine4Materail)
coneLine4.position.y = 4
coneLine4.position.x = -6
coneLine4.rotation.z = 5
if (search.includes('cone4') || !search) {
group.add(cone4, coneLine4)
}

const coneMaterial5 = new THREE.MeshBasicMaterial({color: 0x9370DB, side: THREE.DoubleSide})
const coneGeometry5 = new THREE.ConeGeometry(2, 4, 8, 8, true, 0, 1 * Math.PI)
const cone5 = new THREE.Mesh(coneGeometry5, coneMaterial5)
cone5.position.y = 4
cone5.position.x = 6
cone5.rotation.z = 3

const coneLine5Materail = new THREE.LineBasicMaterial({color: 0xffffff})
const coneLine5Geometry = new THREE.WireframeGeometry(coneGeometry5)
const coneLine5 = new THREE.Line(coneLine5Geometry, coneLine5Materail)
coneLine5.position.y = 4
coneLine5.position.x = 6
coneLine5.rotation.z = 3
if (search.includes('cone5') || !search) {
group.add(cone5, coneLine5)
}

}

const scene = new THREE.Scene()
{
const lightColor = 0xffffff; // 灯光颜色
const intensity = 1; // 灯光强度
const light = new THREE.DirectionalLight(lightColor, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
scene.add(group)

function resizeRenderSizeToDisplaySize () {
const pixelRatio = window.devicePixelRatio;
const displayWidth = canvas.clientWidth * pixelRatio
const displayHeight = canvas.clientHeight * pixelRatio
const renderWidth = canvas.width;
const renderHeight = canvas.height;
const needResize = displayWidth !== renderWidth || displayHeight !== renderHeight
return needResize
}
function render (time) {
if (resizeRenderSizeToDisplaySize()) {
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 *= 0.001
group['children'].forEach((each, index) => {
if (each.position.y === 0) {
each.rotation.z = -time
} else {
each.rotation.y = -time
}
// if (index % 2 === 0) {
// each.rotation.z = -time
// } else {
// each.rotation.z = time
// }
})

// group.position.y = time
// group.position.x = time
renderer.render(scene, camera)
requestAnimationFrame(render)
}
requestAnimationFrame(render)
// render()
}
main()
</script>
</body>
</html>