09着色(插值、高级纹理映射)
重心坐标
基础定义
在三角形中的任意一点,可以写为ABC三点坐标的一个线性组合,即(x,y)=\alpha A+\beta B+\gamma C特别的,当 \alpha + \beta+\gamma =1 的时候,这个点表示的是三角形的重心坐标。
几何定义
几何观点-利用面积求重心位置
由这个定义,可以求出重心的坐标。
重心坐标公式
可以通过几何定义和行列式求出重心坐标的公式:

插值
- 让三角形内部有一个平滑的过度
- 插值可以是纹理坐标,颜色,法向量等等
- 通过三角形的重心进行线性插值
- 三角形的重心,在三角形投影前后并没有保持不变性,因此我们做法是在三维的图像中先寻找到重心坐标之后再进行插值,而非投影到二维在寻找重心坐标插值。
透视矫正插值
我们的重心坐标往往都是在屏幕空间下所得到的,如果直接使用屏幕空间下的重心坐标进行插值会造成一定的误差,与在view space下是不一样的。
具体解决方案:
https://zhuanlan.zhihu.com/p/144331875
这一点在作业3中有所体现。
纹理过大过小的问题
纹理过小的问题
纹理过小会导致显示出来的模型出现失真的现象。导致采样的时候,几个像素对应着一个texture,导致严重的走样问题。
假如我们的墙面是一个500500的一个墙面,而纹理贴图是一个100100的贴图,那么如果想把这个贴图贴在墙面上,那么贴图必然会被放大,也就是一个纹理的纹素会放大,那么几个像素点就会同时存在一个纹理图片当中,例如下面这一张图。
如果我们按照简单的方法,取样这个像素点对应的纹素的值,那么必然会出现几个像素具有相同的纹素返回值,呈现在屏幕上,就是颜色成块出现,有明显的小格子,不连续,非常不自然。
解决办法
对此我们想办法让这个过度变得自然,想到的一个方法就是插值。
双线性插值
分析:
- 由于纹理过小,使得多个像素点查询到的纹理像素的值相同,那么我们让这些返回值不同,不就使得颜色不同了,自然就不会形成一格格的分割。
- 由于颜色相同,并且在纹理像素中,每一个纹理像素对应的是一个值,例如下图,红点和蓝点虽然在不同的纹理像素中,但是这些点都是离散的,会使得图片变得不连续,不自然。
-
- 综合上面的分析讨论,我们给出的解决办法是双线性插值,不仅使得颜色值不同,而且使得离散的点连续化,就解决了上述遇到的问题了。
做法:
假设像素点P(x,y)红点所处纹理像素的坐标 u_{11}(u_{01},v_{11}) 其中 u_{11} 对应的纹理值为 R_{11}
- 确定所查找的像素点在纹理坐标中的位置,得到红点所处纹理像素的坐标 u_{11}(u_{01},v_{11})
- 通过像素点的性质得到附近的纹理像素点u_{10}(u,v-1),u_{01}(u-1,v),u_{00}(u-1,v-1)
- 读取这四个点对应的像素值,首先固定竖直方向的坐标不变,即v轴不变化,算出u_0与u_1的线性插值线性方程 R_1=R_{01}+(x-u_{01})/(u_{11}-u_{01})R_{11} ,R_2同理
- 通过代入x得到同一水平面上的插值,接着用这两个值R_2 $$ R_1,接着再进行插值,得到竖直方向的线性方程
- Target=R_{0}+(y-v_{00})/(v_{01}-v_{00})R_{1}
- 这样就得到了每一个插值之后的纹理值。
双向三次插值
相邻的16个点做水平和竖直的差值,利用三次方程进行两次插值,效果更好,但是计算速度很低
方法对比
上图分别为采用临近的最近的像素点,双线插值,双向三次插值得到的结果。
纹理过大的问题
纹理过大,即一个像素对应着多个纹理像素。例如渲染一个格栅状地面,纹理是一个小格子,在地面的两个方向上重复平铺,远处的一个像素需要装进来更多的纹素,如果按照之前插值的方式处理,渲染出来的结果是远处出现摩尔纹,近处出现锯齿
个人理解:一个像素对应着多个纹理像素,那么在这个点采样得到的信息自然会很多,在上面这个图中我们可以看到,蓝色的点的区域越来越大,覆盖的纹理像素也越来越大,但是我们只是一个像素点,只进行一次采样,得到的就是蓝色点旁的纹理像素信息,但是这里是一个像素对应着多个纹理像素,很多信息并未被采样进去,因此造成了远处的摩尔纹。
解决办法1-细分像素扩大采样频率
这种现象被形象的成为屏幕像素在texture空间的footprint。如上图所示一个屏幕空间的蓝色像素点离相机越远,对应在texture空间的范围也就越大。其实也就是越来越欠采样,那么一种直观的解决方法就是Supersampling,如果一个像素点不足以代表一个区域的颜色信息,那么便把一个像素细分为更多个小的采样点不就可以解决这个问题了吗?对,确实是这样,可以看看如下图512x超采样的结果
个人理解:这里细分一个像素,其实相当于使得采样的次数增加了,如下图最后一个图的蓝色点,我们在这个像素内添加更多的采样点,列如512X的超采样,那么自然而然得到的信息就全了,那么得到的效果变有所改善了。
解决办法2-区域采样
上述方法的效果不过,但是计算量太大了,尤其对于更加遥远的图像,一个像素对应着纹理像素更多,那么需要更高的采样频率。上面我们是用点来采样,那能不能用一个区域去采样呢?效果怎么样呢?这就是我们的第二个解决办法,采用方形区域进行检测。这个方法的存储量仅比上述方法多出1/3的存储,是一个不错的方法,但是在此之前还有一个问题需要解决
个人理解:这里的区域查询,并未是使用四个像素点去对应纹理像素进行区域查询,而是将这个纹理贴图进行了细分分层,分为了多个等级,而原理还是用了一个像素点,去判断得到距离之后,找到对应Level的纹理贴图进行绘制。
如上图,两个白色圈圈处,很明显近处的纹理贴图和远处的纹理贴图大小并未一样的。远处圆圈里的footprint必然比近处的要大,因此必须要准备不同level的区域查询才可以,而这正是Mipmap。
level 0代表的是原始texture,也是精度最高的纹理,随着level的提升,每提升一级将4个相邻像素点求均值合为一个像素点,因此越高的level也就代表了更大的footprint的区域查询。接下来要做的就是根据屏幕像素的footprint大小选定不同level的texture,再进行点查询即可,而这其实就相当于在原始texture上进行了区域查询!
那么如何去确定使用哪个level的texture呢?利用屏幕像素的相邻像素点估算footprint大小再确定level D!如下图:
在屏幕空间中取当前像素点的右方和上方的两个相邻像素点(4个全取也可以),分别查询得到这3个点对应在Texture space的坐标,计算出当前像素点与右方像素点和上方像素点在Texture space的距离,二者取最大值,计算公式如图中所示,那么level D就是这个距离的log2值 (D = log2L) ! 这不难理解,读者可以具体取几个例子比如L = 1,L = 2,L = 4,看看是否符合这样的计算即可。
但是这里D值算出来是一个连续值,并不是一个整数,有两种对应的方法:
1 四舍五入取得最近的那个level D
2 利用D值在 向下和向上取整的两个不同level进行3线性插值
第一个方法很容易理解,具体讲述一下第二个方法,如图:
所谓3线性插值,就是在向下取整的D level上进行一次双线性插值(前文提过),再在D+1 level之上进行一次双线性插值,这二者数据再根据实际的连续D值在向下和向上取整的两个不同level之间的比例,再来一次线性插值,而这整体就是一个三线性插值了。
好了!根据上述的方法算出屏幕上每一个像素点所对应的Mipmap level,再进行三线性插值得到颜色值,是否就能很好的解决走样问题了呢?很遗憾,在本文的那个地板的例子之中,费了这么大力气依然不能完美解决,如下图结果:
虽然和一开始的point sample有了很大的进步,但是有一个严重的问题是,远处的地板产生一种过曝的现象,完全糊在了一起。该如何解决这个最后的问题呢——各向异性过滤。
解决办法3-各向异性过滤
好,接着上文的远处产生过曝的问题继续来谈,产生这种现象的原因是因为,所采用的不同level的Mipmap默认的都是正方形区域的Range Query,然而真实情况并不是如此,见下图:
可以看出不同screen space的像素点所对应的footprint是不同的,有长方形,甚至是不规则图形,那么针对这种情况,有的所需要的是仅仅是水平方向的高level,有的需要的仅仅是竖直方向上的高level,因此这也就启发了各向异性的过滤:
这个方法的存储量增加了原来的3倍,但是并不是很影响性能计算,在显存允许的情况下,可以增加各向异性的次数,使得质量提高但是不影响游戏帧数。使用之后的效果
摩尔纹的一个解释
纹理应用
纹理在现代的GPU的理解:Texture = 一块内存+滤波/范围查询
纹理不必只存储颜色,还可以是高度等等。
可以“伪造”详细的几何。
Environment Map环境光映射
Environment Maps,将环境光存储在一个贴图上。
因为几乎只有观察方向对环境光起到影响,所以各个方向的光源可以用一个球体进行存储,即任意一个3D方向,都标志着一个texel。

