OpenGL Mip贴图、各向异性过滤(七)


1、Mip贴图

 

Mip贴图简介:

纹理对象可以从不同的视点距离进行观察。在一个动态的场景中,当贴了纹理的物体远离视点运动时,屏幕像素与纹理纹素之间的比率会变得非常低(屏幕像素/纹理纹素),因此纹理的采样频率也会变得非常低。这样会产生渲染图像上的瑕疵,因为有纹理数据的下采样(undersampling)的缘故。举例来说,如果要渲染一面砖墙,可能会用到一张很大的纹理图像(比如1024×1024个纹素),在观察者距离墙很近的时候这样是没问题的。但是如果这面墙正在远离观察者运动,直到它在屏幕上变成了一个像素点,那么纹理采样的结果可能会在某个过渡点上发生突然的变化(闪烁)。

为了降低这个效果的影响,可以对纹理贴图进行提前滤波,并且将滤波后的图像存储为连续的低分辨率的版本(原始图像为全分辨率)。这就叫做mipmap,

Mipmap是目前应用最为广泛的纹理映射技术之一。Willams将低一级图像的每边的分辨率取为高一级图像的每边的分辨率的二分之一(因此分辨率比例类推:1、1/4、1/16、1/64、...),而同一级分辨率的纹理组则由红、绿、蓝三个分量的纹理数组组成。由于这一个查找表包含了同一纹理区域在不同分辨率下的纹理颜色值,因此被称为Mipmap。

mipmap_1.png

MIP来源于拉丁文中的multum in parvo(多级渐远纹理),意为在一个小空间里的多数。Mipmap(有时候拼写成mipmap)是一种电脑图形图像技术,用于在三维图像的二维代替物中达到立体感效应。MIPmap技术与材质贴图技术结合,根据距观看者远近距离的不同,以不同的分辨率将单一的材质贴图以多重图像的形式表现出来并代表平面纹理:尺寸最大的图像放在前面显著的位置,而相对较小的图像则后退到背景区域。每一个不同的尺寸等级定义成一个MIPmap水平。MIPmap技术帮助避免了不想要的锯齿边缘(称为锯齿状图形)在图像中出现,这种锯齿状图形可能是由于在不同分辨率下使用bitmap图像产生的。

其实道理类似,纹理过大,每个纹素的大小相较于像素更小了,就导致每个像素覆盖的区域实际上会包含多个纹素,但在片元着色阶段只会进行一次「点采样」,得到的结果显然是不够“精密”的。从信号学的角度上来理解,每个像素的采样频率远远低于纹理上的变化频率,于是也会形成锯齿的效果。假设一张特别大的纹理贴在地面上,由于透视摄像机的近大远小原理,会放大像素与纹素的信号频率差距,因此远处的地面会形成一种叫做“摩尔纹”的效果。

mipmap_2.jpg

既然是因为采样频率不够,那么解决这个问题一个最直观的办法就是 「超级采样(Super Sampling)」 ,每个像素只采样一次不足以覆盖包含的所有纹素的话,那就多采样几次,并将最终的采样结果中和到一起,生成该像素的最终颜色。

超级采样虽然的确能以较高的质量解决锯齿问题,但计算成本实在是太高了!尤其是很远处的像素,为了抗锯齿进行了大量的多重采样,人眼看到的结果却无法辨别细节,这无疑是一种严重的浪费,所以Mipmap便是更好的解决方式。

 

Mip贴图使用场景:

Mip贴图是一种强大的纹理技巧,它不仅可以提高渲染性能,而且可以改善场景的显示质量.它使用标准纹理贴图处理两个常见的问题。

  • 闪烁问题,当屏幕上被渲染物体的表面与它所应用的纹理图像相比显得非常小时,就会出现闪烁效果。类似闪光,当纹理图像采样区域的移动幅度与它在屏幕大小相比显得不成比例时,也会发生这种现象。处于运动状态时,会比较容易看到闪烁的负面效果。

  • 性能问题,加载大的纹理内存并对它们进行过滤处理,但屏幕上实际只是显示很少的一部分片段。纹理越大,这个问题所造成的性能影响也就越明显。

 

Mip贴图的使用:
  • 设置过滤方式

// GL_NEAREST(最邻近过滤)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

// GL_LINEAR(线性过滤)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

// GL_NEAREST_MIPMAP_NEAREST(选择最邻近的Mip层,并执行最邻近过滤)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);

// GL_NEAREST_MIPMAP_LINEAR(在Mip层之间执行线性插补,并执行最邻近过滤)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);

// GL_NEAREST_MIPMAP_LINEAR(选择最邻近Mip层,并执行线性过滤)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);

