logo头像
Snippet 博客主题

threejs使用外部模型

前面我们都是用Threejs提供的几何体来创建网格,对于简单几何体(如球体和方块)来说非常有效,但当你想要创建复杂的三维模型时,这不是最好的方法。通常情况下,你可以使用三维建模工具(如Blender和3D Studio Max)来创建复杂几何体。

本节就来学习如何加载和展示由这些三维建模工具所创建的模型。

网格对象组合与合并

在学习使用外部三维建模工具所创建的模型前,我们先了解两个基本操作:将对象组合在一起,以及将多个网格合并为一个网格。

1. 网格组合

这个不是什么新东西了,前面我们很多示例其实早就使用了。就是把多个网格对象添加到一个对象里(THREE.Group),对这1个对象移动、缩放、旋转变换操作其子对象会一起变换。

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
<!-- chapter-08-01.html -->
<!DOCTYPE html>
<html>
<head>
<title>Group</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 = 30;
camera.position.z = 30;
camera.lookAt(new THREE.Vector3(0, 0, 0));

var ground = new THREE.PlaneGeometry(100, 100, 50, 50);
var groundMesh = THREE.SceneUtils.createMultiMaterialObject(ground,
[new THREE.MeshBasicMaterial({wireframe: true, overdraw: true, color: 000000}),
new THREE.MeshBasicMaterial({color: 0x00ff00, transparent: true, opacity: 0.5}
)
]);
groundMesh.rotation.x = -0.5 * Math.PI;
scene.add(groundMesh);

document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var step = 0.03;
var sphere;
var cube;
var group;
var controls = new function () {
this.cubePosX = 0;
this.cubePosY = 3;
this.cubePosZ = 10;

this.spherePosX = 10;
this.spherePosY = 5;
this.spherePosZ = 0;

this.groupPosX = 10;
this.groupPosY = 5;
this.groupPosZ = 0;

this.grouping = false;
this.rotate = false;

this.groupScale = 1;
this.cubeScale = 1;
this.sphereScale = 1;

this.redraw = function () {
scene.remove(group);
sphere = createMesh(new THREE.SphereGeometry(5, 10, 10));
cube = createMesh(new THREE.BoxGeometry(6, 6, 6));
sphere.position.set(controls.spherePosX, controls.spherePosY, controls.spherePosZ);
cube.position.set(controls.cubePosX, controls.cubePosY, controls.cubePosZ);

// 将球体和立方体网格添加到组合对象中
group = new THREE.Group();
group.add(sphere);
group.add(cube);
scene.add(group);

// 在group组合对象中心位置标志一个箭头
var arrow = new THREE.ArrowHelper(new THREE.Vector3(0, 1, 0), group.position, 10, 0x0000ff);
scene.add(arrow);
};
};

var gui = new dat.GUI();
var sphereFolder = gui.addFolder("sphere");
sphereFolder.add(controls, "spherePosX", -20, 20).onChange(function (e) {
sphere.position.x = e;
});
sphereFolder.add(controls, "spherePosZ", -20, 20).onChange(function (e) {
sphere.position.z = e;
});
sphereFolder.add(controls, "spherePosY", -20, 20).onChange(function (e) {
sphere.position.y = e;
});
sphereFolder.add(controls, "sphereScale", 0, 3).onChange(function (e) {
sphere.scale.set(e, e, e);
});

var cubeFolder = gui.addFolder("cube");
cubeFolder.add(controls, "cubePosX", -20, 20).onChange(function (e) {
cube.position.x = e;
});
cubeFolder.add(controls, "cubePosZ", -20, 20).onChange(function (e) {
cube.position.z = e;
});
cubeFolder.add(controls, "cubePosY", -20, 20).onChange(function (e) {
cube.position.y = e;
});
cubeFolder.add(controls, "cubeScale", 0, 3).onChange(function (e) {
cube.scale.set(e, e, e);
});

