图形学笔记七 纹理和贴图 AO

参考课程:
https://www.bilibili.com/video/BV1X7411F744?p=8 从第54分钟开始
https://www.bilibili.com/video/BV1X7411F744?p=9
https://www.bilibili.com/video/BV1X7411F744?p=10 这一讲虽然标题是Geometry,但前面40分钟仍然是纹理

一、uv

闫令琪视频对uv部分讲的并不详细,这里参考其它链接做一些补充。

参考
知乎 玩家们所谓的贴图是什么?作者:Focux
冯乐乐 shader入门精要第二章

所谓的贴图,到头来就是内存中的一段数据:纹理。

直观的第一印象,很容易会将纹理理解成一张图片,实际上在计算机图形学中,我们也确实会经常通过一张张格式诸如png、jpg之类的图片来获得纹理数据。但在图形学中,纹理更多地是被认作 「一块数据」,它也不再局限在2D空间,也会有一维纹理、三维纹理、立方体映射纹理、数组纹理等。

纹理是由 「纹素(texel)」 构成的,每个纹素记录着相应的颜色信息,它类似于屏幕的 「像素(pixel)」 但又与像素不保证是一一对应的。

那么纹理为什么会被广泛使用呢?这是因为,我们看到现实世界中所呈现出丰富的颜色,是由于不同物体会吸收一部分光的波长并反射出另一部分出来(如红色物体实际上是因为反射了大部分红色波长出来),最终通过眼睛传入大脑计算成像。而通过计算机去实时模拟这一过程无疑是复杂且耗时的,尤其是游戏要求在短短的一帧(少于0.016s)中就完成一次场景渲染。

为了解决这个问题,人们想到了一个办法:找到一张图片(离线渲染生成),然后把它“粘”在物体的表面上,就像贴墙纸一样。而这一“粘”的过程,我们管它叫做 「纹理映射(texture mapping)」,实际上也就是一块纹理数据与3D模型建立关联的过程。看过武侠小说的应该都听说过“易容术”,将不同的“人皮子”贴在脸上,就可以易容成别人的模样。其实这就是一个简单的纹理映射的过程。有人可能会有疑问,每个人的面部肌肉都不尽相同,仅用一张纹理是怎么做到“易容”的呢?卖个关子,后面在纹理的应用中会有介绍。

1.纹理采样,处于渲染管线的哪个阶段?

节选自冯乐乐 shader入门精要第二章:

图2.13 根据上一步插值后的片元信息,片元着色器计算该片元的输出颜色

这一阶段可以完成很多重要的渲染技术,其中重要的技术之一就是纹理采样。为了在片元着色器中进行纹理采样,我们通常会在顶点着色器阶段输出每个顶点对应的纹理坐标,然后经过光栅化阶段对三角网格的3个顶点对应的纹理坐标进行插值后,就可以得到其覆盖的片元的纹理坐标了。

2.纹理映射原理

在渲染管线中,一般在 「片元着色阶段」 最常使用纹理。在美术建模时,会为每个顶点分配一个纹理坐标,也被叫做UV坐标。在进入光栅化阶段,生成的每个fragment会根据相关顶点的纹理坐标插值后新的uv,根据这个uv来对传入的纹理数据进行「采样(使用纹理坐标获取纹理颜色)」。


image.png

根据上图说明一下简单的纹理映射,某个三角形包含三个顶点,对应记录的uv坐标分别是(0,0)、(1,0)和(0.5,1)。光栅化之后,每个小格子就是一个fragment,对应屏幕的像素。在这个三角形区域中的每个像素,会根据顶点所记录的纹理坐标来进行插值计算,这就涉及到了三角形内的重心坐标计算。已知三角形ABC内任意一点(x, y)以及三个顶点ABC的位置,即可求出该点相对三角形的重心坐标位置。进而可以计算出该点所在像素对应的UV坐标:


image.png

一般来说,纹理坐标的范围都是[0, 1],但在某些情况下,可能会使用超出该范围的结果。例如墙面纹理,我们可以只制作一块砖的纹理图片,在根据模型实际宽度来放大顶点纹理坐标,使得可以循环重复采样同一块砖的纹理。这就涉及到了设置「纹理环绕方式」,在OpenGL中,常见的选择如下:
image.png

image.png

还有一种比较常见的灵活纹理采样方式,就是UV序列帧动画。美术会做好一张 N * N 的图片,最最常见的就是火焰效果了。


image.png

每次在片元着色器对该纹理进行采样的时候,根据一定的频率来调整修改需要传入的UV坐标,使得每次恰好只截取展示其中的一个tile,连续起来会实现出帧动画的效果。

