前面我们介绍了各种可以用来覆盖对象的材质,也介绍了如何修改材质的颜色、关泽和不透明度,但是我们还没有详细介绍如何在材质中使用外部图片(也叫纹理).
将纹理应用于材质
1. 加载纹理并应用到网格
纹理最基础的用法是作为贴图被添加在材质上,当你使用这样的材质创建网格时,网格的颜色则来源于纹理。
注: 为了达到最好效果,图片最好使用2的次方的正方形。
由于纹理可以放大或缩小,所以纹理上的像素不会一对一映射到面的像素上。为此,Threejs提供了各种不同的选项,你可以指定magFilter或minFilter属性来设置纹理如何放大或缩小。
magFilter可选值:
| 名称 |
描述 |
| THREE.NearestFilter(最邻近过滤) |
会将纹理上最近的像素颜色应用于面上。在放大时,会导致方块化;在缩小时,会丢失很多细节 |
| THREE.LinearFilter(线性过滤) |
最终颜色由周围四个像素值来决定。这样虽然在缩小时仍会丢失一些细节,但是在放大时会平滑很多,方块化也比较少出现(默认选择) |
minFilter属性是要使用mipmap,mipmap是把纹理按照2的倍数进行缩小,直到图形为1×1的大小,然后把这些图都存储起来,这些图片是在加载纹理时创建的,可以用于生成比较光滑的过滤效果。
过滤模式有下面这些:
| 名称 |
描述 |
| THREE.NearestMipMapNearestFilter |
选择最邻近的mip层,并执行上表中最邻近过滤。虽然放大时仍然会有方块化,但是缩小时效果会好很多 |
| THREE.NearestMipMapLinearFilter |
选择最邻近的两个mip层,并分别在这两个mip层上运行最邻近过滤获取两个中间值,最后将这两个中间值传递到线性过滤器中获取最终值 |
| THREE.LinearMipMapNearestFilter |
选择最邻近的mip层,并执行前表中的线性过滤 |
| THREE.LinearMipMapLinearFilter |
选择最邻近的两个mip层,并分别在这两个mip层上运行线性过滤获取两个中间值,最后将这两个中间值传递到线性过滤器中获取最终值(默认选择) |
除了使用THREE.ImageUtils.loadTexture方法加载标志格式的png,gif或jpeg图片,Threejs还提供了一些自定义加载器,以此来加载其它格式的纹理文件。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| <!-- chapter-10-01.html --> <!DOCTYPE html> <html> <head> <title>Basic textures</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 polyhedron = createMesh(new THREE.IcosahedronGeometry(5, 0), "metal-rust.jpg"); polyhedron.position.x = 12; scene.add(polyhedron); var sphere = createMesh(new THREE.SphereGeometry(5, 20, 20), "floor-wood.jpg"); scene.add(sphere); var cube = createMesh(new THREE.BoxGeometry(5, 5, 5), "brick-wall.jpg"); cube.position.x = -12; scene.add(cube); console.log(cube.geometry.faceVertexUvs);
camera.position.x = 00; camera.position.y = 12; camera.position.z = 28; camera.lookAt(new THREE.Vector3(0, 0, 0));
var ambiLight = new THREE.AmbientLight(0x141414); scene.add(ambiLight);
var light = new THREE.DirectionalLight(); light.position.set(0, 30, 20); scene.add(light);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement); function createMesh(geom, imageFile) { var texture = THREE.ImageUtils.loadTexture("../assets/textures/general/" + imageFile); var mat = new THREE.MeshPhongMaterial(); mat.map = texture; var mesh = new THREE.Mesh(geom, mat); return mesh; } var step = 0; function render() { stats.update(); polyhedron.rotation.y = step += 0.01; polyhedron.rotation.x = step; cube.rotation.y = step; cube.rotation.x = step; sphere.rotation.y = step; sphere.rotation.x = step; requestAnimationFrame(render); webGLRenderer.render(scene, camera); } render();
function initStats() { var stats = new Stats(); stats.setMode(0); 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. 使用凹凸贴图创建褶皱
凹凸贴图用于为材质添加厚度,网格面看起来有起伏、深度的感觉,更加立体。我们使用材质的bumpMap属性和bumpScale属性。
但是注意像素的密集程度定义的是凹凸的高度,但是凹凸图只包含像素的相对高度,没有任何倾斜的方向信息。所以使用凹凸贴图所能表达的深度信息有限,要想实现更多的细节可以使用法向贴图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
| <!-- chapter-10-02.html --> <!DOCTYPE html> <html> <head> <title>Bump maps</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 sphere1 = createMesh(new THREE.BoxGeometry(15, 15, 2), "stone.jpg"); sphere1.rotation.y = -0.5; sphere1.position.x = 12; scene.add(sphere1); var sphere2 = createMesh(new THREE.BoxGeometry(15, 15, 2), "stone.jpg", "stone-bump.jpg"); sphere2.rotation.y = 0.5; sphere2.position.x = -12; scene.add(sphere2); console.log(sphere2.geometry.faceVertexUvs); var floorTex = THREE.ImageUtils.loadTexture("../assets/textures/general/floor-wood.jpg"); var plane = new THREE.Mesh(new THREE.BoxGeometry(200, 100, 0.1, 30), new THREE.MeshPhongMaterial({ color: 0x3c3c3c, map: floorTex })); plane.position.y = -7.5; plane.rotation.x = -0.5 * Math.PI; scene.add(plane);
camera.position.x = 00; camera.position.y = 12; camera.position.z = 28; camera.lookAt(new THREE.Vector3(0, 0, 0));
var ambiLight = new THREE.AmbientLight(0x242424); scene.add(ambiLight);
var light = new THREE.SpotLight(); light.position.set(0, 30, 30); light.intensity = 1.2; scene.add(light); document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
var controls = new function () { this.bumpScale = 0.2; this.rotate = false; this.changeTexture = function (e) { var texture = THREE.ImageUtils.loadTexture("../assets/textures/general/" + e + ".jpg"); sphere2.material.map = texture; sphere1.material.map = texture;
var bump = THREE.ImageUtils.loadTexture("../assets/textures/general/" + e + "-bump.jpg"); sphere2.material.bumpMap = bump; };
this.updateBump = function (e) { console.log(sphere2.material.bumpScale); sphere2.material.bumpScale = e; } };
var gui = new dat.GUI(); gui.add(controls, "bumpScale", -2, 2).onChange(controls.updateBump); gui.add(controls, "changeTexture", ['stone', 'weave']).onChange(controls.changeTexture); gui.add(controls, "rotate");
function createMesh(geom, imageFile, bump) { var texture = THREE.ImageUtils.loadTexture("../assets/textures/general/" + imageFile); geom.computeVertexNormals(); var mat = new THREE.MeshPhongMaterial(); mat.map = texture; if (bump) { var bump = THREE.ImageUtils.loadTexture("../assets/textures/general/" + bump); mat.bumpMap = bump; mat.bumpScale = 0.2; console.log('d'); }
var mesh = new THREE.Mesh(geom, mat); return mesh; }
function render() { stats.update(); if (controls.rotate) { sphere1.rotation.y -= 0.01; sphere2.rotation.y += 0.01; } requestAnimationFrame(render); webGLRenderer.render(scene, camera); } render();
function initStats() { var stats = new Stats(); stats.setMode(0); 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. 使用法向贴图创建更加细致的凹凸和褶皱
法线贴图保存的不是高度信息,而是法向量的方向。简单来讲,使用法向量贴图只需要使用很少的顶点和面就可以创建出细节很丰富的模型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
| <!-- chapter-10-03.html --> <!DOCTYPE html> <html> <head> <title>Normal maps</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 sphere1 = createMesh(new THREE.BoxGeometry(15, 15, 15), "plaster.jpg"); sphere1.rotation.y = -0.5; sphere1.position.x = 12; scene.add(sphere1); var sphere2 = createMesh(new THREE.BoxGeometry(15, 15, 15), "plaster.jpg", "plaster-normal.jpg"); sphere2.rotation.y = 0.5; sphere2.position.x = -12; scene.add(sphere2); console.log(sphere2.geometry.faceVertexUvs); var floorTex = THREE.ImageUtils.loadTexture("../assets/textures/general/floor-wood.jpg"); var plane = new THREE.Mesh(new THREE.BoxGeometry(200, 100, 0.1, 30), new THREE.MeshPhongMaterial({ color: 0x3c3c3c, map: floorTex })); plane.position.y = -7.5; plane.rotation.x = -0.5 * Math.PI; scene.add(plane);
camera.position.x = 00; camera.position.y = 12; camera.position.z = 38; camera.lookAt(new THREE.Vector3(0, 0, 0));
var ambiLight = new THREE.AmbientLight(0x242424); scene.add(ambiLight);
var light = new THREE.SpotLight(); light.position.set(0, 30, 30); light.intensity = 1.2; scene.add(light);
var pointColor = "#ff5808"; var directionalLight = new THREE.PointLight(pointColor); scene.add(directionalLight); var sphereLight = new THREE.SphereGeometry(0.2); var sphereLightMaterial = new THREE.MeshBasicMaterial({color: 0xac6c25}); var sphereLightMesh = new THREE.Mesh(sphereLight, sphereLightMaterial); sphereLightMesh.castShadow = true; sphereLightMesh.position = new THREE.Vector3(3, 3, 3); scene.add(sphereLightMesh);
document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
var controls = new function () { this.normalScale = 1; this.changeTexture = "plaster"; this.rotate = false;
this.changeTexture = function (e) { var texture = THREE.ImageUtils.loadTexture("../assets/textures/general/" + e + ".jpg"); sphere2.material.map = texture; sphere1.material.map = texture;
var bump = THREE.ImageUtils.loadTexture("../assets/textures/general/" + e + "-normal.jpg"); sphere2.material.normalMap = bump; };
this.updateBump = function (e) { sphere2.material.normalScale.set(e, e); } };
var gui = new dat.GUI(); gui.add(controls, "normalScale", -2, 2).onChange(controls.updateBump); gui.add(controls, "changeTexture", ['plaster', 'bathroom', 'metal-floor']).onChange(controls.changeTexture); gui.add(controls, "rotate"); function createMesh(geom, imageFile, normal) { if (normal) { var t = THREE.ImageUtils.loadTexture("../assets/textures/general/" + imageFile); var m = THREE.ImageUtils.loadTexture("../assets/textures/general/" + normal); var mat2 = new THREE.MeshPhongMaterial(); mat2.map = t; mat2.normalMap = m; var mesh = new THREE.Mesh(geom, mat2); return mesh; } else { var t = THREE.ImageUtils.loadTexture("../assets/textures/general/" + imageFile); var mat1 = new THREE.MeshPhongMaterial({ map: t }); var mesh = new THREE.Mesh(geom, mat1); return mesh; } return mesh; }
var invert = 1; var phase = 0; var step = 0; function render() { stats.update(); step += 0.1; if (controls.rotate) { sphere1.rotation.y -= 0.01; sphere2.rotation.y += 0.01; }
if (phase > 2 * Math.PI) { invert = invert * -1; phase -= 2 * Math.PI; } else { phase += 0.03; }
sphereLightMesh.position.z = +(21 * (Math.sin(phase))); sphereLightMesh.position.x = -14 + (14 * (Math.cos(phase)));
if (invert < 0) { var pivot = 0; sphereLightMesh.position.x = (invert * (sphereLightMesh.position.x - pivot)) + pivot; } directionalLight.position.copy(sphereLightMesh.position);
requestAnimationFrame(render); webGLRenderer.render(scene, camera); } render();
function initStats() { var stats = new Stats(); stats.setMode(0); 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. 使用光照贴图创建阴影效果
前面我们也介绍了可以受光照影响实时产生阴影的材质。本节来看看另一种产生阴影的方法–光照贴图。它是预先渲染好的阴影,可以用它来模拟真实的阴影,效率更高,主要用于静态场景的阴影。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
| <!-- chapter-10-04.html --> <!DOCTYPE html> <html> <head> <title>LightMap</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 renderer; var webGLRenderer = new THREE.WebGLRenderer(); webGLRenderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0)); webGLRenderer.setSize(window.innerWidth, window.innerHeight); webGLRenderer.shadowMapEnabled = true; renderer = webGLRenderer; var groundGeom = new THREE.PlaneGeometry(95, 95, 1, 1); var lm = THREE.ImageUtils.loadTexture('../assets/textures/lightmap/lm-1.png'); var wood = THREE.ImageUtils.loadTexture('../assets/textures/general/floor-wood.jpg'); var groundMaterial = new THREE.MeshBasicMaterial( { color: 0x777777, lightMap: lm, map: wood });
groundGeom.faceVertexUvs[1] = groundGeom.faceVertexUvs[0];
var groundMesh = new THREE.Mesh(groundGeom, groundMaterial); groundMesh.rotation.x = -Math.PI / 2; groundMesh.position.y = 0; scene.add(groundMesh); var cubeGeometry = new THREE.BoxGeometry(12, 12, 12); var cubeGeometry2 = new THREE.BoxGeometry(6, 6, 6); var meshMaterial = new THREE.MeshBasicMaterial(); meshMaterial.map = THREE.ImageUtils.loadTexture('../assets/textures/general/stone.jpg'); var cube = new THREE.Mesh(cubeGeometry, meshMaterial); var cube2 = new THREE.Mesh(cubeGeometry2, meshMaterial); cube.position.set(0.9, 6, -12); cube2.position.set(-13.2, 3, -6); scene.add(cube); scene.add(cube2);
camera.position.x = -20; camera.position.y = 20; camera.position.z = 30; camera.lookAt(new THREE.Vector3(0, 0, 0));
var ambientLight = new THREE.AmbientLight(0x0c0c0c); scene.add(ambientLight); document.getElementById("WebGL-output").appendChild(renderer.domElement); function render() { stats.update(); requestAnimationFrame(render); renderer.render(scene, camera); } render();
function initStats() { var stats = new Stats(); stats.setMode(0); 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. 使用环境贴图创建反光效果
计算环境的反光效果对CPU耗费是非常巨大的,而且通常会使用光线追踪算法。本节我们用另一种方式实现反光效果,通过创建一个对象所处环境的纹理来伪装反光,并将它应用到指定的对象上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
| <!-- chapter-10-05.html --> <!DOCTYPE html> <html> <head> <title>dynamic envmap</title> <script src="../libs/three.js"></script> <script src="../libs/dat.gui.js"></script> <script src="../libs/OrbitControls.js"></script> <style> body { margin: 0; overflow: hidden; } </style> </head> <script> var renderer; var scene; var camera, cubeCamera; var control; var orbit; var sphere; function init() { scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000); orbit = new THREE.OrbitControls(camera); renderer = new THREE.WebGLRenderer(); renderer.setClearColor(0x000000, 1.0); renderer.setSize(window.innerWidth, window.innerHeight); var textureCube = createCubeMap(); textureCube.format = THREE.RGBFormat; var shader = THREE.ShaderLib["cube"]; shader.uniforms["tCube"].value = textureCube; var material = new THREE.ShaderMaterial({ fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: shader.uniforms, depthWrite: false, side: THREE.DoubleSide }); var skybox = new THREE.Mesh(new THREE.BoxGeometry(10000, 10000, 10000), material); scene.add(skybox); cubeCamera = new THREE.CubeCamera(0.1, 20000, 256); scene.add(cubeCamera); var sphereGeometry = new THREE.SphereGeometry(4, 15, 15); var dynamicEnvMaterial = new THREE.MeshBasicMaterial({envMap: cubeCamera.renderTarget, side: THREE.DoubleSide}); sphere = new THREE.Mesh(sphereGeometry, dynamicEnvMaterial); sphere.name = 'sphere'; scene.add(sphere); var cylinderGeometry = new THREE.CylinderGeometry(2, 4, 10, 20, 20, false); var envMaterial = new THREE.MeshBasicMaterial({envMap: textureCube, side: THREE.DoubleSide}); var cylinder = new THREE.Mesh(cylinderGeometry, envMaterial); cylinder.name = 'cylinder'; scene.add(cylinder); cylinder.position.set(10, 0, 0); var boxGeometry = new THREE.BoxGeometry(5, 5, 5); var cube = new THREE.Mesh(boxGeometry, envMaterial); cube.name = 'cube'; scene.add(cube); cube.position.set(-10, 0, 0);
camera.position.x = 0; camera.position.y = 5; camera.position.z = 33; camera.lookAt(scene.position); document.body.appendChild(renderer.domElement); render(); }
function createCubeMap() { var path = "../assets/textures/cubemap/parliament/"; var format = '.jpg'; var urls = [ path + 'posx' + format, path + 'negx' + format, path + 'posy' + format, path + 'negy' + format, path + 'posz' + format, path + 'negz' + format ]; var textureCube = THREE.ImageUtils.loadTextureCube(urls, new THREE.CubeReflectionMapping()); return textureCube;
}
function render() { orbit.update(); sphere.visible = false; cubeCamera.updateCubeMap(renderer, scene); sphere.visible = true; renderer.render(scene, camera); scene.getObjectByName('cube').rotation.x += 0.005; scene.getObjectByName('cube').rotation.y += 0.005; scene.getObjectByName('cylinder').rotation.x += 0.005; requestAnimationFrame(render); }
window.onload = init; </script> <body> </body> </html>
|

6. 使用高光贴图创建闪亮效果
通过高光贴图,你可以为材质指定一个闪亮的、色彩明快的贴图。
下面示例:海洋区域反光、明亮;陆地不反光、暗淡。细节效果需要自调参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| <!-- chapter-10-06.html --> <!DOCTYPE html> <html> <head> <title>Specular map</title> <script type="text/javascript" src="../libs/three.js"></script> <script type="text/javascript" src="../libs/stats.js"></script> <script type="text/javascript" src="../libs/dat.gui.js"></script> <script type="text/javascript" src="../libs/OrbitControls.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;
var sphere = createMesh(new THREE.SphereGeometry(10, 40, 40)); scene.add(sphere);
camera.position.x = 15; camera.position.y = 15; camera.position.z = 15; camera.lookAt(new THREE.Vector3(0, 0, 0));
var orbitControls = new THREE.OrbitControls(camera); orbitControls.autoRotate = false; var ambi = new THREE.AmbientLight(0x3300000); scene.add(ambi);
var spotLight = new THREE.DirectionalLight(0xffffff); spotLight.position.set(350, 350, 150); spotLight.intensity = 0.4; scene.add(spotLight); document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
function createMesh(geom) { var planetTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/Earth.png"); var specularTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthSpec.png"); var normalTexture = THREE.ImageUtils.loadTexture("../assets/textures/planets/EarthNormal.png");
var planetMaterial = new THREE.MeshPhongMaterial(); planetMaterial.specularMap = specularTexture; planetMaterial.specular = new THREE.Color(0xff0000); planetMaterial.normalMap = normalTexture; planetMaterial.map = planetTexture; planetMaterial.shininess = 150; var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, [planetMaterial]); return mesh; } var clock = new THREE.Clock(); function render() { stats.update(); var delta = clock.getDelta(); orbitControls.update(delta); sphere.rotation.y += 0.005; requestAnimationFrame(render); webGLRenderer.render(scene, camera); } render();
function initStats() { var stats = new Stats(); stats.setMode(0); stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; document.getElementById("Stats-output").appendChild(stats.domElement); return stats; } } window.onload = init; </script> </body> </html>
|