var cubeFolder = gui.addFolder("group");
cubeFolder.add(controls, "groupPosX", -20, 20).onChange(function (e) {
group.position.x = e;
});
cubeFolder.add(controls, "groupPosZ", -20, 20).onChange(function (e) {
group.position.z = e;
});
cubeFolder.add(controls, "groupPosY", -20, 20).onChange(function (e) {
group.position.y = e;
});
cubeFolder.add(controls, "groupScale", 0, 3).onChange(function (e) {
group.scale.set(e, e, e);
});

gui.add(controls, "grouping");
gui.add(controls, "rotate");
controls.redraw();
render();

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

function render() {
stats.update();
if (controls.grouping && controls.rotate) {
group.rotation.y += step;
}

if (controls.rotate && !controls.grouping) {
sphere.rotation.y += step;
cube.rotation.y += step;
}

requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}

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.Geometry.merge()函数可以将多个网格对象合并成一个。如果场景中网格太多是有性能瓶颈的,合并它们可以提升渲染效率。但是注意合并后你就不能再单独操作某个网格了。

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-08-02.html -->
<!DOCTYPE html>
<html>
<head>
<title>Merge objects</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, 1, 500);

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

camera.position.x = 0;
camera.position.y = 40;
camera.position.z = 50;
camera.lookAt(scene.position);
document.getElementById("WebGL-output").appendChild(renderer.domElement);

var step = 0;
var cubeMaterial = new THREE.MeshNormalMaterial({color: 0x00ff00, transparent: true, opacity: 0.5});

var controls = new function () {
this.combined = false;
this.numberOfObjects = 500;

this.redraw = function () {
var toRemove = [];
// traverse遍历场景对象是不能增、删操作
scene.traverse(function (e) {
if (e instanceof THREE.Mesh) toRemove.push(e);
});
toRemove.forEach(function (e) {
scene.remove(e)
});

if (controls.combined) {
// 将所有网格对象合并到geometry一个对象中
var geometry = new THREE.Geometry();
for (var i = 0; i < controls.numberOfObjects; i++) {
var cubeMesh = addCube();
cubeMesh.updateMatrix(); // 变换矩阵,保证合并后正确定位和旋转
geometry.merge(cubeMesh.geometry, cubeMesh.matrix); // 添加合并网格
}
scene.add(new THREE.Mesh(geometry, cubeMaterial));

} else {
// 不合并,网格对象一个个添加到场景中
for (var i = 0; i < controls.numberOfObjects; i++) {
scene.add(addCube());
}
}
};
};

var gui = new dat.GUI();
gui.add(controls, 'numberOfObjects', 0, 20000);
gui.add(controls, 'combined').onChange(controls.redraw);
gui.add(controls, 'redraw');

controls.redraw();
render();

// 添加立方体
function addCube() {
var cubeSize = 1.0;
var cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = -60 + Math.round((Math.random() * 100));
cube.position.y = Math.round((Math.random() * 10));
cube.position.z = -150 + Math.round((Math.random() * 175));
return cube;
}

var rotation = 0;
function render() {
rotation += 0.005;
stats.update();
camera.position.x = Math.sin(rotation) * 50;
camera.position.z = Math.cos(rotation) * 50;
camera.lookAt(scene.position);
requestAnimationFrame(render);
renderer.render(scene, camera);
}

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>

创建2W个网格直接添加进场景,看帧率降到14了。

同样2W个,合并后,帧率正常了。


从外部资源加载网格

threejs支持多种三维文件格式,可以读取并从中导入几何体和网格。下面是threejs支持的文件格式:

下面依次介绍这些三维文件格式在Threejs中怎么导入/导出的。

1. 以Threejs的JSON格式保存和加载

你可以在两种情形下使用Threejs的JSON文件格式:用它来保存和加载单个THREE.Mesh(网格),或者用它来保存和加载整个场景。

  1. 保存和加载THREE.Mesh

保存:通过mesh.toJSON()可以将网格转换为json对象,后面就是js的常规保存了。

加载:Threejs提供了一个叫THREE.ObjectLoader的辅助对象,使用它可以将JSON转换成THREE.Mesh对象。

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
<!-- chapter-08-03.html -->
<!DOCTYPE html>
<html>
<head>
<title>Save & Load Mesh</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 knot = createMesh(new THREE.TorusKnotGeometry(10, 1, 64, 8, 2, 3, 1));
scene.add(knot);

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