说到这里不难发现,跟纹理打交道,本质上就是确定「数据内容」和「采样方式」。在片元(像素)着色器中,根据UV坐标对纹理数据中对应纹素的颜色进行提取,再渲染到像素中去。但是,这仅仅是最简单、理想的情况。无论是纹素还是像素,你都可以理解为它们是有「大小」的,只不过人眼不易察觉罢了,毕竟现在手机一般都是1920 * 1020分辨率起步的,还是仅仅在一块6寸大小的空间下。当纹素与像素的大小比例过于悬殊时,会出现一些明显的“锯齿”现象,即纹理过小和纹理过大。

3.纹理坐标系为什么是0到1,这是一个相对的比例值

参考纹理映射基础
纹理映射就是要实现,如何把纹素映射到几何对象的每个点。一个2D的纹理有一个宽度和高度,通过宽度和高度相乘即可得到有多少个纹素。

那么如何来指定顶点的纹素呢?通过坐标来指定,但是这个坐标不应该是具体纹理中的坐标,而应该是抽象的纹理坐标空间中的坐标;否则通过指定具体纹理的坐标,当更换纹理,例如改变纹理的宽度和高度时,这些坐标值可能变得无意义,而不得不更新所有顶点的坐标值,因此需要使用抽象的纹理坐标空间的坐标。纹理坐标一般都规范化到[0,1]范围内。例如一个纹理宽度为320,高度为200,而纹理坐标(0.5,0.1)则表示纹素的位置在: (320 x 0.5,200 x 0.1)=(160,20)。通常使用UV坐标系来表示纹理坐标系。

4.纹理接缝

参考图形学底层探秘 - 纹理采样、环绕、过滤与Mipmap的那些事

image.png

5.

后面的内容可以看闫令琪的视频进一步了解。

二、重心坐标

TIPS:闫令琪视频P9开始讲重心坐标

参考图形学数学 | 利用重心坐标平滑插值三角形顶点的任何属性

1. 三角形重心坐标插值的作用?

在图形学中,利用重心坐标在三角形内部进行任何属性(位置、纹理坐标、颜色、法线、深度、材质属性..)的插值。一般我们通过其他步骤都会得到三角形顶点上的属性,但继续计算时需要用到三角形内部的某点的属性值,利用重心坐标可以得到三角形内部该值的平滑过渡。

2. 什么是重心坐标(此处只考虑三角形)?

重心坐标是由三角形顶点定义的坐标。

三角形上的任一顶点p(x,y)都可以用三个顶点的线性组合表示:p=w1*a+w2*b+w3*c
且系数之和为1:w1+w2+w3=1 (只有系数之和为1,p点才在这个三角形所在的平面上)
当满足三个系数都为非负数时,该点在三角形内部

可以看出,重心坐标是齐次坐标的一种。这三个系数所表示的坐标(w1,w2,w3),就是该三角形上p点的(归一化)重心坐标。

插值举例:假设三角形顶点的三个颜色C1,C2,C3,那么p点的颜色就是 Cp=w1*C1+w2*C2+w3*C3

3.为什么三个和为1的系数可以表示p点?为什么用三个系数就可以平滑过渡?
image.png

由此也可以理解重心坐标为什么可以让三角形顶点的属性平滑过渡了,首先这三个系数是线性变化的,类比笛卡尔直角坐标系中一点从(x1,y1)线性变化到(x2,y2),那么其经过的每一个位置都是均匀变化、平滑过渡的。

4. 重心坐标名称的由来

参考重心坐标(Barycentric coordinates)

image.png

重心坐标不是惟一的:对任何不等于零的k,(k λ1, ...,k λn)也是p的重心坐标。但总可以取坐标满足λ1+ ...+λn= 1,称为正规化坐标。——wiki “ 这句话在挂重物的情况下可以这么理解,如果在每个顶点出挂上同等倍数重量的重物,p点仍能保持平衡,所以重心坐标也有很多个。在图形学中使用的就是正规化(和为1)了的重心坐标。

5. 计算重心坐标的方法?

利用向量叉积求面积,通过面积计算:


image.png

比如,A点对面的三角形AA,除以总面积,就是α。至于这个公式如何证明,视频中没有讲。然后又给出一个更简化的方法:


image.png
6. 重心坐标与重心不要混淆(一开始我混淆了)

三角形内每一个点都有它的重心坐标,换句话说重心坐标其实是三角形内的点换了一个重心坐标系(参考标题3)来表示该点的方法。因此三角形的重心也有它的重心坐标(1/3,1/3,1/3)。
三角形的重心坐标将该三角形分成了三个面积相等的三角形,所以坐标都是1/3。


image.png
7.应用
image.png

注意,重心坐标投影之后会发生改变。也就是,要在三维坐标中利用重心坐标找插值,而不能投影之后再找插值。

比如深度,在光栅化中,三角形已经投影到屏幕上,会覆盖很多像素,知道投影的三角形三个顶点在哪里,此时使用重心坐标做插值就是不正确的。应该是找到三角形像素中心点对应在三维空间中的坐标,在三维空间中插值深度。至于怎么把投影到屏幕上的坐标再投影回去,这里需要做一个逆变换。

