logo头像
Snippet 博客主题

threejs高级几何体

1. THREE.ConvexGeometry

它可以围绕一组点创建一个凸包,所谓凸包就是包围这组点得最小图形。

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
<!-- chapter-06-01.html -->
<!DOCTYPE html>
<html>
<head>
<title>Advanced 3D geometries - Convex Hull</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/ConvexGeometry.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 50;
camera.lookAt(new THREE.Vector3(10, 0, 0));
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var spGroup; // 20个随机圆点网格组对象
var hullMesh; // 凸包网格
generatePoints();

var controls = new function () {
this.redraw = function () {
scene.remove(spGroup);
scene.remove(hullMesh);
generatePoints();
};
};

var gui = new dat.GUI();
gui.add(controls, 'redraw');

function generatePoints() {
// 随机创建20个点坐标
var points = [];
for (var i = 0; i < 20; i++) {
var randomX = -15 + Math.round(Math.random() * 30);
var randomY = -15 + Math.round(Math.random() * 30);
var randomZ = -15 + Math.round(Math.random() * 30);
points.push(new THREE.Vector3(randomX, randomY, randomZ));
}

// 创建20个圆点网格添加到spGroup
spGroup = new THREE.Object3D();
var material = new THREE.MeshBasicMaterial({color: 0xff0000, transparent: false});
points.forEach(function (point) {
var spGeom = new THREE.SphereGeometry(0.2);
var spMesh = new THREE.Mesh(spGeom, material);
spMesh.position.copy(point);
spGroup.add(spMesh);
});
scene.add(spGroup);

// 创建凸包网格
var hullGeometry = new THREE.ConvexGeometry(points);
hullMesh = createMesh(hullGeometry);
scene.add(hullMesh);
}

function createMesh(geom) {
var meshMaterial = new THREE.MeshBasicMaterial({color: 0x00ff00, transparent: true, opacity: 0.2});
meshMaterial.side = THREE.DoubleSide;
var wireFrameMat = new THREE.MeshBasicMaterial();
wireFrameMat.wireframe = true;
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial, wireFrameMat]);
return mesh;
}

var step = 0;
function render() {
stats.update();
spGroup.rotation.y = step;
hullMesh.rotation.y = step += 0.01;
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
render();

function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>

2. THREE.LatheGeometry

它允许你从一条光滑曲线创建图形。这条曲线绕物体的中心z轴旋转,就得到了类似花瓶或铃铛的图形。

属性 描述
points 指定构成曲线的点,然后基于这条曲线生成类似铃铛或花瓶的图形
segments 指定创建图形时所用分段数目。这个数值越高,最终的图形越圆。默认值为12
phiStart 指定创建图形时从何处开始画,默认值为0
phiLength 指定绘制多少(弧度)图形,默认360度
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
<!-- chapter-06-02.html -->
<!DOCTYPE html>
<html>
<head>
<title>Advanced 3D geometries - Lathe</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 50;
camera.lookAt(new THREE.Vector3(10, 0, 0));
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

// 圆点网格组
var spGroup;
// 样条曲线网格
var latheMesh;
generatePoints(12, 2, 2 * Math.PI);

var controls = new function () {
this.segments = 12;
this.phiStart = 0;
this.phiLength = 2 * Math.PI;
this.redraw = function () {
scene.remove(spGroup);
scene.remove(latheMesh);
generatePoints(controls.segments, controls.phiStart, controls.phiLength);
};
};

var gui = new dat.GUI();
gui.add(controls, 'segments', 0, 50).step(1).onChange(controls.redraw);
gui.add(controls, 'phiStart', 0, 2 * Math.PI).onChange(controls.redraw);
gui.add(controls, 'phiLength', 0, 2 * Math.PI).onChange(controls.redraw);

function generatePoints(segments, phiStart, phiLength) {
// 通过sin/cos创建30个点坐标
var points = [];
var height = 5;
var count = 30;
for (var i = 0; i < count; i++) {
points.push(new THREE.Vector3((Math.sin(i * 0.2) + Math.cos(i * 0.3)) * height + 12, 0, ( i - count ) + count / 2));
}

// 在这30个点坐标位置创建圆点网格,添加到spGroup网格组中
spGroup = new THREE.Object3D();
var material = new THREE.MeshBasicMaterial({color: 0xff0000, transparent: false});
points.forEach(function (point) {
var spGeom = new THREE.SphereGeometry(0.2);
var spMesh = new THREE.Mesh(spGeom, material);
spMesh.position.copy(point);
spGroup.add(spMesh);
});
scene.add(spGroup);

// 创建样条曲线网格
var latheGeometry = new THREE.LatheGeometry(points, segments, phiStart, phiLength);
latheMesh = createMesh(latheGeometry);
scene.add(latheMesh);
}

function createMesh(geom) {
var meshMaterial = new THREE.MeshNormalMaterial();
meshMaterial.side = THREE.DoubleSide;
var wireFrameMat = new THREE.MeshBasicMaterial();
wireFrameMat.wireframe = true;
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial, wireFrameMat]);
return mesh;
}

