【计算机图形学】【GAMES101学习笔记】Shading 着色

着色(Shading)概述

前面我们讲光栅化是填充像素格,将图像显示在屏幕上,而这里要讲的shading则是负责计算出颜色,换句话说,shading就是计算出每个采样像素点的颜色是多少。shading计算要考虑的因素通常有:光照、纹理、着色频率(着色单位)等。

Blinn-Phong反射模型

Blinn-Phong光照模型,又称为Blinn-phong反射模型(Blinn–Phong reflection model)或者 phong 修正模型(modified Phong reflection model),是由 Jim Blinn于 1977 年在文章中对传统 phong 光照模型基础上进行修改提出的。它是一个经验模型,并不完全符合真实世界中的光照现象,但由于实现起来简单方便,并且计算速度和得到的效果都还不错,因此在早期被广泛的使用。

它将进入摄像机的光线分为三个部分,每个部分使用一种方法来计算它的贡献度,这三个部分分别是环境光(Ambient)、漫反射(Diffuse)和高光反射(Specular)
BP
也就是说,只要将上述的三种光计算出来,就可以实现Blinn-Phong模型的效果了。在计算之前,我们先定义一些基本的向量,因为只表示方向,所以我们默认它们都是单位向量。

  • Viewer direction,观察方向,使用v表示;
  • Surface normal,法线方向,使用n表示;
  • Light direction,法线方向,使用l(小写的L)表示;
    定义变量

漫反射(Diffuse reflection)

当光线照射到一个点时,该光线会被均匀的反射到各个方向,这种反射称为漫反射。也就是说,在漫反射中,视角的位置是不重要的,因为反射是完全随机的,因此可以认为漫反射光在任何反射方向上的分布都是一样的,如下图所示
漫反射
那影响漫反射光照强度的因素有哪些呢?

  • 入射光线与法线的夹角,即入射光线角度;
  • 入射光线自身的强度;

入射光线与法线的夹角

如下图,只有当入射光线与平面垂直的时候才能完整地接受所有光的能量,而入射角度越倾斜,平面接收的光越少,损失的能量越大。定量地来说,应该将光强乘上一个 cos ⁡ θ = n ⋅ l \cos \theta = n ·l cosθ=nl(因为 n ⋅ l = ∣ n ∣ ⋅ ∣ l ∣ cos ⁡ θ n·l = |n| · |l|\cos \theta nl=nlcosθ,而 n 、 l n、l nl为单位向量,长度为1),这样才能正确表示平面所接收的所有光照。
影响1
入射光线自身的强度

光的强度会随着距离的增加而衰减,这是显而易见的。假设我们有一个光源,在距离它单位长度1的圆上(图中最内圈)每一个点接收到光的强度时 I I I,根据能量守恒定律,且不考虑衰减,在距离光源 r r r位置的圆上每个点接收到光的强度就是 I r 2 \frac{I}{r^2} r2I(这里是3D模型,故考虑的是光散射的圆壳的面积)。
影响2
至此,我们可以较好地得到漫反射的模型:
在这里插入图片描述
即,我们可以推出漫反射公式:
L d = k d ( I / r 2 ) max ⁡ ( 0 , n ⋅ L ) L_d = k_d(I/r^2)\max (0, n·L) Ld=kd(I/r2)max(0,nL)其中 L d L_d Ld为漫反射光照强度, k d k_d kd为物体表面的漫反射系数, I r 2 \frac{I}{r^2} r2I是当前点接受的光照强度, max ⁡ ( 0 , L ⋅ n ) \max (0, L·n) max(0,Ln)是当前点接受到的能量。为什么当前接受到的能量要这么表示?试想如果光照从着色点底部入射,那么其与法线的夹角就会大于90°,值会变成负数没有意义,此种情况下我们默认漫反射光照为0。

为什么要有反射系数?

反射系数指光(入射光)投向物体时,其表面反射光的强度与入射光的强度之比值(有多少入射光能够被反射出去,其值介于0~1之间),受入射光的投射角度、强度、波长、物体表面材料的性质以及反射光的测量角度等因素影响。一般来讲,在颜色系列中,黑色的反射系数较小,为0.03,白色的反射系数较大,为0.8。(百度百科)

简单来说, k d k_d kd与颜色有关,这一点将在下面的部分着重讨论。