结论:先插值,再投影到二维。

8.实例

以上面漫反射shader例子


image.png

屏幕采样点的位置是(x,y),可以找出在此位置插值的uv在哪里(使用上面的重心坐标先算αβγ,相当于权重,然后插值得到采样点的uv),然后查询一下,得到对应的纹理颜色texcolor。这个颜色就可以用做漫反射系数,相当于贴到了物体上。

三、Texture Magnification 纹理贴图放大

纹理太小,就会被拉大。如果屏幕像素映射到贴图的非整数位,以四舍五入法处理,就出现屏幕像素附近的几个都映射到纹理的同一个位置,效果会有重叠。比如下图中标记的红框,红点在左下角和右上角,都会映射到同一个纹理位置。


image.png

此时可以采用双线性插值进行优化,取出离红色点最近的4个黑色顶点,分别算出,该红色点在水平及竖直方向偏移的比率s,t。


image.png

如图,先使用S进行横向的两次插值得到u0,u1。再使用t对u0,u1进行插值得到最终结果。如此这样利用两次线性插值,考虑到了所有4个点的颜色值。这里总共做了三次插值,但因为只有水平方向和竖直方向,所以称为"双线性"插值。
image.png

其中,Bicubic效果更好(可以在头发和眼角处观察锯齿进行对比),双线性插值取周围四个,Bicubic取的是16个。

四、纹理过大
image.png

近处会看到锯齿,远处看到摩尔纹。相当是采样频率不足,导致了之前出现过的走样问题。最简单粗暴的就是增加采样,比如一个像素用512个采样点,然后插值,这样算法效率就会比较差。现在需要一个Range Query范围查询算法,即给定一片区域,立即得出平均值;这样就避免了之前的点查询。

image.png

如上图,假如两个红圈处使用的是同一个纹理。远处看起来更小,相当于一个像素容纳了更多的纹理区域。也可以说,同样的像素,在远处显示了一幅图,而在近处只显示了一部分。

1.mipmap

这个概念和LOD很类似:

LOD 对模型进行 分层级显示,视角离近时,我们让物体显示精细度高的模型;视角离远时,我们让物体显示精细度低的模型。这就是 Level of detail 多层次细节优化,模型精度低了,性能负担也就降下来了。

image.png

经过累加计算就知道,mipmap只比原始图片,多使用了三分之一的空间。

2.如何计算要查询的区域
image.png

这里是有微分的,然后简单处理,直接取两边之中的最大值,就可以近似得到正方形区域。

现在如果这个区域大小是1乘1,那么直接使用原图。
如果区域大小是4乘4,那么就使用第2张图。比如原图是128*128,那么第2张图64*64,实际上只有原来的四分之一。这里就有个快速求出第几层的算法,就是:


image.png
3.三线性插值

经过上面的mipmap会出现新的问题,就是第1层和第2层的交界处,会出现不连续的缝,因为没有像1.8层这样的贴图。解决办法仍然是插值:


image.png

如图,就是两次插值的基础上,再插值一次。三线性插值的计算量并不大,因此在游戏中得到了非常广泛的应用。

4.各向异性过滤

mipmap真的解决问题了么,看下图:


image.png

这种现象称为overblur,即模糊过份。


image.png

看上图,屏幕上的像素,映射到纹理上,很有可能是一个矩形条,此时如果按照上面的算法,拉成正方形,就会产生很大偏差。各向异性过滤就可以处理这种矩形的情况,但是那种斜长条仍然不行。
这里额外开销就是左上角原图的3倍

如图,和mipmap的区别就是,能方便地找到单方向压扁的矩形,它的名字就是这个意思,就是在各个方向上表现并不相同。各向异性过滤,还会看到有2X,4X这些,对照上图,就是单方向压扁1次,2次。所以在游戏中这个参数主要是吃显存,对运算速度影响不大。

而斜长条的处理,需要这个:


image.png

通过椭圆,进行多次查询。

五、环境纹理 天空盒

可以用纹理去描述环境光,此处假设环境光是无限远。因为如果太近,在不同角度看到的反射应该是不同的。


image.png

这个球的纹理在展开之后,会像世界地图一样在顶部和底部出现变形。比如俄罗斯在平面地图上看起来非常大,实际上就是有一定的变形。


墨卡托投影
可以看到顶部和底部产生的扭曲

为了解决这个问题,我们把球的表面信息记录在一个立方体的表面上,嗯,就是熟悉的天空盒,6张图:


从球心向6个方向投影
六、为什么需要法线数据
1.知识回顾

在图形学笔记六 Shading 渲染管线中,计算光照的公式如下:

image.png