var step = 0;
function render() {
stats.update();
spGroup.rotation.x = step;
latheMesh.rotation.x = step += 0.01;
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
render();

function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>

3. 通过拉伸创建几何体

THREE.ExtrudeGeometry

它可以从一个二维图形通过沿着某个轴(默认z轴)拉伸创建出一个三维图形。

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
<!-- chapter-06-03.html -->
<!DOCTYPE html>
<html>
<head>
<title>Extrude Geometry</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

// 创建自定义二维图形
var shape = createMesh(new THREE.ShapeGeometry(drawShape()));
scene.add(shape);

camera.position.x = -20;
camera.position.y = 60;
camera.position.z = 60;
camera.lookAt(new THREE.Vector3(20, 20, 0));
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var controls = new function () {
this.amount = 2;
this.bevelThickness = 2;
this.bevelSize = 0.5;
this.bevelEnabled = true;
this.bevelSegments = 3;
this.bevelEnabled = true;
this.curveSegments = 12;
this.steps = 1;

this.asGeom = function () {
scene.remove(shape);
var options = {
amount: controls.amount,
bevelThickness: controls.bevelThickness,
bevelSize: controls.bevelSize,
bevelSegments: controls.bevelSegments,
bevelEnabled: controls.bevelEnabled,
curveSegments: controls.curveSegments,
steps: controls.steps
};
// 创建拉伸几何体网格
shape = createMesh(new THREE.ExtrudeGeometry(drawShape(), options));
scene.add(shape);
};
};

var gui = new dat.GUI();
gui.add(controls, 'amount', 0, 20).onChange(controls.asGeom);
gui.add(controls, 'bevelThickness', 0, 10).onChange(controls.asGeom);
gui.add(controls, 'bevelSize', 0, 10).onChange(controls.asGeom);
gui.add(controls, 'bevelSegments', 0, 30).step(1).onChange(controls.asGeom);
gui.add(controls, 'bevelEnabled').onChange(controls.asGeom);
gui.add(controls, 'curveSegments', 1, 30).step(1).onChange(controls.asGeom);
gui.add(controls, 'steps', 1, 5).step(1).onChange(controls.asGeom);

// 上一节面具示例
function drawShape() {
var shape = new THREE.Shape();
shape.moveTo(10, 10);
shape.lineTo(10, 40);
shape.bezierCurveTo(15, 25, 25, 25, 30, 40);
shape.splineThru(
[new THREE.Vector2(32, 30),
new THREE.Vector2(28, 20),
new THREE.Vector2(30, 10),
]);
shape.quadraticCurveTo(20, 15, 10, 10);

var hole1 = new THREE.Path();
hole1.absellipse(16, 24, 2, 3, 0, Math.PI * 2, true);
shape.holes.push(hole1);

var hole2 = new THREE.Path();
hole2.absellipse(23, 24, 2, 3, 0, Math.PI * 2, true);
shape.holes.push(hole2);

var hole3 = new THREE.Path();
hole3.absarc(20, 16, 2, 0, Math.PI, true);
shape.holes.push(hole3);

return shape;
}

function createMesh(geom) {
geom.applyMatrix(new THREE.Matrix4().makeTranslation(-20, 0, 0));
var meshMaterial = new THREE.MeshNormalMaterial({
shading: THREE.FlatShading,
transparent: true,
opacity: 0.7
});
var wireFrameMat = new THREE.MeshBasicMaterial();
wireFrameMat.wireframe = true;
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial]);
return mesh;
}

