GAMES101 系列总结(四):如何应用纹理贴图
上一篇关于Shading的博客中最后我们提到了Texture是我们用来对点进行输入的方式。
之前我们的说,每个点都有自己的UV坐标,然后通过UV坐标去贴图上找到对应的点,然后把值取出来就好,三角形中间的像素点我们可以通过中心坐标来进行插值。
但是问题来,如果我们的贴图过小,就可能导致模型是上多个点对应相同的UV,那么就会造成模糊,反之如果贴图过大,UV坐标差别过大,会造成最后产生锯齿或者摩尔纹的效果。
本文就来讲一下,游戏引擎是如何解决这两种问题的。
基本的采样规则
(x,y)是屏幕上的像素坐标,(u,v)是模型上的点对应的纹理坐标
1 | for each rasterized screen sample (x,y): |
如果贴图过小会怎样
方案一:纹理放大
这个方案是最简单的方案,贴图过小就通过一定的算法进行放大。
这里我们引入一个新的概念,叫做纹素(texel),即纹理上的一个点(A pixel on a texture),我们的UV坐标从贴图上取到的就是一个纹素
纹理放大的算法常见的有三种:Nearest,Biliner,Bicubic
我们以Biliner为例来讲解下纹理放大的过程。
假设我们要采样下图中红色点的值,我们选取该点周围的四个点,然后计算我们要采样的点与四个纹素中心之间的距离,并用这个距离进行插值:
如果纹理过大会怎样
首先我们看一个效果图:
它在近的地方,因为贴图过大,丢失了一些细节导致出现了锯齿,而在远处,又因为过多的细节产生了摩尔纹。(原因属于个人理解,但效果是这样的没错)
当贴图过大的时候,实际上就是如下效果:
也就是当我们利用一开始说的的基本采样规则
去找贴图上的某个纹素的时候,找到的不是一个,而是一片。
换句话说,贴图过小,多个像素找到同一个纹素,贴图过大,一个像素可以匹配多个纹素
方案一:超采样
这个超采样技术的算法其实和刚才的纹理放大是一样的,只不过针对贴图过小的情况,超采样针对的是原始贴图,用于生成一个分辨率更高的原始贴图,而当贴图过大时,一个像素可能对应多个纹素,那么该像素的从贴图上的采样结果是有其周围的多个纹素共同决定的。
下图为超采样的效果图:
可以看到,超采样确实是有效果的,但是问题就是,计算代价过大,每个像素都需要计算多个点的值并进行插值。
方案二:求平均值
超采样计算麻烦,我们就简单的在一个范围内求平均值就好
方案三:Mipmap
这是一种更快的范围查询,是一种空间换时间的思路,需要我们事先计算好不同采样频率下范围查询的结果存储到贴图中,即生成不同程度低分辨率的贴图,当出现贴图过大的情况时,直接去更小分辨率的贴图上进行查找。
Mipmap是一种纹理映射的技术,用于在不同距离和角度下呈现不同细节的纹理。一个Mipmap纹理是由多个不同分辨率的纹理图像组成的,每个图像都是前一个图像的1/4大小。Mipmap的level是指Mipmap纹理中的第几个图像,从0开始计数,0级是原始纹理图像。
这种方案有两个关键点:
- 生成不同级别的Mipmap
- 如何确定使用哪个级别的Mipmap
第一点是图像压缩的算法,有很多,我们不多赘述,也不是渲染特有的知识,我们主要看一下如何确定级别,即这个D
Mipmap的level可以通过以下公式计算:
level = log2(max(w, h))
其中,w和h是纹理图像的宽度和高度,max(w, h)表示宽度和高度中的最大值。这个公式的意思是,每个Mipmap级别的图像都是前一个级别的图像的1/4大小,因此在计算时使用对数函数以2为底数,可以得到最大级别的大小。
例如,如果原始纹理图像的大小为512x512像素,则Mipmap纹理包含8个级别,分别是0级到7级。0级是原始图像,1级是256x256像素的图像,2级是128x128像素的图像,以此类推。
我们需要采用合理的Level才会有比价好的效果,级别越高,细节越少,如果因为过小再使用贴图过小时的超采样方法,会更加模糊:
方案四:Anisotropic Filtering
Mipmap其实还是有限制的,它只能去对正方向的贴图做mipmap。
因为他的这个限制,一方面,在做采样的时候会用到很多用不到的点,增加计算量,其次是一些像素对应的是覆盖多个纹素的长方形,导致超采样模糊。
针对长方形的情况,我们可以采用Anisotropic Filtering
按比例缩小的正方形贴图在对角线,其他位置存放一些宽度压缩活着长度压缩的图