从公式中能看出,除了环境光之外,漫反射和高光都要考虑法线。对于每个点最终的颜色,漫反射需要考虑光线的照射角度和法线方向;高光要考虑摄像机的角度,即眼睛恰好在那个反射角度上,就会看到反射很强烈。而反射角度,和光线照射角度、法线方向同时相关。

也就是说,如果改了法线数据,那么光照效果就会发生变化。

2.效果对比

如果都是默认的法线方向,模型是失真的。效果图可以参考计算机图形学(OPENGL):法线贴图

模型的网格构成其结构,纹理赋予其光影,但如果观察我们之前的所有例子,都会发现模型的表面都是扁平的,就算赋予了纹理也看起来不真实,因为现实生活中的大部分物体都是表面粗糙,凹凸不平的。

比如,一个贴有砖块纹理的平面。砖墙本身应该表面凹凸不平,有缝隙,有划痕,有孔洞,运用我们之前学过的技术,我们会在之前的平面上贴上纹理来模拟砖墙:


image.png

仔细观察的话,所有的凹凸不平、缝隙和孔洞这些细节全都没有表现出来,平面看上去非常扁平。我们其实可以利用一些技术来表现细节,比如使用高光贴图来让某些地方照亮的更少,但这不算是一种真正的解决方式。

如果我们从灯光的角度去思考的话:平面是怎么样被渲染为完全扁平的平面的?我们可以想到是平面的法线,决定物体形状的就是它的法线。平面使用的是单独的法线,所以表面没有起伏变化,那么如果我们为每个片段计算不同的法线,并操作这些法线产生一些变化的话,表面应该就能看起来起伏了。下面的图说明了这个想法:

image.png

通过每个片段使用不同的法线,我们可以欺骗灯光一个平面由许多不同的小片段构成,这样可以增加很多细节。这项技术被称为法线贴图或凹凸贴图。

七、前置知识 冯乐乐入门精要第7章第2节 凹凸映射

纹理另一种常见的应用就是凹凸映射(bump mapping)。凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。这种方法不会真的改变模型的顶点位置,只是让模型看起来好像是“凹凸不平”的,但可以从模型的轮廓处看出“破绽”。

有两种主要的方法可以用来进行凹凸映射:一种方法是使用一张高度纹理(height map)来模拟表面位移(displacement),然后得到一个修改后的法线值,这种方法也被称为高度映射(height mapping);另一种方法则是使用一张法线纹理(normal map)来直接存储表面法线,这种方法又被称为法线映射(normal mapping)。尽管我们常常将凹凸映射和法线映射当成是相同的技术,但读者需要知道它们之间的不同。

1.高度纹理

我们首先来看第一种技术,即使用一张高度图来实现凹凸映射。高度图中存储的是强度值(intensity),它用于表示模型表面局部的海拔高度。因此,颜色越浅表明该位置的表面越向外凸起,而颜色越深表明该位置越向里凹。这种方法的好处是非常直观,我们可以从高度图中明确的知道一个模型表面的凹凸情况,但缺点是计算更加复杂,在实时计算时不能直接得到表面法线,而是要由像素的灰度值计算而得,因此需要消耗更多的性能。

图7.11 高度图

高度图通常会和法线映射一起使用,用于给出表面凹凸的额外信息。也就是说,我们通常会使用法线映射来修改光照。

2.法线纹理

由于法线向量由几何方式存储,而纹理存储的是颜色信息,所以这二者的转换不那么直接。纹理中颜色由r、g、b三个组件构成向量表示,法线由x、y、z三个组件构成向量表示。法线的组件取值范围为[-1,1],颜色的范围是[0, 1],所以我们需要进行一下映射:

vec3 rgb_normal = normal * 0.5 + 0.5; // transforms from [-1,1] to [0,1]  

通常使用的映射就是:pixel=(normal+1)/2。这就要求,我们在Shader中对法线纹理进行纹理采样后,还需要对结果进行一次反映射的过程,以得到原先的法线方向。反映射的过程实际上就是使用上面映射函数的逆函数:normal=pixel x 2-1

然而,由于方向是相对于坐标空间来说的,那么法线纹理中存储的法线的方向在哪个坐标空间中呢?对于模型顶点自带的法线,它们是定义在模型空间中的,因此一种直接的想法就是将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为是模型空间的法线纹理(object-space normal map)。然而,在实际制作中,我们往往会采用另一种坐标空间,即模型顶点的切线空间(tangent space)来存储法线。对于模型的每个顶点,它都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身,而z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而y轴可以由法线和切线叉积得到,也被称为是副切线(bitangent,b)或副法线,如下图所示

图7.12 模型顶点的切线空间。其中,原点对应了顶点坐标,x轴是切线方向(t),y轴是副切线方向(b),z轴是法线方向(n)

3.这里插入补充切线空间的概念