function render() {
stats.update();
shape.rotation.y += 0.01;
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
render();

function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>

THREE.TubeGeometry

它可以沿着一条三维样式曲线拉伸出一根管子。

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
<!-- chapter-06-04.html -->
<!DOCTYPE html>
<html>
<head>
<title>Extrude TubeGeometry</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;
camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 50;
camera.lookAt(new THREE.Vector3(10, 0, 0));
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

// the points group
var spGroup;
// the mesh
var tubeMesh;

var controls = new function () {
this.numberOfPoints = 5;
this.segments = 64;
this.radius = 1;
this.radiusSegments = 8;
this.closed = false;
this.points = [];

this.newPoints = function () {
var points = [];
for (var i = 0; i < controls.numberOfPoints; i++) {
var randomX = -20 + Math.round(Math.random() * 50);
var randomY = -15 + Math.round(Math.random() * 40);
var randomZ = -20 + Math.round(Math.random() * 40);
points.push(new THREE.Vector3(randomX, randomY, randomZ));
}
controls.points = points;
controls.redraw();
};

this.redraw = function () {
scene.remove(spGroup);
scene.remove(tubeMesh);
generatePoints(controls.points, controls.segments, controls.radius, controls.radiusSegments, controls.closed);
};

};

var gui = new dat.GUI();
gui.add(controls, 'newPoints');
gui.add(controls, 'numberOfPoints', 2, 15).step(1).onChange(controls.newPoints);
gui.add(controls, 'segments', 0, 200).step(1).onChange(controls.redraw);
gui.add(controls, 'radius', 0, 10).onChange(controls.redraw);
gui.add(controls, 'radiusSegments', 0, 100).step(1).onChange(controls.redraw);
gui.add(controls, 'closed').onChange(controls.redraw);

function generatePoints(points, segments, radius, radiusSegments, closed) {
// 创建圆点网格组对象(三维样条曲线路径)
spGroup = new THREE.Object3D();
var material = new THREE.MeshBasicMaterial({color: 0xff0000, transparent: false});
points.forEach(function (point) {
var spGeom = new THREE.SphereGeometry(0.2);
var spMesh = new THREE.Mesh(spGeom, material);
spMesh.position.copy(point);
spGroup.add(spMesh);
});
scene.add(spGroup);

// 三维样条曲线拉伸出的管子网格
var tubeGeometry = new THREE.TubeGeometry(new THREE.SplineCurve3(points), segments, radius, radiusSegments, closed);
tubeMesh = createMesh(tubeGeometry);
scene.add(tubeMesh);
}

function createMesh(geom) {
var meshMaterial = new THREE.MeshBasicMaterial({color: 0x00ff00, transparent: true, opacity: 0.2});
var wireFrameMat = new THREE.MeshBasicMaterial();
wireFrameMat.wireframe = true;
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial, wireFrameMat]);
return mesh;
}

