3D数学-基础纹理

3D数学-基础纹理

好记性不如烂笔头啊,还是记录一下!


概述

纹理最初的目的就是使用一张图片来控制模型的外观。使用纹理映射(texture mapping)技术,我们可以把一张图“黏”在模型表面,逐纹素(texel)(纹素的名字是为了和像素进行区分)地控制模型的颜色。

在美术人员建模的时候,通常会在建模软件中利用纹理展开技术把纹理映射坐标(texture——mapping coordinates)存储在每个顶点上。纹理映射坐标定义了该顶点在纹理中对应的2D坐标。通常,这些坐标使用一个二维变量(u,v)来表示,其中u是横向坐标,而v是纵向坐标。因此,纹理映射坐标被称为UV坐标,如图:

3D数学-基础纹理_1.png

尽管纹理的大小可以是多种多样的,可以是256*256或者1024*1024,但顶点的UV坐标的范围通常都被归一化到[0,1]范围内。


漫反射纹理

漫反射纹理(Diffuse Map)是最基础的一种纹理。其实就是一张覆盖物体的图像,让我们能够逐片段索引其独立的颜色值。在光照场景中,它通常只是用采样到的颜色值取代光照模型中的漫反射部分的颜色值。回忆一下基础光照中的光照模型:

c_{diffuse}=(c_{light} \cdot m_{diffuse}) \cdot max(0, n \cdot l)

如果对这个公式有疑问可以参考《3D数学-基础光照》

采样的颜色值替换m_{diffuse}部分,漫反射纹理通常是纹理是什么样子,渲染到物体上就是什么样子,例如:

3D数学-基础纹理_2.png

渲染出来会是这样:

3D数学-基础纹理_3.png

镜面光纹理

大家可能会注意到,应用了镜面高光后看起来会有点奇怪,因为这张图片中有两种材质,木材和钢材,但是木头不应该有这么强的镜面高光的。所以我们想让物体的某些部分以不同的强度显示镜面高光,我们就可以使用一张专门用于镜面高光的纹理贴图。我们可以使用一张黑白纹理来定义物体每个部分的镜面光强度,例如上面渲染的木箱它的镜面光纹理(Specular Map)

3D数学-基础纹理_4.png

镜面高光的强度可以通过图像每个像素的亮度来获取。镜面光纹理上的每个像素可以由一个颜色向量来表示。比如黑色代表颜色向量<0, 0, 0>、灰色代表颜色向量<0.5, 0.5, 0.5>,那么我们用光照模型计算出来的高光部分c_{specular}就可以点乘这个向量,这样就可以方便的控制不同部分的反光强度。在镜面光纹理(Specular Map)中,一个像素越白,说明说明物体表面的镜面光强度越大,经过镜面光纹理的后的渲染,如图:

3D数学-基础纹理_5.png

经过镜面光纹理的处理,看起来更逼真一些,但是感觉面还是平平的缺少一些细节。


法线纹理

现实中的物体表面并非平坦的,想提升一个表面的细节主要有两种方式。第一种通过增加顶点来提升面数来增加一个表面的细节,这种方式表现力强,但是比较耗费性能。另一种方法就是使用凹凸映射(bump mapping),给模型提供更多的细节表现。这种方法不会真的改变模型的顶点位置,只是让模型看起来好像是“凹凸不平”的,但可以从模型的轮廓处看出“破绽”

在光照模型的中,影响光照强度的是辐照度(irradiance),而辐照度(irradiance)通常是由光源方向l和表面法线n点积来计算。我们通常无法改变光的强度,但是我们可以改变表面法线n来控制一个表面的光照强度,表面只有一个法向量,使得这个平面被同样的一种辐照度(irradiance)照亮。如果每个fragment都有自己不同的法线会怎样,我们就可以根据表面细微的细节,来改变这些法线,这样在一个表面上就可以产生出表面并不平坦的错觉,如图:

3D数学-基础纹理_6.png

每个fragment都有自己不同的法线,也就可以看成这个表面是由很多微小的(垂直于法向量的)平面组成,物体经过光照模型计算后,表面细节会得到极大的提升,这种每个fragment使用各自的法线,替代一个面上所有fragment使用同一个法线的技术叫做法线纹理(normal mapping)凹凸映射(bump mapping),下图展示了没有使用法线纹理和使用了法线纹理的区别:

3D数学-基础纹理_7.png

可以看到细节获得了极大的提升,性能消耗确不大。因为我们只需要改变每个fragment的法线向量,不需要更改光照模型。现在我们是为每个fragment传递一个法线,不再使用插值的表面法线。这样光照使表面的每个fragment拥有了自己的细节。