参考知乎为什么要有切线空间(Tangent Space),它的作用是什么?

什么是切线空间?
Tangent Space,其实是一个坐标系,也就是原点+三个坐标轴决定的一个相对空间,我们只要搞清楚原点和三个坐标轴是什么就可以了。在Tangent Space中,坐标原点就是顶点的位置,其中z轴是该顶点本身的法线方向(N)。另外两个坐标轴就是和该点相切的两条切线。这样的切线本来有无数条,但模型一般会给定该顶点的一个tangent,这个tangent方向一般是使用和纹理坐标方向相同的那条tangent(T)。而另一个坐标轴的方向(B)就可以通过normal和tangent的叉乘得到。

image.png

image.png

通常我们所见的法线纹理还是基于原法线信息构建的坐标系来构建出来的。那种偏蓝色的法线纹理其实就是存储了在每个顶点各自的Tangent Space中,法线的扰动方向。也就是说,如果一个顶点的法线方向不变,那么在它的Tangent Space中,新的normal值就是z轴方向,也就是说值为(0, 0, 1)。但这并不是法线纹理中存储的最终值,因为一个向量每个维度的取值范围在(-1, 1),而纹理每个通道的值范围在(0, 1),因此我们需要做一个映射,即pixel = (normal + 1) / 2。这样,之前的法线值(0, 0, 1)实际上对应了法线纹理中RGB的值为(0.5, 0.5, 1),而这个颜色也就是法线纹理中那大片的蓝色。这些蓝色实际上说明顶点的大部分法线是和模型本身法线一样的,不需要改变。总结一下就是,法线纹理的RGB通道存储了在每个顶点各自的Tangent Space中的法线方向的映射值。

为什么要有切线空间
实际上,法线本身存储在哪个坐标系中都是可以的,例如存储在World Space、或者Object Space、或者Tangent Space中。

但问题是,我们并不是单纯的想要得到法线,后续的光照计算才是我们的目的。不管使用哪个坐标系,都面临着一个选择,就是最后光照计算使用的坐标系究竟是哪个。对于Tangent-Space Normal Map,我们一般就是在Tangent Space里计算的,也就是说,我们需要把viewDir、lightDir在Vertex Shader中转换到Tangent Space中,然后在Fragment Shader对法线纹理采样后,直接进行光照计算。

Tangent-Space还有如下一些优点:

  • 自由度很高。Tangent-Space Normal Map记录的是相对法线信息,这意味着,即便把该纹理应用到一个完全不同的网格上,也可以得到一个合理的结果。
  • 可进行UV动画。比如,我们可以移动一个纹理的UV坐标来实现一个凹凸移动的效果,这种UV动画在水或者火山熔岩这种类型的物体会会用到。
  • 可以重用Normal Map。比如,一个砖块,我们可以仅使用一张Normal Map就可以用到所有的六个面上。
  • 可压缩。由于Tangent-Space Normal Map中法线的Z方向总是正方向的,因此我们可以仅存储XY方向,而推导得到Z方向。
4.继续冯乐乐的,我觉得例子举得非常好
图7.13 左图:模型空间下的法线纹理。右图:切线空间下的法线纹理

从图中可以看出,模型空间下的法线纹理看起来是“五颜六色”的。这是因为所有法线所在的坐标空间是同一个坐标空间,即模型空间,而每个点存储的法线方向是各异的,有的是(0,1,0),经过映射后存储到纹理中就对应了RGB(0.5,1,0.5)浅绿色,有的是(0,-1,0),经过映射后存储到纹理中就对应了(0.5,0,0.5)紫色。

而切线空间下的法线纹理看起来几乎全部是浅蓝色的。这是因为,每个法线方向所在的坐标空间是不一样的,既是表面每点各自的切线空间,这种法线纹理其实就是存储了每个点在各自的切线空间中的法线扰动方向。也就是说,如果一个点的法线方向不变,那么在它的切线空间中,新的法线方向就是z轴方向,即值为(0,0,1),经过映射后存储在纹理中就对应了RGB(0.5,0.5,1)浅蓝色。而这个颜色就是法线纹理中大片的蓝色。这些蓝色实际上说明顶点的大部分法线和模型法线本身是一样的,不需要改变。

总体来说,模型空间下的法线纹理更符合人类的直观认识,而且法线纹理本身也很直观,容易调整,因为不同的法线方向就代表了不同的颜色。但美术人员更喜欢用切线空间下的法线纹理。那么为什么他们更偏好使用这种看起来“很蹩脚”的切线空间呢?

实际上,法线本身存储在哪个坐标系里都是可以的,我们甚至可以选择存储在世界空间下。但问题是,我们并不是单纯的想要得到法线,后续的光照计算才是我们的目的。而选择哪个坐标系意味着我们需要把不同的信息转换到相应的坐标系中。例如,如果选择了切线空间,我们需要把从法线纹理中得到的法线方向从切线空间转换到世界空间或其它空间中。