同时,我们观察漫反射公式可以发现,漫反射关照与观察方向v没有关系。这是可以理解的,因为漫反射光在任何反射方向上的分布都是一样的,所以无论在哪个方向观测,只要入射光线强度不变,入射光线与法线夹角不变,漫反射光照均相同。

镜面/高光反射(Specular highlights)

高光反射也称为镜面反射,若物体表面很光滑,当平行入射的光线射到这个物体表面时,仍会平行地向一个方向反射出来。

下图中一根入射光线l,照射在光滑的平面上,会沿着R方向反射,由于平面并非完全光滑,所以反射光的方向并非只有R一个点,而是R周边的一小块区域,只要眼睛(摄像机)在R附近都可以看得到,越靠近R反射光照强度越大。我们可以得出一条结论:高光反射和观察角度有关
高光
phong模型中认为,高光反射的强度与反射光线R和观察角度v之间夹角的余弦值成正比。计算它的强度,相当于是求向量v在R上的投影。怎么求R呢?

l l l n n n之间的夹角为 θ \theta θ,将 m m m l l l的顶部相连,并且 m m m垂直 n n n,向量 p p p的绝对值 ∣ p ∣ |p| p n n n上的投影:
在这里插入图片描述
根据一些已有的知识容易得出:
R = 2 m − l R = 2m - l R=2ml现在变成了怎么计算 m m m m = l − p m = l - p m=lp因为 ∣ p ∣ |p| p n n n上的投影
p = ∣ p ∣ ⋅ n ∣ n ∣ p = |p| · \frac{n}{|n|} p=pnn ∣ p ∣ = ∣ l ∣ cos ⁡ θ |p| = |l|\cos \theta p=lcosθ cos ⁡ θ = l ⋅ n ∣ l ∣ ⋅ ∣ n ∣ \cos \theta = \frac{l·n}{|l|·|n|} cosθ=lnln结合上面三个公式可得: p = ∣ l ∣ l ⋅ n ∣ l ∣ ⋅ ∣ n ∣ ⋅ n ∣ n ∣ = l ⋅ n ∣ n ∣ 2 ⋅ n p = |l|\frac{l·n}{|l|·|n|}·\frac{n}{|n|}=\frac{l·n}{|n|^2}·n p=llnlnnn=n2lnn
因为 n n n是单位向量,所以 ∣ n ∣ = 1 |n|=1 n=1,现在已经具备求出投影 R R R的条件了:
R = l − 2 ( l ⋅ n ) ⋅ n R = l - 2(l·n)·n R=l2(ln)n因此完整的高光反射公式可以表示为:
L s = k s ( I / r 2 ) max ⁡ ( 0 , cos ⁡ α ) p = k s ( I / r 2 ) max ⁡ ( 0 , v ⋅ R ) p L_s = k_s(I/r^2) \max (0, \cos\alpha)^p = k_s(I/r^2)\max(0, v·R)^p Ls=ks(I/r2)max(0,cosα)p=ks(I/r2)max(0,vR)p其中 k s k_s ks是高光反射系数, l / r 2 l/r^2 l/r2是当前点接受的光照强度。

为什么要加一个p次方呢?这是为了控制高光反射面积的大小和能够看到高光的范围。当p值越大,反射面积(能看到高光的范围)越小,如下图所示:
在这里插入图片描述
下图中许多小球,展示了反射系数和p值变动对高光反射效果的影响。
在这里插入图片描述

使用半程向量(Bisector)

计算反射光线R虽然不是很难,但还是有些繁琐,有没有其他方法计算呢?Blinn提出了一个简单的修改方法来得出类似的效果,通过对向量 l l l和向量 v v v取平均然后归一化得到一个新的向量 h h h,其中 h h h被称为半程向量(bisector),使用 h h h与法线 n n n点乘来计算高光,这样就可以避免计算R了:
h = l + v ∣ l ∣ + ∣ v ∣ h = \frac{l+v}{|l|+|v|} h=l+vl+v
在这里插入图片描述
使用半程向量计算的公式和之前类似,不过计算的时候简单了不少,具体公式如下:
L s = k s ( I / r 2 ) max ⁡ ( 0 , cos ⁡ α ) p = k s ( I / r 2 ) max ⁡ ( 0 , n ⋅ h ) p L_s = k_s(I/r^2)\max(0, \cos\alpha)^p = k_s(I/r^2)\max(0, n·h)^p Ls=ks(I/r2)max(0,cosα)p=ks(I/r2)max(0,nh)p

环境光(Ambient lighting)

环境光也称间接光,是光线经过周围环境表面多次反射后形成的,利用它可以描述一块区域的亮度,在光照模型中,通常用一个常量来表示。
L a = K a I a L_a = K_aI_a La=KaIa

综上所述:我们得到了Blinn-Phong光照模型的三个光照要素,整体的计算公式可以表示为:
L = L a + L d + L s = k a I a + k d ( I / r 2 ) max ⁡ ( 0 , n ⋅ l ) + k s ( I / r 2 ) max ⁡ ( 0 , n ⋅ h ) p L = L_a + L_d + L_s = k_aI_a + k_d(I/r^2)\max(0, n·l) + k_s(I/r^2)\max(0, n·h)^p L=La+Ld+Ls=kaIa+kd(I/r2)max(0,nl)+ks(I/r2)max(0,nh)p

重心坐标(Barycentric Coordinates)

这里为了后面插值引入的方便,介绍一下重心坐标。给定三角形的三点坐标 A , B , C A, B, C A,B,C,该平面内一点 ( x , y ) (x, y) (x,y)可以写成这三点坐标的线性组合形式,即 ( x , y ) = α A + β B + γ C (x, y) = \alpha A + \beta B + \gamma C (x,y)=αA+βB+γC且满足 α + β + γ = 1 \alpha + \beta + \gamma = 1 α+β+γ=1,则称此时3个坐标 A , B , C A, B, C A,B,C的权重 α , β , γ \alpha, \beta, \gamma αβγ为点 ( x , y ) (x, y) (x,y)的重心坐标。特别的,三角形重心的重心坐标为 ( 1 3 , 1 3 , 1 3 ) (\frac{1}{3}, \frac{1}{3}, \frac{1}{3}) (31,31,31)

说明:我们要保证 α , β , γ \alpha, \beta, \gamma α,β,γ是非负的,这样才能确保点与三角形在同一平面内,而 α + β + γ = 1 \alpha + \beta + \gamma=1 α+β+γ=1是为了点在三角形内。这都是数学上的正确定义,在此不做深究。

运用上述公式我们并不能计算出一个点具体的重心坐标,那么具体怎么计算得到呢?

在这里插入图片描述
将一点 ( x , y ) (x, y) (x,y) A , B , C A, B, C A,B,C三点直接连接,构成三个三角形面积分别为 A A , A B , A C A_A, A_B, A_C AA,AB,AC,即可直接定义出重心坐标如图中公式所示。根据这条定义,我们只需求出各个三角形的面积便可以直接得出重心坐标,但由于面积比较难算,我们可以通过所求点与顶点的坐标等式转换为下面这个式子:
α = − ( x − x B ) ( y C − y B ) + ( y − y B ) ( x C − x B ) − ( x A − x B ) ( y C − y B ) + ( y A − y B ) ( x C − x B ) \alpha = \frac{-(x-x_B)(y_C-y_B)+(y-y_B)(x_C-x_B)}{-(x_A-x_B)(y_C-y_B)+(y_A-y_B)(x_C-x_B)} α=(xAxB)(yCyB)+(yAyB)(xCxB)(xxB)(yCyB)+(yyB)(xCxB) β = − ( x − x C ) ( y A − y C ) + ( y − y C ) ( x A − x C ) − ( x B − x C ) ( y A − y C ) + ( y B − y C ) ( x A − x C ) \beta = \frac{-(x-x_C)(y_A-y_C)+(y-y_C)(x_A-x_C)}{-(x_B-x_C)(y_A-y_C)+(y_B-y_C)(x_A-x_C)} β=(xBxC)(yAyC)+(yByC)(xAxC)(xxC)(yAyC)+(yyC)(xAxC) γ = 1 − α − β \gamma = 1 - \alpha - \beta γ=1αβ
重心坐标在图形学中最重要的运用便是插值,它可以根据三个顶点 A , B , C A, B, C A,B,C的属性插值出任意点的属性,无论是位置,颜色,深度,法线向量等等,而这些属性在之后的着色或是消除隐藏曲面都有很大的作用。它还可以在处理光栅化问题中判断一个点是否在三角形内,利用重心坐标是否都大于0来代替之前的叉乘。利用该条件来进行sample再判断是否画出该像素。

着色方法/频率(shading frequency)

上述光照模型主要利用了观察方向,入射光线与法线向量的位置关系进行公式上的推导。其中对于“着色点”(即引出法线的那个点)我们并没有过分强调,但这其实是十分重要的。“着色点”可以是一个面,也可以是顶点,也可以是像素,我们都可以对对应的“着色点”进行着色,如下图分别对应面着色,顶点着色,像素着色,下面将逐一进行介绍。
在这里插入图片描述

面着色(Flat Shading)

面着色,顾名思义以每一个面作为单位进行着色。前面我们说三角形是最基础的多边形,所以实际模型数据中大多以很多个三角面进行存储,因此也记录了每个三角形面的法线向量,利用每个面的法线向量进行一次Blinn-Phong反射光照模型的计算,将该颜色赋予整个面至整个物体,效果如下:
在这里插入图片描述
面着色只需对每一个面进行一次着色计算,因此计算速度很快,但效果也是比较差的,通过上图我们可以看到一块块面形状,因此不适用与光滑的物体平面。但当物体被细分成无数多的小三角形时,也会有较好的着色效果。

顶点着色(Gouraud Shading)

在面着色中,整个面都是同样的颜色,所成的效果显然不好。而在点着色中,我们期望从三角形顶点到顶点之间内部呈现一个颜色渐变的状态,更自然也符合常理。Gouraud Shading会对每个三角形的顶点进行一次着色,但问题是,我们只能对一个面求法线向量,怎么对一个顶点求法线向量呢?我们可以这样,将所有共享这个点的面的法线向量加起来求均值,最后再标准化就得到了该顶点的法线向量了。有了每个三角形的顶点向量之后,自然就可以计算出每个顶点的颜色了。那么对于三角形内部的每一个点该怎么办呢?我们可以在这里引入重心坐标来做插值!
在这里插入图片描述
其中 V A , V B , V C V_A, V_B, V_C VA,VB,VC代表三个顶点的颜色, V V V代表当前所求点, α , β , γ \alpha, \beta, \gamma α,β,γ为点 V V V的重心坐标,我们只需将重心坐标与顶点颜色进行加权计算就可以得到所求点的颜色。
在这里插入图片描述
最终渲染效果如上图所示,Gouraud Shading的效果有着明显的提升,但这样依然还不是最好的做法,因为我们实际上只对每个三角形顶点进行了着色,然后其它的颜色都是通过插值得到。

像素着色(Phong Shading)

有没有一种做法可以真正地对每个点用Blinn-Phong模型计算得出颜色呢?
Phong Shading正是这样的思路,上述提到了如何得到每个顶点的法线向量,那么对三角形内部的每一个点的法线向量自然也可以像插值颜色一般得到:
n = α n 0 + β n 1 + γ n 2 n = \alpha n_0 + \beta n_1 + \gamma n_2 n=αn0+βn1+γn2其中 n 0 , n 1 , n 2 n_0, n_1, n_2 n0,n1,n2分别是三角形三个顶点的法线向量, α , β , γ \alpha, \beta, \gamma α,β,γ为三角形面内一点的重心坐标, n n n为该点插值之后得到的法线向量。如此便得到了任意一点的法线向量了,也当然可以对任意一点进行Blinn-Phong模型的计算了,最终渲染效果如下:
在这里插入图片描述

图形/实时渲染管线(Graphics/Real time Pipeline)

所谓图形/实时渲染管线指的是一系列操作的流程,这个流程具体来说就是将一堆具有三维几何信息的数据点最终转换到二维屏幕空间的像素。其实也就是将之前的所有知识连贯起来。以如下图作为一个总结,再具体分步骤讲解:
在这里插入图片描述
首先是顶点处理,其作用是指对所有的顶点数据进行Model,View和Projection(MVP)的变换,最终得到投影到二维平面的坐标信息(同时为了Zbuffer保留深度z)。
在这里插入图片描述
而第二步三角形处理也十分容易理解,就是将所有的顶点按照原几何信息,变成三角面,每个面由3个顶点组成,得到了许许多多个三角形之后,接下来的操作自然就是三角形光栅化了:
在这里插入图片描述
在进行完三角形的光栅化之后,知道了哪些在三角形内的点可以被显示,那么如何确定每个像素点或者说Fragment(片元)的颜色呢?
在这里插入图片描述
最后一步是Framebuffer的处理,就是将所有的像素颜色信息整合在一起,输送给显示设备加以显示,也就完成了整个图形渲染管线。
在这里插入图片描述

纹理映射(Texture Mapping)

纹理映射(Texture Mapping),又称纹理贴图,是将纹理空间中的纹理像素映射到屏幕空间中的像素的过程。简单来说,就是把一幅图像贴到三维物体的表面上来增强真实感,可以和光照计算、图像混合等技术结合起来形成许多非常漂亮的效果。

通过纹理映射我们可以将三维物体上的任意一个点都映射到一个2维平面之上。具体地,我们将地球仪上每个点的颜色信息即漫反射系数存储在2维的Texture之上,每次利用光照模型进行计算的时候根据映射关系就能查到这个点的漫反射系数是多少,所有点计算完之后,结果就像最左边的screen space之中,整个Texture被贴在了模型之上。
在这里插入图片描述
也就是说,有了Texture,就有了映射关系,对渲染结果会有一个非常打的提升,因为很多看着fancy的效果都可以通过texture的设计得到,例如下图的独眼巨人:
在这里插入图片描述
到目前为止,我们在将物体光栅化映射到二维屏幕之后,需要考虑着色问题,我们运用Blinn-Phong光照模型对每一个像素进行着色,如何考虑着色过程中的漫反射系数,就是通过纹理映射得到的。确切地说纹理映射不仅仅是用来得到漫反射系数的,可以通过它得到着色点的各个属性。

那么有了一张Texture之后,这种纹理到模型的映射关系究竟是如何表示的呢?我们从纹理坐标 ( U , V ) (U, V) (U,V)说起。在纹理空间之内任意一个二维坐标都在 [ 0 , 1 ] [0, 1] [0,1]之内,如下图是一个可视化纹理坐标的结果:
在这里插入图片描述
横轴和纵轴的最大值都为1,一幅Texture上的任意一点都可以用一个 ( u , v ) (u, v) (u,v)坐标来表示,其中 0 ⩽ u ⩽ 1 , 0 ⩽ v ⩽ 1 0\leqslant u \leqslant1, 0 \leqslant v \leqslant 1 0u1,0v1,因此只需要在三维world space中每个顶点的信息之中存储下该顶点在texture space的 ( u , v ) (u, v) (u,v)坐标信息,自然而然的就直接得到了这种映射关系。伪代码如下:
在这里插入图片描述
简而言之就是对每个光栅化的屏幕坐标算出它的 u v uv uv坐标(利用三角形顶点重心坐标插值),再利用这个 u v uv uv坐标去查询texture上的颜色,把这个颜色信息当作漫反射系数 K d K_d Kd

纹理过小带来的问题

纹理过小的问题相对容易理解,想想我们把一张100x100的纹理贴图应用在一个500x500的屏幕之上,必然要对纹理进行方法,这就会导致走样失真,因为屏幕空间的几个像素点对应在纹理贴图的坐标上都是集中在一个像素大小之内。那么如果仅仅是使用对应 ( u , v ) (u, v) (u,v)坐标的texture贴图下最近的那个像素点,往往造成严重的走样。
在这里插入图片描述
比如上图中红色点是屏幕空间下一像素所对应在texture空间中的点,因为它没有精确地对应texture中的像素点,如果去选择离它最近的那个橙色框起来的点,那必然会导致走样。如下图:
在这里插入图片描述

双线性插值(Bilinear Interpolation)

那么如何改善走样的问题呢?我们可以利用双线性插值,我们依然取上图的点作为例子,第一步,取出离红色点最近的4个黑色顶点,分别算出该红色点在水平及竖直方向偏移的比率 s , t s, t s,t,图示如下:
在这里插入图片描述
接着先利用 s s s,可以线性插值出如下图所示的 u 0 , u 1 u_0, u_1 u0,u1点的颜色值:
在这里插入图片描述
接着利用 t t t,颜色值 u 0 , u 1 u_0, u_1 u0,u1插值出红色点的颜色值: f ( x , y ) = l e r p ( t , u 0 , u 1 ) f(x, y) = lerp(t, u_0, u_1) f(x,y)=lerp(t,u0,u1)如此这样利用两次线性插值,综合考虑到了所有4个点的颜色值,能够很好地缓解走样失真现象,并且计算速度较高,效果如图:
在这里插入图片描述

纹理过大带来的问题

可能对于我们地第一直觉来说,纹理小确实会引发问题,但是纹理大那不是更好吗,为什么会引发问题呢?但事实是纹理过大所引发地走样甚至更加严重。想象一张很大的地板,在上面铺满了重复的放个贴图,我们所期望看到的结果应该是这样的:
在这里插入图片描述
这符合透视投影近大远小,不过这是标准答案。如果此时纹理过大,采用采样着色之后的效果是这样的:
在这里插入图片描述
可以说是不堪入目,近处锯齿,远处摩尔纹,非常严重的走样现象,为什么会导致这样的一个现象呢?地板上铺满了重复的方格贴图,根据近大远小,远处的一张完整的贴图可能在屏幕空间中仅仅是几个像素大小,那么必然屏幕空间的一个像素对应了纹理贴图上的一片范围的点,这其实就是纹理过大所导致的,直观来说想用一个点采样的结果代替纹理空间一片范围的颜色信息,必然会导致严重走样!就是前面所说的采样导致的走样本质:采样频率跟不上信号频率
在这里插入图片描述
上图就是一个很好的例子,一个屏幕空间的蓝色像素点离相机越远,对应在texture空间的范围也就越大。其实也就是越来越少采样,那么一种直观的解决方法就是Supersampling,如果一个像素点不足以代表一个区域的颜色信息,那么便把一个像素细分为更多个小的采样点不就可以解决这个问题了?事实上,我们就是这么做的,可以看看下图的超采样结果:
在这里插入图片描述
效果虽然称不上完美,但也极大地缓解了走样现象,但不足是计算量太大了,一个像素点被分成了512x512个采样点,计算量几乎多出了25万倍!这显然是我们不愿意看到的,那我们能不能换一种思路,如果不去超采样,仅仅是求出每个屏幕像素里所有的texels的颜色均值呢?

Mipmap

正如上文所提,一个采样点的颜色信息不足以代表texture里一个区域的颜色信息,如果可以求出这样一个区域里面所有颜色的均值,是不是就是一种可行的方法呢?是的,我们的目标就是从点查询Point Query迈向区域查询Range Query。但依然存在一个问题,不同的屏幕像素所对应的纹理区域是不一样大小的,即离相机的像素点必然会对应很大一片纹理区域,看下图:
在这里插入图片描述
远处的圆圈里对应的纹理区域必然比近处的要大,因此必须要准备不同等级(level)的区域查询才可以,而这正是Mipmap。
在这里插入图片描述
Level 0代表的是原始texture,也是精度最高的纹理,随着level的提升,每提升一级将4个相邻像素点求均值合为一个像素点,因此越高的level也就代表了更大的纹理的区域查询。接下来要做的就是根据屏幕像素的大小,选定不同Level的texure,再进行点查询即可,而这其实就相当于在原始texture上进行了区域查询!

那么如何去确定使用哪个Level的texture呢?利用屏幕像素的相邻像素点估算纹理大小再确定Level,如下图:
在这里插入图片描述
在屏幕空间中取当前像素点的右方和上方的两个相邻像素点(4个全取也可以),分别查询得到这3个点对应的Texture space的坐标,计算出当前像素点与右方像素点和上方像素点在Texture space的距离,二者取最大值,计算公式如图中所示。

那么Level D就是这个距离的 l o g 2 log_2 log2值,这不难理解,可以具体取几个例子比如L=1, L=2, L=4,看看是否符合这样的计算即可。但这里D值算出来是一个连续的值,并不是一个整数,有两种对应的方法:

  • 四舍五入取得最近的那个Level D
  • 利用D值在向下和向上取整的两个不同Level进行3线性插值

第一个方法很容易理解,具体讲述一下第二个方法,如图:
在这里插入图片描述
所谓3线性插值,就是在向下取整的Level D上进行一次双线性插值,再在Level D+1之上进行一次双线性插值,这二者数据再根据实际的连续D值在向下和向上取整的两个不同Level之间的比例,再来一次线性插值,而这整体就是一个三线性插值了。

根据上述的方法算出屏幕上每一个像素点所对应的Mipmap level,再进行三线性插值得到颜色值,是否就能很好的解决走样问题了呢?很遗憾,在本文的那个地板的例子之中,费了这么大力气依然不能完美解决,如下图:
在这里插入图片描述
虽然和一开始的point sample有了很大的进步,但是有一个严重的问题是,远处的地板产生一种过曝的现象,完全糊在了一起。

为什么会这样?主要有以下两个原因:

  • Mipmap所规定的区域查询,这个区域必须是正方形,而纹理映射中可不仅仅只有正方形
  • 3线性插值本身就含有误差

在这里插入图片描述
可以看出不同screen space的像素点所对应的footprint是不同的,有长方形,甚至是不规则圆形,那么针对这种情况,有的需要的仅仅是水平方向的搞Level,有的需要的仅仅是竖直方向上的高Level,因此这也就启发了各向异性的过滤来改善Mipmap。

各向异性过滤(Anisotropic Filtering)是用来过滤、处理当视角变化导致3D物体表面倾斜时造成的纹理错误。传统的双线性和三线性过滤技术都是指“lsotropy”(各向同性)的,其各方向上矢量时一致的,就像正方形和正方体。
在这里插入图片描述
图示来说,Mipmap所求得的是上图中对角线的图片,而各向异性是针对长宽进行不同比例的缩放来生成图片,在具体的映射中,需要选择一个最合适的纹理。这个跟上面的Mipmap一样,所额外需要的存储空间仅仅增加了原来的 1 3 \frac{1}{3} 31,故对性能几乎没有影响。打游戏的人应该知道游戏画面设置里有各向异性过滤这个选项,其中的2x、4x、16x其实就是所额外生成的贴图数量,由于其仅对内存开销有一定的增加,并不会导致性能上的损失降低帧率,所以打游戏建议把各向异性拉满。

利用这样不同的贴图,更加精细地选择后结果就会明显好很多,当然这只是一种改善Mipmap的方法,并不能真正解决问题。
在这里插入图片描述

纹理映射的运用

环境光映射(Environment Map)

顾名思义就是将环境光存储在一个贴图之上。想象这样一个情形,光照离物体的距离十分遥远,因此对于物体上的各个点光照方向几乎没有区别,那么唯一的变量就是人眼所观察的方向了,因此各个方向的光源就可以用一个球体进行存储,即任意一个3D方向,都标志着一个texel:
在这里插入图片描述
就跟前面的地球仪一样,利用墨卡托投影或是其他类似的方法将球上的信息转换成一个平面上,就得到了环境Texture了:
在这里插入图片描述
但是用一个球体来存储环境光有一个比较明显的缺点,仔细观察上面展开的Texture图可以观察看到,上方和下方均有较为严重的扭曲,因此另外一种存储的方法就是Cube Map,也就是天空盒:

一个天空盒有6幅Texture来表示,明显相对球体少了很多扭曲的情况,但是中间多了一些从方向到面上的计算:
在这里插入图片描述
简单来说就是利用球心与球面连线方向计算出与对应平面上的交点坐标,剔除平面所对应的一维,剩下来的两维坐标转换到 ( 0 , 1 ) (0, 1) (0,1)范围之内即为 ( u , v ) (u, v) (u,v)坐标,一个图例:
在这里插入图片描述

凹凸贴图/法线贴图(Bump/Normal Map)

假设有一个球体,由许多三角形构成。我们想把它修改成一个月球模型,那我们知道月球表面有许多坑坑洼洼的小洞,十分不平整,如何表示这种洞的凹或者丘的凸呢?如果继续采用用许多三角形表示的话,工作量又上了一个台阶,有没有什么好的方法呢?

我们仍然认为所要设计的月球模型内核是一个光滑球体,但是我们可以在纹理贴图上定义每个点的高度进而改变法线向量,进而改变shading。Bump/Normal Map就是这样的思想,它存储了每一个点逻辑上的相对高度(可正可负),该高度的变化实际上表现了物体表面凹凸不平的特质,利用高度信息,再计算出该点法线向量,最后再利用法线计算光照,这就是Bump Map的过程:
在这里插入图片描述
那么所需要关心的问题就是,如何从相对高度计算出法线向量呢?
在这里插入图片描述
在二维情况下,我们可以先求一个点的切线,然后再逆时针旋转90°求法线:
在这里插入图片描述
三维情况下,可以类推得到:
在这里插入图片描述

位移贴图(Displacement Map)

Displacement Map其实又与Bump Map十分类似了,出发点都是改变点的相对高度来改变法线。但Bump Maps是逻辑上的高度改变,它实则并没有改变内部的模型,是假的改变。而Displacement Map则是物理上的高度改变,它真正改变了模型。二者的区别就在此处,可以通过物体阴影的边缘发现这点:
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/charenCsdn/article/details/125868195