var loadedMesh;
var controls = new function () {
this.radius = knot.geometry.parameters.radius;
this.tube = 0.3;
this.radialSegments = knot.geometry.parameters.radialSegments;
this.tubularSegments = knot.geometry.parameters.tubularSegments;
this.p = knot.geometry.parameters.p;
this.q = knot.geometry.parameters.q;
this.heightScale = knot.geometry.parameters.heightScale;

this.redraw = function () {
scene.remove(knot);
knot = createMesh(new THREE.TorusKnotGeometry(controls.radius, controls.tube, Math.round(controls.radialSegments), Math.round(controls.tubularSegments), Math.round(controls.p), Math.round(controls.q), controls.heightScale));
scene.add(knot);
};

// 保存
this.save = function () {
// 网格对象转换为JSON对象
var result = knot.toJSON();
// 调用HTML5本地保存数据接口
localStorage.setItem("json", JSON.stringify(result));
};

// 加载
this.load = function () {
scene.remove(loadedMesh);
// 调用HTML5本地读取数据接口
var json = localStorage.getItem("json");
if (json) {
// JSON字符串转换为json对象
var loadedGeometry = JSON.parse(json);
// JSON对象转换为网格对象
var loader = new THREE.ObjectLoader();
loadedMesh = loader.parse(loadedGeometry);
loadedMesh.position.x -= 50;
scene.add(loadedMesh);
}
}
};

var gui = new dat.GUI();
var ioGui = gui.addFolder('Save & Load');
ioGui.add(controls, 'save').onChange(controls.save);
ioGui.add(controls, 'load').onChange(controls.load);
var meshGui = gui.addFolder('mesh');
meshGui.add(controls, 'radius', 0, 40).onChange(controls.redraw);
meshGui.add(controls, 'tube', 0, 40).onChange(controls.redraw);
meshGui.add(controls, 'radialSegments', 0, 400).step(1).onChange(controls.redraw);
meshGui.add(controls, 'tubularSegments', 1, 20).step(1).onChange(controls.redraw);
meshGui.add(controls, 'p', 1, 10).step(1).onChange(controls.redraw);
meshGui.add(controls, 'q', 1, 15).step(1).onChange(controls.redraw);
meshGui.add(controls, 'heightScale', 0, 5).onChange(controls.redraw);

render();

function createMesh(geom) {
var meshMaterial = new THREE.MeshBasicMaterial({
vertexColors: THREE.VertexColors,
wireframe: true,
wireframeLinewidth: 2,
color: 0xaaaaaa
});
meshMaterial.side = THREE.DoubleSide;
var mesh = new THREE.Mesh(geom, meshMaterial);
return mesh;
}

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

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>

  1. 保存和加载场景

使用Threejs提供的导出器和加载器: THREE.SceneExporterTHREE.SceneLoader。也支持从URL地址加载。

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
<!-- chapter-08-04.html -->
<!DOCTYPE html>
<html>
<head>
<title>Load and save scene</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/SceneLoader.js"></script>
<script type="text/javascript" src="../libs/SceneExporter.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 renderer = new THREE.WebGLRenderer();
renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));
renderer.setSize(window.innerWidth, window.innerHeight);

var planeGeometry = new THREE.PlaneGeometry(60, 20, 1, 1);
var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});
var plane = new THREE.Mesh(planeGeometry, planeMaterial);

plane.rotation.x = -0.5 * Math.PI;
plane.position.x = 15;
plane.position.y = 0;
plane.position.z = 0;
scene.add(plane);

var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xff0000});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.x = -4;
cube.position.y = 3;
cube.position.z = 0;
scene.add(cube);

var sphereGeometry = new THREE.SphereGeometry(4, 20, 20);
var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x7777ff});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.x = 20;
sphere.position.y = 0;
sphere.position.z = 2;
scene.add(sphere);

camera.position.x = -30;
camera.position.y = 40;
camera.position.z = 30;
camera.lookAt(scene.position);

