OpenGL Mip贴图、各向异性采样和压缩纹理
Mip贴图
介绍
以上内容均节选自《OpenGL编程指南第9版》第6章纹理与帧缓存
到底什么是Mip贴图?
MIP贴图是一种功能强大的纹理技巧。它可以提高渲染性能同时可以改善场景的显示质量。
想象一下,假设我们有一个包含着上千物体的大房间,每个物体上都有纹理。有些物体会很远,但其纹理会拥有与近处物体同样高的分辨率。由于远处的物体可能只产生很少的片段,OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。在小物体上这会产生不真实的感觉,更不用说对它们使用高分辨率纹理浪费内存的问题了。
OpenGL使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的闭值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。同时,多级渐远纹理另一加分之处是它的性能非常好。让我们看一下多级渐远纹理是什么样子的:
手工为每个纹理图像创建一系列多级渐远纹理很麻烦,幸好。OpenGL有一个glGenerateMipmaps函数,在创建完一个纹理后调用它就会承担接下来的所有工作了。
常见问题
闪烁问题
当屏幕上被渲染物体的表面与它所应用的纹理图像相比显得非常小时,就会出现闪烁效果。类似闪光,当纹理图像采样区域的移动幅度与它在屏幕大小相比显得不成比例时,也会发生这种现象。处于运动状态时,会比较容易看到闪烁的负面效果。
性能问题
加载大量的纹理内存并对它们进行过滤处理,但屏幕上实际只是显示的只是很少一部分的片段。纹理越大,这个问题所造成的性能影响也很明显。
解决方案
这2种问题,我当然可以用很简单的方法解决,就是使用更小的纹理图像。但是这种解决方法又将产生一个新的问题,就是当一个物体更靠近观察者时,它必须渲染的比原来更大一些,这样,纹理就不得不拉伸。拉伸的结果,形成视觉效果很差的模糊或者斑驳状的纹理化效果。
从根本上解决方案,就是使用Mip贴图。不是单纯的把单个图像加载到纹理状态而是把一系列从最大到最小的图像加载到单个“Mip贴图”纹理状态。然后,OpenGL使用一组的新的过滤模式,为一个特定的几何图形选择具有最佳过滤效果的纹理。
Mip纹理由一系列的纹理图像组成,每个图像大小在每个轴的方向上都缩小一半。或者是原来图像像素的总数的四分之一。Mip贴图每个图像大小都依次减半,直到最后一个图像大小是1*1的纹理单元为止。
设置Mip贴图
当我们在讲glTexParameteri()
函数时,谈到了level
参数。level
参数在Mip
贴图发挥作用,因为它指定图像数据用于哪个mip
层。第一层是0,后面依次类推。如果Mip
贴图未使用,那么只有第0层才会被加载。在默认情况下,为了能使用mip
贴图,所有的mip
贴图都要加载。我们可以通过设置GL_TEXTURE_BASE_LEVEL
和GL_TEXTUREMAX_LEVEL
纹理参数特别设置需要使用的基层和最大层。
// 设置mip贴图基层 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); // 设置mip贴图最大层 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_LEVEL, 0);复制代码
我们虽然可以通过设置。GL_TEXTURE_BASE_LEVEL
和GL_TEXTUREMAX_LEVEL
纹理参数控制哪些mip层被加载,但是我们仍然可以使用GL_TEXTURE_MIN_LOD
和GL_TEXTURE_MAX_LOD
参数限制已加载的Mip层的使用范围。
什么时候生成Mip贴图
只有minFilter等于以下4种模式,才可以生成Mip贴图。
GL_NEAREST_MIPMAP_NEAREST
具有非常好的性能,并且闪烁现象非常弱GL_LINEAR_MIPMAP_NEAREST
常常用于对游戏进行加速,它使用了高质量的线性过滤器GL_LINEAR_MIPMAP_LINEAR
和GL_NEAREST_MIPMAP_LINEAR
过滤器在Mip层之间执行了一些额外的插值,以消除他们之间的过滤痕迹GL_LINEAR_MIPMAP_LINEAR
三线性贴图。纹理过滤的黄金准则,具有最高的精度。
if(minFilter == GL_LINEAR_MIPMAP_LINEAR || minFilter == GL_LINEAR_MIPMAP_NEAREST || minFilter == GL_NEAREST_MIPMAP_LINEAR || minFilter == GL_NEAREST_MIPMAP_NEAREST) // 纹理生成所有的Mip层 glGenerateMipmap(GL_TEXTURE_2D);复制代码
glGenerateMipmap函数解析
目的:为纹理对象生成一组完整的mipmap void giGenerateMipmap (GLenum target)
参数:指定将生成mipmap的纹理对象绑定到的活动纹理单元的纹理目标,比如GL_TEXTURE_2D
描述:giGenerateMipmap计算从零级数组派生的一组完整的mipmap数组。无论先前的内容如何,最多包括1x1维度纹理图像的数组级别都将替换为派生数组。零级纹理图像保持不变(原图)。
派生的mipmap数组的内部格式都与零级纹理图像的内部格式相匹配。通过将零级纹理图像的宽度和高度减半来计算派生数组的尺寸,然后将每个阵列级别的尺寸减半直到达到1x1尺寸纹理图像。
经过Mip贴图的纹理过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEARET); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);复制代码
常见的错误
一个常见的错误是,将放大过滤的选项设置为多级渐远纹理过滤选项之一。这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM
错误代码。
各向异性过滤
是什么
各向异性纹理过滤(Anisotropic texture filtering)并不是OpenGL核心规范中的一部分,但它是一种得到广泛使用的扩展,可以极大提高纹理过滤操作的质量。
前面讲过,2种基本过滤,最邻近过滤(GL_NEARES丁)和线性过滤(GL_LINEAR),当一个纹理贴图被过滤时,OpenGL使用纹理坐标来判断一个特定的几何片段将落在纹理什么地方。然后,紧邻这个位置的纹理单使用GL_NEAREST和GL_ L工NEAR过滤操作进行采样。
当几何图形进行纹理贴图时,如果它的观察方向和观察点恰好垂直。那么这个过程是相当完美的。如下图:
当我们从一个角度倾斜地观察这个几何图形时,对周围纹理单元进行常规采样,会导致一些纹理信息丢失(看上去显得模糊)。为了更加逼真和准确的采样应该沿着包含纹理的平面方向进行延伸。如果我们进行处理纹理过滤时,考虑了观察角度,那么这个过滤方法就叫各向异性过滤。
怎么用
查询得到支持的各向异性过滤的最大数量,可以使用
gl_GetFloatv
函数,并以GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
作为参数
GLfloat flargest; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &flargest);复制代码
我们可以使用
glTexParameter
函数以及GL_TEXTURE_MAX_ANISOTROPY_EXT
,设置各向异性过滤数据。
// 设置纹理参数(各向异性采样) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest); // 设置各向同性过滤,数量为1.0表示(各向同性采样) glTexPParamaterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);复制代码
注意: 各向异性过滤所应用的数量越大,沿着最大变化方向(沿最强的观察点)所采样的纹理单元就越多。值1.0表示常规的纹理过滤(各向同性过滤)。
各向异性过滤是会增加额外的工作包括其他纹理单元,很可能对性能造成影响,但是,在现代硬件上,应用这个特性对速度造成影响不大。
最重要的是,目前它已经成为流行游戏、动画和模拟程序的一个标准特性。
压缩纹理
通用压缩纹理格式
判断压缩与选择压缩方式
GLint comFlag; // 判断纹理是否被成功压缩 glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &comFlag); // 根据选择的压缩纹理格式,选择最快、最优、自行选择的算法方式选择压缩格式 glHint(GL_TEXTURE_COMPRESSION_HINT, GL_FASTEST) glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST) glHint(GL_TEXTURE_COMPRESSION_HINT, GL_DONT_CARE);复制代码
加载压缩纹理
void glCompressedTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLint height, GLint border, GLsizei imageSize, void *data); void glCompressedTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLint height, GLint border, GLsizei imageSize, void *data); void glCompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLint height, GLint border, GLsizei imageSize, void *data);复制代码
target:
GL_TEXTURE_1D
、GL_TEXTURE_2D
、GL_TEXTURE_3D
Level:指定所加载的mip贴图层次。一般我们都把这个参数设置为0。
internalFormat:每个纹理单元中存储多少颜色成分
width、height、depth:指加载纹理的宽度、高度和深度。注意:这些值必须是2的整数次方。(这是因为OpenGL旧版本上的遗留下的⼀个要求,当然现在已经可以⽀持不是2的整数次方,但是开发者们还是习惯使⽤以2的整数次⽅去作为参数)
border:允许为纹理贴图指定一个边界宽度
format:OpenGL的像素格式
type:解释参数data指向的数据,告诉OpenGL使用缓冲区中的什么数据类型来存储颜色分量
data:指向图形数据的指针
glGetTexLevelParameter函数提取的压缩纹理格式
GL_EXT_texture_compression_s3tc压缩格式
作者:Gray皓白
链接:https://juejin.cn/post/7063049278427496461