美文网首页
Three.js源码解读二:Geometry

Three.js源码解读二:Geometry

作者: federerchou | 来源:发表于2017-09-18 17:17 被阅读503次

    (一)基础知识

    网格(Mesh)

    Geometry是Three.js对3D物体的一个整合,记录了渲染一个3D物体所需要的基本数据。本文选取最重要的三个属性,包括顶点(vertices),面(faces),法向量(normal)。
    3D物体由网格(Mesh)组成,网格由三角形组成,三角形由点组成。这里,组成网格的三角形叫做面(face),组成三角形的点叫做顶点(vertex),法向量(normal)决定了每个顶点在光照下所呈现出的颜色。

    图1.多个三角形组成球体网格

    上面中绿色的框就是球体的网格,可以看到这个是网格通过把顶点连接组合成多个三角形而组成的。

    Face & Vertex

    网格由面(Face)组成,在计算机图形学中,每个基本的面都是三角形。

    图2.一个面

    我们思考一下如何画出上面的三角形,假设这面的三个点为a,b,c。
    一种思路是直接指定这三个点的位置,如下:

    face.a = (-50,50,0)
    face.b = (50,50,0)
    face.c = (-50,-50,0)
    

    第二种思路,我们将这三个点单独放在统一的缓存中,a,b,c则用来指定这三个点在缓存中的位置:

    //将顶点统一存储
    const vertices = [
      (-50,50,0),
      (50,50,0),
      (-50,-50,0)
    ];
    //为face指定顶点在缓存中的位置
    face.a = 2
    face.b = 1
    face.c = 0
    

    第二种思路表面看起来似乎更加复杂了一些,但它却是实际中采用的方式,我们看看为什么。
    假设我们需要画一个矩形,在3D世界中,我们需要通过两个三角形来实现

    图3.两个三角形组成矩形

    如果用第一种思路,我们需要6个点来存储位置信息,然而其中有两个点的位置是完全一样的,造成极大的内存浪费。因此,我们通过顶点缓存加位置引用的方式来指定每个三角形的顶点。

    //将顶点统一存储
    const vertices = [
      (-50,50,0),
      (50,50,0),
      (-50,-50,0),
      (50,-50,0)
    ]
    //为face指定顶点在缓存中的位置
    face1.a = 2
    face1.b = 1
    face1.c = 0
    //为face指定顶点在缓存中的位置
    face2.a = 1
    face2.b = 2
    face2.c = 3
    
    图4.画出矩形

    法向量(normal)

    简单说来,法向量就是垂直于平面的向量。光照和法向量的夹角决定了平面反射出的光照强度。

    图5. 光照和法向量(图片来自网络)
    以上图片来自网络
    假设法向量为norm, 入射光线向量为in,只需要将norm和in做简单的点积,就能得到物体的反射光照强度。
    //如果点击小于0,说明表面处背光,显示为黑色
    //注意,这里的光照是针对漫反射的光照。通常情况下场景中还会有环境光,因此即使背光面也不会纯黑
    lightness = max(dot(in, norm),0)
    

    那么如何得到法向量呢?由3D图形学的知识可以知道,对两个向量a,b做叉积,能得到一个向量c,并且这个向量同时垂直于a和b,也即垂直于a和b形成的平面。然后将向量c标准化(normalize),就得到了平面ab的法向量。

    //将向量a,b做叉乘,得到垂直于a,b平面的向量c
    c = a x b
    //将向量c标准化(c / ||c||, ||c||!==0)
    norm = normalize(c)
    
    图5.叉积(图片来自网络)
    以上图片来自网络

    (二)代码分析

    通过上面的分析,我们知道了顶点(Vertex),面(Face),法向量(Normal)的重要性,现在,探索一下Three.js中是如何维护这3个属性的。

    Vertices(顶点)

    Geometry.js中的属性this.vertices通过数组的形式保存了一个3D物体所有的顶点位置信息。

    this.vertices: Array = [
      v_1: THREE.Vector3,
      v_2: THREE.Vector3,
      ...
      v_n: THREE.Vector3
    ]
    

    Face(面)

    Geometry.js中的属性this.faces通过数组的形式保存了一个3D物体所有的三角面信息。

    this.faces: Array = [
      face_1: THREE.Face3,
      face_2: THREE.Face3,
      ...
      face_n: THREE.Face3,
    ]
    

    Three.js提供了类Face3来更好的封装一个三角面。一个Face3类,除了用上面提到的a,b,c属性来指定3个顶点在顶点缓存vertices中的位置外,还有以下重要属性:

    • normal: THREE.Vector3 : 三角面的法向量
    • vertexNormals: Array: 每个顶点的法向量
    • color: THREE.Color: 指定面的颜色
    • vertexColors:Array 指定每个顶点的颜色
      一个三角面只会有一个法向量。一个顶点会属于不同的三角面,因此一个顶点会有多个法向量:
    法向量

    上图中,红色短线表示顶点法向量,黄色短线表示面法向量。可以看到,一个顶点有多个红色法向量,一个三角面只有一个黄色法向量。
    color的作用很好理解,用它来指定三角面的颜色。那么vertextColors的作用是不是仅仅指定顶点的颜色呢?要理解vertexColors,我们需要了解GPU的着色流程。

    片元着色流程

    一个三角面可能包含上百个像素。vertexColors指定了三角面顶点的颜色,GPU通过插值的方式算出其他像素的颜色,最终实现整个三角面着色。下图中的渐变效果就是GPU通过插值的方式实现的。

    插值着色

    Geometry形变

    3D物体的形变(位移,旋转,缩放)涉及到两个方面,一是顶点位置的变化,二是法向量的变化。顶点位置变化才能实现形变的各种效果,法向量变化是更新物体的光照反射效果。在Geometry.js中,物体形变通过applyMatrix实现。我们来分析applyMatrix代码:

    1. 更新顶点位置
    // 遍历Geometry中的全部顶点,为每个顶点加上形变
    // new_vertex = dot(matrix,old_vertex)
    for ( var i = 0, il = this.vertices.length; i < il; i ++ ) {
        var vertex = this.vertices[ i ];
        vertex.applyMatrix4( matrix );
    }
    
    1. 更新法向量
      随着物体形变,计算物体形变后的法向量是很复杂的。幸运的是,有一个规则可以直接使用:
      用法向量乘以形变矩阵的逆转置举证,就可以得到形变后的法向量
    //逆转置矩阵 = 逆矩阵的转置
    // inverse: 获取逆矩阵
    //transpose: 获取转置矩阵
    逆转置矩阵 = transpose(inverse(matrix)) 
    新法向量 = dot(逆转置矩阵,旧法向量)
    
    //获取形变矩阵的逆转置矩阵,并且标准化
    var normalMatrix = new Matrix3().getNormalMatrix( matrix );
    for ( var i = 0, il = this.faces.length; i < il; i ++ ) {
      var face = this.faces[ i ];
      //遍历物体每个面的法向量,得到形变后的法向量
      face.normal.applyMatrix3( normalMatrix ).normalize();
      //遍历物体每个面的顶点法向量,得到形变后的顶点法向量
      for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {
        face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize();
      }
    }
    

    基于此,就不难理解rotateXrotateYrotateZrotateZscale的实现原理。

    相关文章

      网友评论

          本文标题:Three.js源码解读二:Geometry

          本文链接:https://www.haomeiwen.com/subject/lxbvsxtx.html