var ambientLight = new THREE.AmbientLight(0x0c0c0c);
scene.add(ambientLight);

var spotLight = new THREE.PointLight(0xffffff);
spotLight.position.set(-40, 60, -10);
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(renderer.domElement);

var controls = new function () {
// 导出场景
this.exportScene = function () {
var exporter = new THREE.SceneExporter();
var sceneJson = JSON.stringify(exporter.parse(scene));
localStorage.setItem('scene', sceneJson);
};

// 清理场景
this.clearScene = function () {
scene = new THREE.Scene();
};

// 导入场景
this.importScene = function () {
var json = (localStorage.getItem('scene'));
var sceneLoader = new THREE.SceneLoader();
sceneLoader.parse(JSON.parse(json), function (e) {
scene = e.scene;
}, '.'); // 最后参数是外部纹理资源路径,这个示例没有使用外部资源,所以传入当前目录即可。
}
};

var gui = new dat.GUI();
gui.add(controls, "exportScene");
gui.add(controls, "clearScene");
gui.add(controls, "importScene");

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

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. 使用Blender导出JSON格式加载

有很多三维软件可以用来创建复杂的网格。其中一个流行的开源的软件叫作Blenderwww.blender.org)。

Threejs库目前已经提供了支持Blender以及Maya和3D Studio Max的导出器(插件扩展的形式),可以直接将文件导出为Threejs的JSON格式。

注:怎么安装和使用Blender支持导出json格式的插件这里不详细介绍了,详情参考《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
<!-- chapter-08-05.html -->
<!DOCTYPE html>
<html>
<head>
<title>Load blender model </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(0, 10, 0));

var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(0, 50, 30);
spotLight.intensity = 2;
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var mesh;
// 加载Blender导出的JSON模型文件
var loader = new THREE.JSONLoader();
/* 参数:
* JSON模型文件
* 回调函数,返回几何体和材质数组
* 材质所在路径,即JSON中mapDiffuse字段图片路径
*/
loader.load('../assets/models/misc_chair01.js', function (geometry, mat) {
mesh = new THREE.Mesh(geometry, mat[0]);
mesh.scale.x = 15;
mesh.scale.y = 15;
mesh.scale.z = 15;
scene.add(mesh);
}, '../assets/models/');

render();
function render() {
stats.update();
if (mesh) {
mesh.rotation.y += 0.02;
}
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}

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. 加载OBJ/MTL格式模型

OBJ和MTL是相互配合的两种格式,经常一起使用。OBJ文件定义几何体,MTL文件定义所用材质。

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
<!-- chapter-08-06.html -->
<!DOCTYPE html>
<html>
<head>
<title>Load OBJ and MTL </title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/OBJLoader.js"></script>
<script type="text/javascript" src="../libs/MTLLoader.js"></script>
<script type="text/javascript" src="../libs/OBJMTLLoader.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(0xaaaaff, 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(0, 10, 0));

var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(0, 40, 30);
spotLight.intensity = 2;
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var mesh;
// 加载OBJ/MTL格式
var loader = new THREE.OBJMTLLoader();
loader.load('../assets/models/butterfly.obj', '../assets/models/butterfly.mtl', function (object) {
// 对加载的网格模型材质属性微调
var wing1 = object.children[4].children[0];
var wing2 = object.children[5].children[0];
wing1.material.opacity = 0.6;
wing1.material.transparent = true;
wing1.material.depthTest = false;
wing1.material.side = THREE.DoubleSide;

wing2.material.opacity = 0.6;
wing2.material.depthTest = false;
wing2.material.transparent = true;
wing2.material.side = THREE.DoubleSide;

object.scale.set(140, 140, 140);
mesh = object;
scene.add(mesh);

object.rotation.x = 0.2;
object.rotation.y = -1.3;
});

render();
function render() {
stats.update();
if (mesh) {
mesh.rotation.y += 0.006;
}
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}

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. 加载Collada模型(.dae)

这是另一种非常通用的格式,不仅可以定义模型(网格和材质),还可以定义场景以及动画。

