此为个人学习笔记,总结内容来源于网络各个平台,如有错误欢迎指摘
本节内容附加资料:
一个顶点之中包含的信息可能有:
等一切跟顶点相关的信息都可以保存,只需要能够让顶点着色器正确处理即可
而面的一些信息可由顶点信息插值获得,例如UV、颜色
顶点法线是用于光照信息处理的,而面法线只是垂直于面的一个向量,用于规定面的正反,即便是建模软件中,顶点法线的最初是的默认情况也并非是面法线的平均,只有当在建模软件中对物体进行了平滑着色后,才会根据面法线平均得到顶点法线
着色(shading):
对实例通过使用线或颜色块进行明暗或颜色的变化
在此次课程中可理解为对不同物体使用不同材质(material)的过程
材质:
材质在渲染程式中,它是表面各可视属性的结合,这些可视属性是指表面的色彩、纹理、光滑度、透明度、反射率、折射率、发光度等。正是有了这些属性,才能让我们识别三维中的模型是什么做成的,也正是有了模型材质。
本文所要介绍的是局部光照模型,并不是真正准确的模型,但是优点是计算快,效果可以接受,至今依然广泛的运用在各种游戏之中
于是着色将发生在局部,即每一个shading point
接下来就将定义更多着色的内容
由于使用的是局部光照模型,将每一个着色点作为起点定义向量
另一点需要注意的是,着色中不会有阴影被渲染
之所以物体能被我们观察,是因为人眼接收到了从物体来的光。 没错,这其实也就是局部光照模型的基础,可以具体看看究竟有几种类型光线能从物体到人眼呢
图中三种类型的光分别为:
接下来将具体讲解这三种类型的光
漫反射,是投射在粗糙表面上的光向各个方向反射的现象。当一束平行的入射光线射到粗糙的表面时,表面会把光线向着四面八方反射,所以入射线虽然互相平行,由于各点的法线方向不一致,造成反射光线向不同的方向无规则地反射,这种反射称之为“漫反射”或“漫射”。
简而言之如图所示
与现实世界中的漫反射些许不同,这里做出了一些简化
于是漫反射在着色点的情况都被定义出了,即颜色与亮度,颜色是物体的属性,那么亮度如何计算出呢
类似于投影一样,使用光照方向与着色点的法线方向计算出夹角,即两向量点乘结果,求出光被着色点实际接收的量
常识中离光源近的物体会比离得远的物体更亮,于是现在另一点需要知道的是,光在空间中传播是如何衰减的
图中中心为一个点光源,光线均匀的向周围发射,假定能量守恒,那么在任意两个垂直于光线的圆面上接受到的能量之和一定相等。而离圆心越远,圆的面积越大,单位面积所接受能量也就越弱
因此将光强 I I I关于距离 r r r满足平方反比定律
由此漫反射可定义为
图中内容为
值得注意的是漫反射公式中并没有用到观测方向 v v v,附加上颜色一致,因此在任意位置观察到达漫反射效果均一致
如同字面意思一般,像是镜子反射一样的亮光指光源照射到物体然后反射到人的眼睛里时,物体上最亮的那个点就是高光。高光不是光,而是物体上最亮的部分
不难分析得出,当观测方向与光源方向关于法线的夹角接近或相等时,这一着色点会产生高光
或者说,是观测方向与光源方向的中线接近于法线
图中内容为
通常情况下仅在很小的范围会产生高光,但普通的 c o s α cos α cosα函数阈值很大,需要叠加多次幂增加高光衰减度
具体情况如图所示
除去直接被物体接受到的光,还有很多光是经过其他物体的多次反射而最终到达物体的光,我们需要真的计算那么多次吗
Blion-Phong Model作为一种经验模型,给出了经验的效果, L a La La仅仅由一个系数 k a k_a ka和一个亮度 I a I_a Ia简单点乘得到
在上文中我们讲解完了局部光照模型,其中主要利用了观察方向,入射光线与法线向量的位置关系,但并没有具体说究竟是三角形面的法线向量还是三角形顶点的法线向量,这也就牵扯出了本章内容——着色频率
着色频率(面着色,顶点着色,像素着色),这3种不同的着色频率其实也就对应了三种不同方法
面着色,顾名思义以每一个面作为一个着色单位。模型数据大多以很多个三角面进行存储,因此也就记录了每个面的法线向量,利用每个面的法线向量进行一次Blinn-Phong反射光照模型的计算,将该颜色赋予整个面
Flat Shading 虽然计算很快,只需对每一个面进行一次着色计算,但是效果确是很差的,可以很明显的看到一块块面形状
Gouraud Shading会对每个三角形的顶点进行一次着色
可我们只有每个面的法线向量,如何得到每个顶点的法线向量呢
做法其实很简单,将所有共享这个点的面的法线向量加起来求均值,最后再归一化就得到了该顶点的法线向量了
有了每个三角形的顶点向量之后,自然就可以计算出每个顶点的颜色了,那么对于三角形内部的每一个点应该怎么办呢
如果你还记得之前提到过的插值,你就可以理解下面的公式了
其中 c c c为插值出的点的颜色, c 0 , c 1 , c 2 c_0, c_1, c_2 c0,c1,c2为三角形三个顶点的颜色, α , β , γ α, β, γ α,β,γ为三角形面内的重心坐标
重心坐标将会在下一节讲授
由此还可以利用重心坐标插值出更多属性,例如法线
既然要对每个点都进行光照计算,那么自然我们应该要有每个点的法线向量才可以,Phong Shading的做法是利用重心坐标插值出三角形面内任一点的法线向量
与颜色的公式类似, n n n代表法线,对于方向请记得要归一化
着色频率是应用着色模型的基础量,从三角形面、三角形面顶点再到三角形面内每一个点,性能的开销是可想而知的,那么效果一定就会好吗
对于低模来说,是这样的
但随着模型面数的增加,三种频率的效果反而会趋于相近
但性能开销却不会趋于相近
合适的,比更好的更好
在上一章的作业中有这样的一行代码,作为注释中的附加代码,用于插值求出深度值,这就是应用了重心坐标
auto[alpha, beta, gamma] = computeBarycentric2D(x, y, t.v);
由上行代码可知,重心坐标具有三个参数,并且需要三角形的三个顶点和一个目标点求出,事实上确实如此
重心坐标的三个参数是可以出现负值的,但只有三个值都是非负项点才会在三角形内
可是三个参数都是怎么来的呢
α , β , γ \alpha,\beta,\gamma α,β,γ分别对应这个点靠近三角形 A , B , C A,B,C A,B,C的程度,由此可用分割出的三角形面积占比求出
使用行列式的几何意义算出各个三角形的面积,均为列向量
具体计算方式如图所示
重心坐标在图形学中最重要的运用便是插值,他可以根据三个顶点A,B,C的属性插值出任意点的属性,无论是位置,颜色,深度,法线向量等等,而这些属性在之后的着色或是消除隐藏曲面都有很大的作用
如果你仔细阅读了上图,最后一行加粗的字意为
重心坐标在投影变换后并不会保持不变
此处内容搬运自本节参考内容,可跳过推理过程直接记住结论,且作业框架中并没有进行校正
正如上文中所提到的,我们的重心坐标往往都是在屏幕空间下所得到的,如果直接使用屏幕空间下的重心坐标进行插值会造成一定的误差,与在view space下是不一样的
这个问题可由图解释出,在屏幕空间image plane上点c的intensity为0.5,但在实际的AB上这点的intensity并不为0.5,那么本节内容就会具体介绍如何矫正这种误差,利用屏幕空间下的重心坐标达到正确的插值
首先先分别定义屏幕空间的比例为s,view space中为t,其余符号含义如下图所示:
为了简便证明,将点的坐标用2维表示,第一维为图中所示的x轴,第二维为z轴。 简而言之,我们的目标就是得出t与s的关系式,这样就可以正确的利用屏幕空间的系数s插值到正确的view space的结果
由上图所示的投影所造成的三角形相似性可以轻易得出如下几个式子:
同样,分别利用screen space 以及 view space的线性插值可以得到以下几个式子:
将4式与5式代入3式得:
再将1式与2式代入7式得:
最后将6式代入8式的左边得到:
仔细观察9式,我们已经成功得出了t与s的一个关系式,其中的其它参数都是已知,因此进一步化简不在这里展开,可以得到如下t与s关系:
如此就可以利用屏幕空间下的系数得到正确插值结果了,计算如下:
以上证明虽然只是针对线性插值的矫正结果,对于重心坐标插值,我们可以类似类推得出:
正确得出深度的插值结果之后,再看看任意属性(法线向量,纹理坐标,view space 坐标)插值结果,依然以线性插值为例先进行推导:用 I I I代表任意属性
不难看出插值的分母就是深度值的倒数,类推得出重心坐标任意属性的正确插值如下:
任意属性自然包括深度值,将深度值z代入16式可以得出与12式一样的结果
至此,我们就可以利用16式进行所有的属性的重心坐标插值,并且保证结果正确了!
所谓图形渲染管线指的是一系列操作的流程,这个流程具体来说就是将一堆具有三维几何信息的数据点最终转换到二维屏幕空间的像素
整个图形管线的步骤可能有不同的分法,不一定就是上图所述的5部分,但整体流程一定是一样的
顶点处理的作用是指对所有的顶点数据进行Model,View,和Projection的变换,最终得到投影到二维平面的坐标信息(同时为了Z-buffer保留深度z值)
顶点信息如果超出观察空间会被剪裁掉,具体内容请在本节参考资料中查看
将所有的顶点按照原几何信息,变成三角面,每个面由3个顶点组成
在得到了需要显示的图形后,这里将确定出像素需要显示多少,例如像素Pixel或者片元Fragment,像素即图中的小方格,而片元则是采样点,片元可比像素更小,例如在之前MSAA 2*2的代码中,我们将一个像素分配出了四个片元
使用深度测试来判断哪些像素点应该被显示出来
片元着色,给物体赋予材质
上个图片使用的是Blinn-Phong模型,其中的 k d k_d kd属性可由一张图片提供,这部分将接下来介绍
最后一步Framebuffer的处理,就是将所有的像素颜色信息整合在一起,输送给显示设备加以显示
此处并未涉及过多内容
对于shader而言只需要给出单个顶点或者片元的操作即可,无需使用循环
另附一个shader在线书写的网站
在之前的小节中,我们在表示漫反射系数时使用的是一个三维向量来描述颜色,但是在实际情况中颜色的情况会复杂的多,情况如图中所示
无论是地板的木纹或者是球体的图案,单纯使用向量来描述颜色将会变得非常困难,这里需要引入纹理的概念
三维的物体其表面会是二维的,如图中的地球仪所示,使用两个系数就可以将三维物体的表面一点与二维平面的一点联系起来
在纹理坐标中,使用两个介于0到1的系数, u u u和 v v v来定位纹理的位置,如图中红绿颜色的变化可以看出位置变化情况
建立联系后,每个三角形会从纹理图中获取颜色
建立联系的过程不在此次课程讨论之内,通常由模型制作者完成
有些情况下物体表面只是特定的纹理而已,没有过多的细节,例如砖墙或者木纹,这时可以使用一张纹理图就完成整个物体的映射
例如上面的这个场景,使用的纹理图只是一张,这一点可以在下面的纹理坐标中得到验证
这样的贴图可不是随便就能用的,其必须满足四方连续,即对应的边界相接可以无缝拼接
在上面提到的纹理映射中,获取的都是漫反射系数,操作过程如下图所示
纹理除了漫反射系数外,还可以提供更多的信息,例如法线、置换、凹凸、AO、光泽度等等
理想状态下的纹理中每一个像素点都将被一比一地对应到物体中,但如果这个平衡被打破了呢
这种情况十分常见,例如你从网上下了一张图片设为了桌面背景,却发现模糊不堪,可能它只是一张54×54的缩略图,你却放到了1920×1080的桌面上
纹理过小就会导致纹理空间的一点被过多的屏幕空间点采样到
如图所示,红点是屏幕空间下一个像素对应在图片中的位置,在上述的极端情况下,一个纹理中的颜色点可能会被多个像素对应到,产生的情况就如图打了马赛克一样
如图中的Nearest所示,如果放任像素采样到最近的贴图中,就会出现这种情况
而后面的Bilinear 双线性和Bicubic 双立方则是接下来要提到的优化方案
这里又提到了插值,之前在MSAA处使用的思想在这里又一次使用上了
选取从屏幕空间对应来的点四个最近的在纹理空间中的点
L i n e a r i n t e r p o l a t i o n ( 1 D ) Linear\;interpolation\;(1D) Linearinterpolation(1D): l e r p ( . . . ) lerp(...) lerp(...)是一种插值函数,在 l e r p ( . . . ) lerp(...) lerp(...)中的三个参数分别为距离 v 0 v0 v0的比例,即图中的 t t t和 s s s,起点与终点,有关 l e r p ( . . . ) lerp(...) lerp(...)函数的更多内容请阅读本节参考资料
通过一次线性插值获取到水平方向的插值 u 1 u_1 u1和 u 0 u_0 u0,之后再使用这两个值作为起点与终点进行第二次线性插值
通过使用两次线性插值获取到这一点的颜色,即双线性插值
关于双立方插值请参考资料
纹理过大会比纹理过小更难解决
这个问题可以这样思考:在天上的星星看起来只有一个亮点,但实际上它们的表面或许是不规则的坑坑洼洼的,因为它的许多信息都汇聚成了一个点后才被我们看到
而纹理过大就会导致屏幕空间的一点包含了过多的在纹理空间中的信息
在图中从左到右表示了之前举的例子的情况,最左侧的蓝色点代表了屏幕空间的像素,黑色点代表纹理空间的像素,淡蓝色的区域为该屏幕空间像素点对应到纹理空间的范围,从最开始的几乎一对一,到一对多,有许多细节在此过程中丢失
这种现象被形象的成为屏幕像素在texture空间的footprint
具体情况如图所示,在远处的一点可能对应到了超过一整个纹理的大小,从而产生了走样
是否可以使用抗锯齿来反走样呢?
可以但代价高昂
与纹理过小相反的是,在纹理过大的情况下,我们不能单纯地把屏幕空间的一点对应到纹理空间后,选取最近的一点作为该屏幕空间点的颜色,我们需要用这一点代表它所覆盖的一整个范围的颜色
我们需要的是范围查询
这是一项提供正方范围查询的,快速的,近似的技术
mipmap就像是把答案写好后来进行范围查询,它提供了多个层级的纹理,层级越高表示范围越大,这会增加三分之一的空间占用
现在将说明如何确定该使用哪一层级的纹理来返回给屏幕空间的像素点
选取这一点的周围点,并在纹理坐标中标记处来,图中为上方和右方的点
在纹理空间中分别求出这一点距离两个相邻点的距离,选取其中的最大值作为 L L L,再算出 D D D的值,即是mipmap的层数,关于为什么要 D = l o g 2 L D=log_2L D=log2L的问题,因为mipmap每层的长宽都是上一层的一半
D D D在实际情况中完全可能有出现小数的情况,但mipmap的层数都是整数,如果单纯地以四舍五入来近似求解的话,会出现突变的效果
图为mipmap层级的可视化结果,可以看到许多的突变效果
如何解决这一问题呢,依旧是插值
例如算出的 D = 1.4 D=1.4 D=1.4,那么就在 D = 1 D=1 D=1和 D = 2 D=2 D=2的mipmap中进行双线性插值,之后再将这两个结果再使用一次线性插值
得到的结果就好多了
以512x的超采样结果作为标准
那么mipmap可以做到什么情况呢
远处的情况被过分模糊了,还记得最开头提到的吗
mipmap是一项提供正方范围查询的,快速的,近似的技术
而对于非正方的情况mipmap处理本来就十分乏力,且插值的方法本就是近似,注定无法得到真实情况
但我们可以做的更好
与mipmap不同的是,各向异性过滤可以对非正方情况有更好的效果,mipmap即图中斜对角线的情况,而在这里会产生更多的被称为ripmap的层级,它所占用的大小将会是原来的三倍,且从图中可以看出,其代价不是简单的正相关关系,意味着16x的会比8x的占用大小不是简单的二倍关系,如果可以请拉满
但各向异性过滤仅仅能对长方的情况做出更好的优化,对于斜放的图形,依然没有更好的办法
更好的办法如下图便是其中一种
纹理=信息存储+范围查询
GPU需要的信息很多都可通过纹理存储,一般用于将数据发送给片元计算单元,应用如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ekw7dIom-1664499411064)(…/…/…/…/…/…/现代计算机图形学基础入门/image-20220621191111266.png)]
这里的环境光照只记录了位置信息,没有记录深度信息,认为所有的光都是无限远的
环境光可以记录在一个球面上,被称为Spherical Environment Map
但这种记录方式在展开时候会导致边缘失真,如同地球仪一样,纬度高的地方偏大,而接近赤道的地方才是真实尺寸
环境光可以记录在一个立方体表面上,被称为Cube Map
这样就可避免球面展开后的失真问题了,但在寻找光的信息在贴图上的位置时需要一定额外的计算,因为需要找出是在六个面的哪一个面存储了信息
两者的原理是相似的,都是通过改变物体原本的法线而使得在着色时使物体产生虚假的几何变换
他们的区别在于
凹凸贴图对物体没有实质性影响,仅在渲染阶段改变原本的法线,由此产生凹凸不平的效果
图中黑线为原本的物体表面,则 p p p点的法线 n ( p ) n(p) n(p)被如此定义
橙色线为凹凸贴图中提供的表面高度,由此做出这一点顺着法线方向的延长线,交点的垂线即改变后的法线 n n n
首先求解出在二维平面中的计算方法
由平面设出原本法线为 n ( p ) = ( 0 , 1 ) n(p)=(0,1) n(p)=(0,1),表示原本的法线都是垂直向上的,将所有法线都这样表示显然是不妥的,这个问题将在之后解决
以这个 p p p点为顶点构建出一个三角形,这里的 d p dp dp是一个梯度,或者说导数,代表着从 p p p点出发向右移动一个单位距离的同时向上移动了多少
它的计算方式即使用下一个点的高度与这个点的高度做出差值,同时除以移动的距离1,类似于一个 t a n tan tan函数的东西,再乘上一个系数 c c c,它将反应出凹凸强度或者是法线强度
得到切线后,做出它的垂线即是变换后的法线,使用一个简单的旋转变化即为 ( − d p , 1 ) . n o r m a l i z e d ( ) (-dp,1).normalized() (−dp,1).normalized(),它需要归一化以保证法线的长度为1
对于在三维情况中类比得到类似公式,推导省略
这里的一切都是基于一个局部坐标系之中计算的,得到的法线需要再次变换到世界空间中去,需要左乘一个TBN矩阵变换到世界空间去
此处仅做了解,具体内容可从附加资料中获取
上文已经提到过所有的计算都基于一个局部坐标系,它就是切线空间
法线贴图中的法线向量定义在切线空间中,在切线空间中,法线永远指着正z方向
切线空间是位于三角形表面之上的空间:法线相对于单个三角形的本地参考框架。它就像法线贴图向量的本地空间;它们都被定义为指向正z方向,无论最终变换到什么方向。使用一个特定的矩阵我们就能将本地/切线空间中的法线向量转成世界或视图空间下,使它们转向到最终的贴图表面的方向
这种矩阵叫做TBN矩阵这三个字母分别代表tangent、bitangent和normal向量,其中N是顶点包含的信息,是模型自带的
计算出切线和副切线并不像法线向量那么容易。从图中可以看到法线贴图的切线和副切线与纹理坐标的两个方向对齐。我们就是用到这个特性计算每个表面的切线和副切线的
可以将三角形的边与写成切线向量和副切线向量的线性组合,而这个方程又可以写成矩阵乘法,即TBN矩阵
只要知道可以用公式、三角形的两条边以及纹理坐标计算出切线向量和副切线,理解切线空间的含义即可
到此你可能充满疑惑,为什么要存在这样一个局部坐标系呢
由于法线向量是个几何工具,而纹理通常只用于储存颜色信息,用纹理储存法线向量不是非常直接,如果你想一想,就会知道纹理中的颜色向量用r、g、b元素代表一个3D向量
类似的我们也可以将法线向量的x、y、z元素储存到纹理中,代替颜色的r、g、b元素。法线向量的范围在-1到1之间,所以我们先要将其映射到0到1的范围:
vec3 rgb_normal = normal * 0.5 + 0.5; // 从 [-1,1] 转换至 [0,1]
将法线向量变换为像这样的RGB颜色元素,我们就能把根据表面的形状的fragment的法线保存在2D纹理中,如图所示:
这会是一种偏蓝色调的纹理(你在网上找到的几乎所有法线贴图都是这样的)。这是因为所有法线的指向都偏向z轴(0, 0, 1)这是一种偏蓝的颜色
法线向量从z轴方向也向其他方向轻微偏移,颜色也就发生了轻微变化,这样看起来便有了一种深度。例如,你可以看到在每个砖块的顶部,颜色倾向于偏绿,这是因为砖块的顶部的法线偏向于指向正y轴方向(0, 1, 0),这样它就是绿色的了
需要注意的是:OpenGL读取的纹理的y(或V)坐标和纹理通常被创建的方式相反。所以这样的法线贴图的y(或绿色)元素是相反的(即图中绿色的部分在下而红色在上)
正常来说,她应该被渲染成这样,但我们使用的那个法线贴图里面的所有法线向量都是指向正z方向的。上面的例子能用,是因为那个平面的表面法线也是指向正z方向的
可是,如果我们在表面法线指向正y方向的平面上使用同一个法线贴图会发生什么呢
光照看起来完全不对!发生这种情况是平面的表面法线现在指向了y,而采样得到的法线仍然指向的是z。结果就是光照仍然认为表面法线和之前朝向正z方向时一样;这样光照就不对了。下面的图片展示了这个表面上采样的法线的近似情况
你可以看到所有法线都指向z方向,而它们本该朝着表面法线指向y方向的
一个可行方案是为每个表面制作一个单独的法线贴图。如果是一个立方体的话我们就需要6个法线贴图,但是如果模型上有无数的朝向不同方向的表面,这就不可行了
如果把朝向各个方向的法线存储在同一张贴图上,会得到不只是蓝色的法线贴图,但这样的做法必须记住模型的起始朝向,如果模型运动了还要记录模型的变换,这是非常不方便的
另一个稍微有点难的解决方案是,在一个不同的坐标空间中进行光照,这个坐标空间里,法线贴图向量总是指向这个坐标空间的正z方向;所有的光照向量都相对与这个正z方向进行变换。这样我们就能始终使用同样的法线贴图,不管朝向问题。这个坐标空间叫做切线空间
于是这三者的关系可理解为:
这样就可以在方便计算的同时,满足不同朝向的法线需求
与凹凸贴图使用同一种纹理,但会真正地改变顶点位置
要求模型三角形面数够多,需要保证三角形频率不低于纹理频率才能有足够好的表现,在DirectX中有动态细分可判断三角形频率与纹理频率之间的关系,并完成细分操作
利用noise函数计算三维空间中任意一点的噪声,经过处理后得到特定形状,这个过程没有产生真正的纹理,它是动态得到的
纹理可记录着色信息,环境光遮蔽可计算好后写入贴图中,为模型添加丰富层次
记录模型的变换,这是非常不方便的
另一个稍微有点难的解决方案是,在一个不同的坐标空间中进行光照,这个坐标空间里,法线贴图向量总是指向这个坐标空间的正z方向;所有的光照向量都相对与这个正z方向进行变换。这样我们就能始终使用同样的法线贴图,不管朝向问题。这个坐标空间叫做切线空间
于是这三者的关系可理解为:
这样就可以在方便计算的同时,满足不同朝向的法线需求
[外链图片转存中…(img-yiUllwnu-1664499411074)]
与凹凸贴图使用同一种纹理,但会真正地改变顶点位置
要求模型三角形面数够多,需要保证三角形频率不低于纹理频率才能有足够好的表现,在DirectX中有动态细分可判断三角形频率与纹理频率之间的关系,并完成细分操作
[外链图片转存中…(img-QaDRREKX-1664499411074)]
利用noise函数计算三维空间中任意一点的噪声,经过处理后得到特定形状,这个过程没有产生真正的纹理,它是动态得到的
[外链图片转存中…(img-iyLXIpmo-1664499411075)]
纹理可记录着色信息,环境光遮蔽可计算好后写入贴图中,为模型添加丰富层次
[外链图片转存中…(img-lWLSZ1dD-1664499411075)]
在医学中使用广泛,记录了任何一个点的密度,这是一个三维纹理