纹理贴图是Threejs一个很重要的内容,游戏、产品720展示、物联网3D可视化等项目程序员加载模型的同时需要处理纹理贴图。
刚开始学习的学习的时候,也没必要去掌握Threejs所有种类纹理贴图的细节,关键是建立一个整体概念,用到的时候,知道需要查找那一节课,但是学习的时候,还是需要都打开相应的案例源码体验一边,跟着文字介绍,调试一下参数,体验感受一下。在以后的开发中遇到没有学过的纹理贴图,知道如何查找文档或找到学习方向。
创建纹理贴图
通过纹理贴图加载器TextureLoader的load()方法加载一张图片可以返回一个纹理对象Texture,纹理对象Texture可以作为模型材质颜色贴图.map属性的值。
材质的颜色贴图属性.map设置后,模型会从纹理贴图上采集像素值,这时候一般来说不需要再设置材质颜色.color。.map贴图之所以称之为颜色贴图就是因为网格模型会获得颜色贴图的颜色值RGB。
- // 纹理贴图映射到一个矩形平面上
- var geometry = new THREE.PlaneGeometry(204, 102); //矩形平面
- // TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
- var textureLoader = new THREE.TextureLoader();
- // 执行load方法,加载纹理贴图成功后,返回一个纹理对象Texture
- textureLoader.load('/img/earth.jpg', function(texture) {
- var material = new THREE.MeshLambertMaterial({
- // color: 0x0000ff,
- // 设置颜色纹理贴图:Texture对象作为材质map属性的属性值
- map: texture,//设置颜色贴图属性值
- }); //材质对象Material
- var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
- scene.add(mesh); //网格模型添加到场景中
- //纹理贴图加载成功后,调用渲染函数执行渲染操作
- // render();
- })
不同的几何体有不同的UV坐标来设置贴图和模型的映射规律,你可以尝试把颜色纹理贴图映射到不同的几何体上查看渲染效果。
- var geometry = new THREE.BoxGeometry(100, 100, 100); //立方体
- var geometry = new THREE.SphereGeometry(60, 25, 25); //球体
纹理对象Texture
如果你想进一步了解.map的属性值Texture可以阅读下面的代码。
通过图片加载器ImageLoader可以加载一张图片,所谓纹理对象Texture简单地说就是,纹理对象Texture的.image属性值是一张图片。
- // 图片加载器
- var ImageLoader = new THREE.ImageLoader();
- // load方法回调函数,按照路径加载图片,返回一个html的元素img对象
- ImageLoader.load('/img/earth.jpg', function(img) {
- // image对象作为参数,创建一个纹理对象Texture
- var texture = new THREE.Texture(img);
- // 下次使用纹理时触发更新
- texture.needsUpdate = true;
- var material = new THREE.MeshLambertMaterial({
- map: texture, //设置纹理贴图
- });
- var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
- scene.add(mesh); //网格模型添加到场景中
- });
总结
几何体顶点纹理坐标UV
在之前的课程中对Threejs几何体Geometry和BufferGeometry的顶点概念做过比较多的介绍,讲解过顶点位置坐标数据、顶点颜色数据、顶点法线方向向量数据,不过顶点的UV数据没有去讲解,主要是几何体顶点的纹理坐标数据和纹理贴图的映射有关系,所以放在了本章节去讲解。
纹理UV坐标
纹理坐标含义就是字面意思,一张纹理贴图图像的坐标,选择一张图片,比如以图片左下角为坐标原点,右上角为坐标(1.0,1.0),图片上所有位置纵横坐标都介于0.0~1.0之间。
映射
纹理UV坐标和顶点位置坐标是一一对应关系,这也就是为什么一张图片可以映射到一个模型的表面,只要把图片的每个纹理坐标和模型的顶点位置建立一对一的关系,就可以实现图像到模型的映射。
矩形贴图和球面的映射图:
两组UV坐标
几何体有两组UV坐标,第一组组用于.map、.normalMap、.specularMap等贴图的映射,第二组用于阴影贴图.lightMap的映射,这里不过过多阐述,本章除了即将讲述的“光照贴图添加阴影”小节,用到的是第二组UV坐标,其它的章节内部程序用到的都是第一组UV坐标。
修改纹理坐标
你可以尝试修改上节课代码中几何体的纹理坐标,然后体会纹理坐标的作用。
几何体表面所有位置全部对应贴图(0.4,0.4)坐标位置的像素值,这样话网格模型不会显示完整的地图,而是显示采样点纹理坐标(0.4,0.4)对应的RGB值。
- //矩形平面,细分数默认1,即2个三角形拼接成一个矩形
- var geometry = new THREE.PlaneGeometry(204, 102);
- ...
- /**
- * 遍历uv坐标
- */
- geometry.faceVertexUvs[0].forEach(elem => {
- elem.forEach(Vector2 => {
- // 所有的UV坐标全部设置为一个值
- Vector2.set(0.4,0.4);
- });
- });
原来几何体平面默认是两个三角形构成,把细分数设置为4,三角形数量变为16个。
- // 矩形平面 设置细分数4,4
- var geometry = new THREE.PlaneGeometry(204, 102, 4, 4);
- ...
- /**
- * 局部三角面显示完整纹理贴图
- */
- var t0 = new THREE.Vector2(0, 1); //图片左下角
- var t1 = new THREE.Vector2(0, 0); //图片右下角
- var t2 = new THREE.Vector2(1, 0); //图片右上角
- var t3 = new THREE.Vector2(1, 1); //图片左上角
- var uv1 = [t0, t1, t3]; //选中图片一个三角区域像素——用于映射到一个三角面
- var uv2 = [t1, t2, t3]; //选中图片一个三角区域像素——用于映射到一个三角面
- // 设置第五、第六个三角形面对应的纹理坐标
- geometry.faceVertexUvs[0][4] = uv1
- geometry.faceVertexUvs[0][5] = uv2
Geometry自定义顶点UV坐标
一般Threejs的球体、圆柱等几何体创建的时候,都会通过特定算法自动生成几何体的UV坐标。
下面代码通过几何体Geometry自定义了一个由两个三角形组成的矩形几何体,并且通过几何体的.faceVertexUvs[0]属性设置了每个顶点对应的第一组UV坐标。
- var geometry = new THREE.Geometry(); //创建一个空几何体对象
- /**顶点坐标(纹理映射位置)*/
- var p1 = new THREE.Vector3(0,0,0); //顶点1坐标
- var p2 = new THREE.Vector3(160,0,0); //顶点2坐标
- var p3 = new THREE.Vector3(160,80,0); //顶点3坐标
- var p4 = new THREE.Vector3(0,80,0); //顶点4坐标
- geometry.vertices.push(p1,p2,p3,p4); //顶点坐标添加到geometry对象
- /** 三角面1、三角面2*/
- var normal = new THREE.Vector3( 0, 0, 1 ); //三角面法向量
- var face0 = new THREE.Face3( 0, 1, 2, normal); //三角面1
- var face1 = new THREE.Face3( 0, 2, 3, normal); //三角面2
- geometry.faces.push( face0,face1 ); //三角面1、2添加到几何体
- /**纹理坐标*/
- var t0 = new THREE.Vector2(0,0);//图片左下角
- var t1 = new THREE.Vector2(1,0);//图片右下角
- var t2 = new THREE.Vector2(1,1);//图片右上角
- var t3 = new THREE.Vector2(0,1);//图片左上角
- uv1 = [t0,t1,t2];//选中图片一个三角区域像素——映射到三角面1
- uv2 = [t0,t2,t3];//选中图片一个三角区域像素——映射到三角面2
- geometry.faceVertexUvs[0].push(uv1,uv2);//纹理坐标传递给纹理三角面属性
BufferGeometry自定义顶点UV坐标
下面代码通过几何体BufferGeometry自定义了一个由两个三角形组成的矩形几何体,并且通过几何体的.attributes.uv属性设置了每个顶点对应的第一组UV坐标。
- var geometry = new THREE.BufferGeometry(); //声明一个空几何体对象
- //类型数组创建顶点位置position数据
- var vertices = new Float32Array([
- 0, 0, 0, //顶点1坐标
- 80, 0, 0, //顶点2坐标
- 80, 80, 0, //顶点3坐标
- 0, 80, 0, //顶点4坐标
- ]);
- // 创建属性缓冲区对象
- var attribue = new THREE.BufferAttribute(vertices, 3); //3个为一组
- // 设置几何体attributes属性的位置position属性
- geometry.attributes.position = attribue
- var normals = new Float32Array([
- 0, 0, 1, //顶点1法向量
- 0, 0, 1, //顶点2法向量
- 0, 0, 1, //顶点3法向量
- 0, 0, 1, //顶点4法向量
- ]);
- // 设置几何体attributes属性的位置normal属性
- geometry.attributes.normal = new THREE.BufferAttribute(normals, 3); //3个为一组,表示一个顶点的xyz坐标
- // Uint16Array类型数组创建顶点索引数据
- var indexes = new Uint16Array([
- 0, 1, 2, 0, 2, 3,
- ])
- // 索引数据赋值给几何体的index属性
- geometry.index = new THREE.BufferAttribute(indexes, 1); //1个为一组
- /**纹理坐标*/
- var uvs = new Float32Array([
- 0,0, //图片左下角
- 1,0, //图片右下角
- 1,1, //图片右上角
- 0,1, //图片左上角
- ]);
- // 设置几何体attributes属性的位置normal属性
- geometry.attributes.uv = new THREE.BufferAttribute(uvs, 2); //2个为一组,表示一个顶点的纹理坐标
加载一个包含UV坐标的模型文件
下面案例代码是通过Threejs加载一个包含UV坐标的外部三维模型文件,加载成功后,给模型设置一张贴图:
- // 创建一个加载threejs格式JSON文件的加载器
- var loader = new THREE.ObjectLoader();
- // TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
- var textureLoader = new THREE.TextureLoader();
- loader.load('model.json',function (obj) {
- console.log(obj);
- scene.add(obj);//加载返回的对象插入场景中
- // 执行load方法,加载纹理贴图成功后,返回一个纹理对象Texture
- textureLoader.load('Earth.png', function(texture) {
- // 设置球体网格模型材质的map属性
- obj.children[0].material.map = texture;
- // 告诉threejs渲染器系统,材质对象的map属性已更新
- obj.children[0].material.needsUpdate=true;
- })
- })
数组材质、材质索引.materialIndex
本节为大家讲解数组材质和三角形面Face3的材质索引属性.materialIndex。
数组材质
你可以测试把数组材质作为几何体的纹理贴图,所谓数组材质就是多个材质对象构成一个数组作为模型对象的材质。
- var geometry = new THREE.BoxGeometry(100, 100, 100); //立方体
- // var geometry = new THREE.PlaneGeometry(204, 102, 4, 4); //矩形平面
- // var geometry = new THREE.SphereGeometry(60, 25, 25); //球体
- // var geometry = new THREE.CylinderGeometry(60, 60, 25,25); //圆柱
- //
- // 材质对象1
- var material_1 = new THREE.MeshPhongMaterial({
- color: 0xffff3f
- })
- var textureLoader = new THREE.TextureLoader(); // 纹理加载器
- var texture = textureLoader.load('/img/earth.jpg'); // 加载图片,返回Texture对象
- // 材质对象2
- var material_2 = new THREE.MeshLambertMaterial({
- map: texture, // 设置纹理贴图
- // wireframe:true,
- });
- // 设置材质数组
- var materialArr = [material_2, material_2, material_1, material_2, material_1, material_1];
- // 设置数组材质对象作为网格模型材质参数
- var mesh = new THREE.Mesh(geometry, materialArr); //网格模型对象Mesh
- scene.add(mesh); //网格模型添加到场景中
你会发现,在这个立方体中,只有2面(位置相对)是由世界地图构成的,其他面是由单纯的黄色高光材质构成。
材质索引属性
三角形面Face3可以设置材质索引属性.materialIndex,Face3.materialIndex指向数组材质中的材质对象,表达的意思是数组材质中哪一个元素用于渲染该三角形面Face3。
通过材质属性Face3.materialIndex的介绍,你应该可以明白上面案例代码中数组材质的渲染规律。
- var geometry = new THREE.BoxGeometry(100, 100, 100); //立方体
- // 你可以测试BoxGeometry、PlaneGeometry、CylinderGeometry三角形面的材质索引
- // 查看face3对象的materialIndex属性
- console.log(geometry.faces);
- geometry.faces.forEach(elem => {
- console.log(elem.materialIndex);
- });
自定义Face3的材质索引案例(部分版本three.js不支持):
- var geometry = new THREE.PlaneGeometry(204, 102, 4, 4); //矩形平面
- // 材质对象1
- var material1 = new THREE.MeshPhongMaterial({
- color: 0xffff3f,
- // wireframe:true,
- })
- // 材质对象2
- var material2 = new THREE.MeshPhongMaterial({
- color: 0x0000ff,
- // wireframe:true,
- }); //材质对象Material
- // 数组材质
- var materialArr = [material1, material2];
- // 设置几何体的材质索引(对于PlaneGeometry而言所有Face3的材质索引默认0)
- geometry.faces[4].materialIndex = 1;
- geometry.faces[5].materialIndex = 1;
- var mesh = new THREE.Mesh(geometry, materialArr); //网格模型对象Mesh
总结:
纹理对象Texture阵列、偏移、旋转
前面给大家提到过纹理对象Texture,简单的说纹理对象Texture就是包含一张图片的对象,纹理对象Texture所包含的图片就是.image属性,除此外,纹理对象Texture还提供了一些实际开发中经常会用到的属性和方法。
阵列
纹理贴图阵列映射。
- var texture = textureLoader.load('太阳能板.png');
- // 设置阵列模式 默认ClampToEdgeWrapping RepeatWrapping:阵列 镜像阵列:MirroredRepeatWrapping
- texture.wrapS = THREE.RepeatWrapping;
- texture.wrapT = THREE.RepeatWrapping;
- // uv两个方向纹理重复数量
- texture.repeat.set(4, 2);
偏移
不设置阵列纹理贴图,只设置偏移:
- var textureLoader = new THREE.TextureLoader();
- var texture = textureLoader.load('太阳能板2.png');// 加载纹理贴图
- // 不设置重复 偏移范围-1~1
- texture.offset = new THREE.Vector2(0.3, 0.1)
阵列纹理贴图的同时,进行偏移设置:
- // 设置阵列模式
- texture.wrapS = THREE.RepeatWrapping;
- texture.wrapT = THREE.RepeatWrapping;
- // uv两个方向纹理重复数量
- texture.repeat.set(4, 2);
- // 偏移效果
- texture.offset = new THREE.Vector2(0.5, 0.5)
纹理旋转
- var texture = textureLoader.load('太阳能板.png'); // 加载纹理贴图
- // 设置纹理旋转角度
- texture.rotation = Math.PI/4;
- // 设置纹理的旋转中心,默认(0,0)
- texture.center.set(0.5,0.5);
- console.log(texture.matrix);
案例:草地效果
提供一张宽高尺寸比较小的草地贴图,然后通过该贴图设置一片范围比较广的草地效果,这时候阵列贴图是比较好的选择。
- /**
- * 创建一个地面
- */
- var geometry = new THREE.PlaneGeometry(1000, 1000); //矩形平面
- // 加载树纹理贴图
- var texture = new THREE.TextureLoader().load("grass.jpg");
- // 设置阵列
- texture.wrapS = THREE.RepeatWrapping;
- texture.wrapT = THREE.RepeatWrapping;
- // uv两个方向纹理重复数量
- texture.repeat.set(10, 10);
- var material = new THREE.MeshLambertMaterial({
- map: texture,
- side:THREE.DoubleSide
- });
- var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
- scene.add(mesh); //网格模型添加到场景中
- mesh.rotateX(-Math.PI / 2);
纹理动画
纹理动画比较简单,必须要在渲染函数中render()一直执行texture.offset.x -= 0.06动态改变纹理对象Texture的偏移属性.offset就可以。
- // 渲染函数
- function render() {
- renderer.render(scene, camera); //执行渲染操作
- requestAnimationFrame(render);
- // 使用加减法可以设置不同的运动方向
- // 设置纹理偏移
- texture.offset.x -= 0.06
- }
- render();
- /**
- * 创建一个设置重复纹理的管道
- */
- var curve = new THREE.CatmullRomCurve3([
- new THREE.Vector3(-80, -40, 0),
- new THREE.Vector3(-70, 40, 0),
- new THREE.Vector3(70, 40, 0),
- new THREE.Vector3(80, -40, 0)
- ]);
- var tubeGeometry = new THREE.TubeGeometry(curve, 100, 0.6, 50, false);
- var textureLoader = new THREE.TextureLoader();
- var texture = textureLoader.load('/img/threejs/run.jpg');
- // 设置阵列模式为 RepeatWrapping
- texture.wrapS = THREE.RepeatWrapping
- texture.wrapT=THREE.RepeatWrapping
- // 设置x方向的偏移(沿着管道路径方向),y方向默认1
- //等价texture.repeat= new THREE.Vector2(20,1)
- texture.repeat.x = 20;
- var tubeMaterial = new THREE.MeshPhongMaterial({
- map: texture,
- transparent: true,
- });
总结
canvas画布、视频作为纹理贴图
通过Three.js两个类CanvasTexture和VideoTexture可以分别实现把Canvas画布、视频作为纹理贴图使用。
Canvas画布作为Three.js纹理贴图(CanvasTexture)
Canvas画布可以通过2D API绘制各种各样的几何形状,可以通过Canvas绘制一个轮廓后然后作为Three.js网格模型、精灵模型等模型对象的纹理贴图。
下面是一段与WebGL或者说Threejs无关的Canvas代码,你可以复制到.html文件中打开查看一下,通过下面代码绘制的Canvas画布可以作为Three.js模型对象的纹理贴图。
- <body>
- <script type="text/javascript">
- var canvas = document.createElement("canvas");
- canvas.width = 512;
- canvas.height = 128;
- var c = canvas.getContext('2d');
- // 矩形区域填充背景
- c.fillStyle = "#ff00ff";
- c.fillRect(0, 0, 512, 128);
- c.beginPath();
- // 文字
- c.beginPath();
- c.translate(256,64);
- c.fillStyle = "#000000"; //文本填充颜色
- c.font = "bold 48px 宋体"; //字体样式设置
- c.textBaseline = "middle"; //文本与fillText定义的纵坐标
- c.textAlign = "center"; //文本居中(以fillText定义的横坐标)
- c.fillText("w3xue.com", 0, 0);
- document.body.appendChild(canvas)
- </script>
- </body>
把绘制了几何图案的canvas元素作为构造函数CanvasTexture的参数创建一个canvas纹理贴图。
- /**
- * 创建一个canvas对象,并绘制一些轮廓
- */
- var canvas = document.createElement("canvas");
- // 上面canvas代码省略
- ...
- c.fillText("w3xue.com", 0, 0);
- // canvas画布对象作为CanvasTexture的参数重建一个纹理对象
- // canvas画布可以理解为一张图片
- var texture = new THREE.CanvasTexture(canvas);
- //打印纹理对象的image属性
- // console.log(texture.image);
- //矩形平面
- var geometry = new THREE.PlaneGeometry(128, 32);
- var material = new THREE.MeshPhongMaterial({
- map: texture, // 设置纹理贴图
- });
- // 创建一个矩形平面网模型,Canvas画布作为矩形网格模型的纹理贴图
- var mesh = new THREE.Mesh(geometry, material);
Canvas画布加载图片
如果作为纹理贴图使用的Canvas画布加载了图片,注意在图片加载完成的时候更新Threejs相关模型的纹理贴图。如果不更新纹理,你会发现canvas画布上的图片无法现在是Threejs模型的纹理上。
- var canvas = document.createElement("canvas");
- ...
- var ctx = canvas.getContext('2d');
- var Image = new Image();
- Image.src = "./贴图.jpg";
- Image.onload = function() {
- var bg = ctx.createPattern(Image, "no-repeat");
- ...
- // 注意图片加载完成执行canvas相关方法后,要更新一下纹理
- texture.needsUpdate = true;
- }
视频作为Three.js纹理贴图(VideoTexture)
视频本质上就是一帧帧图片流构成,把视频作为Threejs模型的纹理贴图使用,就是从视频中提取一帧一帧的图片作为模型的纹理贴图,然后不停的更新的纹理贴图就可以产生视频播放的效果。
下面是一段视频作为纹理贴图的代码。
- // 创建video对象
- let video = document.createElement('video');
- video.src = "1086x716.mp4"; // 设置视频地址
- video.autoplay = "autoplay"; //要设置播放
- video.loop="loop"; //设置循环播放模式
- // video对象作为VideoTexture参数创建纹理对象
- var texture = new THREE.VideoTexture(video)
- var geometry = new THREE.PlaneGeometry(108, 71); //矩形平面
- var material = new THREE.MeshPhongMaterial({
- map: texture, // 设置纹理贴图
- }); //材质对象Material
- var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
- scene.add(mesh); //网格模型添加到场景中
VideoTexture.js封装了一个update函数,Threejs每次执行渲染方法进行渲染场景中的时候,都会执行VideoTexture封装的update函数,执行update函数中代码this.needsUpdate = true;读取视频流最新一帧图片来更新Threejs模型纹理贴图。
凹凸贴图bumpMap和法线贴图.normalMap
一个复杂的曲面模型,往往模型顶点数量比较多,模型文件比较大,为了降低模型文件大小,法线贴图.normalMap算法自然就产生了,复杂的三维模型3D美术可以通过减面操作把精模简化为简模,然后把精模表面的复杂几何信息映射到法线贴图.normalMap上。
法线贴图
下面代码在没有设置法线贴图之前就是一个立方体网格模型Mesh,然后把一个携带圆形凹坑信息的法线贴图3_256.jpg设置到立方体网格模型的面上,你可以看到面上多个凹陷效果。你可以测试源码案例中法线贴图目录下的其它法线贴图文件,查看渲染效果。
- // TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
- var textureLoader = new THREE.TextureLoader();
- // 加载法线贴图
- var textureNormal = textureLoader.load('/img/threejs/nm.jpg');
- var material = new THREE.MeshPhongMaterial({
- color: 0xff0000,
- normalMap: textureNormal, //法线贴图
- //设置深浅程度,默认值(1,1)。
- normalScale: new THREE.Vector2(3, 3),
- }); //材质对象Material
- var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
- scene.add(mesh); //网格模型添加到场景中
法线贴图:地球案例
地球表面法线贴图记录了地面表面的几何信息。
你可以对比两个地球的渲染效果,一个设置法线贴图,一个不设置法线贴图。
- var geometry = new THREE.SphereGeometry(100, 25, 25); //球体
- // TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
- var textureLoader = new THREE.TextureLoader();
- // 加载纹理贴图
- var texture = textureLoader.load('/img/threejs/earth.jpg');
- // 加载法线贴图
- var textureNormal = textureLoader.load('/img/threejs/threejs54EarthNormal.png');
- var material = new THREE.MeshPhongMaterial({
- map: texture, // 普通颜色纹理贴图
- normalMap: textureNormal, //法线贴图,注释这行和下面的一行,就可以查看没有法线时的效果
- normalScale: new THREE.Vector2(1.2, 1.2), //设置深浅程度,默认值(1,1)。
- }); //材质对象Material
凹凸贴图
凹凸贴图和法线贴图功能相似,只是没有法线贴图表达的几何体表面信息更丰富。凹凸贴图是用图片像素的灰度值表示几何表面的高低深度,如果模型定义了法线贴图,就没有必要再使用凹凸贴图。
你可以对比两面墙一个使用凹凸贴图一个不使用凹凸贴图的视觉效果。
- var textureLoader = new THREE.TextureLoader();
- // 加载颜色纹理贴图
- var texture = textureLoader.load('./凹凸贴图/diffuse.jpg');
- // 加载凹凸贴图
- var textureBump = textureLoader.load('./凹凸贴图/bump.jpg');
- var material = new THREE.MeshPhongMaterial({
- map: texture,// 普通纹理贴图
- bumpMap:textureBump,//凹凸贴图
- bumpScale:3,//设置凹凸高度,默认值1。
- side:THREE.DoubleSide
- }); //材质对象Material
光照贴图添加阴影(·lightMap)
在三维场景中有时候需要设置模型的阴影,也就是阴影贴图或者说光照贴图·lightMap,一般Threejs加载外部模型的光照贴图·lightMap,三维模型加载器可以自动设置,不需要程序员通过代码去设置,不过为了让大家更好理解光照贴图·lightMap,这里就通过Three.js代码设置场景模型的阴影贴图·lightMap。
- //创建一个平面几何体作为投影面
- var planeGeometry = new THREE.PlaneGeometry(300, 200);
- planeGeometry.faceVertexUvs[1] = planeGeometry.faceVertexUvs[0];
- var textureLoader = new THREE.TextureLoader();
- // 加载光照贴图
- var textureLight = textureLoader.load('shadow.png');
- var planeMaterial = new THREE.MeshLambertMaterial({
- color: 0x999999,
- lightMap:textureLight,// 设置光照贴图
- // lightMapIntensity:0.5,//烘培光照的强度. 默认 1.
- });
- var planeMesh = new THREE.Mesh(planeGeometry, planeMaterial); //网格模型对象Mesh
“光源和外部光2”章节设置模型的阴影是通过实时计算得到的,而光照贴图·lightMap是3D美术员渲染好提供给程序员。这两种方式相比较通过贴图的方式更为节约资源,提高渲染性功能。其原理等同贴图,其工作量转给了3D美术员。
Geometry属性.faceVertexUvs
一般几何体有两套UV坐标,对于Geometry类型几何体而言
Geometry.faceVertexUvs[0]包含的纹理坐标用于颜色贴图map、法线贴图normalMap等,Geometry.faceVertexUvs[1]包含的第二套纹理贴图用于光照阴影贴图
一般通过Threejs几何体API创建的几何体默认只有一组纹理坐标Geometry.faceVertexUvs[0],所以为了设置光照阴影贴图,需要给另一组纹理坐标赋值Geometry.faceVertexUvs[1]
= Geometry.faceVertexUvs[0];
BufferGeometry属性.uv和.uv2
一般通过Threejs加载外部模型,解析三维模型数据得到的几何体类型是缓冲类型几何体BufferGeometry,对于BufferGeometry而言两套纹理坐标分别通过.uv和.uv2属性表示。
- geometry.attributes.uv2 = geometry.attributes.uv;
高光贴图(.specularMap)
高光网格材质MeshPhongMaterial具有高光属性.specular,如果一个网格模型Mesh都是相同的材质并且表面粗糙度相同,或者说网格模型外表面所有不同区域的镜面反射能力相同,可以直接设置材质的高光属性.specular。如果一个网格模型表示一个人,那么人的不同部位高光程度是不同的,不可能直接通过.specular属性来描述,在这种情况通过高光贴图.specularMap的RGB值来描述不同区域镜面反射的能力,.specularMap和颜色贴图.Map一样和通过UV坐标映射到模型表面。高光贴图.specularMap不同区域像素值不同,表示网格模型不同区域的高光值不同。
下面是一个地球的案例,地球地面和海面的高光值是不同的,海面更为高亮,你可以测试使用高光贴图和不使用高光贴图的渲染效果有什么不同。
- var geometry = new THREE.SphereGeometry(100, 25, 25); //球体
- // TextureLoader创建一个纹理加载器对象,可以加载图片作为几何体纹理
- var textureLoader = new THREE.TextureLoader();
- // 加载纹理贴图
- var texture = textureLoader.load('/img/threejs/earth.jpg');
- // 加载法线贴图
- var textureNormal = textureLoader.load('/img/threejs/threejs54EarthNormal.png');
- var material = new THREE.MeshPhongMaterial({
- map: texture, // 普通颜色纹理贴图
- specularMap: textureNormal, //高光贴图,注释本行,则陆地反光效果会和海洋一样
- shininess: 30,//高光部分的亮度,默认30
- }); //材质对象Material
- var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
- scene.add(mesh); //网格模型添加到场景中
高光贴图属性.specularMap和高光属性.specular是对应的,也就是说只有高光网格材质对象MeshPhongMaterial才具备高光贴图属性.specularMap。
环境贴图(.envMap)
Three.js环境贴图.envMap字面意思就是三维模型周边环境,比如你渲染一个立方体,立方体放在一个屋子里面,屋子里面的周边环境肯定影响立方体的渲染效果,目的是为了渲染该立方体而不是立方体周围环境,为了更方便所以没必要创建立方体周边环境所有物体的网格模型,可以通过图片来表达立方体周边的环境。
创建一个立方体盒子作为天空盒使用,然后把一个环境中上下左右前后六张视图图片作为立方体盒子的纹理贴图使用。
加环境贴图的6张纹理贴图,可以通过CubeTextureLoader类趋势线。
- var geometry = new THREE.BoxGeometry(100, 100, 100); //立方体
- var loader = new THREE.CubeTextureLoader();
- // 所有贴图在同一目录下,可以使用该方法设置共用路径
- loader.setPath('环境贴图/');
- // 立方体纹理加载器返回立方体纹理对象CubeTexture
- var CubeTexture = loader.load(['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg']);
- //材质对象Material
- var material = new THREE.MeshPhongMaterial({
- //网格模型设置颜色,网格模型颜色和环境贴图会进行融合计算
- // color:0xff0000,
- envMap: CubeTexture, //设置环境贴图
- side:THREE.DoubleSide
- // 环境贴图反射率 控制环境贴图对被渲染三维模型影响程度
- // reflectivity: 0.1,
- });
- console.log(CubeTexture.image);
- var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
- scene.add(mesh); //网格模型添加到场景中
高光网格材质MeshPhongMaterial和物理PBR材质MeshStandardMaterial通常会使用环境贴图.envMap来实现更好的渲染效果。一个材质对应的是普通次时代模型,一个材质对应的是PBR模型。
数据纹理对象DataTexture
Three.js数据纹理对象DataTexture简单地说就是通过程序创建纹理贴图的每一个像素值。
程序生成一张图片的RGB值
- var geometry = new THREE.PlaneGeometry(128, 128); //矩形平面
- /**
- * 创建纹理对象的像素数据
- */
- var width = 32; //纹理宽度
- var height = 32; //纹理高度
- var size = width * height; //像素大小
- var data = new Uint8Array(size * 3); //size*3:像素在缓冲区占用空间
- for (let i = 0; i < size * 3; i += 3) {
- // 随机设置RGB分量的值
- data[i] = 255 * Math.random()
- data[i + 1] = 255 * Math.random()
- data[i + 2] = 255 * Math.random()
- }
- // 创建数据文理对象 RGB格式:THREE.RGBFormat
- var texture = new THREE.DataTexture(data, width, height, THREE.RGBFormat);
- texture.needsUpdate = true; //纹理更新
- //打印纹理对象的image属性
- // console.log(texture.image);
- var material = new THREE.MeshPhongMaterial({
- map: texture, // 设置纹理贴图
- side:THREE.DoubleSide
- }); //材质对象Material
- var mesh = new THREE.Mesh(geometry, material);
- scene.add(mesh); //网格模型添加到场景中
程序生成一张图片的RGBA值:
- var geometry = new THREE.PlaneGeometry(128, 128); //矩形平面
- /**
- * 创建纹理对象的像素数据
- */
- var width = 32; //纹理宽度
- var height = 32; //纹理高度
- var size = width * height; //像素大小
- var data = new Uint8Array(size * 4); //size*4:像素在缓冲区占用空间
- for (let i = 0; i < size * 4; i += 4) {
- // 随机设置RGB分量的值
- data[i] = 255 * Math.random()
- data[i + 1] = 255 * Math.random()
- data[i + 2] = 255 * Math.random()
- // 设置透明度分量A
- data[i + 3] = 255 * 0.5
- }
- // 创建数据文理对象 RGBA格式:THREE.RGBAFormat
- var texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
- texture.needsUpdate = true; //纹理更新
- //打印纹理对象的image属性
- console.log(texture.image);
- var material = new THREE.MeshPhongMaterial({
- map: texture, // 设置纹理贴图
- transparent:true,//允许透明设置
- });
- var mesh = new THREE.Mesh(geometry, material);
图片格式
像素值包含RGB三个分量的图片格式有.jpg、.BMP等格式,通过WebGL原生API加载解析这些类型格式的图片需要设置gl.RGB,对于Threejs而言对WebGL进行封装了,gl.RGB对应的设置是THREE.RGBFormat。
像素值包含RGBA四个分量的图片格式有.PNG等格式,通过WebGL原生API加载解析这些类型格式的图片需要设置gl.RGBA,对于Threejs而言对WebGL进行封装了,gl.RGBA对应的设置是THREE.RGBAFormat。
转载本站内容时,请务必注明来自W3xue,违者必究。
Copyright © 2021 .长沙麦涛网络科技有限公司 All rights reserved.
湘ICP备20015126号-2
联系我们