为使法线纹理工作,我们需要为每个fragment提供一个法线。2D纹理不仅可以储存颜色和光照数据,还可以储存法线向量。这样我们可以从2D纹理中采样得到特定纹理的法线向量。像漫反射纹理(Diffuse Map)镜面光纹理(Specular Map)一样,我们可以使用一张2D纹理来储存法线数据。

由于法线向量是个几何工具,而纹理通常只用于存储颜色信息,用纹理存储法线向量不是非常直观。法线方向的分量范围在[-1, 1],而像素的分量范围为[0, 1],因此我们需要做一个映射,通常使用的映射就是:

pixel = normal \times 0.5 + 0.5

这就要求我们在对法线纹理采样后,还需要对结果进行一次反映射的过程来得到原先法线的方向。反映射的过程就是使用上面映射函数的逆函数:

normal = pixel \times 2 - 1

将法线向量变换为这样的RGB颜色,我们就能把根据表面形状的fragment的法线保存在2D纹理中,例如上面渲染的砖块的例子:

3D数学-基础纹理_8.png

一般来说,法线纹理都会是这种偏蓝色调的纹理。这是因为所有法线的指向都偏向z<0, 0, 1>,映射到像素即为<0.5, 0.5, 1>,也就是浅蓝色。这些浅蓝色实际上说明fragment的大部分法线是和模型本身法线一样,不需要改变。法线向量从z轴方向向其他方向轻微的偏移,颜色也就发生了变化,这样看起来编有了一种深度。然后我们就可以用采样的颜色值,反映射出对应的法线向量,然后进行光照计算,就可以得到上面渲染的表现效果。

这个方法看起来很完美,这样使用有很大的限制,如果法线纹理的所有向量都是基于z<0, 0, 1>的偏移,那么必须模型表面的法向量必须是指向z<0, 0, 1>。如果将表面旋转使得法向量指向y<0, 1, 0>,计算出来的光照完全不对,如图:

3D数学-基础纹理_9.png

有一个解决方案是为每个表面制作一张单独的法线纹理。这样的话,如果一个立方体我们就需要6张法线纹理,如果一个模型上有无数朝向不同方向的面,这就不靠谱了。

另一个解决方案是,在一个不同的坐标空间中进行光照,这个坐标空间里,法线纹理向量总是指向这个坐标控件的正z<0, 0, 1>方向。所有的光照向量都是相对于这个正z<0, 0, 1>方向进行变换。这样我们就能始终使用同样的法线纹理,不管朝向问题。这就是切线空间(tangent space)

切线空间(tangent space)

切线空间(tangent space)也称为图像空间(image space),法线纹理中的法线向量都是定义在切线空间(tangent space)中,顶点法线永远指向z<0, 0, 1>方向:

3D数学-基础纹理_10.png

然后我们需要确定切线(Tagent)方向:

3D数学-基础纹理_11.png

然而垂直与法线的切线有很多条,理论上哪一条都行。但我们需要保持连续一致性,以免衔接出现瑕疵。标准的做法是将切线方向和纹理空间对齐:

3D数学-基础纹理_12.png

定义一个空间坐标系需要三个基向量,因此我们还得计算副切线(Bitangent)

我们得到这三个基向量后就可以构建TBN矩阵T代表tangentB代表bitangentN代表normal),可以用这个矩阵把任意向量从切线空间(tangent space)转换到模型空间,然后我们只需要对TBN矩阵求逆就可以实现从模型空间转换到切线空间(tangent space)的变换矩阵。

现在我们现在已知法线(Normal),需要将切线(Tangent)副切线(Bitangent)对齐到纹理空间的u轴和v

3D数学-基础纹理_13.png

假设一个fragment进行采样:

3D数学-基础纹理_14.png

P_{1}<U_{1}, V_{1}>是切线空间中的TB平面上的一个坐标点

P_{2}<U_{2}, V_{2}>是切线空间中的TB平面上的一个坐标点

P_{3}<U_{3}, V_{3}>是切线空间中的TB平面上的一个坐标点

E_{1}是连接P_{1}P_{2}的的直线

E_{2}是连接P_{2}P_{3}的的直线

则有以下关系式:

\begin{cases} \Delta U_{1} = |U_{2} - U_{1}| \\[2ex] \Delta V_{1} = |V_{2} - V_{1}| \\[2ex] \Delta U_{2} = |U_{3} - U_{2}| \\[2ex] \Delta V_{2} = |V_{3} - V_{2}| \end{cases}

就可以得出一下关系:

