纹理贴图

把下面一张纹理贴图映射到平面、立方体、球体等几何体上。

three.js纹理贴图

  先利用three.js的平面几何体构造函数创建一个矩形网格模型,然后加载一张图片,把图片映射到矩形平面网格模型上。

纹理贴图步骤

  1. 创建一个可以用于纹理映射的几何体Geometry对象
  2. 加载一张图片作为纹理贴图(创建纹理对象)
  3. 给材质对象material的map属性赋值纹理对象
    /**
     * 纹理贴图网格模型
     */
    var geometry = new THREE.PlaneGeometry(60,50);//矩形平面
    var texture = THREE.ImageUtils.loadTexture("glb.jpg");//加载纹理贴图
    var material=new THREE.MeshLambertMaterial({//贴图通过材质添加给几何体
        map:texture,//给纹理属性map赋值
        side:THREE.DoubleSide,//两面可见
    });//材质对象
    var mesh = new THREE.Mesh(geometry,material);//纹理贴图网格模型对象
    scene.add(mesh);//网格模型添加到场景中

  执行代码texture =THREE.ImageUtils.loadTexture("glb.jpg")返回一个纹理对象,它的作用是加载纹理贴图,参数是字符串格式的纹理贴图路径或者说纹理贴图URL。 返回的纹理对象texture赋值给材质对象的纹理贴图属性map( map:texture),多数网格模型材质都具有纹理贴图属性map。

  测试上面的代码你可以发现,一张图片的四个顶点会默认与平面几何体的四个顶点对应起来,在映射的过程中,矩形网格模型的尺寸会影响纹理贴图进行缩放, 比如矩形网格模型的长宽比例大于图片像素的长宽比例,图片的显示效果就会在长度方向上拉伸,如果矩形网格模型的长宽大于图片,图片的显示效果就会整体放大。

不同几何体默认的映射方式

  three.js引擎提供了常见几何体的构造函数,使用这些构造函数创建几何体的时候,几何体除了顶点数据,还包含了纹理映射的相关数据,比如纹理坐标。 这里不深入探讨three.js如何封装WebGL纹理映射的程序,先简单展示集中几何体默认映射效果,使用下面的几何体对象创建代码代替上面的平面几何体对象创建代码即可。

球体几何体映射(配图引用维基)

three.js球体映射

  球体是一个封闭的连续曲面,thrre.js默认的映射算法就像世界地图和地球仪的一一对应关系,一个球体曲面对应一张图片。


      var geometry = new THREE.SphereGeometry( 50, 32, 32 );//球体SphereGeometry

  立方体映射(配图引用维基)

three.js立方体映射

  立方体由六个平面组成,相当于六个矩形网格模型,默认的映射规则是每个平面对应一张纹理贴图,纹理贴图完全填充平面。


     var geometry = new THREE.BoxGeometry( 60, 60, 60 );//立方体BoxGeometry

圆柱映射

  圆柱映射默认的是圆柱面、两个底面分别对应一张纹理贴图。


     var geometry=new THREE.CylinderGeometry(40,40,100,40);//圆柱体CylinderGeometry

立方体添加局部贴图

  three.js立方体几何体默认是每个面都映射一个完整的纹理贴图,而且纹理贴图完全填充立方体的表面。 现在大家思考一个问题,如果一个立方体是一个商品,现在需要在立方体一个表面的局部位置粘贴一个商标,对于这样一个问题如何解决。

基本思路

  创建两个几何体,立方体几何体表示一个产品对象,创建一个矩形平面几何体对象,把纹理贴图映射到平面几何体上,然后调整平面几何体的位置, 使之与立方体的一个平面重合,就像商标粘贴在产品几何体的外表面上。

    /**
     * 立方体网格模型(产品)
     */
    var box=new THREE.BoxGeometry(100,100,100);//创建一个立方体几何对象
    var boxMaterial=new THREE.MeshLambertMaterial({color:0x0000ff});//材质对象
    var boxMesh=new THREE.Mesh(box,boxMaterial);//网格模型对象
    scene.add(boxMesh);//网格模型添加到场景中
    /**
     * 纹理贴图网格模型(商标)
     */
    var plane = new THREE.PlaneGeometry(60,30);//矩形平面
    var texture = THREE.ImageUtils.loadTexture("glb.jpg");//加载纹理贴图
    var planeMaterial=new THREE.MeshLambertMaterial({//贴图通过材质添加给几何体
        map:texture//给纹理属性map赋值
    });//材质对象
    var planeMesh = new THREE.Mesh(plane,planeMaterial);//纹理贴图网格模型
    planeMesh.translateZ(50.1);//平移纹理贴图网格模型
    scene.add(planeMesh);//纹理贴图网格模型添加到场景中

  以上代码都是已经学过的知识,没有太多需要解释,要注意两点,一方面是矩形平面PlaneGeometry(60,30)的宽高比例注意要与纹理贴图的像素宽高比例一致,一面纹理贴图纵向或横向不等比例拉伸, 另一方面是纹理贴图的网格模型沿着Z轴拉伸的距离,语句planeMesh.translateZ(50.1)表示网格模型沿着Z轴平移50.1,平移距离是50刚好与立方体前面的平面重合, 但是重合的时候系统无法判断立方体的平面与纹理贴图的网格模型平面前后问题,所以平移距离多偏移了0.1,当然0.01,0.001,也可以。

图片像素

  素材提供的图片glb.jpg像素尺寸是512x256,大家可以看到宽高值都是2的幂函数,在实际开发的时候要注意纹理贴图的像素宽高值保证是2n(n是整数)。

纹理映射(WebGL)