加载这种格式,使用上和加载OBJ/MTL模型步骤基本一样。主要区别是回调函数的返回结构不同:

1
2
3
4
5
6
7
8
9
10
11
var result = {
...
scene: scene,
morphs: morphs,
skins: skins,
animations: animData,
dae: {
...
}
...
}

还一个需要注意的点是,导出.dae格式模型,如果描述文件中纹理是用的.tga格式,那么需要把它转换为.png,并对应修改.dae模型文件的XML元素,指向转换后的.png文件。因为WebGL不支持.tga格式的纹理。

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
<!-- chapter-08-07.html -->
<!DOCTYPE html>
<html>
<head>
<title>Load collada model </title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/ColladaLoader.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(0xcccccc, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

camera.position.x = 150;
camera.position.y = 150;
camera.position.z = 150;
camera.lookAt(new THREE.Vector3(0, 20, 0));

var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(150, 150, 150);
spotLight.intensity = 2;
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var mesh;
// 加载.dae模型文件
var loader = new THREE.ColladaLoader();
loader.load("../assets/models/dae/Truck_dae.dae", function (result) {
// 模型中找到我们需要的网格对象
mesh = result.scene.children[0].children[0].clone();
mesh.scale.set(4, 4, 4);
scene.add(mesh);
});

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

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. 加载STL、CTM、VTK、AWD、Assimp、VRML和Babylon模型

这些使用都基本相同,就不一一列完整示例了,下面是它们的加载方式:

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
// STL
var loader = new THREE.STLLoader();
loader.load("../assets/models/SolidHead_2_lowPoly_42k.stl", function (geometry) {
console.log(geometry);
var mat = new THREE.MeshLambertMaterial({color: 0x7777ff});
var mesh = new THREE.Mesh(geometry, mat);
mesh.rotation.x = -0.5 * Math.PI;
mesh.scale.set(0.6, 0.6, 0.6);
scene.add(mesh);
});

// CTM
var loader = new THREE.CTMLoader();
loader.load("../assets/models/auditt_wheel.ctm", function (geometry) {
var mat = new THREE.MeshLambertMaterial({color: 0xff8888});
var group = new THREE.Mesh(geometry, mat);
group.scale.set(20, 20, 20);
scene.add(group);
}, {});

// VTK
var loader = new THREE.VTKLoader();
loader.load("../assets/models/moai_fixed.vtk", function (geometry) {
var mat = new THREE.MeshLambertMaterial({color: 0xaaffaa});
var group = new THREE.Mesh(geometry, mat);
group.scale.set(9, 9, 9);
scene.add(group);
});

// AWD
var loader = new THREE.AWDLoader();
loader.load("../assets/models/awd/PolarBear.awd", function (model) {
console.log(model);
model.traverse(function (child) {
if (child instanceof THREE.Mesh) {
child.material = new THREE.MeshLambertMaterial({color: 0xaaaaaa});
console.log(child.geometry);
}
});
model.scale.set(0.1, 0.1, 0.1);
scene.add(model);
});

// Assimp
var loader = new THREE.AssimpJSONLoader();
loader.load("../assets/models/assimp/spider.obj.assimp.json", function (model) {
console.log(model);
model.traverse(function (child) {
if (child instanceof THREE.Mesh) {
// child.material = new THREE.MeshLambertMaterial({color:0xaaaaaa});
console.log(child.geometry);
}
});
model.scale.set(0.1, 0.1, 0.1);
scene.add(model);
});

// VRML
var loader = new THREE.VRMLLoader();
loader.load("../assets/models/vrml/tree.wrl", function (model) {
console.log(model);
model.traverse(function (child) {
if (child instanceof THREE.Mesh) {
// child.material = new THREE.MeshLambertMaterial({color:0xaaaaaa});
console.log(child.geometry);
}
});
model.scale.set(10, 10, 10);
scene.add(model);
});

// Babylon
var loader = new THREE.BabylonLoader();
loader.load("../assets/models/babylon/skull.babylon", function (loadedScene) {
console.log(loadedScene.children[1].material = new THREE.MeshLambertMaterial());
scene = loadedScene;
});

6. 加载PDB模型(分子结构)

这是一种特殊的模型,用于显示分子结构。

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
<!-- chapter-08-08.html -->
<!DOCTYPE html>
<html>
<head>
<title>Load pdb model </title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/PDBLoader.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(0x000, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

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

var dir1 = new THREE.DirectionalLight(0.4);
dir1.position.set(-30, 30, -30);
scene.add(dir1);

var dir2 = new THREE.DirectionalLight(0.4);
dir2.position.set(-30, 30, 30);
scene.add(dir2);

var dir3 = new THREE.DirectionalLight(0.4);
dir3.position.set(30, 30, -30);
scene.add(dir3);

var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(30, 30, 30);
scene.add(spotLight);

document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var mesh;
var loader = new THREE.PDBLoader();
var group = new THREE.Object3D();
loader.load("../assets/models/aspirin.pdb", function (geometry, geometryBonds) {
//loader.load("../assets/models/diamond.pdb", function (geometry, geometryBonds) {
// 在分子结构顶点处创建圆点
var i = 0;
geometry.vertices.forEach(function (position) {
var sphere = new THREE.SphereGeometry(0.2);
var material = new THREE.MeshPhongMaterial({color: geometry.colors[i++]});
var mesh = new THREE.Mesh(sphere, material);
mesh.position.copy(position);
group.add(mesh);
});

// 分子圆点之间的键创建连接管
for (var j = 0; j < geometryBonds.vertices.length; j += 2) {
var path = new THREE.SplineCurve3([geometryBonds.vertices[j], geometryBonds.vertices[j + 1]]);
var tube = new THREE.TubeGeometry(path, 1, 0.04);
var material = new THREE.MeshPhongMaterial({color: 0xcccccc});
var mesh = new THREE.Mesh(tube, material);
group.add(mesh);
}

scene.add(group);
});

render();
function render() {
stats.update();
if (group) {
group.rotation.y += 0.006;
group.rotation.x += 0.006;
}
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}

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>

7. 把加载的外部模型创建为粒子系统

这里用PLY格式模型举例,其加载流程都差不多,没什么要说的。我们做一些不一样的操作,将使用加载的模型信息来创建一个粒子系统。

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
<!-- chapter-08-09.html -->
<!DOCTYPE html>
<html>
<head>
<title>Load ply model </title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/PLYLoader.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(0x000, 1.0));
webGLRenderer.setSize(window.innerWidth, window.innerHeight);
webGLRenderer.shadowMapEnabled = true;

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

var spotLight = new THREE.SpotLight(0xffffff);
spotLight.position.set(20, 20, 20);
scene.add(spotLight);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);