// GL_LINEAR_MIPMAP_LINEAR(在Mip层之间执行线性插补,并执行线性过滤,又称为三线性过滤)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

  • 生成一组完整的mip贴图

//为纹理对象生成一组完整的mip贴图
//参数1:纹理维度,GL_TEXTURE_1D,GL_TEXTURE_2D,GL_TEXTURE_3D
glGenerateMipmap(GL_TEXTURE_2D);

常量 描述
GL_NEAREST 在Mip基层上执行最邻近过滤
GL_LINEAR 在Mip基层执行线性过滤
GL_NEAREST_MIPMAP_NEAREST 在最邻近Mip层,并执行最邻近过滤
GL_NEAREST_MIPMAP_LINEAR 在Mip层之间执行线性插补,并执行最邻近过滤
GL_LINEAR_MIPMAP_NEAREST 选择最邻近Mip层,并执行线性过滤
GL_LINEAR_MIPMAP_LINEAR 在Mip层之间执行线性插补,并执行线性过滤,又称三线性Mip贴图

参数GL_TEXTURE_MIN_FILTER负责控制mipmap层次大于0的时候,纹素构建的方式。这个参数总共有6个可以设置的参数值。前2个值纹理放(magnification)的参数值是相同的:GL_NEAREST和GL_LINEAR。选择这2个参数,OpenGL会关闭mipmap并且仅仅使用纹理的基本层(0层)。

其他4个参数分别是∶ GL_NEAREST_MIPMAP_ NEAREST、GL_NEAREST_MIPMAPLINEAR、GL_LINEAR_MIPMAP_NEAREST、 GL_LINEAR_MIPMAP_LINEAR。注意每个模式参数都是由两部分组成的,名称的结构总是GL_{A}MIMPMAP{B}的形式。这里的{A}和{B}都可以是NEAREST或者LINEAR中的一个。第一部分{A}负责控制每个mipmap层次的纹素构成方式,它与GL_TEXTURE_MAG_FILTER中的设置是按照同样的方式工作的。第二部分{B}负责控制采样点在两个mipmap层次之间的融合方式。如果设置为NEAREST,那么计算只用到最近的mipmap层。如果设置为LINEAR,那么两个最近的mipmap层数据之间会进行线性的插值计算。

glGenerateMipmap计算从零级数组派生的一组完整的mipmap数组。无论先前的内容如何,最多包括1x1维度纹理图像的数组级别都将替换为派生数组。零级纹理图像保持不变(原图)。

派生的mipmap数组的内部格式都与零级纹理图像的内部格式相匹配。通过将零级纹理图像的宽度和高度减半来计算派生数组的尺寸,然后将每个阵列级别的尺寸减半,直到达到1x1尺寸纹理图像。



//设置mip贴图基层
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);

//设置mip贴图最大层
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);

使用glTexParameteri()函数时,用到level参数,它指定图像数据用于哪个mip层。第一层是0,后面依次类推。如果未使用Mip贴图,就只加载第0层。默认情况下,为了使用Mip贴图,mip层都要被加载,可以设置GL_TEXTURE_BASE_LEVEL和GL_TEXTURE_MAX_LEVEL纹理参数使用基层和最大层。

虽然可以通过设置GL_TEXTURE_BASE_LEVEL和GL_TEXTURE_MAX_LEVEL纹理参数控制哪些mip层被加载。但仍然可以使用GL_TEXTURE_MIN_LOD和GL_TEXTURE_MAX_LOD参数限制已加载的mip层使用范围。

 

什么时候生成Mip贴图:

只有minFilter等于以下四种模式,才可以生成Mip贴图。

  • GL_NEAREST_MIPMAP_NEAREST具有非常好的性能,并且闪烁现象很弱。
  • GL_LINEAR_MIPMAP_NEAREST常用于对游戏加速,它使用高质量的线性过滤器。
  • GL_NEAREST_MIPMAP_LINEAR过滤器在Mip层之间执行了一些额外的插值,以消除它们之间的过滤痕迹。
  • GL_LINEAR_MIPMAP_LINEAR三线性Mip贴图。纹理过滤的黄金准则,具有最高的精度。

一个常见的错误是将放大过滤的选项设置为多级渐远纹理过滤选项之一,这样是没有任何效果的,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个 GL_INVALID_ENUM 错误代码。

 

OpenGL实现了Mip贴图功能:

// 函数定义一个二维纹理映射。
glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);


// 参数target: 是常数GL_TEXTURE_2D。

// 参数level: 表示多级分辨率的纹理图像的级数,若只有一种分辨率,则level设为0。

// 参数internalformat: 是一个从1到4的整数,指出选择了R、G、B、A中的哪些分量用于调整和混合,1表示选择了R分量,2表示选择了R和A两个分量,3表示选择了R、G、B三个分量,4表示选择了R、G、B、A四个分量。

