threejs纹理

个人博客地址: https://cxx001.gitee.io

前面我们介绍了各种可以用来覆盖对象的材质,也介绍了如何修改材质的颜色、关泽和不透明度,但是我们还没有详细介绍如何在材质中使用外部图片(也叫纹理).

将纹理应用于材质

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还提供了一些自定义加载器,以此来加载其它格式的纹理文件。

<!-- 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); // 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. 使用凹凸贴图创建褶皱

凹凸贴图用于为材质添加厚度,网格面看起来有起伏、深度的感觉,更加立体。我们使用材质的bumpMap属性和bumpScale属性。

但是注意像素的密集程度定义的是凹凸的高度,但是凹凸图只包含像素的相对高度,没有任何倾斜的方向信息。所以使用凹凸贴图所能表达的深度信息有限,要想实现更多的细节可以使用法向贴图。

<!-- 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); // 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. 使用法向贴图创建更加细致的凹凸和褶皱

法线贴图保存的不是高度信息,而是法向量的方向。简单来讲,使用法向量贴图只需要使用很少的顶点和面就可以创建出细节很丰富的模型。

<!-- 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;  // 指定外部纹理// 使用法向贴图最大问题是这个贴图的创建,需要使用如Blender或Photoshop这样的特殊工具。// 这些工具可以讲高分辨率的效果图或纹理作为输入来创建法向贴图。// Threejs同样提供了在运行期创建法向贴图的方法,THREE.ImageUtils对象的getNormalMap方法。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); // 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. 使用光照贴图创建阴影效果

前面我们也介绍了可以受光照影响实时产生阴影的材质。本节来看看另一种产生阴影的方法–光照贴图。它是预先渲染好的阴影,可以用它来模拟真实的阴影,效率更高,主要用于静态场景的阴影。

<!-- 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       // 指定纹理});// 要让光照贴图显示出来还需要指定它的UV映射,详情请参考:// http://stackoverflow.com/questions/15137695/three-js-lightmap-causes-an-error-webglrenderingcontext-gl-error-gl-invalid-op// https://github.com/mrdoob/three.js/pull/2372groundGeom.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); // 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. 使用环境贴图创建反光效果

计算环境的反光效果对CPU耗费是非常巨大的,而且通常会使用光线追踪算法。本节我们用另一种方式实现反光效果,通过创建一个对象所处环境的纹理来伪装反光,并将它应用到指定的对象上。

<!-- 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);//1. 创建天空盒,你处于盒子中间,看到的是盒子里的纹理图形,就像你身处场景中一样。// 创建盒子贴图纹理(6个面是场景的360全景图纹理)var textureCube = createCubeMap();textureCube.format = THREE.RGBFormat;// 使用Shader材质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);// 2. 将场景中的纹理反射到网格上(环境纹理获取两种方式:1.THREE.CubeCamera快照 2.直接使用上面创建的CubeMap纹理)// 创建一个获取环境贴图的cubeCamera,可以为场景中的所有渲染的物体创建快照。cubeCamera = new THREE.CubeCamera(0.1, 20000, 256);scene.add(cubeCamera);// 动态反射:将THREE.CubeCamera生成的环境贴图纹理指定到球体材质上var sphereGeometry = new THREE.SphereGeometry(4, 15, 15);var dynamicEnvMaterial = new THREE.MeshBasicMaterial({envMap: cubeCamera.renderTarget, side: THREE.DoubleSide}); // envMap环境贴图属性,通过cubeCamera的renderTarget对象获取生成的立方体纹理sphere = new THREE.Mesh(sphereGeometry, dynamicEnvMaterial);sphere.name = 'sphere';scene.add(sphere);// 静态反射:将加载全景图的6面生成的环境贴图纹理指定到圆锥材质上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';// 360全景图转换而来, 有在线工具可以转var urls = [path + 'posx' + format, path + 'negx' + format,  // 左右面纹理path + 'posy' + format, path + 'negy' + format,  // 上下面纹理path + 'posz' + format, path + 'negz' + format   // 前后面纹理];// 创建CubeMap对象var textureCube = THREE.ImageUtils.loadTextureCube(urls, new THREE.CubeReflectionMapping());return textureCube;/** threejs也可以直接使用全景图来创建CubeMap对象* var textureCube = THREE.ImageUtils.loadTexture("360-degrees.png", new THREE.UVMapping());*/}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. 使用高光贴图创建闪亮效果