var loader = new THREE.PLYLoader();
var group = new THREE.Object3D();
loader.load("../assets/models/test.ply", function (geometry) {
// 粒子系统的点云材质
var material = new THREE.PointCloudMaterial({
color: 0xffffff,
size: 0.4,
opacity: 0.6,
transparent: true,
blending: THREE.AdditiveBlending,
map: generateSprite() // 外部纹理信息
});

group = new THREE.PointCloud(geometry, material);
group.sortParticles = true;
scene.add(group);
});
render();

// 获取画布的纹理信息
function generateSprite() {
var canvas = document.createElement('canvas');
canvas.width = 16;
canvas.height = 16;

var context = canvas.getContext('2d');
var gradient = context.createRadialGradient(canvas.width / 2, canvas.height / 2, 0, canvas.width / 2, canvas.height / 2, canvas.width / 2);
gradient.addColorStop(0, 'rgba(255,255,255,1)');
gradient.addColorStop(0.2, 'rgba(0,255,255,1)');
gradient.addColorStop(0.4, 'rgba(0,0,64,1)');
gradient.addColorStop(1, 'rgba(0,0,0,1)');

context.fillStyle = gradient;
context.fillRect(0, 0, canvas.width, canvas.height);

var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
return texture;
}

function render() {
stats.update();
if (group) {
group.rotation.y += 0.006;
}
requestAnimationFrame(render);
webGLRenderer.render(scene, camera);
}

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>