本专栏内容整理了GAMES101的计算机图形学课程的主要内容,作为我学习计算机图形学的一份复习备份或叫做笔记。内容中如有错误,或有其他建议,欢迎大家指出。
附上GAMES101计算机图形学课程:GAMES101: 现代计算机图形学入门正在上传…重新上传取消https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html正在上传…重新上传取消https://sites.cs.ucsb.edu/~lingqi/teaching/games101.html编辑https://sites.cs.ucsb.edu/~lingqi/teaching/games101.htmlhttps://sites.cs.ucsb.edu/~lingqi/teaching/games101.html
纹理映射是在做一个什么事情
上图展现的场景非常简单,两个台灯在照亮地板和一个球。如果我们只盯着球来看的话,我们可以使用Blinn-Phong Reflection Model 这么一个反射模型去渲染出一个没有图案的球。但是在图中的球是有图案的。也就是说在球的不同位置的颜色不一样。那么回顾我们之前讲到的漫反射。k_d这个参数对应的就是颜色。也就是说在这个球表面共用一个漫反射模型。只不过不同位置的漫反射系数不同。所以要在物体表面渲染一个图案我们希望能够在物体表面的不同位置定义不同的漫反射系数。
可以说在对一个物体使用纹理贴图的时候,就是定义物体表面任何一个点的布林冯反射模型中的基本属性。
那么怎么定义任何一个点的基本属性。
首先我们先有一个计算机图形学的概念,任何一个物体表面它都是二维的。比如一个地球仪的表面展开图是世界地图。
一个物体表面可以和一张图有一个一一对应的关系。那么我们这里就定义一个名称叫:纹理(Texture)
纹理是什么,纹理就是一张图,这张图可以被任意地裁切,拉伸,压缩,然后可以贴在任意一个三维物体的表面的图。被任意地裁切,拉伸,压缩,然后可以贴在任意一个三维物体的表面就叫纹理映射。映射就是物体表面的一个点,和纹理上的一个点他们之间的关系。
上图是一个纹理映射的例子,
第一个图片是模型通过布林冯反射模型得到的一个结果,然后我们想在这个模型上贴上一张图片,得到第二个图案的结果。那怎么办?
三维空间中最基本的形状是三角形。那么我们只要找到在物体上的细分出来的三角面,在纹理上对应的内容是哪里。也就是说,在模型上的一个顶点,我都能找到纹理中对应的一个位置。该如何找到这个对应位置。目前有两种方式。
一种是通过艺术家,设计人员们预先设计好了,模型和纹理的一个映射位置。(这是一个大量的工作)
另一种是一个自动化的过程。就是对于任何一个模型,都能够将其展开成为一个平面。并且希望能够使得产生的三角形尽可能的少扭曲。(参数化,这是个几何上很难的研究)
所以这里该如何找到这个对应位置,作为程序员,不关心这个问题。默认已经知道他们的映射关系。
纹理贴图的坐标表示
有了映射关系,我们就要把模型上的点位置对应到纹理上的点位置。模型上顶点位置可以用空间坐标系(x,y,z)来表示。那么在纹理上某一点的位置要怎么表示呢?
在纹理贴图中,我们用一个二维的坐标系来表示纹理的位置。横坐标用u,纵坐标用v,计算机图形学中也会把纹理贴图称为uv贴图。上面的图中的红色和绿色是计算机图形学中常用来表示u和v的大小。方便我们在模型上观察贴图的各个位置。
在纹理贴图的uv坐标中。u和v的取值范围,都是在0~1之内。不如你贴图的分辨率是多少,长宽比是多少(就算贴图是个长方形的贴图)u和v都是0~1之间的值。这是为了方便计算。于是,我们模型表面的三角面的每一个顶点(x,y,z),都对应一个纹理坐标(u,v)
我们观察下面这个建筑表面的砖块纹理
会发现在这个建筑物表面或者地板的砖块是连续一块接着一块,大小都一样。没有明显的贴图拼接缝。这并不是因为这个建筑表面用了一个非常大的纹理。而是用了一个左边和右边,上边和下边可以无缝拼接的纹理贴图。再像贴瓷砖一样,一张一张贴上去。
通过上面这个纹理的uv显示,可以看到实际上建筑表面是由非常多贴图拼接在一起的。不同的位置可以应用相同的纹理。
对于这种左右,上下可以无缝拼接的纹理就叫做tilable-texture,可以用少量的内存占用渲染大面积的墙面,地板等。
三角形内的纹理映射
在我们知道了三角形三个顶点分别对应在纹理贴图上的哪一个位置(u,v)后。我们可以使用三角形重心插值法找出三角形内部的任何一个点对应的纹理贴图位置。我们就可以将纹理贴图上这个位置的颜色,应用到三角形内对应位置的漫反射系数(Kd)上。
纹理放大
当我们使用我们前面讲的方法去映射纹理的时候,存在几个问题。
纹理放大:假如说,我们的渲染屏幕是一个高分辨率的屏幕,达到了4K分辨率。但是我们的纹理贴图的像素分辨率只有360P。那么一个屏幕上的像素去查询映射的纹理的时候就无法映射到一个整数上。比如屏幕第(10,10)像素映射到纹理贴图像素上可能是第(1.425,1.552)像素 这样一种情况。
图1.1
对于这种情况,最直接的我们可以使用四舍五入的方法,得到对应的纹理贴图像素。(左1)我们可以看到在这种方法下,屏幕的多个像素会一起映射到纹理贴图的同一个像素。得到一幅充满锯齿的画面。
那我们如何能得到一幅各个像素过渡比较平滑连续一些的渲染图呢?
我们就需要借助到双线性插值的方法。
双线性插值是一种在数学和计算机图形学中常用的插值方法,它是线性插值在二维空间的扩展。这种方法通过在两个方向上分别进行线性插值来估算未知函数值。具体来说,双线性插值用于在一个由四个已知点构成的矩形网格内,计算内部任意点的值。
假设我们有一个函数 ( f ) 在四个点 ( (x_1, y_1) ), ( (x_1, y_2) ), ( (x_2, y_1) ), 和 ( (x_2, y_2) ) 上的值已知,我们想要计算该函数在内部点 ( (x, y) ) 上的值。双线性插值的步骤如下:
- 在 ( x ) 方向上对 ( y_1 ) 和 ( y_2 ) 进行线性插值,得到两个临时点的值。
- 在 ( y ) 方向上对这两个临时点进行线性插值,得到最终点 ( (x, y) ) 的值。
比如屏幕上的像素点是图中的红点,背景是第分辨率的纹理贴图。当屏幕像素映射到纹理贴图中的某个纹理内部。
采用双线性插值,首先我们先找到映射点周围的四个像素。u_00, u_01,u_10, u_11 ,
然后,我们计算出来红点跟左下角那个像素的水平距离s , 和竖直距离 t 。因为每个像素之间的距离都是1。所以s和t肯定是0~1之间的数值。
线性插值公式 :lerp(x, v0, v1) = v0 + x(v1 v0) ;
线性插值描述:当x为0,值为v0,x为1,值为v1,x是0.5值就在v0和v1的中间。
所以接下来,我们可以先在u_00和u_10之间做一次线性插值,得到一个值u_0。u0 = lerp(s, u00, u10)
再在u_01和u_11之间做一次线性插值,得到一个值u_1。u1 = lerp(s, u01, u11)
得到两个水平的插值后。只需要再使用u_0和u_1和竖直距离t 做一次竖直方向上的插值就能得到一个结合了四个像素点的颜色。
并且我们可以发现使用双线性插值的方法,映射点越靠近哪一个像素点。颜色就越靠近那个像素的颜色。图1.1中间的图像就是使用双线性插值方法得到的。可以发现对比直接四舍五入的方法,双线性插值法画面过渡更平滑。
当然还有一些渲染质量更高的插值方法。比如说图1.1最右边的图。使用了双向三次的插值方法,取了映射点周围16个的像素作为插值参考量。得到的效果更好。
但是在图形学中,好的质量通常会花费更高的性能消耗。
纹理分辨率太大
前面介绍了纹理太小,图像容易出现锯齿。那么当纹理像素过大,会出现什么情况呢。
在上面的图中左边是一幅正常渲染应该得到的场景图。而右边的图片在中间部分出现了锯齿,远处的部分出现了摩尔纹的情况。这就是纹理的分辨率太大。在远处的地方,在较小的像素范围内。需要渲染较大的纹理图片。此时,屏幕上的一个像素,就覆盖了纹理贴图上面很大的一块区域。甚至一个像素要渲染一整个贴图的颜色。如果我们继续按照前面介绍的方法四舍五入,或者插值就会出现右图的情况。
图中背景是纹理贴图。前面的蓝色覆盖区域是屏幕的像素,蓝色是屏幕像素中心点,这里用屏幕像素中心点所在的位置去采样。
我们会发现右图的摩尔纹,锯齿跟我们之前介绍过的走样一样。这里同样是采样点屏幕像素,过少过于离散导致的走样。所以我们也可以考虑使用反走样方法。
使用MSAA超采样,每个屏幕像素使用512个采样点可以得到下面右边的图像。
结果还不错,但是一个像素512个采样点才能得到这么一个效果,性能代价有些太大了。
采样会引起走样,那如果我们不采样呢?
对于纹理映射的采样。我们是预先知道要采样的源信号(纹理贴图)是什么样的,如果我们预先对纹理贴图进行分辨率降低(Mipmap)。在屏幕映射的时候我们立刻选择适合的分辨率贴图(范围查询)。这不就能降低性能的消耗了吗?多加几张低分辨率的纹理贴图,用空间换时间。用内存换性能。
图中两个白色圆圈处使用了相同样子的纹理贴图。我们可以知道,在近处的地方应当选用分辨率高的纹理贴图,在远处的墙上,应该选择分辨率很低的纹理贴图。
Mipmap
Mipmap是一个在图形学上广泛应用的经典概念。它允许大家做范围查询。
Mipmap做的范围查询特点:快,但是不准确。
Mipmap就是从一张图片,生成的一系列图。
就比如上图。通过八层的降低分辨率,得到了一共8张不同的分辨率图片。
这些图片(Mipmap)是可以提前计算生成的。就是在渲染之前,先将纹理处理一遍,得到低分辨率的一系列mipmap。
计算机视觉中,Mipmap称为图像金字塔。
我们说到,Mipmap是以空间换时间。那么mipmap对比原本的贴图会多出多少空间呢?
答案是:1/3 !!!!!!!所以这是一个非常划算的生意。
上图中,我们将原图复制三份,填充在一个矩形空间中,再将图像分辨率减小一倍,再复制三份放在右下角剩余的矩形空间中,再继续。。。。。。。会发现右下角多出来的内容空间是无限接近1份贴图的。对比原本复制的3份贴图,多出来的比例就是1/3
我们要用Mipmap做一个近似的范围查询,给到一个值立刻要得到这个值对应给定的范围内的平均值是多少。
任何一个像素都可以映射到纹理上的一个区域,那么这个区域我们要怎么得到。就是采用一个近似的办法。
比如:上图中,左边是屏幕空间,右边是纹理贴图。蓝色的像素和红色的像素要映射到纹理贴图上的对应部分(这里要渲染的面跟视角有一定的角度)。
在屏幕空间中,各个点的距离都是一致的,都是一个像素。根据映射关系,我们会知道映射到纹理上两个点之间会间隔多大的uv距离。(x,y;u,v都知道。)
然后我们用计算出来的L作为正方形边长,取纹理贴图中映射点周围的这么一个正方形作为屏幕像素的映射区域。
接下来我们用这个L * L 的矩形去查询合适像素的纹理贴图。如果L是1那么,我们就用原本第0层的材质贴图去映射到屏幕像素。如果L是2,那我们就可以用第1层的纹理贴图。如果L是4,那就使用第2层的纹理贴图。
上面的图中,我们用不同的颜色表示使用不同层级的Mipmap。近处使用较第层级的Mipmap,远处使用高层级的Mipmap。但是我们会发现,各个层级之间并不连续。如何更连续地查询Mipmap,比如查询出第1.8层出的纹理贴图。
连续就是线性,线性就是连续。
所以我们又可以使用线性插值。
线性插值查询Mipmap
当我们要查询第1.8层时,我们可以用双线性插值先得到第1层和第2层的值。再对得到的值再进行一次线性插值。总共做了三次的插值。
这样不管我们的纹理映射在哪一个位置,我们都可以通过三线性插值,做一次查询,得到屏幕像素内覆盖的颜色的平均。
采用三线性插值我们那就得到了一个连续效果很好的层级分布图。
接下来我们用Mipmap来重新渲染前面的那个地面网格图
我们会发现在远处全都糊掉了。这种情况叫Oveblur。过分模糊。
所以Mipmap还存在一定的问题。
各向异性过滤
因为Mipmap近似出来的是一个正方形的区域,在远处的时候,由于视角变化导致屏幕上的一个正方形像素,覆盖到的区域变成了一个狭长的长方形。在贴图的u和v方向上压缩的比例不一致。
在这种情况下,我们就需要引入一个新的方法来帮助我们部分结局这个问题。
这个方法就是各向异性过滤。(可以打开你的3A大作的设置选项中是否有这么一个选项,开关查看效果)
各向异性就是在各个方向上表现出来的形状是不同的。
我们可以看到采用了各项异性过滤后,在远处的渲染效果明显好于普通的Mipmap。
Mipmap中我们的长和宽是等比例缩小的,无法适应对于长宽比例压缩不一致的纹理映射。Mipmap计算的就是上面卫星图中左上到右下的对角线的图片。
而我们用各向异性过滤补充了长宽非等比例缩小的内容,更适合像素映射成矩形的区域。
那么各向异性过滤的空间增加了多少呢?从上面的卫星图,我们大概可以看出,是增加了三倍
还有一种情况。
EWA过滤
对于这种斜着的这种纹理映射区域使用各向异性过滤就无法处理。
对于这种情况就需要使用EWA过滤。
EWA:通俗讲:你有一个任意的不规则形状。对于这个不规则形状,我们都可以把它给拆成一堆的椭圆。用这些椭圆来覆盖这个不规则形状。渲染时我都去查询每一个椭圆,经过多次查询,就得到了可以覆覆盖这个不规则区域的几个椭圆(个人理解,仅供参考)。
下面是gpt的论述:
EWA过滤,即椭圆加权平均(Elliptical Weighted Average)过滤,是计算机图形学中一种高级的纹理过滤方法。它主要用于纹理映射(texture mapping)过程中,以减少纹理放大(texture magnification)时产生的失真和锯齿效果。
EWA过滤的基本思想是考虑像素在屏幕空间的椭圆形区域(footprint),并对这个区域内的纹理元素(texels)进行加权平均。这种方法可以有效地处理纹理映射时的各向异性问题,特别是当纹理表面与观察平面成较大角度时。
具体来说,EWA过滤会追踪当前像素的纹理坐标对于屏幕空间坐标的微分,即 ( \frac{du}{dx}, \frac{du}{dy}, \frac{dv}{dx}, \frac{dv}{dy} )。这些微分值可以通过实时渲染中的ddx和ddy函数获得,在离线渲染中则需要使用射线微分来获得。通过这些微分值,EWA过滤可以确定纹理空间中的采样频率,从而选择合适的mipmap级别进行采样。
EWA过滤算法的核心步骤包括:
- 使用微分值确定mipmap级别。
- 构建椭圆形状来近似投影后的footprint。
- 扫描椭圆所在的包围盒来决定哪个纹素在椭圆内,并进行卷积处理。
到目前为止,传统的图形渲染----光栅化的内容就记录完了(除了阴影的部分)。