重心坐标与插值、纹理映射

本节会详细介绍重心坐标的定义以及解法,并简略的提及重心坐标在图形学中的运用。其实插值在计算机图形学中是一个非常通用的概念,无论是坐标、还是颜色、还是深度、都可以作为插值的目标,因为插值的本质就是加权平均,理解这个思想很重要。另外会介绍纹理映射,以及纹理过大、纹理过小的解决方案,如:双线性插值、三线性插值、Mipmap、各向异性过滤、EWA。

重心坐标定义 barycentric coordinates

给定三角形的三点坐标 A、B、C,该平面内一点 (x, y) 可以写成这三点坐标的线性组合形式 :

几何面积角度求解

除了重心坐标的基础定义之外,重心坐标还有一个另外一个角度的等价定义,如下图:

将一点 $(x,y)$ 与 A, B, C 三点直接连接,构成三个三角形面积分别为 $A_A , A_B , A_C$ 即可直接定义出重心坐标如图中公式所示。如果这个点在某个边的外侧,那么该点与这条边构成的三角形面积计算为负值。

好了根据这条定义,我们只需求出各个三角形的面积便可以直接得出重心坐标了!

我们已知图中4点坐标,那么求出面积自然是很简单的,这里利用行列式的几何意义直接求解,即任意2个2维向量组合成矩阵的行列式的绝对值,为这两条向量所围成平行四边形的面积 (如果对行列式几何意义不清楚的话建议前往B站搜索3B1B的线代本质)。

设任意一点为 $P(x,y)$ ,则由:

$$ \mathrm{A}_{\mathrm{B}}=|\mathrm{AP}, \mathrm{AC}| $$

$$ \mathrm{A}_{\mathrm{C}}=|\mathrm{AB}, \mathrm{AP}| $$

$$ \mathrm{A}_{\mathrm{A}}=|\mathrm{BC}, \mathrm{BP}| $$

其中AP、AC为列向量,符号 || 为行列式,此处省略行列式计算步骤,不难得出:

$$ \alpha =\frac{-\left(x-x_{B}\right)\left(y_{C}-y_{B}\right)+\left(y-y_{B}\right)\left(x_{C}-x_{B}\right)}{-\left(x_{A}-x_{B}\right)\left(y_{C}-y_{B}\right)+\left(y_{A}-y_{B}\right)\left(x_{C}-x_{B}\right)} $$

$$ \beta =\frac{-\left(x-x_{C}\right)\left(y_{A}-y_{C}\right)+\left(y-y_{C}\right)\left(x_{A}-x_{C}\right)}{-\left(x_{B}-x_{C}\right)\left(y_{A}-y_{C}\right)+\left(y_{B}-y_{C}\right)\left(x_{A}-x_{C}\right)} $$

$$ \gamma =1-\alpha-\beta $$

注意,求出其中2个之后第三个可以根据性质直接求出。

重心坐标的运用

重心坐标在图形学中最重要的运用便是插值,他可以根据三个顶点 A、B、C 的属性插值出任意点的属性,无论是位置,颜色,深度,法线向量等等,而这些属性在之后的着色或是消除隐藏曲面都有很大的作用。

纹理映射 Texture Mapping

先来看看纹理映射的概念,我们可以将三维物体上的任意一个点都映射到一个二维平面之上,反之我们可以把二维平面上的像素映射到3D物体上,这就是纹理映射。举一个简单例子,地球仪:

倘若拥有从三维 World space 到二维 Texture space 的一个映射关系,那么只需要将每个点的颜色信息即漫反射系数存储在二维的 Texture 之上,每次利用光照模型进行计算的时候根据映射关系就能查到这个点的漫反射系数是多少,所有点计算完之后,结果就像最左边的 screen space 之中,整个 Texture 被贴在了模型之上。

有了Texture,有了映射关系,对渲染结果会有一个非常大提升。可以看这样一个有点丑的例子。

