Three.js源码解读二:Geometry

(一)基础知识

网格(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的实现原理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容