var step = 0;
function render() {
stats.update();
spGroup.rotation.y = step;
tubeMesh.rotation.y = step += 0.01;
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
render();

function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>

从SVG拉伸

读取SVG中路径信息转换为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
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
<!-- chapter-06-05.html -->
<!DOCTYPE html>
<html>
<head>
<title>Extrude SVG</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script type="text/javascript" src="../libs/d3-threeD.js"></script>
<script type="text/javascript" src="../libs/OrbitControls.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<div id="batman" style="display:none">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="1152px" height="1152px" xml:space="preserve">
<g>
<path id="batman-path" style="fill:rgb(0,0,0);" d="M 261.135 114.535 C 254.906 116.662 247.491 118.825 244.659 119.344 C 229.433 122.131 177.907 142.565 151.973 156.101 C 111.417 177.269 78.9808 203.399 49.2992 238.815 C 41.0479 248.66 26.5057 277.248 21.0148 294.418 C 14.873 313.624 15.3588 357.341 21.9304 376.806 C 29.244 398.469 39.6107 416.935 52.0865 430.524 C 58.2431 437.23 63.3085 443.321 63.3431 444.06 C 63.4748 446.883 102.278 479.707 120.51 492.418 C 131.003 499.734 148.168 509.93 158.654 515.075 C 169.139 520.22 179.431 525.34 181.524 526.454 C 187.725 529.754 187.304 527.547 179.472 515.713 C 164.806 493.553 158.448 464.659 164.322 446.861 C 169.457 431.303 192.013 421.501 214.324 425.132 C 234.042 428.341 252.142 439.186 270.958 459.064 C 286.677 475.67 292.133 482.967 295.31 491.634 C 297.466 497.514 298.948 495.91 304.862 481.293 C 313.673 459.519 329.808 445.735 346.35 445.851 C 367.654 446 399.679 478.239 412.801 512.745 C 414.093 516.144 416.593 522.632 418.355 527.163 C 420.118 531.695 423.604 542.319 426.103 550.773 C 430.848 566.832 432.355 566.851 434.872 550.88 C 436.395 541.215 451.403 502.522 455.655 497.298 C 457.038 495.599 460.63 489.896 463.636 484.625 C 471.696 470.498 492.318 452.688 505.387 448.568 C 514.602 445.663 517.533 445.549 525.51 447.782 C 539.676 451.749 553.43 467.773 560.706 488.788 L 563.242 496.114 L 567.096 490.012 C 577.709 473.208 593.665 453.899 602.47 447.206 C 607.884 443.09 613.378 438.825 614.679 437.729 C 615.98 436.632 622.927 433.259 630.118 430.233 C 655.159 419.693 681.195 423.407 693.273 439.241 C 697.957 445.382 698.932 448.971 699.538 462.294 C 700.174 476.284 699.51 479.864 693.686 493.854 C 690.073 502.533 684.912 512.883 682.217 516.854 C 679.523 520.825 678.172 524.074 679.215 524.074 C 681.932 524.074 718.787 504.481 732.525 495.734 C 760.018 478.228 788.909 452.599 803.9 432.418 C 807.266 427.886 810.569 423.715 811.239 423.149 C 814.498 420.395 828.253 393.099 833.17 379.627 C 838.223 365.782 838.713 361.822 838.741 334.582 C 838.776 300.425 836.431 291.124 820.154 260.873 C 810.649 243.207 807.498 239.005 788.417 218.543 C 751.511 178.968 688.147 142.549 621.582 122.654 C 581.7 110.734 580.388 110.465 580.388 114.195 C 580.388 115.328 581.302 116.255 582.418 116.255 C 584.279 116.255 587.705 122.106 603.399 152.085 C 613.977 172.29 618.077 189.427 618.264 214.21 C 618.42 234.928 617.88 238.368 612.285 252.269 C 604.327 272.04 590.066 286.889 572.829 293.352 C 558.526 298.714 549.193 297.86 535.704 289.955 C 526.777 284.723 512.304 267.644 509.816 259.404 C 509.132 257.138 507.129 251.358 505.366 246.558 C 503.602 241.759 501.646 231.564 501.018 223.902 C 500.39 216.24 498.491 198.402 496.797 184.261 C 495.104 170.121 493.307 152.047 492.803 144.097 C 492.299 136.147 491.292 125.625 490.565 120.715 L 489.242 111.787 L 483.323 118.267 C 480.067 121.832 477.404 125.618 477.404 126.681 C 477.404 127.744 476.603 128.613 475.624 128.613 C 474.645 128.613 471.275 132.321 468.135 136.852 L 462.426 145.091 L 431.038 145.091 L 399.65 145.091 L 386.811 128.494 C 379.749 119.365 373.509 112.36 372.943 112.926 C 372.377 113.491 371.57 118.875 371.15 124.888 C 370.73 130.902 368.94 147.744 367.172 162.315 C 365.405 176.887 363.523 195.424 362.99 203.509 C 360.283 244.622 352.784 266.044 335.323 282.544 C 326.456 290.923 312.488 297.497 303.508 297.518 C 294.864 297.539 278.732 290.063 269.473 281.748 C 246.952 261.521 238.846 229.614 245.481 187.314 C 247.894 171.928 266.562 131.612 275.927 121.56 C 277.987 119.348 279.673 116.786 279.673 115.867 C 279.673 114.947 279.905 113.593 280.188 112.856 C 281.28 110.017 271.977 110.837 261.136 114.536 L 261.135 114.535 "/>
</g>

</svg>
</div>

<script type="text/javascript">
var orbit;

function init() {
var stats = initStats();

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

// 根据svg绘制路径创建svg图形网格
var shape = createMesh(new THREE.ShapeGeometry(drawShape()));
scene.add(shape);

camera.position.x = -80;
camera.position.y = 80;
camera.position.z = 80;
camera.lookAt(new THREE.Vector3(60, -60, 0));

var spotLight = new THREE.DirectionalLight(0xffffff);
spotLight.position = new THREE.Vector3(70, 170, 70);
spotLight.intensity = 0.7;
spotLight.target = shape;
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

// 可以对三维场景进行缩放,平移,旋转操作,其本质上改变的幷不是场景,而是相机的参数
// 鼠标操作:通过拖动鼠标左键可以720旋转展示三维场景,通过拖动鼠标右键可以平移三维场景,通过上下滚动鼠标中键可以缩放三维场景。
orbit = new THREE.OrbitControls(camera, webGLRenderer.domElement);

var controls = new function () {
this.amount = 2;
this.bevelThickness = 2;
this.bevelSize = 0.5;
this.bevelEnabled = true;
this.bevelSegments = 3;
this.bevelEnabled = true;
this.curveSegments = 12;
this.steps = 1;

this.asGeom = function () {
scene.remove(shape);
var options = {
amount: controls.amount,
bevelThickness: controls.bevelThickness,
bevelSize: controls.bevelSize,
bevelSegments: controls.bevelSegments,
bevelEnabled: controls.bevelEnabled,
curveSegments: controls.curveSegments,
steps: controls.steps
};

// THREE.ExtrudeGeometry拉伸svg图形
shape = createMesh(new THREE.ExtrudeGeometry(drawShape(), options));
scene.add(shape);
};
};

var gui = new dat.GUI();
gui.add(controls, 'amount', 0, 20).onChange(controls.asGeom);
gui.add(controls, 'bevelThickness', 0, 10).onChange(controls.asGeom);
gui.add(controls, 'bevelSize', 0, 10).onChange(controls.asGeom);
gui.add(controls, 'bevelSegments', 0, 30).step(1).onChange(controls.asGeom);
gui.add(controls, 'bevelEnabled').onChange(controls.asGeom);
gui.add(controls, 'curveSegments', 1, 30).step(1).onChange(controls.asGeom);
gui.add(controls, 'steps', 1, 5).step(1).onChange(controls.asGeom);

function drawShape() {
// 获取svg中绘制图形的路径信息
var svgString = document.querySelector("#batman-path").getAttribute("d");
// 将svg图形路径转换为threejs的shape几何体
var shape = transformSVGPathExposed(svgString);
return shape;
}

function createMesh(geom) {
// 矩阵变换(平移、旋转、缩放),
geom.applyMatrix(new THREE.Matrix4().makeTranslation(-390, -74, 0)); // 平移矩阵

var meshMaterial = new THREE.MeshPhongMaterial({color: 0x333333, shininess: 100, metal: true});
var mesh = new THREE.Mesh(geom, meshMaterial);
mesh.scale.x = 0.1;
mesh.scale.y = 0.1;
mesh.rotation.z = Math.PI;
mesh.rotation.x = -1.1;
return mesh;
}

function render() {
stats.update();
shape.rotation.y += 0.005;
orbit.update();
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
render();

function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>

4. THREE.ParametricGeometry

它可以创建基于公式的几何体。

属性 描述
function 该函数以u、v值作为参数来定义每个顶点的位置,参数取值范围都是0~1
slices 该属性定义u值应该分成多少份
stacks 该属性定义v值应该分成多少份
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
<!-- chapter-06-06.html -->
<!DOCTYPE html>
<html>
<head>
<title>Parametric geometries</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

camera.position.x = -30;
camera.position.y = 50;
camera.position.z = 50;
camera.lookAt(new THREE.Vector3(10, -20, 0));

var spotLight = new THREE.DirectionalLight();
spotLight.position = new THREE.Vector3(-20, 250, -50);
spotLight.target.position.x = 30;
spotLight.target.position.y = -40;
spotLight.target.position.z = -20;
spotLight.intensity = 0.3;
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

// 公式1
klein = function (u, v) {
u *= Math.PI;
v *= 2 * Math.PI;

u = u * 2;
var x, y, z;
if (u < Math.PI) {
x = 3 * Math.cos(u) * (1 + Math.sin(u)) + (2 * (1 - Math.cos(u) / 2)) * Math.cos(u) * Math.cos(v);
z = -8 * Math.sin(u) - 2 * (1 - Math.cos(u) / 2) * Math.sin(u) * Math.cos(v);
} else {
x = 3 * Math.cos(u) * (1 + Math.sin(u)) + (2 * (1 - Math.cos(u) / 2)) * Math.cos(v + Math.PI);
z = -8 * Math.sin(u);
}

y = -2 * (1 - Math.cos(u) / 2) * Math.sin(v);

return new THREE.Vector3(x, y, z);
};

// 公式2, 类似波浪
radialWave = function (u, v) {
var r = 50;
var x = Math.sin(u) * r;
var z = Math.sin(v / 2) * 2 * r;
var y = (Math.sin(u * 4 * Math.PI) + Math.cos(v * 2 * Math.PI)) * 2.8;
return new THREE.Vector3(x, y, z);
};

// 通过公式创建几何体网格
var mesh = createMesh(new THREE.ParametricGeometry(radialWave, 120, 120, false));
scene.add(mesh);

function createMesh(geom) {
// 平移矩阵变换
geom.applyMatrix(new THREE.Matrix4().makeTranslation(-25, 0, -25));

var meshMaterial = new THREE.MeshPhongMaterial({
specular: 0xaaaafff,
color: 0x3399ff,
shininess: 40,
metal: true
});
meshMaterial.side = THREE.DoubleSide;
var plane = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial]);
return plane;
}

var step = 0;
function render() {
stats.update();
mesh.rotation.y = step += 0.01;
mesh.rotation.x = step;
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
render();

function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>

5. THREE.TextGeometry

创建三维字体文本。其内部使用的是THREE.ExtrudeGeometry拉伸。

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
<!-- chapter-06-07.html -->
<!DOCTYPE html>
<html>
<head>
<title>Text geometry</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script type="text/javascript" src="../assets/fonts/helvetiker_regular.typeface.js"></script>
<script type="text/javascript" src="../assets/fonts/helvetiker_bold.typeface.js"></script>
<script type="text/javascript" src="../assets/fonts/bitstream_vera_sans_mono_roman.typeface.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(new THREE.Color(0x000000, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

camera.position.x = 100;
camera.position.y = 300;
camera.position.z = 600;
camera.lookAt(new THREE.Vector3(400, 0, -300));

var dirLight = new THREE.DirectionalLight();
dirLight.position.set(25, 23, 15);
scene.add(dirLight);

var dirLight2 = new THREE.DirectionalLight();
dirLight2.position.set(-25, 23, 15);
scene.add(dirLight2);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var text1;
var text2;
var controls = new function () {
this.size = 90;
this.height = 90;
this.bevelThickness = 2;
this.bevelSize = 0.5;
this.bevelEnabled = true;
this.bevelSegments = 3;
this.bevelEnabled = true;
this.curveSegments = 12;
this.steps = 1;
this.font = "helvetiker";
this.weight = "normal";
// this.style = "italics";

this.asGeom = function () {
scene.remove(text1);
scene.remove(text2);

var options = {
size: controls.size,
height: controls.height,
weight: controls.weight,
font: controls.font,
bevelThickness: controls.bevelThickness,
bevelSize: controls.bevelSize,
bevelSegments: controls.bevelSegments,
bevelEnabled: controls.bevelEnabled,
curveSegments: controls.curveSegments,
steps: controls.steps
};

// 创建三维字体
text1 = createMesh(new THREE.TextGeometry("Learning", options));
text1.position.z = -100;
text1.position.y = 100;
scene.add(text1);

text2 = createMesh(new THREE.TextGeometry("Three.js", options));
scene.add(text2);
};
};
controls.asGeom();

var gui = new dat.GUI();
gui.add(controls, 'size', 0, 200).onChange(controls.asGeom);
gui.add(controls, 'height', 0, 200).onChange(controls.asGeom);
gui.add(controls, 'font', ['bitstream vera sans mono', 'helvetiker']).onChange(controls.asGeom);
gui.add(controls, 'bevelThickness', 0, 10).onChange(controls.asGeom);
gui.add(controls, 'bevelSize', 0, 10).onChange(controls.asGeom);
gui.add(controls, 'bevelSegments', 0, 30).step(1).onChange(controls.asGeom);
gui.add(controls, 'bevelEnabled').onChange(controls.asGeom);
gui.add(controls, 'curveSegments', 1, 30).step(1).onChange(controls.asGeom);
gui.add(controls, 'steps', 1, 5).step(1).onChange(controls.asGeom);

function createMesh(geom) {
var meshMaterial = new THREE.MeshPhongMaterial({
specular: 0xffffff,
color: 0xeeffff,
shininess: 100,
metal: true
});
var plane = THREE.SceneUtils.createMultiMaterialObject(geom, [meshMaterial]);
return plane;
}

function render() {
stats.update();
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
render();

function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
}
window.onload = init;
</script>
</body>
</html>

6. 二元操作

就是将各种标准几何体组合在一起创建出新的几何体。这被称为CSG技术,在ThreeBSP扩展库中(https://github.com/skalnik/ThreeBSP)。这个扩展库提供了如下三个函数:

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
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
<!-- chapter-06-08.html -->
<!DOCTYPE html>
<html>
<head>
<title>Binary operations</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
<script type="text/javascript" src="../libs/spin.js"></script>
<script type="text/javascript" src="../libs/ThreeBSP.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
}
</style>
</head>
<body>

<div id="Stats-output">
</div>
<div id="WebGL-output">
</div>

<script type="text/javascript">
function init() {
var stats = initStats();
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);

var webGLRenderer = new THREE.WebGLRenderer();
webGLRenderer.setClearColor(0x999999, 1.0);
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

var sphere1 = createMesh(new THREE.SphereGeometry(5, 20, 30));
sphere1.position.x = -2;

var sphere2 = createMesh(new THREE.SphereGeometry(5, 20, 30));
sphere2.position.set(3, 0, 0);

var cube = createMesh(new THREE.BoxGeometry(5, 5, 5));
cube.position.x = -7;
scene.add(sphere1);
scene.add(sphere2);
scene.add(cube);

camera.position.x = 0;
camera.position.y = 20;
camera.position.z = 20;
camera.lookAt(new THREE.Vector3(0, 0, 0));
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var controls = new function () {
this.sphere1PosX = sphere1.position.x;
this.sphere1PosY = sphere1.position.y;
this.sphere1PosZ = sphere1.position.z;
this.sphere1Scale = 1;

this.sphere2PosX = sphere2.position.x;
this.sphere2PosY = sphere2.position.y;
this.sphere2PosZ = sphere2.position.z;
this.sphere2Scale = 1;

this.cubePosX = cube.position.x;
this.cubePosY = cube.position.y;
this.cubePosZ = cube.position.z;
this.scaleX = 1;
this.scaleY = 1;
this.scaleZ = 1;

this.actionCube = "subtract"; // add, substract, intersect
this.actionSphere = "subtract";

this.showResult = function () {
redrawResult();
};

this.hideWireframes = false;
this.rotateResult = false;
};

var gui = new dat.GUI();
var guiSphere1 = gui.addFolder("Sphere1");
guiSphere1.add(controls, "sphere1PosX", -15, 15).onChange(function () {
sphere1.position.set(controls.sphere1PosX, controls.sphere1PosY, controls.sphere1PosZ)
});
guiSphere1.add(controls, "sphere1PosY", -15, 15).onChange(function () {
sphere1.position.set(controls.sphere1PosX, controls.sphere1PosY, controls.sphere1PosZ)
});
guiSphere1.add(controls, "sphere1PosZ", -15, 15).onChange(function () {
sphere1.position.set(controls.sphere1PosX, controls.sphere1PosY, controls.sphere1PosZ)
});
guiSphere1.add(controls, "sphere1Scale", 0, 10).onChange(function (e) {
sphere1.scale.set(e, e, e)
});

var guiSphere2 = gui.addFolder("Sphere2");
guiSphere2.add(controls, "sphere2PosX", -15, 15).onChange(function () {
sphere2.position.set(controls.sphere2PosX, controls.sphere2PosY, controls.sphere2PosZ)
});
guiSphere2.add(controls, "sphere2PosY", -15, 15).onChange(function () {
sphere2.position.set(controls.sphere2PosX, controls.sphere2PosY, controls.sphere2PosZ)
});
guiSphere2.add(controls, "sphere2PosZ", -15, 15).onChange(function () {
sphere2.position.set(controls.sphere2PosX, controls.sphere2PosY, controls.sphere2PosZ)
});
guiSphere2.add(controls, "sphere2Scale", 0, 10).onChange(function (e) {
sphere2.scale.set(e, e, e)
});
guiSphere2.add(controls, "actionSphere", ["subtract", "intersect", "union", "none"]);

var guiCube = gui.addFolder("cube");
guiCube.add(controls, "cubePosX", -15, 15).onChange(function () {
cube.position.set(controls.cubePosX, controls.cubePosY, controls.cubePosZ)
});
guiCube.add(controls, "cubePosY", -15, 15).onChange(function () {
cube.position.set(controls.cubePosX, controls.cubePosY, controls.cubePosZ)
});
guiCube.add(controls, "cubePosZ", -15, 15).onChange(function () {
cube.position.set(controls.cubePosX, controls.cubePosY, controls.cubePosZ)
});
guiCube.add(controls, "scaleX", 0, 10).onChange(function (e) {
cube.scale.x = e
});
guiCube.add(controls, "scaleY", 0, 10).onChange(function (e) {
cube.scale.y = e
});
guiCube.add(controls, "scaleZ", 0, 10).onChange(function (e) {
cube.scale.z = e
});
guiCube.add(controls, "actionCube", ["subtract", "intersect", "union", "none"]);

gui.add(controls, "showResult");
gui.add(controls, "rotateResult");
gui.add(controls, "hideWireframes").onChange(function () {
if (controls.hideWireframes) {
sphere1.material.visible = false;
sphere2.material.visible = false;
cube.material.visible = false;
} else {
sphere1.material.visible = true;
sphere2.material.visible = true;
cube.material.visible = true;
}
});

var result;
function redrawResult() {
scene.remove(result);
// 将网格对象转成ThreeBSP对象(才能使用subtract/intersect/union方法)
var sphere1BSP = new ThreeBSP(sphere1);
var sphere2BSP = new ThreeBSP(sphere2);
var cube2BSP = new ThreeBSP(cube);

var resultBSP;

// 中间圆与右边圆二元操作
switch (controls.actionSphere) {
case "subtract":
resultBSP = sphere1BSP.subtract(sphere2BSP);
break;
case "intersect":
resultBSP = sphere1BSP.intersect(sphere2BSP);
break;
case "union":
resultBSP = sphere1BSP.union(sphere2BSP);
break;
case "none": // noop;
}

// 中间圆与左边立方二元操作
if (!resultBSP) resultBSP = sphere1BSP;
switch (controls.actionCube) {
case "subtract":
resultBSP = resultBSP.subtract(cube2BSP);
break;
case "intersect":
resultBSP = resultBSP.intersect(cube2BSP);
break;
case "union":
resultBSP = resultBSP.union(cube2BSP);
break;
case "none": // noop;
}


if (controls.actionCube === "none" && controls.actionSphere === "none") {
// do nothing
} else {
// BSP对象转网格对象
result = resultBSP.toMesh();
// 二元操作后重新计算
result.geometry.computeFaceNormals();
result.geometry.computeVertexNormals();
scene.add(result);
}
}

function createMesh(geom) {
var meshMaterial = new THREE.MeshNormalMaterial();
meshMaterial.side = THREE.DoubleSide;
var wireFrameMat = new THREE.MeshBasicMaterial({transparency: true, opacity: 0.5, wireframeLinewidth: 0.5});
wireFrameMat.wireframe = true;
var mesh = new THREE.Mesh(geom, wireFrameMat);
return mesh;
}

function render() {
stats.update();
if (controls.rotateResult && result) {
result.rotation.y += 0.04;
result.rotation.z -= 0.005;
}
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}
render();

function initStats() {
var stats = new Stats();
stats.setMode(0); // 0: fps, 1: ms
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.getElementById("Stats-output").appendChild(stats.domElement);
return stats;
}
};
window.onload = init();
</script>
</body>
</html>