通过高光贴图,你可以为材质指定一个闪亮的、色彩明快的贴图。

下面示例:海洋区域反光、明亮;陆地不反光、暗淡。细节效果需要自调参数。

<!-- 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); // 通常与specularMap一起使用,决定反光的颜色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); // 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>

在这里插入图片描述


纹理的高级用途

1. 自定义UV映射

UV映射是指指定纹理的哪部分显示在物体的表面上。默认是将整个纹理显示到物体表面上,一般不需要修改,如果要自定义UV映射,指定部分纹理显示到物体表面,通常都是借助建模工具完成(Blender)。

原理是修改几何体对应面的每个顶点的faceVertexUvs属性,这个属性有两个维度u/v,对应faceVertexUvs属性的x/y,取值范围0~1。

如修改几何体第一个面的UV映射:

// 第一个面(由3个顶点组成)
geom.faceVertexUvs[0][0][0].x = 0.5;  // 第一个面的第一个顶点的u值
geom.faceVertexUvs[0][0][0].y = 0.7;  // 第一个面的第一个顶点的v值
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映射我们一般通过专业工具完成,然后加载模型文件即可。

<!-- 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 () {// 第一组自定义UV模型this.loadCube1 = function () {var loader = new THREE.OBJLoader();// 加载自定义UV模型文件(修改的是面的顶点的uv值),决定纹理的哪些部分显示到表面上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);});};// 第二组自定义UV模型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); // 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>

loadCube1:

loadCube2:

在这里插入图片描述

2. 复制纹理

上面UV映射是指定纹理哪些部分显示到物体表面,Threejs会用选定的纹理包围整个物体表面。但是对于有些情形,你可能不想将纹理遍布整个面或整个几何体,而是让纹理进行重复(复制)。

通过material.map.wrapSmaterial.map.wrapT属性来设置纹理的包裹类型(wrapS是x轴方向纹理的行为,wrapT是y轴方向纹理的行为):

纹理包裹类型描述
THREE.RepeatWrapping允许纹理重复自己
THREE.ClampToEdgeWrapping默认值,纹理不会重复,用选择的纹理尽量包裹物体整个表面,只是在边缘的像素用重复纹理来填充剩下的空间
<!-- 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) {// 设置重复纹理在x/y轴方向多久重复1次,值为1则不重复,大于1开始重复,小于1纹理被放大,负数则会产生一个纹理镜像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); // 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. 在画布上绘制图案并作为纹理

前面我们都是加载本地静态图片作为纹理,其实Threejs也支持将HTML5的画布作为纹理使用。

下面示例我们将用Literally库(http://literallycanvas.com) ,来创建一个交互式的画布。在画布上你可以进行绘图,它可以作为纹理实时显示到网格对象上。

<!-- 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'}); // 使用Literally库的literallycanvas创建绘图工具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); // 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. 将视频输出作为纹理

Threejs也支持将HTML5的视频元素作为纹理输出。

<!-- 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); // 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>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/2426.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java中的可变参数

文章目录 可变参数概要可变参数例题可变参数的细节 可变参数概要 看到有道题&#xff0c;让你求出n个数的和&#xff0c;那么这个时候就可以使用Java中的可变参数来做。 格式&#xff1a; 数据类型...参数名 例如&#xff1a; int...args 其实可变参数的本质就是一个数组&…

使用 Jetpack Compose 实现 ViewPager2

在此博客中&#xff0c;我们将介绍如何在Jetpack Compose中实现ViewPager2的功能。我们将使用Accompanist库中的Pager库&#xff0c;这是由Google开发的一个用于Jetpack Compose的库。 首先&#xff0c;需要将Pager库添加到你的项目中&#xff1a; implementation androidx.co…

Jmeter远程服务模式运行时引用csv文件的路径配置

目录 前言&#xff1a; 问题 解决方法 前言&#xff1a; 在JMeter远程服务模式下运行时&#xff0c;你可以通过配置CSV文件的路径来引用该文件。CSV文件经常用于存储测试数据&#xff0c;可以在测试中进行参数化。 问题 在使用jmeter过程中&#xff0c;本机的内存等配置不…

【Linux】程序员的基本素养学习

这是目录 写在前面一、内存管理1、分段2、分页 二、线程管理三、静态库1、编译1.1、预处理1.2、编译1.3、汇编1.4、链接2、编译器3、目标文件**.text****.data****.bss****__attribute__** 3.1、符号3.2、兼容C语言 -- extern C4、链接 -- ld 写在前面 本文记录自己的学习生涯…

微服务架构之网关详解

前言 由于互联网的高速发展&#xff0c;网络数据请求数激增&#xff0c;使得服务器承受的压力越来越大。在早期的系统架构中&#xff0c;为减轻单台服务器的压力&#xff0c;通常使用 Load Balancer 来将网络流量平摊到多个服务器中。如今后端服务的种类和数量在不断变多&…

Linux--获取文件相关的各种时间:stat

stat是state英文的简写 语法&#xff1a; stat 文件名 功能&#xff1a; 获取文件的access&#xff08;访问时间&#xff09;、modify&#xff08;修改时间&#xff09;、change&#xff08;更改时间&#xff09; 文件的access时间&#xff08;访问时间&#xff09;指的是最…

npm报错(npm ERR! Unexpected token ‘.‘)

使用 nvm 将 node 切换到高版本后&#xff0c;运行 npm 相关的命令报的这个错 解决办法&#xff1a; 1、通过nvm list 命令查看当前都安装的node版本列表&#xff0c;依次通过 nvm uninstall [version] 命令将已经安装的 node 版本依次删除。 [version] 代表 node 版本号。 2…

音视频BUG学习

找Bug流程 1、首先看出现概率是偶现还是必现 2、如果是必现&#xff0c;则复现整个bug过程&#xff0c;看Bug是否出现 如果是偶现&#xff0c;则分析问题视频 问题一 【欧立】【远程抓拍】安卓-远程抓拍的视频&#xff0c;下载到手机本地相册&#xff0c;声音慢放 一、额外知…

Android逆向解析加壳与脱壳技术

加壳 加壳是指在 APK 文件中插入额外的代码或数据&#xff0c;使得原始代码难以被分析和反编译。通常加壳是为了保护软件的知识产权或者防止逆向工程。下面是 Android 加壳的一般流程&#xff1a; 选择加壳工具&#xff1a;选择合适的加壳工具进行加壳&#xff0c;比如市面上…

[计算机入门]了解键盘

2.1 了解键盘 键盘一般可以根据按键的功能进行分区&#xff0c;一般分为&#xff1a;主键盘区、小键盘区、控制键区、功能键区、指示灯区。下面介绍键盘的各个分区按键及功能。 2.1.1 主键盘区 主键盘区又叫打字键盘区或字符键区&#xff0c;具有标准英文打字机键盘的格式。…

基于Dlib的疲劳检测系统

需要源码的朋友可以私信我 基于Dlib的疲劳检测系统 1、设计背景及要求2、系统分析3、系统设计3.1功能结构图3.2基于EAR、MAR和HPE算法的疲劳检测3.2.1基于EAR算法的眨眼检测3.2.2基于MAR算法的哈欠检测3.3.3基于HPE算法的点头检测 4、系统实现与调试4.1初步实现4.2具体实现过程…

用雪花 id 和 uuid 还是自增id做 MySQL 主键

MySQL中设计表的时候&#xff0c;MySQL官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一)&#xff0c;而是推荐连续自增的主键id&#xff0c;官方的推荐是auto_increment&#xff0c;那么为什么不建议采用uuid&#xff0c;使用uuid究竟有什么坏处&#xff1f; MySQ…