上图中(α,β,γ)就是点(x,y)的重心坐标。重心坐标是定义在一个三角形上的,三角形平面上的任何一点(x,y)都可以表示为三个顶点坐标的线性组合,如上图α,β,γ分别为点A、B、C的系数,三个点线性组合能够得到三角形上任一点的坐标。如果α,β,γ的和为1,那么点(x,y)就在三角形所在的平面上,如果和不为1,那么点(x,y)就不在三角形所在的平面内。如果三个数和为1,并且都非负,则这个点在三角形内部。
重心坐标的例子如下:
点A的重心坐标为(1,0,0).
任意一点的的重心坐标可以通过求面积比计算出来,下面是一种简化的计算方式:
前面提到过,插值需要使用重心坐标。重心坐标在插值中的用法如下:
由于对三角形进行操作时,都是对三角形的三个顶点进行操作,所以三个顶点的属性都是知道的。要对三角形内部进行插值,就运用到了重心坐标,前面已经说过,三角形内部的任意一点都可以用三个顶点线性表述出来,三个顶点的系数就是插值的权重的大小,然后使用同样的系数,对三个顶点的属性进行线性组合,得到的结果就是三角形内部插值的值。假设三角形内部的某一点V的重心坐标是(α,β,γ),则对该点属性进行插值就是:
重心坐标并不是永久不变的,在经过投影后,重心坐标可能会发生改变。因为在投影之后,原来的三角形会发生变形,点的相对位置就会发生改变,投影之后计算出的重心坐标和投影前的重心坐标就并不相同。
所以要插值一些三维空间的属性时,要先在三维空间中算出重心坐标,然后再用在三维空间中算出重心坐标进行插值,而不能使用投影之后的坐标进行插值。
因为要考虑的深度的因素。三角形投影到屏幕上,覆盖很多像素,像素都有中心,可以判断出像素的中心在投影到屏幕上的三角形的某一位置上,在投影的三角形里对三个顶点的深度做插值,这种算法是不对的,因为三角形在投影到屏幕的过程中会经过一个深度测试的过程,其中深度值较大的顶点会被丢弃,所以如果用投影后的三角形的重心坐标计算插值,可能就会少某些顶点或者说对应的顶点的属性发生改变(因为没有通过深度测试的顶点被丢弃了,在这个位置上时其他不同属性的顶点)。而正确的做法是找到这个这个像素对应的三维空间中的三角形,在三维空间中将深度插值好,然后再放回来。
例如下面这种情况,当纹理的解析度不足时,如1080的屏幕只用上了360的纹理,就会出现下面这中情况:
提前介绍一个概念:纹素(texel): 一个像素在纹理上对应的一个单位。
4*4的格子为纹理,而红色的点则是像素对应映射到纹理上的位置,如果想知道在红点处的纹理的值是多少,最简单的方法就是取就近原则,那么映射到一个格子范围内的纹理都相同,就会出现上面的那种可以明显看出一个格子一个格子的纹理情况。
但是我不想要这种效果,则采取下面的方法:
找红点附近的临近的4个texel,并且可以得到红点到 2*2的四个texel中的右下脚u00的偏移量 s 和 t,假设 s 和 t在【0,1】之间。在这里需要定义一个操作:线性插值。
定义一个x在【0,1】,比如v0 - v1为一条直线,当x = 0的时候,lerp在v0出,当x = 1的时候, lerp在v1处,当x = 0.5时,lerp在v0、v1的正中间。
引入了线性插值的概念后,就可以接着求红点对应的纹理值:
可以看到图中水平的两条黑线,先在这两条黑线上分别做两次线性插值得到u0,u1。
然后再在红点所在的竖线上,再做一次线性插值。红点上下对应的黑点的值分别为u0,u1,用这两个值和t进行线性插值。就能够得到最终的纹理效果。这种做法称作双线性插值。
可以看到图像稍微边模糊了一些,但是图像变成连续的,而不是像之前那种一个格子一个格子的效果。
左图为正常效果,而右边的图则是简单的应用纹理得到的效果。可以看到,在近处出现了锯齿,远处出现了摩尔纹。这里可以看出出现了走样。
因为近处一个像素覆盖的纹理区域相对较小,在远处一个像素则覆盖了一片纹理。屏幕上的像素覆盖的纹理大小是各不相同的。如果是近处,用像素中心去纹理上进行取样,在一小块纹理上,采样中心的对应的纹理做一小片纹理区域的平均值问题不大;但是当远处用像素的纹理中心进行采样时,采样中心对应的纹理做一个很大块纹理区域的平均值就有问题(如上图最右边)。、
之前解决锯齿问题的时候,通过超采样,一个像素点设置多个采样中心解决了问题,这里我们也可以用同样的道理,如果在一个像素上设置512个采样点,对纹理进行采样,得到的效果就会好很多。
可以看到,超采样的效果很好,但是其增加的计算量太大,所以我们需要一些更好的办法。
如果我们不采样,而是求一个纹理范围内的平均值会怎样?这种方法叫做范围查询(range queries)
MipMap能够快速的,近似的查询一个正方形范围内的平均值。这里回到MipMap上来,什么是MipMap?
MipMap能够生成一系列的纹理,但是下一层纹理的分辨率要比上一层的分辨率缩小一半。比如第0层为128*128,而第1层则是64 *64,依次类推,一直做到只有1 * 1的分辨率位置,生成纹理如下:
问题来了:生成了许多额外的纹理,那么需要多消耗多少额外的存储量?
所有的纹理加一起消耗的存储量为原来的纹理存储量的4/3,这是一个级数求和的问题。也就是多消耗了1/3的存储量。
要用MipMap做一个近似的、正方形范围内的范围查询。首先要确定一个正方形的范围。
左图为屏幕上的像素点,右图为纹理空间。可以看到,左图的一个红点到上下两个红点都是一个像素,将这三个红点映射到纹理空间时,我们要求左图左下角的红点像素在纹理空间中要占据多少范围(即右图中的不规则四边形区域)。因为MipMap只能在正方形范围内进行查询,所以我就只要求在纹理空间中左下角的像素到上下两个像素距离的最大值,然后将这个最大值作为正方形范围的边长(因为在像素空间中,两个像素之间的距离就是一个像素正方形的边长大小,所以在纹理空间中也是求响铃像素的距离作为原像素点在纹理空间中所占范围的大小)。边长L的求法:
在求得正方形范围的大小之后,再去MipMap生成纹理的第D层寻找一个1*1的纹理,作为这个范围内的平均值,D的求法为:
通过上面这个公式就可以看出,在近处时,像素覆盖的纹理范围很小,L也就小,就只需要在低层的纹理中就可以查到,而在远处的纹理,一个像素覆盖的纹理范围很大,L就越大,就需要在高层的纹理中去查找。
图中可以看到,有些地方为渐变,但有的地方就是突变,并不连续,为什么?比如说在近处的可能在第一层查,稍远处就在第二层了,更远一些就在第三层查,没有1.5层,1.8层的,所有就会导致变化不连续。如果我希望能查1.8层该怎么办?用插值。
先找第一层,再找第二层,然后将两层的值进行一次插值,最后就能够得到一个连续的变化效果。
因为在层中进行插值使用的是双线性插值,在层间又进行了一次线性插值,所以这种方法是三线性插值。
可以看到,在远处所有的细节全部都被糊掉了,这种情况就叫Overblur,过度模糊了。
那么到底哪出现问题了?从一开始就说道,MipMap只能在一个方形的区域内查询,如果不是方形区域呢?后面的三线性插值都是一个近似的估计,近似的太多,远处就会出现这样一种过度模糊的效果。
有一个办法可以解决MipMap的问题,下面细说:
MipMap的工作:给一张原始的图,长宽各缩小一半,做的就是下图中对角线上的工作。
但是有一些图的长款并不同,MipMap则没有功能对这些图进行处理。可以看到上图中,在水平方向上,高度没有变,但是宽度被不断的压缩,而在竖直方向,宽度没有变,高度被不断的压缩。也就是说,各向异性过滤比MipMap多做了这些工作,能够为宽高不均匀的图片进行压缩。
从上图中可以看出,屏幕上的像素映射到纹理空间上时,并不总是一个规则的形状,可能会出现这种斜的长方形,数值的长方形或者别的形状。
如果在长方形上还用正方形来进行范围查询,那么就会在一个很大的并且多余的范围内进行查询,精度就会下降,所以就会更糊,而用长方形的范围查询则能够直接在目标区域上进行范围查询,效果就会更好,至于斜的长方形,用各向异性过滤也会出现在一个比原来更大的长方形范围内进行范围查询,所以还需要别的方法来解决。