5.总体来说,使用模型空间存储法线的优点如下:

(1)实现简单,更加直观。我们甚至不需要模型原始的法线和切线等信息,也就是说,计算更少。生成它也非常简单,而如果要生成切线空间下的法线纹理,由于模型的切线一般和UV方向相同,因此想要得到比较好的法线映射就要求纹理映射也是连续的。

(2)在纹理坐标的缝合处和尖锐的边角部分,可见的突变(缝隙)较少,即可以提供平滑的边界。这是因为模型空间下的法线纹理存储的是同一坐标系下的法线信息,因此在边界处通过插值得到的法线可以平滑变换。而切线空间下的法线纹理中的法线信息是依靠纹理坐标的方向得到的结果,可能会在边缘处或尖锐的部分造成更多可见的缝合迹象。

6.但使用切线空间有更多优点:

(1)自由度很高。模型空间下的法线纹理记录的是绝对法线信息,即可用于创建它时的那个模型,而应用到其它模型上的效果就完全错误了。而切线空间下的法线纹理记录的是相对法线信息,这意味着,即便把该纹理应用到一个完全不同的网格上,也可以得到一个合理的结果。

(2)可进行UV动画。比如,我们可以移动一个纹理的UV坐标来实现一个凹凸移动的效果,但使用模型空间下的法线纹理会得到完全错误的结果。原因同上。这种UV动画在水或火山熔岩这种类型的物体上会经常用到。

(3)可以重用法线纹理。比如一个砖块,我们仅使用一张法线纹理就可以用到所有的六个面上,原因同上。

(4)可压缩,由于切线空间下的法线纹理中的法线z方向总是正方向,因此我们可以仅存储XY方向,从而推导得到Z方向。而模型空间下的法线纹理由于每个方向都是可能的,因此必须存储3个方向的值,不可压缩。参考Shader小常识之——法线纹理在切线空间下的存储,假设法线向量为(x,y,z),它可以由(x,y,0)和(0,0,z)两条边组成,既然法线向量是一条单位向量,这两条边构成的三角形又是直角三角形,那么其实只要知道一条边长,就可以用勾股定理计算出另一条边长。实际上法线纹理只存储了xy信息,对应shader代码:

fixed3 bump = UnpackNormal(tex2D(_BumpTex, i.uv.xy)).xyz;
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));

切线空间下的法线纹理的前两个优点足以让很多人放弃模型空间下的法线纹理而选择它。从上面的优点可以看出,切线空间在很多情况下都优于模型空间,而且可以节省美术人员的工作。因此我们也使用切线空间下的法线纹理。

八、前置知识二 Displacement mapping

参考置换贴图,法线贴图,凹凸贴图的区别,这里转载部分内容:

1.Normal Map的缺点

Normal Map看来可以增加细节,但是它的缺点也很明显。不过在说缺点之前,要提前说一句--Normal Map带来的优势是远远大于它的缺点的。因此仍然是个极好的东西,不要对它有偏见,特别是在我们后面介绍的更牛的技术前面,千万不要。

最大的也是最明显的缺点应该就是它的视角问题。因为Normal Map只是改变的表面上的光照结果,并没有改变表面上的形状。因此,表面上看来,似乎只要是不接近水平,NormalMap就不会有视角问题。其实不然,NormalMap因为不能实现自身内部的遮挡,因此不能表现平面上凹凸起伏比较大的场合。

比如说我们一个桌面上突出一块,然后在突出的这块东西边上放一支牙签。根据经验,这个凸起会很轻易的挡住我们的视线,让我们看不见那支牙签。可是Normal Map却不会这么做。因此我们一直能看见障碍物背后的东西,这一点是个问题--也就是说只有在垂直于平面的时候NormalMap才会发挥最好的作用。这样一来,Normal Map只能用在大家对遮挡关系不敏感的场合,比如场景等,不是不能用于人物,而是用Normal Map的人物不太经得起特写,放大了,角度刁钻了都容易穿帮。

虽然Normal Map有个不能平视的巨大问题,但是依然是好处远大于小障碍,因此还是非常值得推广的。后面的几种新兴算法其实都是由Normal Mapping发展起来的,因此做为基础的东西,也还是最有理解价值的。

2.Parallax mapping 视差贴图

视差贴图是一种NormalMapping算法的增强算法,其本质上和NormalMapping没有区别。优势是只需要增加3个HLSL语句和一个控制纹理通道(只需要几个GPU指令,代价小到可以忽略)就可以显著的增加物体表面的深度感。但是NormalMap中出现的问题,Parallax mapping基本上都有--特别是视角接近平行的时候,凹凸感消失的问题,并没有明显改善--其实这个使用NormalMap带来的问题就像是液晶屏的可视角度问题一样令人挥之不去。或者按照FXCarl个人的说法--Parallax mapping才是真正具有实用价值的NormalMapping。

