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