个人博客地址: https://cxx001.gitee.io
前面我们都是用Threejs提供的几何体来创建网格,对于简单几何体(如球体和方块)来说非常有效,但当你想要创建复杂的三维模型时,这不是最好的方法。通常情况下,你可以使用三维建模工具(如Blender和3D Studio Max)来创建复杂几何体。
本节就来学习如何加载和展示由这些三维建模工具所创建的模型。
网格对象组合与合并
在学习使用外部三维建模工具所创建的模型前,我们先了解两个基本操作:将对象组合在一起,以及将多个网格合并为一个网格。
1. 网格组合
这个不是什么新东西了,前面我们很多示例其实早就使用了。就是把多个网格对象添加到一个对象里(THREE.Group),对这1个对象移动、缩放、旋转变换操作其子对象会一起变换。
<!-- 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()
函数可以将多个网格对象合并成一个。如果场景中网格太多是有性能瓶颈的,合并它们可以提升渲染效率。但是注意合并后你就不能再单独操作某个网格了。
<!-- 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(网格),或者用它来保存和加载整个场景。
- 保存和加载THREE.Mesh
保存:通过mesh.toJSON()
可以将网格转换为json对象,后面就是js的常规保存了。
加载:Threejs提供了一个叫THREE.ObjectLoader
的辅助对象,使用它可以将JSON转换成THREE.Mesh对象。
<!-- 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>
- 保存和加载场景
使用Threejs提供的导出器和加载器: THREE.SceneExporter
、THREE.SceneLoader
。也支持从URL地址加载。
<!-- 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格式加载
有很多三维软件可以用来创建复杂的网格。其中一个流行的开源的软件叫作Blender
(www.blender.org)。
Threejs库目前已经提供了支持Blender以及Maya和3D Studio Max的导出器(插件扩展的形式),可以直接将文件导出为Threejs的JSON格式。
注:怎么安装和使用Blender支持导出json格式的插件这里不详细介绍了,详情参考《Three.js开发指南-第八章》。
<!-- 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文件定义所用材质。
<!-- 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模型步骤基本一样。主要区别是回调函数的返回结构不同:
var result = {
...
scene: scene,
morphs: morphs,
skins: skins,
animations: animData,
dae: {
...
}
...
}
还一个需要注意的点是,导出.dae格式模型,如果描述文件中纹理是用的.tga格式,那么需要把它转换为.png,并对应修改.dae模型文件的XML元素,指向转换后的.png文件。因为WebGL不支持.tga格式的纹理。
<!-- 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模型
这些使用都基本相同,就不一一列完整示例了,下面是它们的加载方式:
// 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模型(分子结构)
这是一种特殊的模型,用于显示分子结构。
<!-- 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格式模型举例,其加载流程都差不多,没什么要说的。我们做一些不一样的操作,将使用加载的模型信息来创建一个粒子系统。
<!-- 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>