Parallax mapping使用的还是单张的控制纹理,一张NormalMap。如果我们用AcdSee来看这张NormalMap,我们会发觉似乎和NormalMapping用的控制纹理是一样的。而如果我们打开这张NormalMap的Alpha通道,就会发现其中的玄机所在。原来Alpha通道里存储的是对应这张NormalMap的BumpMap!(就是HeightMap,就是用饱和度记录表面高度)

Parallax mapping是如何达到增加NormalMap的效果的呢。我们要从NormalMap的特性说起。我们假设在NormalMap表面制作一个凸起。然后我们转转角度看看。我们会发现,其实这个凸起的背对我们视线的面~并不会因为我们视角的逐渐放平而消失--这显然是不正确的,要知道背后的东西应该是看不见的才对。

因此Parallax mapping就是来缓解这个问题的,具体的代码这里不提。我来试着白话解释一下原理。其实为了不让我们看见“不该看的东西”应该试着挪动纹理坐标……把那个不该给玩家看见的图素(Texel)跳过去。也就是说根据高度图提供的数据,把那个位置较低那个纹理的后面的纹理向前拉。相当于在图素采样的时候刻意的把那个图素跳过去。这样那个不该被玩家看见的像素就会因为图素的消失而不见了--

很明显,这个算法是不太站得住脚的,虽然计算的时候会参考玩家视线的角度。但仍然是一种来自于经验的估算。值得欣慰的是,对于本身NormalMap所需要表现的微小细节来说,这样的改进已经看上去不错。因此开始有大量的游戏决定采用。特别是它的优点是所消耗的代价极为有限,而需要增加的工作量只是让美工把高度图保存到Alpha通道里而已。很划算。

3.Displacement mapping 位移贴图 置换贴图

和前面说的几种方式不同,DisplacementMapping是一种真正改变物体表面的方式。通过一种称为micropolygons(微多边形)tessellate(镶嵌)的技巧来实现真正的改变物体表面的细节。

具体流程是这样的。首先,根据屏幕的分辨率,在模型的可见面上镶嵌和最终象素尺寸相同的微多边形。这个过程叫做镶嵌。然后读取一张Bump贴图。根据表面的灰度确定高度。然后根据镶嵌所得到的多边形,沿着原先的表面法线方向移动微多边形。接着再为新的多边形确定好新的法线方向。此时,物体的表面确实已经真的增加出了细节。

其实这种技巧,我们在使用ZBrush的时候就可以看见了。大家用过Zbrush的时候会知道,在表面刷过的细节,只有在画面静止下来之后才会越来越清晰。而微多边形镶嵌起到的就是类似的作用。只增强面对屏幕的多边形的表面粗糙细节,而不是整个模型。因此性能代价并不会像直接上高模那么大。相比来说位移贴图在效果上是没有任何瑕疵的,但是也未必没有缺点。

首先就是,对硬件的要求很高,必须支持ShaderMode3.0才可以,因为只有支持SM3才可以在顶点阶段进行纹理操作。同时镶嵌对于性能的消耗也不小。不过其实就对于GPU的压力而言,反而似乎要更合理一些(因为对顶点的运算要求提高,对象素级别的运算要求反而没有影响)想必在将来的DX10统一渲染构架中会更有价值。

和我们介绍的所有凹凸贴图技术相比,位移贴图是唯一真正改变多边形表面几何形状的方法。相比之后将要介绍的切空间光线追踪算法,这种算法的性能消耗虽然并不占优,但其实要更为合理。给予画面更多特效的机会,同时更有趣的是,其实他和其他基于象素着色的凹凸贴图并没有什么冲突。其实这种位移贴图在新世代主机的游戏中大家都有可能见到。

九、现在可以从闫老师P10第17分钟继续看了
1.如何算法线
image.png

如图,在2D空间中,假设原本的水平线被拉伸成蓝色的凹凸线,对任意一点,如何求出新的法线向量n呢。这里是先求导算出切线dp,相当于是(1,dp)。然后逆时针旋转90度,即点乘为0。很明显,(1,dp)点乘(-dp,1)正是0。

然后推广到3D空间中,UV的法线:


image.png

这里先算出u,v变化一个单位时,这个点如何变化。然后就能算出切线方向如何变化,然后再算法线。注意这里perturbed normal是扰动法线,而开头的normal n(p) =(0,0,1)说明这个过程,已经是在切线空间中在做计算。视频中写的local coordinate我觉得就是切线空间。

关于如何计算,也可以扩展阅读切线空间(Tangent Space)完全解析

2.Displacement mapping 位移贴图 置换贴图

凹凸贴图会露馅,在边缘和阴影都看不到凹凸感,而位移贴图就有,因为它真的改变了物体表面。