\begin{cases} E_{1} = \Delta U_{1}T + \Delta V_{1}B \\[2ex] E_{2} = \Delta U_{2}T + \Delta V_{2}B \end{cases}

我们也可以写成这样:

\begin{cases} (E_{1x}, E_{1y}, E_{1z}) = \Delta U_{1}(T_{x}, T_{y}, T_{z}) + \Delta V_{1}(B_{x}, B_{y}, B_{z}) \\[2ex] (E_{2x}, E_{2y}, E_{2z}) = \Delta U_{2}(T_{x}, T_{y}, T_{z}) + \Delta V_{2}(B_{x}, B_{y}, B_{z}) \end{cases}

这样我们就可以方便的写成矩阵的形式:

\begin{bmatrix} E_{1x} & V_{1x} \\[2ex] E_{1y} & V_{1y} \\[2ex] E_{1z} & V_{2z} \end{bmatrix}= \begin{bmatrix} \Delta U_{1} & \Delta U_{2} \\[2ex] \Delta V_{1} & \Delta V_{2} \end{bmatrix} \cdot \begin{bmatrix} T_{x} & B_{x} \\[2ex] T_{y} & B_{y} \\[2ex] T_{z} & B_{z} \end{bmatrix}

然后我们可以进行变换,成为:

\begin{bmatrix} \Delta U_{1} & \Delta U_{2} \\[2ex] \Delta V_{1} & \Delta V_{2} \end{bmatrix}^{-1} \cdot \begin{bmatrix} E_{1x} & V_{1x} \\[2ex] E_{1y} & V_{1y} \\[2ex] E_{1z} & V_{2z} \end{bmatrix}= \begin{bmatrix} T_{x} & B_{x} \\[2ex] T_{y} & B_{y} \\[2ex] T_{z} & B_{z} \end{bmatrix}

然后就可以计算出delta纹理坐标矩阵的逆矩阵,就可以计算出TB,计算逆矩阵的方法这里就不详细介绍了,主要方式是计算矩阵的行列式,然后用1除以行列式再乘以它的伴随矩阵(Adjugate Matrix)

\begin{bmatrix} T_{x} & B_{x} \\[2ex] T_{y} & B_{y} \\[2ex] T_{z} & B_{z} \end{bmatrix}= \frac{1}{\Delta U_{1}\Delta V_{2}-\Delta U_{2}\Delta V_{1}} \begin{bmatrix} \Delta V_{2} & -\Delta V_{1} \\[2ex] -\Delta U_{2} & \Delta U_{1} \end{bmatrix} \cdot \begin{bmatrix} E_{1x} & V_{1x} \\[2ex] E_{1y} & V_{1y} \\[2ex] E_{1z} & V_{2z} \end{bmatrix}

这样我们就计算出了TBN矩阵,我们只需要得到TBN矩阵的逆矩阵就可是实现模型空间转换到切线空间(tangent space),在理想的情况下TBN矩阵是正交矩阵,我们就可以通过求转置矩阵来获得逆矩阵,即:

\begin{bmatrix} T_{x} & B_{x} & N_{x} \\[2ex] T_{y} & B_{y} & N_{y} \\[2ex] T_{z} & B_{z} & N_{z} \end{bmatrix}^{T}= \begin{bmatrix} T_{x} & T_{y} & T_{z} \\[2ex] B_{x} & B_{y} & B_{z} \\[2ex] N_{x} & N_{y} & N_{z} \end{bmatrix}

然而实际情况计算出的TBN矩阵往往不是正交矩阵,所以我们需要对这个矩阵进行格拉姆-施密特正交化过程(Gram-Schmidt process)来进行正交化:

3D数学-基础纹理_15.png

t_{o} = normalize(t - n \times (n \cdot t) \\[2ex] b_{o} = n \times t_{o}

这样我们就得到了正交的TBN矩阵,一般来说有两种方式来使用它:

  1. 我们直接使用TBN矩阵,可以将切线坐标空间的向量转换到世界坐标空间。因此我们可以将从法线纹理中采样到的法线乘以TBN矩阵转换到世界空间中,这样法线、光照参数都在一个坐标系中,就可以进行光照模型的计算了。
  2. 我们也可以使用TBN的逆矩阵,可以将世界坐标空间的向量转换到切线坐标空间.我们使用这个矩阵将光照参数转换到切线空间中,然后就进行光照模型的计算了。

本节教程就到此结束,希望大家继续阅读我之后的教程。

谢谢大家,再见!


饮水思源

参考文献:

《3D游戏与图形学中的数学方法》
《Unity Shader 入门精要》
《法线贴图》
《法线贴图》


版权声明:原创技术文章,撰写不易,转载请注明出处!

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