// 参数width和height: 给出了纹理图像的长度和宽度,

// 参数border: 为纹理边界宽度,它通常为0,width和height必须是2m+2b,这里m是整数,长和宽可以有不同的值,b是border的值。纹理映射的最大尺寸依赖于OpenGL,但它至少必须是使用64x64(若带边界为66×66),若width和height设置为0,则纹理映射有效地关闭。

// 参数format和type: 描述了纹理映射的格式和数据类型,参数format可以是GL_COLOR_INDEX、GL_RGB、GL_RGBA、GL_RED、GL_GREEN、GL_BLUE、GL_ALPHA、GL_LUMINANCE或GL_LUMINANCE_ALPHA(注意:不能用GL_STENCIL_INDEX和GL_DEPTH_COMPONENT)。类似地,参数type是GL_BYPE、GL_UNSIGNED_BYTE、GL_SHORT、 GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT、GL_FLOAT或GL_BITMAP。

// 参数pixels: 包含了纹理图像数据,这个数据描述了纹理图像本身和它的边界。

 

Mip贴图的优缺点:

Mipmap是一种典型的空间换时间的解决方案,通过范围查询来替代之前双线性差值所做的点查询,它的好处就是速度快,但是也有缺点:结果不够准确、只能用于正方形(2的幂次方)贴图,以及增加了额外的存储量(增量约为1/3)。

当计算出的层数恰好不是整数的时候(事实上经常会出现这种情况),同一纹理上的不同Mip之间会出现明显的断层现象,十分地不自然。

出现了这种不自然的过渡,解决办法依然是老办法:「差值」 !

假设计算出的Mip层数是0.7,那么需要将该采样点在第0层和第1层分别进行双线性差值,然后再将两个Mip计算出的颜色结果再进行一次线性差值,得到最终的颜色。我们管这种计算方法叫做:「三线性差值(Trilinear Interpolation)」。

但是某些情况下,使用三线性差值计算出的Mipmap颜色依旧会有问题。类似开放世界的第一人称视角,看向地平面很远处的位置,可能会产生出over blur的效果。还记得Mipmap的缺点吗?它只能针对正方形纹理进行Mip分层,当透视效果越明显,越远处的像素对应纹理中的区域会越不接近正方形(更像长条的矩形,甚至连矩形都不是),如果使用面积更大的正方形来包住这个区域,此时的结果就会愈加不准确,造成了越远处越模糊的感觉。

为了解决这个问题,很多硬件都支持 「各项异性过滤」的特性,例如OpenGL中可以通过 GL_TEXTURE_MAX_ANISOTROPY_EXT 来设置纹理中各项异性过滤的数值。


如果正好是一个纹素的大小,也就是一个像素对应一个纹素,那么就选择第0层的Mip
如果对应四个纹素的大小,也就是一个像素对应4个纹素,那么就选择第1层的Mip


 

2、各向异性过滤

 

各向异性过滤简介:

各向异性纹理过滤(Anisotropic texture filtering) 并不是OpenGL核心规范中的一部分,但它是一种得到广泛使用的扩展,可以极大提高纹理过滤操作的质量。

当几何图形进行纹理贴图时,如果它的观察方向和观察点怡好垂直,那么这个过程是相当完整的。当我们从一个角度倾斜地观察这个几何图形时,对周围纹理单元进行常规采样,会导致一些纹理信息丟失(如下图的轨道与我们的观察视角不垂直,看上去显得模糊)。为了更加逼真和准确的采样应该沿著包含纹理的平面方向进行延伸。如果我们进行处理纹理过滤时,考虑了观察角度,那么这个过滤方法就叫“各向异性过滤”。在Mip贴图纹理过滤模型中,或者其它所有的基本纹理过滤我们都可以应用各向异性过滤。

mipmap_3.png

 

各向异性过滤使用:



// 获取各向异性过滤的最大数量
// 查询当前系统支持的最大各向异性过滤的数值,数值越大,表示沿着最大变化方向所采样的纹理单元越多,显示效果就越好:
GLfloat fLargest;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);

// 设置纹理参数(各向异性采样)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);


 

各向同性过滤:

各向异性过滤所应用的数量越大,沿著最大变化方向(沿最强的观察点)所采样的纹理单元就越多。值1.0表示常规的纹理过滤(各向同性过滤)。各向异性过滤,是会增加额外的工作,包括其他纹理单元,很可能对性能造成影响。但是,在现代硬件上,应用这个特性对速度造成影响不大。最重要的是,目前它已经成为流行游戏、动画和模拟程序的一个标准特性。


// 设置各向同性过滤,数量为1.0表示(各向同性采样)
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);

你可能感兴趣的:(OpenGL Mip贴图、各向异性过滤(七))