那么有了一张Texture之后,这种映射关系究竟是如何表示的呢?这就要从纹理坐标(UV)说起了。在纹理空间之内任意一个二维坐标都在 [0,1] 之内。如下图是一个可视化纹理坐标的结果:

横轴和纵轴的最大值都为1,一幅Texture上的任意一点都可以用一个 (u, v) 坐标来表示 (0 <= u <= 1,0 <= v <= 1),因此只需要在三维world space中每个顶点的信息之中存储下该顶点在 texture space 的 (u, v) 坐标信息,自然而然的就直接的得到了这种映射关系。

有一种特殊的纹理称为tile,这种纹理的特征是重复拼接之后上下左右都是连续的,因此这种纹理可以复制很多张贴在墙面或地板上,比如下面这种砖块的纹理:

简而言之就是对每个光栅化的屏幕坐标算出它的 uv 坐标(利用三角形顶点重心坐标插值),再利用这个 uv 坐标去查询 texture上的颜色,把这个颜色信息当作漫反射系数 $K_d$。

纹理过大和过小处理方式

通常我们把纹理上的一个像素称之为 texel(纹素),A pixel on a texture!

在理解了纹理映射的基础之后,考虑如果纹理精度特别小或者纹理精度特别大会分别引起什么问题呢?

纹理过小引发的问题

纹理过小的问题相对容易理解,想想我们把一张100x100 的纹理贴图应用在 500x500 的屏幕之上必然会导致走样失真,因为屏幕空间的几个像素点对应在纹理贴图的坐标上都是集中在一个像素大小之内。那么如果仅仅是使用对应 (u,v) 坐标在 texture 贴图下最近的那个像素点,往往会造成严重的走样。

如图中红色点是屏幕空间下一像素所对应在 texture 空间中的点,会去选择离他最近的那个橙色框起来的点。

这种方法是不可取的,接下来会介绍利用双线性插值的方法缓解这种走样现象。

双线性插值 Bilinear Interpolation

双线性插值通常能在合理的代价下得到很好的结果,我们依然取上图的点作为例子,解释双线性插值。

第一步,取出离红色点最近的 4 个黑色顶点,分别算出,该红色点在水平及竖直方向偏移的比率 $s, t$ ,图示如下:

$$ \operatorname{lerp}\left(x, v_{0}, v_{1}\right)=v_{0}+x\left(v_{1}-v_{0}\right) $$

$$ \begin{array}{l} u_{0}=\operatorname{lerp}\left(s, u_{00}, u_{10}\right) \ u_{1}=\operatorname{lerp}\left(s, u_{01}, u_{11}\right) \end{array} $$

再利用比例 t,颜色值 $u0, u1$ 插值出红色点的颜色值:

$$\mathrm{f}(\mathrm{x}, \mathrm{y})=\operatorname{lerp}\left(\mathrm{t}, \mathrm{u}{0}, \mathrm{u}{1}\right)$$

纹理过大引发的问题

可能对于我们的第一直觉来说,纹理小确实会引发问题,但是纹理大那不是更好吗,为什么会引发问题呢?但事实是纹理过大所引发的走样甚至会更加严重。想象一张很大的地板,在上面铺满了重复的方格贴图,我们所期望看到的结果和我们实际看到的结果:

实际上我们看到的效果就是右边这样,近处有锯齿,远处有摩尔纹!非常严重的走样现象,为什么会导致这样的一个现象呢?这里作者尝试给出自己的两种解释:

1、如开头所说,地板上铺满了重复的方格贴图,根据近大远小,远处的一张完整的贴图可能在屏幕空间中仅仅是几个像素的大小,那么必然屏幕空间的一个像素对应了纹理贴图上的一片范围的点,这其实就是纹理过大所导致的,直观来说想用一个点采样的结果代替纹理空间一片范围的颜色信息,必然会导致严重失真!从信号的角度来说就是,采样频率过低无法还原信号原貌。

2、换一种想法,考虑离相机很远的一个三角形面,假设该三角形面真正在纹理贴图上对应的一片区域有10个像素点。但是由于透视的关系,距离很远的三角形面投影到近平面时可能只有1个或2个像素点的大小(远远小于10个像素的原来大小),那么这1个或2个像素采样texture 的结果就要代表原来这个三角形面10个像素点的颜色信息,自然会导致失真!