类比地球仪,把它展开,就可以把球面信息转换到平面上,从而得到环境texture。

类比地球仪,在极点存在拉伸和扭曲问题
解决方法:Cube Map,天空盒。

即用六幅texure表示一个天空盒,再进行一次方向面的计算。
![[games101/笔记/Image/09 着色(插值、高级纹理映射)/Pasted image 20220724201921.png]]
简单来说就是利用方向计算出与对应平面上的交点坐标,剔除平面所对应的一维,剩下来的两维坐标转换到(0,1)范围之内即为(u,v)坐标。
Bump Mapping凹凸贴图
- 记录逻辑上的相对高度(可为负值),表现物体表面凹凸不平的性质。
- 对每个pixel做一个“扰动”
- 利用高度差重新计算法线

计算法线的方法:
在平面上:

c:影响大小系数
在三维空间:

Displacement Mapping位移贴图
- Displacement Mapping,与Bump Mapping类似
- 物理上的高度改变,即改变了三角形顶点位置
- 可以通过物体阴影的边缘发现与Bump Mapping区别
- 代价:要求模型精度高
- DirectX 中的动态曲面细分:开始先用粗糙的三角形,应用Texture的过程中检测是否需要把三角形拆分的更细
三维纹理

三维纹理,定义空间中任意一点的纹理。
并没有真正生成纹理的图,而是定义一个三维空间的噪声函数经过各种处理,变成需要的样子。
阴影纹理

阴影可以计算好,直接写在Texture里。
3D Texture 和 体积渲染
shadow mapping
(这部分内容是第12讲的补充)
Shading Point没有考虑其他物体的影响,所以着色不能解决阴影。
Shadow mapping本质上是一种图像空间做法,生成shadow这一步不需要这个场景的几何信息,但也会走样。
Shadow mapping生成硬阴影,不生成软阴影。
shadow mapping具体操作:
key idea: 一个点,如果不在shadow里,你又能看到这个点,说明这个点可以从camera中看到,也可以从光源中看到。
- 从光源看向场景,记录看到的点的深度。

- 从摄像机看向场景,并投影回光源所在的成像上。
- 深度一致:说明能看到

- 深度不一致:摄像机能看到,但是光看不到 \rightarrow 阴影

- 深度一致:说明能看到
例
shadow mapping最终效果:

可视化shadow maps:

shadow mapping问题:
- 浮点数难以判断相等,所以一般会有一个tolerance
- shadow maps查询时不采用双线性插值,只寻找最近的点,因为倘若插值发生在物体边缘时,与邻接点的深度差距很大,会导致插值结果会有很大的误差
- 属于硬阴影,只适用于点光源
硬阴影vs软阴影