image.png

关于位移贴图,它本身就要求模型面数非常细,细到顶点的间隔比纹理还要高。但是我们不希望模型那么细,DirectX有Dynamic的曲面细分插值法,根据需要对模型做插值,看情况把模型变得细致。

3.Procedural textures

三维的纹理,定义了空间中任意一点的值。对于这种纹理,并没有真正生成这个纹理的图,它们定义了三维空间的噪声函数,函数经过各种处理,可以变成需要的样子。这个在闫老师后续的课程中还会提到。


image.png
4.Precomputed Shading 预计算着色

用空间换时间,先计算好环境光遮蔽贴图,然后再把纹理贴上。相当于阴影算好了,直接贴上去。


image.png
5.Solid modeling & Volume rendering

三维纹理广泛应用到体渲染之中,比如核磁共振等扫描后,得到体积的信息,通过这些信息进行渲染,得到结果


image.png
十、看完视频之后的扩展知识
1.Unity官方文档

Unity 官方文档 法线贴图(凹凸贴图)
Unity 官方文档 高度贴图

2.美术如何做贴图

法线与置换贴图原理讲解以及烘焙制作!
让你彻底搞清楚凹凸、法线、置换的区别(课程涉及影视和次世代PBR流程中,bump、nomal、displacement和矢量(向量)置换等

3.Parallax Mapping

视差贴图(Parallax Mapping)学习笔记

4.Ambient Occlusion

视频P10进度35分钟左右,提到了Ambient Occlusion。在看这段知识之前,和以前一样,建议先阅读一下:
Unity Shader-Ambient Occlusion环境光遮蔽(AO贴图,GPU AO贴图烘焙,SSAO,HBAO),以下为部分摘选:

环境光遮蔽对效果的提升有多重要,看一下顽皮狗在《Uncharted 2: HDR Lighting》的一个对比图,可以看到,左侧的车底遮挡了大部分光线,形成了阴影看起来很自然,而右侧的车感觉就像飘在上面一样,看起来比较假:


image.png

环境光遮蔽(Ambient Occlusion),最经常听到的应该是它的缩写AO。既然名字本身就带Ambient,说明其本身是对于环境光强度的一种控制,所以有必要来先了解一下环境光的计算。

光照是可以线性叠加的,一般来说最终的光照结果 = 直接光照 + 间接光照。我们计算物体的直接光照效果时,可以直接通过BRDF计算,而环境光属于间接光照,要想计算真正的环境光,需要在该点法线方向所对应的半球积分计算,在离线渲染的情况下也只能通过蒙特卡洛积分等方式近似计算,对于光线追踪的方式渲染的情况,自然可以得到比较好的效果,但是即使现在的RTX似乎也不能真正地实时跑光线追踪,所以在实时渲染领域,环境光一般使用的就是环境贴图(SkyBox,Reflection Probe),球谐光照(Spherical Harmonic Lighting),光照贴图(Light Map,需要用离线烘焙),甚至直接加一个固定的环境光值(简单粗暴,比如Unity中的UNITY_LIGHTMODEL_AMBIENT宏)。普通光源的遮挡效果也就是阴影,我们可以通过Shadow Map,模板阴影等来实现,但是对于环境光的遮挡效果,半球上的光线自然没有方法用普通的Shadow Map方式来计算了。所以研究怎样遮挡环境光的强度的就叫环境光遮蔽。

环境光遮蔽主要用来控制物体和物体相交,夹角,褶皱等位置遮挡漫反射光线的效果,简单来说就是某一点对于环境的暴露比例,如果是平面,那么没有遮蔽;如果是夹角,褶皱等那么周围的面就会遮蔽一部分环境光,就导致该点的环境光相对较弱。如果环境光没有遮蔽效果,那么不管褶皱还是平面,环境光照结果是一致的。而环境光遮蔽可以使褶皱,夹角等位置的光照效果变弱(比如一根管子,在管口的位置应该比较亮,而越向内,应该越暗),提高暗部阴影效果达到一种近似自阴影的效果,提升画面的层次感,增加细节。

几种主流的环境光遮蔽效果:

  • AO贴图(使用预烘焙的贴图,实现离线的基于GPU的烘焙AO贴图的工具)
  • SSAO(屏幕空间环境光遮蔽)
  • HBAO(水平基准环境光遮蔽)

扩展阅读:
Unity 官方文档 遮挡贴图
Ambient Occlusion(AO)使用指南
游戏后期特效第四发 -- 屏幕空间环境光遮蔽(SSAO)

接着看视频,弹幕有人提出,AO不会随光源角度发生变化。有人回答,确实是这样,所以应用在不受光影响的地方,也可以说AO属于自阴影。

你可能感兴趣的:(图形学笔记七 纹理和贴图 AO)