其实狠容易理解哈,看下面这样图,一个像素对应了纹理贴图上的一片范围的点,只采样了一个纹素,必然失真:

超采样(可行,不推荐)

如上图所示:这种现象被形象的成为屏幕像素在texture空间的footprint(覆盖区),一个屏幕空间的蓝色像素点离相机越远,对应在 texture 空间的范围也就越大。其实也就是越来越欠采样,那么一种直观的解决方法就是Supersampling,如果一个像素点不足以代表一个区域的颜色信息,那么便把一个像素细分为更多个小的采样点不就可以解决这个问题了吗?对,确实是这样,可以看看如下图 512x 超采样的结果:

效果虽称不上完美但也极大缓解了走样现象,但问题是什么?计算量太大了,一个像素点被分为了 512x512个采样点,计算量几乎多出了25万倍!这显然不是所希望看到的,并且随着屏幕空间的点离相机距离更远,更多的texels(纹理空间的像素)会在屏幕像素的一个footprint 里面,会要更高的超采样频率。

那么另外一种想法,如果不去超采样,仅仅是求出每个屏幕像素所对应的覆盖区里所有 texels 的颜色均值呢?这也就是接下来所要介绍的著名的 Mipmap 技术了!

Mipmap

一个采样点的颜色信息不足以代表 footprint 里一个区域的颜色信息,如果可以求出这样一个区域里面所有颜色的均值,是不是就是一种可行的方法呢?没错我们的目标就是从点查询 Point Query 迈向区域查询 Range Query。但依然存在一个问题,从上图不难看出,不同的屏幕像素所对应的 footprint size是不一样大小的,看下图这样一个例子:

远处圆圈里的 footprint 必然比近处的要大,因此必须要准备不同 level 的区域查询才可以,而这正是Mipmap。Mipmapping技术在1983年由Lance Williams 发明,注意:Allowing (fast, approx, square) range queries,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进行三线性插值,相对来说肯定是三线形插值更准确

三线性插值 —— Trilinear Interpolation

所谓3线性插值,就是在向下取整的 D level上进行一次双线性插值,再在D+1 level之上进行一次双线性插值,这二者数据再根据实际的连续 D 值在向下和向上取整的两个不同 level 之间的比例,再来一次线性插值,而这整体就是一个三线性插值了。

好了!根据上述的方法算出屏幕上每一个像素点所对应的Mipmap level,再进行三线性插值得到颜色值,是否就能很好的解决走样问题了呢?很遗憾,在本文的那个地板的例子之中,费了这么大力气依然不能完美解决,如下图结果:

虽然和一开始的point sample有了很大的进步,但是有一个严重的问题是,远处的地板产生一种过曝的现象,完全糊在了一起。该如何解决这个最后的问题呢——各向异性过滤。再看 Anisotropic Filtering 之前可以先看一个MipMap成功应用的案例:

各向异性过滤 Anisotropic Filtering

因为 Mipmap 采用的计算 level 默认的方式都是以正方形区域的 Range Query,然而真实情况并不是如此,见下图:

可以看出不同 screen space 的像素点所对应的 footprint 是不同的,有长方形,甚至是不规则图形,那么针对这种情况,有的所需要的是仅仅是水平方向的高 level ,有的需要的仅仅是竖直方向上的高 level ,因此这也就启发了各向异性过滤的诞生:

个人感觉,应该是要算出水平方向的level D0,再算一个竖直方向的level D1,然后算根据这两个level去各项异性过滤的texture里面找一张最合适的。利用这样不同的贴图,更加精细的选择后结果就会明显好很多。

EWA过滤 EWA filtering

EWA filtering 能处理不规则的 footprints,用很多圆形去覆盖不规则的形状,核心思想仍然是加权平均,经过多次查询,找到可以覆盖的圆形,性能消耗略大: