计算机图形学八:纹理映射的应用(法线贴图,凹凸贴图与阴影贴图等相关应用的原理详解)

(本篇文章同步发表于知乎专栏:https://zhuanlan.zhihu.com/p/144357517 欢迎三连关注)

在上一节内容中,我们详细介绍了纹理映射的概念,以及纹理贴图过大过小带来的种种问题与解决方案,但纹理映射的应用远不止单单作为diffuse的反射系数来表现出不同颜色。本文会详细介绍一些主要的纹理映射的应用及其原理,首先从法线映射和切线空间开始说起。

1 Normal Maps及切线空间

在Blinn-Phong光照模型中,法线向量扮演着重要的一环,不同的法线向量对光照的计算结果有着很大的影响,打个比方,倘若将一个高精度模型法线信息套用在低精度模型之上,会使低精度模型的渲染效果有着巨大的提升。

那么如何做到呢?我们知道Texture上可以存储3维的颜色信息作为漫反射系数,那么自然也就可以存储法线向量的信息!同样利用(u,v)坐标去查询每个点的法线向量,而不使用原来模型法线信息,达到各种不同的效果,这就是Normal Maps。

明白了Normal Maps的原理之后,有一点重要的是,如何在存储这些法线信息呢?一种可选的方法是,存储object space下的法线向量坐标(这会使得法线贴图看起来五颜六色的),好处是取出来转换到世界坐标就可以直接使用,坏处是一但该法线向量的三角形面发生了变形,那么该法线向量就不再正确。这也就引出了第二种方法,存储切线空间之的法线向量坐标(这会使得法线贴图大部分呈蓝色,原因下文会提)。任何空间坐标系都要由3个互相正交的基底向量构成,切线空间也不例外,如下图所示:

其z轴由原来该面上的几何法线n构成,指向物体表面的外侧。x,y轴分别由该面所对应的贴图上U,V增加的两个方向构成,称之为tangant轴和bitangent轴。这样我们称tangant轴(t)、bitangent轴(b)及法线轴(N)所组成的坐标系,即切线空间(tbn)。

法线向量N可以根据原来的模型信息得到,如何去计算t,b呢?

如上图所示,记一个三角形的面的三个顶点分别为p0,p1,p2,并且使用(ui,vi)来表示对应顶点的texture坐标,那么根据顶点坐标的差值,纹理坐标的差值,以及t,b两轴,可以得到如下关系
p i − p j = ( u i − u j ) t + ( v i − v j ) b \boldsymbol{p}_{i}-\boldsymbol{p}_{j}=\left(u_{i}-u_{j}\right) \mathbf{t}+\left(v_{i}-v_{j}\right) \mathbf{b} pipj=(uiuj)t+(vivj)b
为了进一步简化公式,设:
e 1 = p 1 − p 0 , ( x 1 , y 1 ) = ( u 1 − u 0 , v 1 − v 0 ) e 2 = p 2 − p 0 , ( x 2 , y 2 ) = ( u 2 − u 0 , v 2 − v 0 ) \begin{array}{ll} \mathbf{e}_{1}=\boldsymbol{p}_{1}-\boldsymbol{p}_{0}, & \left(x_{1}, y_{1}\right)=\left(u_{1}-u_{0}, v_{1}-v_{0}\right) \\ \mathbf{e}_{2}=\boldsymbol{p}_{2}-\boldsymbol{p}_{0}, & \left(x_{2}, y_{2}\right)=\left(u_{2}-u_{0}, v_{2}-v_{0}\right) \end{array} e1=p1p0,e2=p2p0,(x1,y1)=(u1u0,v1v0)(x2,y2)=(u2u0,v2v0)
那么便可以把第一个公式简化为如下形式:
e 1 = x 1 t + y 1 b e 2 = x 2 t + y 2 b \begin{array}{l} \mathbf{e}_{1}=x_{1} \mathbf{t}+y_{1} \mathbf{b} \\ \mathbf{e}_{2}=x_{2} \mathbf{t}+y_{2} \mathbf{b} \end{array} e1=x1t+y1be2=x2t+y2b
利用线代知识,将其写为矩阵形式:
[ ↑ ↑ e 1 e 2 ↓ ↓ ] = [ ↑ ↑ t b ↓ ↓ ] [ x 1 x 2 y 1 y 2 ] \left[\begin{array}{ll} \uparrow & \uparrow \\ \mathbf{e}_{1} & \mathbf{e}_{2} \\ \downarrow & \downarrow \end{array}\right]=\left[\begin{array}{ll} \uparrow & \uparrow \\ \mathbf{t} & \mathbf{b} \\ \downarrow & \downarrow \end{array}\right]\left[\begin{array}{ll} x_{1} & x_{2} \\ y_{1} & y_{2} \end{array}\right] e1e2=tb[x1y1x2y2]
相信熟悉线性方程组的同学,已经知道如何解出t,b向量了,两边同乘系数矩阵的逆即可得到:
[ ↑ ↑ t b ↓ ↓ ] = 1 x 1 y 2 − x 2 y 1 [ ↑ ↑ e 1 e 2 ↓ ↓ ] [ y 2 − x 2 − y 1 x 1 ] \left[\begin{array}{ll} \uparrow & \uparrow \\ \mathbf{t} & \mathbf{b} \\ \downarrow & \downarrow \end{array}\right]=\frac{1}{x_{1} y_{2}-x_{2} y_{1}}\left[\begin{array}{ll} \uparrow & \uparrow \\ \mathbf{e}_{1} & \mathbf{e}_{2} \\ \downarrow & \downarrow \end{array}\right]\left[\begin{array}{cc} y_{2} & -x_{2} \\ -y_{1} & x_{1} \end{array}\right] tb=x1y2x2y11e1e2[y2y1x2x1]
其中右边式子中的变量全部已知,自然就已经成功求出t,b两轴向量,再加上原几何法线向量n,至此便已经得出了切线空间(tbn)。法线贴图的数据就存储在空间之下,对于没有变动的法线向量就是(0,0,1)而这恰巧也就解释了为什么法线贴图大部分都是蓝色的(因为大部分法线一般不变动)。

具体实施的时候只需利用[t b n]向量组成的矩阵乘以法线贴图上的存储3维信息,即可得到正确的法线向量了,此时正确法线向量所存在的坐标系与用在构建该坐标系的n是同一个坐标系下(当然也就是记录p0,p1,p2的坐标系)。

以下tips不想深究的可以跳过,不影响之后阅读

tips: 1.求出来的t,b两轴并不一定保证垂直,有时候还需再加一步施密特正交化如下:(提一句施密特正交化的几何意义就是减去除了与之垂直的所有分量值,剩下的就只有垂直分量了)

t ⊥ = normolized ⁡ ( t − ( t ⋅ n ) n ) \mathbf{t}_{\perp}=\operatorname{normolized}(\mathbf{t}-(\mathbf{t} \cdot \mathbf{n}) \mathbf{n}) t=normolized(t(tn)n)(t减去t平行于n的分量,得到t与n垂直分量)

b ⊥ = normolized ⁡ ( b − ( b ⋅ n ) n − ( b ⋅ t ⊥ ) t ⊥ ) \mathbf{b}_{\perp}=\operatorname{normolized}\left(\mathbf{b}-(\mathbf{b} \cdot \mathbf{n}) \mathbf{n}-\left(\mathbf{b} \cdot \mathbf{t}_{\perp}\right) \mathbf{t}_{\perp}\right) b=normolized(b(bn)n(bt)t)
(b减去b平行于 t ⊥ t_{\perp} t的分量,再减去与n的平行分量,剩下的就是与n, t ⊥ t_{\perp} t垂直的分量,想象将b分为3个沿着基向量方向的向量之和即可)

tips: 2.真正存储的时候只需要t 和 n即可,第三轴可以直接叉乘得到。并且将t和n作为顶点的属性进行存储,正如顶点n是共享该点的面法线均值,t同样是所有共享该点的三角面分别计算出来t的均值,与法线可以作为定点属性插值一样,t也可以

2 Bump Maps

Bump Maps其实与Normal Maps十分类似,Normal Maps直接存储了法线信息,而Bump Maps存储的是该点逻辑上的相对高度(可为负值),该高度的变化实际上表现了物体表面凹凸不平的特质,利用该高度信息,再计算出该点法线向量,最后再利用该法线计算光照,这就是Bump Maps的过程,只不过比直接的Normal Maps多了一步从height到normal向量。

那么所需要关心的问题就是,如何从相对高度计算出法线向量呢?

该过程也很容易理解,这里就直接用闫老师课上的Slides作为解答了,2维情况如下:

3维情况可以类推得到:


正如最后一点所标注的,所有计算出来的法线都是局部坐标即切线空间之下,因此还需要左乘[t b n]矩阵转到(世界)相机坐标系之下得到正确法向!

3 Displacement Maps

Displacement Maps其实又与Bump Maps十分类似了,在第二章作者提到了,Bump Maps是逻辑上的高度改变,而Displacement Maps则是物理上的高度改变,二者的区别就在此处,可以通过物体阴影的边缘发现这点:

4 Environment Maps

终于到了最后一点,环境光映射了,顾名思义就是将环境光存储在一个贴图之上。想象这样一个情形,光照离我们的物体的距离十分遥远,因此对于物体上的各个点光照方向几乎没有区别,那么唯一的变量就是人眼所观察的方向了,因此各个方向的光源就可以用一个球体进行存储,即任意一个3D方向,都标志着一个texel:

进一步就像地球仪一样,利用墨卡托投影或是其它类似的方法将球上的信息转换成一个平面上,就得到了环境Texture了:

以下给出分别在光线追踪以及Blinn-Phong模型利用环境映射的伪代码:(不熟悉光线追踪可以掠过该部分伪代码)

可以看到在光线未能撞击物体的时候,会利用光线方向求得展开之后贴图上的(u,v)坐标,再去查询颜色返回。

对于Blinn-Phong模型来说只需增加一项反射的颜色即可,如下:


利用观察方向相对于法线的反射方向去查询环境映射的颜色值。

但是用一个球体来存储环境光有一个比较明显的缺点,仔细观察上文当中展开的那副Texture图可以观察看到,上方和下方均有较为严重的扭曲,因此另外一种存储的方法就是Cube Map,也就是天空盒:

一个天空盒有6幅Texture来表示,明显相对球体少了很多扭曲的情况,但是中间多了一步从方向到面上的计算:

简单来说就是利用方向计算出与对应平面上的交点坐标,剔除平面所对应的一维,剩下来的两维坐标转换到(0,1)范围之内即为(u,v)坐标。

举个例子: 一个方向为(1,2,3)则其与z = 1平面的交点为(1/3,2/3,1) (找最近的平面交点),剔除z轴之后剩下为(1/3,2/3), 在进行(1+x)/2的转换(因为方向存在负值,而uv坐标不存在),则得到z = 1的那一幅Texture上的 uv坐标为(2/3,5/6)。

5 阴影贴图Shadow Maps

5.1 阴影贴图的原理及其缺陷

从光栅化的过程一路走来会发现阴影这个问题一直没有涉及,今天就可以真正的利用阴影贴图来一定程度上的解决这个问题了!

首先,思考一个问题,为什么会有阴影?

因为光源照射不到,更具体点,摄像机能看到的地方,光源“看”不见。

而这正是启发阴影贴图这种做法的动机,接下来我们便来看看详细过程是怎么样的。

第一步,把光源当做一个摄像机让它去看,去渲染整个场景一遍从而得到从光源视角的深度Buffer,记为 d m a p d_{map} dmap
(注:此时得到的这个 d m a p d_{map} dmap 即为shadow maps)

第二步,从设定好的摄像机位置去真正的渲染场景得到摄像机视角的深度Buffer,记为 d d d

第三步,将所有摄像机视角可见点,利用光源视角下的那一套投影矩阵,重新投影回光源,找到与之对应的 d m a p d_{map} dmap上的深度值

如果该点在 d m a p d_{map} dmap上的深度值与 d d d上的深度值相等,则说明此点可被光源与摄像机共同看见,因此不在阴影中,如下图这种情况

如果该点在 d m a p d_{map} dmap上的深度值小于 d d d上的深度值,则说明此点不可被光源看见,但摄像机看得见,即该点前方有物体遮挡,因此在阴影中,如下图这种情况

如此便能确定每个可见像素点是否在阴影之中了,如果在阴影之中就不去计算Blinn-Phong中的镜面反射项与漫反射项。效果如下图:

对应可视化的shadow maps如下,距离光源越近代表深度越小,所以颜色越黑,反之亦然:

对于shadow maps还有几点小细节可以谈(可略过)
1 浮点数难以判断相等,所以一般会有一个tolerance

2 shadow maps查询时不采用双线性插值,只寻找最近的点,因为倘若插值发生在物体边缘时,与邻接点的深度差距很大,会导致插值结果会有很大的误差

3 属于硬阴影,只适用于点光源

软硬阴影示意如下,上方棱角分明为硬阴影,下方为软阴影:
在这里插入图片描述
产生这种问题的原因是因为光源具有体积,导致,有的地方完全看不到光源(本影, Umbra), 有的地方能看到一部分光源(半影,Penumbra)。所以阴影的边缘会有过渡的情况,从而产生软阴影现象,就像上图中太阳与地球的示意一样(全日食与半日食)

总结

这部分纹理映射的应用内容很多,也有一定的难度,我写的时候也在闫令琪老师课上所讲的内容之上添加了不少东西,参考了不少其它的书籍资料。

最后如果大家觉得有用的话求点赞求收藏求一个大大的关注 😃 ,后序会持续更新,感谢阅读!

Reference

[1] Fundamentals of Computer Graphics 4th
[2] GAMES101-现代计算机图形学入门-闫令琪
[3]【D3D11游戏编程】学习笔记二十四:切线空间(Tangent Space)
[4]Foundations of Game Engine Development, Volume 2

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值