three.js纹理映射

  纹理贴图简单地说就像你把一个商标图片粘贴到一个商品上,WebGL提供了简单纹理贴图功能,如果希望实现凹凸、法线、环境等高级贴图需要一定的算法来实现, three.js对常用的贴图算法进行了的封装,即使你不太了解图形学,也能够像使用三维软件一样借助three.js实现各种贴图。

  对于有WebGL基础或者你希望深入学习three.js的读者,可以学习如何自定义一个具有纹理映射功能的几何体对象,如果没有WebGL基础, 初学three.js可以不用关注本节课的代码细节,只要建立纹理映射的概念就可以。

  下面的代码是在第二章三角面的代码基础上进行更改,给几何体添加纹理坐标数据,系统会把纹理坐标对应的图片像素映射到顶点坐标定义三角面。

    /**
     * 纹理贴图网格模型
     */
    var geometry = new THREE.Geometry(); //创建一个空几何体对象
    /**顶点坐标(纹理映射位置)*/
    var p1 = new THREE.Vector3(0,0,0); //顶点1坐标
    var p2 = new THREE.Vector3(80,0,0); //顶点2坐标
    var p3 = new THREE.Vector3(80,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);//纹理坐标传递给纹理三角面属性
    var texture = THREE.ImageUtils.loadTexture("glb.jpg");//加载图片
    var material=new THREE.MeshLambertMaterial({
        map:texture,//纹理属性map赋值
        side:THREE.DoubleSide//两面可见
    });//材质对象
    var mesh=new THREE.Mesh(geometry,material);//纹理贴图网格模型对象
    scene.add(mesh);//纹理贴图网格模型添加到场景中

  三角面像素属性faceVertexUvs和三角面属性faces、顶点属性vertices一样都是几何体对象Geometry的属性,深入理解几何体对象Geometry,有助有threee.js的深度学习, 建立WebGL、计算机图形学与three.js引擎的联系。对于WebGL图形系统,通过三个顶点可以确定一个三角面网格模型,这个三角面的像素值可以通过材质对象的color属性定义, 也可以从图片中获取,从图片中获取元素也是通过三个纹理坐标确定一个三角形区域,三角形区域的像素就会映射到一个三角面上。通过两个三角面拼在一起可以创建一个矩形平面, 一张图片是矩形的,在对角线的位置可以把纹理贴图分割成两个三角形区域。

  三角面是贴图的映射位置,所以三角面的顶点数据使用三维向量对象Vector3定义,纹理坐标是从平面图片上选择一个三角区域,所以纹理坐标使用二维向量对象Vector2表示, 通过构造函数THREE.Vector2()创建了四个纹理坐标t0、t1、t2、t3,分别对应纹理贴图的四个顶点,t0、t1、t2三角形区域和t0、t2、t3定义的三角形区域刚好完整分割纹理贴图, t0、t1、t2三角形区域的像素会映射到三角面1上,t0、t2、t3定义的三角形区域像素会映射到三角面2上。 你可以把四个纹理坐标中的值1更改为0.5,你会发现网格模型上的显示效果只是纹理贴图左下角的四分之一,

肠炎宁包装盒案例

  肠炎宁盒子形状很简单就是一个立方体,但是表面的视觉效果比较复杂,对于2D的视觉效果可以编程实现,也可以通过纹理贴图快速实现, 实现的方式有两种,通过Plane构造函数创建6个平面矩形几何体对象,每个几何体映射一张贴图;也可以创建一个立方体几何对象, 借助平面材质对象MeshFaceMaterial实现同一个几何体的多个面的分别对应一个纹理贴图。

  程序的整体思路是通过THREE.ImageUtils.loadTexture()函数加载肠炎宁盒子6个侧面的纹理贴图返回6个纹理对象,然后使用一个网格材质构造函数创建6个网格材质对象, 给每个网格材质纹理贴图属性map赋值loadTexture()函数返回的6个纹理贴图对象,6个网格材质组成一个数组作为MeshFaceMaterial()构造函数的参数,返回一个材质对象, 用于生成网格模型。

  THREE.MeshFaceMaterial()构造函数返回的对象是材质对象,MeshFaceMaterial对象与其说是材质对象,不如说它只是多个材质的集合,这个集合的特点是,集合里面的每一个材质对象, 对应几何体的一个面,对于立方体而言是6个平面组成,一球体相当于一个面,一个圆柱相当于3个面,两个底面和一个圆周面组成,至于这里所谓的“面”是如何规范的,与平时说的三角面有什么区别, 可以阅读three.js源码。

 /**
     * 立方体网格模型(药盒)
     */
    var box=new THREE.BoxGeometry(70,255,480);//长宽高尺寸70,255,480
    //加载六个面的纹理贴图
    var texture1 = THREE.ImageUtils.loadTexture("1.jpg");
    var texture2= THREE.ImageUtils.loadTexture("2.jpg");
    var texture3 = THREE.ImageUtils.loadTexture("3.jpg");
    var texture4= THREE.ImageUtils.loadTexture("4.jpg");
    var texture5 = THREE.ImageUtils.loadTexture("5.jpg");
    var texture6 = THREE.ImageUtils.loadTexture("6.jpg");
    var materialArr=[
        //纹理对象赋值给6个材质对象
        new THREE.MeshPhongMaterial({map:texture1}),
        new THREE.MeshPhongMaterial({map:texture2}),
        new THREE.MeshPhongMaterial({map:texture3}),
        new THREE.MeshPhongMaterial({map:texture4}),
        new THREE.MeshPhongMaterial({map:texture5}),
        new THREE.MeshPhongMaterial({map:texture6})
    ];
    //6个材质对象组成的数组赋值给MeshFaceMaterial构造函数
    var facematerial=new THREE.MeshFaceMaterial(materialArr);
    var mesh=new THREE.Mesh(box,facematerial);//
    scene.add(mesh);