纹理的高级用途
1. 自定义UV映射
UV映射是指指定纹理的哪部分显示在物体的表面上。默认是将整个纹理显示到物体表面上,一般不需要修改,如果要自定义UV映射,指定部分纹理显示到物体表面,通常都是借助建模工具完成(Blender)。
原理是修改几何体对应面的每个顶点的faceVertexUvs属性,这个属性有两个维度u/v,对应faceVertexUvs属性的x/y,取值范围0~1。
如修改几何体第一个面的UV映射:
1 2 3 4 5 6 7
| geom.faceVertexUvs[0][0][0].x = 0.5; geom.faceVertexUvs[0][0][0].y = 0.7; geom.faceVertexUvs[0][0][1].x = 0.4; geom.faceVertexUvs[0][0][1].y = 0.1; geom.faceVertexUvs[0][0][2].x = 0.4; geom.faceVertexUvs[0][0][2].y = 0.5;
|
很明显这样自定义设置很不直观,所以一般自定义UV映射我们一般通过专业工具完成,然后加载模型文件即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
| <!-- chapter-10-07.html --> <!DOCTYPE html> <html> <head> <title>UV mapping </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/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(0xffffff, 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, 0, 0)); document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement); var mesh; var controls = new function () { this.loadCube1 = function () { var loader = new THREE.OBJLoader(); loader.load('../assets/models/UVCube1.obj', function (geometry) { if (mesh) scene.remove(mesh); var material = new THREE.MeshBasicMaterial({color: 0xffffff}); var texture = THREE.ImageUtils.loadTexture("../assets/textures/ash_uvgrid01.jpg"); material.map = texture; geometry.children[0].material = material; mesh = geometry; geometry.scale.set(15, 15, 15); scene.add(geometry); }); }; this.loadCube2 = function () { var loader = new THREE.OBJLoader(); loader.load('../assets/models/UVCube2.obj', function (geometry) { if (mesh) scene.remove(mesh); var material = new THREE.MeshBasicMaterial({color: 0xffffff}); var texture = THREE.ImageUtils.loadTexture("../assets/textures/ash_uvgrid01.jpg"); material.map = texture; geometry.children[0].material = material; mesh = geometry; geometry.scale.set(15, 15, 15); geometry.rotation.x = -0.3; scene.add(geometry); }); }; };
var gui = new dat.GUI(); gui.add(controls, 'loadCube1'); gui.add(controls, 'loadCube2'); controls.loadCube1();
function render() { stats.update(); requestAnimationFrame(render); webGLRenderer.render(scene, camera); } render();
function initStats() { var stats = new Stats(); stats.setMode(0); 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>
|
loadCube1:

loadCube2:

2. 复制纹理
上面UV映射是指定纹理哪些部分显示到物体表面,Threejs会用选定的纹理包围整个物体表面。但是对于有些情形,你可能不想将纹理遍布整个面或整个几何体,而是让纹理进行重复(复制)。
通过material.map.wrapS 和 material.map.wrapT属性来设置纹理的包裹类型(wrapS是x轴方向纹理的行为,wrapT是y轴方向纹理的行为):
| 纹理包裹类型 |
描述 |
| THREE.RepeatWrapping |
允许纹理重复自己 |
| THREE.ClampToEdgeWrapping |
默认值,纹理不会重复,用选择的纹理尽量包裹物体整个表面,只是在边缘的像素用重复纹理来填充剩下的空间 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
| <!-- chapter-10-08.html --> <!DOCTYPE html> <html> <head> <title>Repeat mapping</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 sphere = createMesh(new THREE.SphereGeometry(5, 20, 20), "floor-wood.jpg"); scene.add(sphere); sphere.position.x = 7;
var cube = createMesh(new THREE.BoxGeometry(6, 6, 6), "brick-wall.jpg"); cube.position.x = -7; scene.add(cube); console.log(cube.geometry.faceVertexUvs);
camera.position.x = 00; camera.position.y = 12; camera.position.z = 20; camera.lookAt(new THREE.Vector3(0, 0, 0));
var ambiLight = new THREE.AmbientLight(0x141414); scene.add(ambiLight);
var light = new THREE.DirectionalLight(); light.position.set(0, 30, 20); scene.add(light); document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
var controls = new function () { this.repeatX = 1; this.repeatY = 1; this.repeatWrapping = true; this.updateRepeat = function (e) { cube.material.map.repeat.set(controls.repeatX, controls.repeatY); sphere.material.map.repeat.set(controls.repeatX, controls.repeatY); if (controls.repeatWrapping) { cube.material.map.wrapS = THREE.RepeatWrapping; cube.material.map.wrapT = THREE.RepeatWrapping; sphere.material.map.wrapS = THREE.RepeatWrapping; sphere.material.map.wrapT = THREE.RepeatWrapping; } else { cube.material.map.wrapS = THREE.ClampToEdgeWrapping; cube.material.map.wrapT = THREE.ClampToEdgeWrapping; sphere.material.map.wrapS = THREE.ClampToEdgeWrapping; sphere.material.map.wrapT = THREE.ClampToEdgeWrapping; } cube.material.map.needsUpdate = true; sphere.material.map.needsUpdate = true; } };
var gui = new dat.GUI(); gui.add(controls, "repeatX", -4, 4).onChange(controls.updateRepeat); gui.add(controls, "repeatY", -4, 4).onChange(controls.updateRepeat); gui.add(controls, "repeatWrapping").onChange(controls.updateRepeat);
function createMesh(geom, texture) { var texture = THREE.ImageUtils.loadTexture("../assets/textures/general/" + texture); texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; geom.computeVertexNormals(); var mat = new THREE.MeshPhongMaterial(); mat.map = texture; var mesh = new THREE.Mesh(geom, mat); return mesh; } var step = 0; function render() { stats.update(); step += 0.01; cube.rotation.y = step; cube.rotation.x = step; sphere.rotation.y = step; sphere.rotation.x = step; requestAnimationFrame(render); webGLRenderer.render(scene, camera); } render();
function initStats() { var stats = new Stats(); stats.setMode(0); 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. 在画布上绘制图案并作为纹理
前面我们都是加载本地静态图片作为纹理,其实Threejs也支持将HTML5的画布作为纹理使用。
下面示例我们将用Literally库(http://literallycanvas.com) ,来创建一个交互式的画布。在画布上你可以进行绘图,它可以作为纹理实时显示到网格对象上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
| <!-- chapter-10-09.html --> <!DOCTYPE html> <html> <head> <title>Canvas texture</title> <link href="../libs/literally/css/literally.css" rel="stylesheet"> <script type="text/javascript" src="../libs/three.js"></script> <script type="text/javascript" src="../libs/stats.js"></script> <script type="text/javascript" src="../libs/dat.gui.js"></script> <script type="text/javascript" src="../libs/perlin.js"></script> <script type="text/javascript" src="../libs/literally/jquery-1.8.2.js"></script> <script type="text/javascript" src="../libs/literally/underscore-1.4.2.js"></script> <script type="text/javascript" src="../libs/literally/js/literallycanvas.js"></script>
<style> body { margin: 0; overflow: hidden; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; }
.fs-container { width: 300px; height: 300px; margin: auto; bottom: 20px; position: absolute; x: 0; y: 0; " }
#canvas-output { width: 300px; height: 300px; } </style> </head> <body>
<div id="Stats-output"> </div>
<div class="fs-container"> <div id="canvas-output" style="float:left"> </div> </div>
<div id="WebGL-output"> </div>
<script type="text/javascript"> var canvas = document.createElement("canvas"); document.getElementById('canvas-output').appendChild(canvas); $('#canvas-output').literallycanvas({imageURLPrefix: '../libs/literally/img'});
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(0xbbbbbb, 1.0)); webGLRenderer.setSize(window.innerWidth, window.innerHeight); webGLRenderer.shadowMapEnabled = true;
var cube = createMesh(new THREE.BoxGeometry(10, 10, 10)); cube.position.x = 0; scene.add(cube);
camera.position.x = 00; camera.position.y = 12; camera.position.z = 28; camera.lookAt(new THREE.Vector3(0, 0, 0));
var ambiLight = new THREE.AmbientLight(0x141414); scene.add(ambiLight);
var light = new THREE.DirectionalLight(); light.position.set(0, 30, 20); scene.add(light); document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
function createMesh(geom) { var canvasMap = new THREE.Texture(canvas); var mat = new THREE.MeshPhongMaterial(); mat.map = canvasMap; var mesh = new THREE.Mesh(geom, mat); return mesh; }
function render() { stats.update(); cube.rotation.y += 0.01; cube.rotation.x += 0.01; cube.material.map.needsUpdate = true; requestAnimationFrame(render); webGLRenderer.render(scene, camera); } render();
function initStats() { var stats = new Stats(); stats.setMode(0); 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. 将视频输出作为纹理
Threejs也支持将HTML5的视频元素作为纹理输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
| <!-- chapter-10-10.html --> <!DOCTYPE html> <html> <head> <title>Video texture - non canvas</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>
<video id="video" style="display: none; position: absolute; left: 15px; top: 75px;" src="../assets/movies/Big_Buck_Bunny_small.ogv" controls="true" autoplay="true"></video>
<div id="WebGL-output"> </div>
<script type="text/javascript"> var texture;
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 video = document.getElementById('video'); texture = new THREE.VideoTexture(video);
var cube = createMesh(new THREE.BoxGeometry(20, 10, 10)); cube.position.y = 2; scene.add(cube);
camera.position.x = 00; camera.position.y = 1; camera.position.z = 28; camera.lookAt(new THREE.Vector3(0, 0, 0));
var ambiLight = new THREE.AmbientLight(0x141414); scene.add(ambiLight);
var light = new THREE.DirectionalLight(); light.position.set(0, 30, 20); scene.add(light); document.getElementById("WebGL-output").appendChild(webGLRenderer.domElement);
var controls = new function () { this.rotate = false; }; var gui = new dat.GUI(); gui.add(controls, "rotate");
function createMesh(geom) { var materialArray = []; materialArray.push(new THREE.MeshBasicMaterial({color: 0x0051ba})); materialArray.push(new THREE.MeshBasicMaterial({color: 0x0051ba})); materialArray.push(new THREE.MeshBasicMaterial({color: 0x0051ba})); materialArray.push(new THREE.MeshBasicMaterial({color: 0x0051ba})); materialArray.push(new THREE.MeshBasicMaterial({map: texture})); materialArray.push(new THREE.MeshBasicMaterial({color: 0xff51ba})); var faceMaterial = new THREE.MeshFaceMaterial(materialArray); var mesh = new THREE.Mesh(geom, faceMaterial); return mesh; }
function render() { stats.update(); if (controls.rotate) { cube.rotation.x += -0.01; cube.rotation.y += -0.01; cube.rotation.z += -0.01; } requestAnimationFrame(render); webGLRenderer.render(scene, camera); } render();
function initStats() { var stats = new Stats(); stats.setMode(0); 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>
|
