个人博客地址: 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>




















