【OpenGL】蓝宝书第五章——基础纹理

目录

纹理贴图 texture mapping

原始图像数据

像素包装

像素图  pixmap

保存像素

读取像素

载入纹理

使用颜色缓冲区

更新纹理

纹理对象

纹理应用

纹理坐标

纹理参数

综合运用

Mip贴图

Mip贴图过滤

生成Mip层

活动的Mip贴图

各向异性过滤

纹理压缩

压缩纹理

加载压缩纹理

最后一个示例


载入纹理图形  glTexImage/glTexSubImage
设置纹理贴图参数 glTexParameter
管理多重纹理 glGenTextures/glDeleteTextures/glBindTexture
生成Mip贴图 glGenerateMipmap
使用各向异性过滤 glGetFloatv/glTexParameter
载入压缩纹理 glCompressedTexImage/glCompressedTexSubImage

纹理贴图 texture mapping

纹理只是一种能够应用到场景中的三角形上的图像数据,它通过经过过滤的纹理单元(texel,相当于基于纹理的像素)填充到实心区域。在UnityShader里我对纹理贴图的运用理解是简单的通过片段上的纹理坐标采样像素颜色信息进行运算,贴图的数据可代表什么取决于使用者,多数情况是直接代表输出颜色,其他的例如遮罩值。

原始图像数据

位图(一系列表示开启和关闭像素值的0和1)表示,每个内存块中的每个位都与屏幕上某一个像素的状态一一对应。
像素图(pixmap),每一个像素都显示了256种不同深度的灰色中的一种。
”位图“经常用在饱含灰度或全彩色数据的图像中,它是.BMP文件扩展名的文件。

像素包装

图像数据在内存中很少以紧密包装的形式存在。出于性能上考虑,一幅图像的每一行都应该从一种特定的字节对齐地址开始。绝大多数编译器会自动把变量和缓冲区放置在一个针对该架构对齐优化的地址上。
默认情况下,OpenGL采用4个字节的对齐方式,普遍上适用于目前正在使用的系统。

一张图片宽度和高度已知,且知道每个像素占据3位,我们一般都认为是高度*宽度*3就是图片字节数,其实并不都是如此,如果硬件本身的体系结构是4字节排列(大部分是这样的),假设图片宽度199个像素,正常思考是199*3=597个字节,但实际是600个,因为硬件体系结构是4字节排列的,它会多出3个空字节,即图片每一行像素会多出3个字节!这是为了使得每一行的存储器地址从一个能被4整除的地址开始!这种问题解决方法就是坚持用二次幂尺寸大小的纹理,例如将199变成2次幂即200,200*3=600字节,虽然这从结果上还是用开辟了不必要的字节开销,但这有利于CPU更高速地获取数据块。

BMP文件是4字节排列的,TGA文件是1字节排列的;为什么内存分配意图对于OpenGL来说这么重要?因为我们向OpenGL提交图像数据或从OpenGL获取图像数据时,OpenGL需知道我们想在内存中对数据进行怎样的包装或解包装操作。

改变像素的存储方式:  void glPixelStorei(GLenum pname, GLint param);
恢复像素的存储方式:  void glPixelStoref(GLenum pname, GLint param);  

例如, glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 从内存缓冲区中读取数据并以紧密包装形式解包?(书籍解释的不是很清楚)
用GL_PACK_ALIGHMENT来告诉OpenGL如何将从像素缓冲区中读取并放置到一个用户指定的内存缓冲区的数据进行包装(即指定如何对已经读出来缓存的图像数据进行包装的)
glPixelStore参数不再列出,可自行查阅文档;这类知识比较难懂,后续可能会反复尝试理解(后续掌握后,补充内容便于理解,缺少实例)

像素图  pixmap

每个像素将需要一个以上的存储位来表示。每个像素的附加位(?)允许存储强度(intensity, 或亮度即luminance值)或者颜色分量值。在OpenGL核心版本中,无法直接将一个像素图绘制到颜色缓冲区中,但可用如下函数将颜色缓冲区的内容作为像素图直接读取。(相当于读屏幕像素,但信息可能不止于RGBA)

void glReadPixels(GLint x, GLint y, GLSizei width, GLSizei height, GLenum format, GLenum type, const void *pixels);

【使用进行时会阻碍应用程序,因它是通过总线传输到系统内存的;若指定一个与图形硬件的本地排列不同的像素格式类型和像素数据类型,那么在转格式时会产生性能开销。】

x,y是屏幕左下角的窗口坐标,width,height是矩形宽、高值(像素形式),如果读取的颜色缓冲区存储的数据与我们要求不同,可以使用format进行制定转换成我们想要的像素格式,可用type制定像素数据类型。例如:glReadPixels(0,0, width, height, GL_BGR, GL_UNSIGNED_BYTE, pBits);  GL_BGR格式按照蓝、绿、红顺序排列GL_UNSIGNED_BYTE类型(每种颜色分量都是1个8位无符号整数)的像素。即这个像素是24位的,0~7位是B分量,8~15位是G分量,16~23位是R分量。

较为特殊的像素数据类型如:UNSIGNED_BYTE_3_2_2 则代表一个像素占8位,且像素只有RGB三个分量,第一个、第二个分量占3位,第三个分量占2位;UNSIGNED_BYTE_2_3_3_REV则代表像素占8位,且只有RGB三个分量,分布[2,3,3] [第三个分量、第二分量、第一个分量];注意我这里的顺序就是存储在8位存储器内的描述,左边是高位,右边是低位,而R、G、B分量谁是第一个,第二个,第三个和像素格式类型有关,使用GL_RGB,则第一个分量是R,使用GL_BGR则第一个分量是B。关于像素数据格式和像素数据类型更多变量自行查阅。

一般情况下双缓存渲染环境下会在后台缓冲区进行读取,而单缓冲区环境下则在前台缓冲区进行。
void glReadBuffer(GLenum mode);  指定读取缓冲区时在哪进行
mode:  GL_FRONT、GL_BACK、GL_LEFT、GL_RIGHT、GL_FRONT_LEFT、GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT、GL_NONE。
(很多参数没搞懂什么意思,书上解释不清,姑且认为是能自定义在什么前台还是后台缓冲区进行读取操作)

保存像素

glWriteTGA函数:从前台颜色缓冲区中读取颜色数据并存储到一个Targa文件格式的图像文件中。
运用实例:https://blog.csdn.net/qq_39574690/article/details/115221543

读取像素

Targa图像格式是一种方便且容易使用的图像格式,并且它即支持简单颜色图像,也支持带有Alpha值的图像。
从磁盘中加载Targa文件的函数:
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat);
szFileName: 文件名(可附加路径), iWidth, iHeight文件图像宽度和高度,iComponents:? eFormat:图像格式。
只有iComponent参数看不太懂,实例是GL_RGB可见它还是一种像素数据格式。Component意为成分,指图像成分是以RGB形式存在还是RGBA存在?
运用实例:https://blog.csdn.net/qq_39574690/article/details/115255624

载入纹理

载入纹理是应用纹理的第一步,一旦被载入,这些纹理就会作为当前纹理状态的一部分。有3个OpenGL函数用于从存储器缓冲区中载入纹理数据(例如:从磁盘文件中读取)

void glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, void *data);

void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, void *data);

void glTexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, void *data);

函数通知OpenGL所需知道的与如何解释数据参数指向的纹理数据有关的所有信息(即解释与纹理数据有关的所有信息)
它们实际都是由同一个函数glTexImage派生的,OpenGL支持一维、二维、三维纹理贴图,并应用相应的函数来载入它们并将它们设置为当前纹理。OpenGL还支持立方图纹理(第七章内容)
使用它们时会从data参数中复制纹理信息,这种数据复制可能会有很大开销,稍后会介绍几种有利于减轻这个问题的方法。

target: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D。也可指定代理纹理:GL_PROXY_TEXTURE_1D、GL_PROXY_TEXTURE_2D、GL_PROXY_TEXTURE_3D,并使用glGetTexParameter函数提取代理查询的结果,更详细的纹理再第七章介绍。

level: 指定了函数所加载的mip贴图层级。对于非mip贴图的纹理可设置为0。

internalformat: 告诉OpenGL我们希望在每个纹理单元中存储多少颜色成分,并在可能的情况下说明这些成分的存储大小,以及是否希望对纹理进行压缩。
常用有:GL_ALPHA 按照alpha值存储纹理单元、GL_LUMINANCE 按照亮度值存储纹理单元 GL_LUMINANCE_ALPHA 按照亮度值和Alpha值存储纹理单元、GL_RGB按照红、绿、蓝成分存储纹理单元、GL_RGBA按照红、绿、蓝、alpha成分存储纹理单元。

width、heighthe depth参数指定了被加载纹理的宽度、高度和深度。注意,这些值必须是2的整数次方(1,2,4,8,16,32..) 但并不需三个维度都相等,因如果有非2次幂的值,可能在较老的OpenGL实现中将会导致纹理贴图被隐式地禁用。虽说2.0版本已支持非2次幂的纹理,但无法保证它们在底层硬件中能够实现足够的速度。追求性能就要避免使用非2次幂的纹理。

border: 指定纹理贴图的边界宽度。纹理边界宽度允许我们对边界处的纹理单元进行额外的设置,来对它的宽度、高度、深度进行扩展。目前来说可以设置为0,后续会了解到。

format、type、data:用于把图像数据放入颜色缓冲区的glDrawPixels函数的对应参数相同。

使用颜色缓冲区

一维和二维纹理也可以从颜色缓冲区中加载数据。可从颜色缓冲区中读取一幅图像,并通过下面这2个函数将它作为一个新的纹理使用(临时纹理)

void glCopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, Glsizei width, GLint border); 一维

void glCopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, Glsizei width, GLsizei height, GLint border); 二维

操作类似glTexImage,但x,y指定了从颜色缓冲区开始的位置,源缓冲区是通过glReadBuffer函数设置的。并不存在三维的glCopyTexImage3D,因为无法从二维的颜色缓冲区获取三维数据。(很显然)

更新纹理

替换纹理函数:
void glTexSubImage1D(GLenum target, GLint level, GLint xOffset, GLsizei width, GLenum format, GLenum type, const GLvoid *data);
void glTexSubImage2D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const GLvoid *data);
void glTexSubImage3D(GLenum target, GLint level, GLint xOffset, GLint yOffset, GLint zOffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *data);

xOffset,yOffset,zOffset参数指定了替换原纹理的开始位置。width,height和depth参数指定了新纹理的宽度、高度和深度(替换纹理)。最后一组函数允许我们从颜色缓冲区中读取纹理,并插入或替换原来纹理的一部分。下面函数都是以glCopyTexSubImage函数的变型,它们都用于完成这个任务。
void glCopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width);
在颜色缓冲区(x,y)位置开始沿着x开始增量截取width单位,替换一维纹理贴图从xoffset开始替换width个单位。
void glCopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height);
在颜色缓冲区(x,y)位置开始截取(width,height)区域内容替换到二维纹理贴图(xoffset,yoffset)开始的(width,height)区域。
void glCopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height);
在颜色缓冲区(x,y)位置开始截取(width,height)区域内容替换到三维纹理贴图(xoffset,yoffset,zoffset)开始的XY平面上的(width,height)区域(即zoffset值固定的平面上的一个(width,height)区域)

纹理对象

目前为止已学了加载和替换纹理的API。管理多重纹理并在它们之间进行转换是接下来学习的内容。纹理图像本身就是所谓的纹理状态的一部分。纹理状态包含了纹理图形本身和一组纹理参数,这些参数控制过滤和纹理坐标的行为。glTexParameter函数设置纹理状态参数相关内容后续讲解。先了解如何加载和管理几种不同的纹理。

glTexImage和glTexSubImage调用所消耗的内存会特别多,并且可能需要重新对这些数据进行格式化以匹配一些内部表示方式。无论是切换或重新加载纹理图像都可能是开销较大的操作。纹理对象允许我们一次性加载一个以上的纹理状态(包括纹理图像),以及在它们之间进行快速切换。纹理状态是由当前绑定的纹理对象维护的,而纹理对象是由一个无符号整数标识的。可用下函数分配一些纹理对象。

void glGenTextures(GLsizei n, GLuint *textures); 【开销小】

纹理对象的数量n 和 一个指向无符号整数数组(由纹理对象标识符填充)的指针textures (即不同的可用纹理对象句柄数组)纹理对象是一个无符号整数。
纹理对象绑定纹理状态可用:void glBindTexture(GLenum target, GLuint texture); target:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D,texture是与之绑定的纹理对象句柄。
绑定纹理状态之后,所有加载的纹理和纹理参数设置都只影响当前绑定的纹理对象。
删除全部纹理对象:void glDeleteTextures(GLsizei n, GLuint *textures); 【在销毁数量多时,可能开销大】
判断指定纹理对象是否有效:GLboolean glIsTexture(GLuint texture);  返回值是否有效, texture是纹理对象句柄。(若texture是已分配的纹理对象名,则返回GL_TRUE,否则GL_FALSE)

纹理应用

加载纹理只是应用纹理的第一步,我们还要提供纹理坐标,并设置纹理坐标环绕模式和纹理过滤,最后可选择对纹理进行Mip贴图,以提高纹理贴图性能或视觉质量(但这要求着色器正在做“正确的事情”)。

纹理坐标

为每个顶点指定一个纹理坐标而直接在几何图形上进行纹理贴图,纹理坐标可自定义或通过算法计算出来。纹理贴图中的纹理单元是作为一个更加抽象(经常是浮点值)的纹理坐标,而不是作为内存位置(在像素图中则是这样)进行寻址的。典型情况下在[0.0,1.0]范围。纹理坐标命名为s、t、r和q(与顶点坐标x、y、z和w相类似),支持从一维到三维的纹理坐标,并且可选择一种对坐标进行缩放的方法。

其中,q分量对应几何坐标w,这是一个缩放因子,作用于其他纹理坐标,实际使用的纹理坐标是s/q、t/q和r/q。在默认情况下,q为1.0。q对于阴影贴图之类高级纹理坐标生成算法来说是有用的。

一个纹理坐标会在每个顶点上应用一个纹理,可存在一种同时应用一个以上纹理的方法。OpenGL根据需求会对纹理进行放大或缩小,将纹理贴图到几何图形上。(放大或缩小是使用当前的纹理过滤器实现的)。

纹理参数

纹理参数会影响渲染的规则和纹理贴图的行为。通过glTexParameter函数的变体函数来进行设置。

void glTexParameterf(GLenum target, GLenum pname, GLfloat param);
void glTexParameteri(GLenum target, GLenum pname, GLint param);
void glTexParameterfv(GLenum target, GLenum pname, GLint *params);
void glTexParameteriv(GLenum target, GLenum pname, GLint *params);

target:指定这些参数将要应用在哪个纹理模式上 GL_TEXTURE_1D、GL_TEXTURE_2D和GL_TEXTURE_3D
pname: 指定需要设置哪个纹理参数
param/params: 设置特定的纹理参数的值

基本过滤(处理纹理贴图单元和纹理贴图应用到的片元无法形成1:1关系时的放大或收缩纹理单元,以达到1:1匹配上对应的片元)例如500*500的贴图,放到需要1000*1000的屏幕,就需要放大贴图反之缩小)

一般情况上纹理单元和屏幕上的像素几乎不会形成1对1关系,基本都会拉伸或收缩图像。而根据一个拉伸或收缩的纹理贴图计算颜色片段的过程称为纹理过滤(Texture Fililtering)
使用OpenGL的纹理参数函数,可同时设置放大和缩小过滤器,分别为GL_TEXTURE_MAG_FILTER和GL_TEXTURE_MIN_FILTER(即指定设置的是什么过滤器的过滤方式)
目前来看,基本在GL_NEAREST和GL_LINEAR两种过滤方式选择其中一个即可,它们分别对应是最邻近过滤和线性过滤。(即指定XXX过滤器的过滤方式)
需确保GL_TEXTURE_MIN_FILTER选择这两种其中的一个,因默认的过滤器不适用于Mip贴图。
最邻近过滤器方式是最简单、最快速的过滤方法。
纹理坐标总是根据纹理图形的纹理单元进行求值和绘图的。决定了纹理坐标后,片段的纹理颜色是纹理坐标对应的纹理图形中的纹理单元颜色。
最邻近过滤特点是当纹理被拉伸到特别大时所出现的大片斑驳状像素(即锯齿像素化),原因是它仅仅是把邻近的像素直接填充到空缺位置。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);  //设置2D纹理进行放大时用最邻近过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);   //设置2D纹理进行缩小时用最邻近过滤

线性过滤比最邻近过滤消耗大,但效果更好,在当今的高速硬件上,线性过滤的开销可几乎忽略不计。
线性过滤是将周围的像素加权平均值应用到纹理坐标上(线性插值),为了使得线性插值更准确,纹理坐标需准确地落在纹理单元中心。
线性过滤特点是当纹理被拉伸时所出现的“失真”图形,但与最邻近过滤呈现的斑驳状效果来看,这种“失真”更接近真实,没有人工操作的痕迹。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

纹理环绕

正常情况下,在[0.0, 1.0]范围内指定纹理坐标,使得它能与纹理贴图中的纹理单元形成映射关系。如果纹理坐标在这个范围之外,OpenGL会根据当前纹理环绕模式(Wrapping Mode)处理这个问题。
使用glTexParameteri函数 并用GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T 或 GL_TEXTURE_WRAP_R 作为参数 标识纹理坐标分量,而后设置GL_REPEAT、GL_CLAMP、GL_CLAMP_TO_EDGE 或 GL_CLAMP_TO_BORDER 指定纹理坐标分量的纹理环绕模式。(即可分别指定三个纹理坐标分量的不同纹理环绕模式)
GL_REPEAT : 在超过1.0的方向上对纹理进行重复,并且能解决GL_LINEAR过滤的边缘过滤问题(即边缘单元进行线性插值时获取周围像素会绕到纹理另一边进行获取)
GL_CLAMP: 超过1.0的方向上的新纹理会从它的合法纹理单元的最后一行或最后一列进行采样 或 TEXTURE_BORDER_COLOR 【用glTexParameterfv函数设置的边缘颜色】
GL_CLAMP_TO_EDGE:在超过1.0方向上的纹理坐标沿着合法的纹理单元最后一行或最后一列进行采样。(即无视TEXTURE_BORDER_COLOR)
GL_CLAMP_TO_BORDER: 在超过1.0方向上的新纹理会从它的边界纹理单元进行采样,边界纹理单元不同与上方说的合法纹理单元边界,而是在加载纹理时存在于纹理图像额外的列和行的纹理单元。
 类似GL_CLAMP的作用是解决瓷砖边界的“留缝”痕迹问题,使用GL_CLAMP_TO_EDGE能将缝隙空缺用纹理合法边界进行填充,从而避免留缝,如果有特殊需求可用GL_CLAM_TO_BORDER用纹理的边界纹理单元进行填充 或 使用glTexParameterfv函数设置特定的边缘颜色 使用GL_CLAMP进行填充。

注意:GL_NEAREST过滤模式中,环绕模式不会起作用!

综合运用

Pyramid(金字塔)实例演示:https://blog.csdn.net/qq_39574690/article/details/115268574

Mip贴图

Mip贴图是一种功能强大的纹理技巧,可提高渲染性能和改善场景的显示质量。

解决2个问题:
①闪烁的效果,即当屏幕上被渲染物体的表面与它所应用的纹理图像相比显得比较小时,就会出现这种效果,当纹理图形的采样区域的移动幅度与它在屏幕上的大小相比显得不成比例时,一般在物体或摄像机移动造成了画面变化时,往往会在那些物体表面大小与所对应纹理图形大小相差较大的地方出现闪烁。
②性能问题,原因和闪烁相同,即加载非常大的纹理贴图,但屏幕上显示的仅是很少的一部分片段,但此时还是会进行过滤处理,当纹理越大这个过滤处理造成的性能问题越大。

简单解决方法是用更小的纹理图像,但当拉近看物体时,物体渲染部分更大,导致纹理需被拉伸,造成模糊或斑驳状效果。所以要在拉近时使用比原来较大的纹理。因此这种动态根据距离物体远近来设定纹理贴图的技术就是Mip贴图。Mip贴图是很多张从最大到最小的图像组成的,更贴切来说Mip贴图纹理状态。OpenGL会为Mip贴图准备新的过滤方法,为一个特定的几何图形实时判断选择出最佳过滤效果的纹理(即近处则选大纹理,远处则选小纹理之类的)但这样肯定是消耗了内存来达到性能和效果最佳,但这解决了因纹理大小和显示大小比例相差较大而产生的运动时闪烁问题 和 大纹理性能问题(不必要的性能开销)。同时,Mip贴图还可以维护一组具有更高分辨率的可用纹理。

Mip贴图纹理由一系列纹理图形组成,每个图像大小在每个轴的方向上都缩小一半,或说是原来图像像素总数的四分之一。Mip贴图层并不一定是正方形的,但每个图形的代销都依次减半,直到最后一个图像是1*1的纹理单元为止。当其中一个维度的大小达到1时,接下来的减半处理就只能发生在其他维度上了。使用一组正方形(各个维度的大小相等)的MIP贴图所要求的内存比不使用Mip贴图的要多出三分之一。

Mip贴图层是通过glTexImage函数加载的。level参数指定加载的图像数据应用于哪个Mip层,0开始。
可用glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0); 设置基层是0开始;
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 4); 设置最高层是4。
Mip贴图的[基层,最高层]都要求加载图像应用上去;
GL_TEXTURE_MIN_LOD和GL_TEXTURE_MAX_LOD参数可限制Mip贴图真正生效的范围
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 2)  
例如这样就只有[0,2]层的会生效。

Mip贴图过滤

在GL_NEAREST和GL_LINEAR的基础上加了新的变换得到Mip贴图过滤:
GL_NEAREST、GL_LINEAR分别在Mip贴图基层上执行最邻近过滤和线性过滤。
GL_NEAREST_MIPMAP_NEAREST: 选择最邻近Mip层并执行最邻近过滤。
GL_NEAREST_MIPMAP_LINEAR : 在Mip层之间执行线性插补(?),并执行最邻近过滤。
GL_LINEAR_MIPMAP_NEAREST: 选择最邻近Mip层,并执行线性过滤
GL_LINEAR_MIPMAP_LINEAR: 在Mip层之间执行线性插值,并执行线性过滤,又称之为三线性Mip贴图。

注意:如果使用了Mip贴图过滤(即带MIPMAP字样的过滤方式)必须要加载Mip贴图。

最佳性能是GL_NEAREST_MIPMAP_NEAREST,但效果不好,可用GL_LINEAR_MIPMAP_NEAREST来取得较好的效果,它们都需要在Mip层之间进行快速选择(最邻近过滤)然后再进行相应的过滤应用到纹理扩大或缩小上。但这两种都是用最邻近选择Mip贴图然后进行过滤的,这会导致可能在一些有坡度的物体上,可能会出现从一个细节层级到另一个细节层级的急剧转变,要解决这种问题是使用GL_LINEAR_MIPMAP_LINEAR、GL_NEAREST_MIPMAP_LINEAR 即在Mip层之间会进行一个线性插值拿到Mip当前渲染贴图,然后再进行过滤,这样就不会出现急剧转变了,这需要额外可观的处理开销。

GL_LINEAR_MIPMAP_LINEAR是三线性Mip贴图,直至最近为止它是纹理过滤的黄金准则(具有最高的精度)。最近,各向异性的纹理过滤逐渐在OpenGL硬件上流行,但它进一步增加了纹理贴图的开销(对性能影响)

生成Mip层

void glGenerateMipmap(GLenum target);
target: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D、GL_TEXTURE_CUBE_MAP、GL_TEXTURE_1D_ARRAY或GL_TEXTURE_2D_ARRAY(最后三个值在第七章解释)
使用glGenerateMipmap加载了第0层纹理后,就可以为纹理生成所有Mip层了(即自动生成?)运行过程中生成Mip贴图通常比加载预设的Mip贴图要慢,在性能关键的应用中应该加载自己预先生成的Mip贴图,而不是依靠自动生成?(希望我没说错,书说的太模糊了)

活动的Mip贴图

Tunnel实例程序:https://blog.csdn.net/qq_39574690/article/details/115273606

各向异性过滤

它是一种比三线性过滤还要高质量的处理过滤方式,当一个纹理贴图被过滤时,OpenGL使用纹理坐标来判断一个特定的片段将落在纹理贴图的什么地方,而紧邻这个位置的纹理单元使用GL_NEAREST或GL_LINEAR过滤操作进行采样。例如:(0,0)纹理坐标对应一个片段A,(1,0)纹理坐标对应一个片段B,如果这2个纹理坐标之间会有一些新的纹理坐标诞生(因为过滤),那么其中的纹理单元是采用最邻近过滤的话,例如(0.2,0)纹理坐标就会采样(0,0)纹理坐标对应的纹理单元数据,如果是线性过滤就是(0,0)->(1,0)坐标纹理单元的线性变换值(即大体等于A*(1-0.2)+B*0.2,但这只是水平影响,还有垂直的影响需考虑)

如果摄像机正好对准图像显示(完全1:1比例显示)这叫各向同性采样,即所有采样点和坐标都完美对应上。但如果倾斜了角度观察图像就会必然导致显示区域缩小而产生过滤,这样原本的图像信息可能就无法真正完整地显示出来,而产生一种模糊的效果。

各向异性过滤则为此而诞生,解决这种丢失信息的过滤问题,即沿着包含纹理的平面方向进行延伸,在纹理过滤时考虑了观察角度。

应用:必须查询扩展字符串GL_EXT_texture_filter_anisotropic 是否支持。
if(gltIsExtSupported("GL_EXT_texture_filter_anisotropic")) 

若支持各向异性过滤,则可查询最大支持的各向异性过滤数量:
GLfloat fLargest;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);

各向异性过滤支持数量越大,沿最大变化方向所采用的纹理单元就越多。各向异性过滤会有额外的开销,有可能对性能造成相当大的影响,但目前来看对速度造成的影响并不大。

glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest); 设置将使用的最大各向异性过滤数量为最大支持数量

各向异性过滤和其他过滤一样是影响每个纹理对象的。
实例程序Anisotropic: https://blog.csdn.net/qq_39574690/article/details/115281821

纹理压缩

早期对纹理压缩的做法是用JPG文件存储图像,在调用glTexImage之前对它进行解压,能节省磁盘空间并减少在网络上传输图像所需的时间,但加载到GPU显存的纹理图形还是那么大。

在1.3版本中,OpenGL添加了对纹理压缩的本地支持。可使用GL_ARB_texture_compression字符串测试是否支持压缩纹理。

OpenGL硬件对纹理压缩不仅仅是支持加载压缩纹理,甚至能在图形硬件内存中仍然保持压缩状态使用。在较小内存中加载更多纹理,更好支持了Mip贴图提高性能和效果,并且这也是能使用更少内存的原因。

压缩纹理

OpenGL支持在我们加载一张没有经过压缩的图像时帮我们进行压缩并存储到显存。
glTexImage的internalFormat参数可设置压缩格式:
设置为GL_COMPRESSED_RGB压缩格式,若无法压缩则用GL_RGB格式加载纹理
设置为GL_COMPRESSED_RGBA压缩格式,若无法压缩则用GL_RGBA格式加载纹理 
设置为GL_COMPRESSED_SRGB压缩格式,若无法压缩则用GL_RGB格式加载纹理 
设置为GL_COMPRESSED_SRGB_ALPHA压缩格式,若无法压缩则用GL_RGBA格式加载纹理
设置为GL_COMPRESSED_RED压缩格式,若无法压缩则用GL_RED格式加载纹理
设置为GL_COMPRESSED_RG压缩格式,若无法压缩则用GL_RG(Red Green)格式加载纹理
OpenGL 3.2新增:GL_COMPRESSED_SIGNED_RED_RGTC1、GL_COMPRESSED_RG_RGTC2和GL_COMPRESSED_SIGNED_RG_RGTC2。它们用于各种单颜色通道和双颜色通道压缩纹理,它们替代了旧版本的GL_LUMINANCE和GL_LUMINANCE_ALPHA功能。

虽然会在加载纹理时进行压缩纹理会开销,在却能节省纹理存储空间从而增加纹理性能。

可用如下方法判断是否压缩成功:
GLint compFlag; //1则成功,0则失败
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compFlag);
第三个参数还可设置为GL_TEXTURE_COMPRESSED_IMAGE_SIZE 获取压缩后的纹理大小(字节为单位)
GL_TEXTURE_INTERNAL_FORMAT 所使用的压缩格式
GL_NUM_COMPRESSED_TEXTURE_FORMATS 受支持的压缩纹理格式的数量
GL_COMPRESSED_TEXTURE_FORMATS 一个包含了一些常量值的数组,每个常量值对应于一种受支持的压缩纹理格式
GL_TEXTURE_COMPRESSED_HINT 纹理压缩提示的值(GL/NICEST/GL_FASTEST)

glHint(GL_TEXTURE_COMPRESSED_HINT, GL_FASTEST); 最快速度压缩
glHint(GL_TEXTURE_COMPRESSED_HINT, GL_NICEEST); 最佳压缩
glHint(GL_TEXTURE_COMPRESSED_HINT, GL_DONT_CARE); 任意压缩格式?

可用GL_NUM_COMPRESSED_TEXTURE_FORMATS查询压缩格式的数量 和 GL_COMPRESSED_TEXTURE_FORMATS查询相关值的列表。
先检查是否支持想要进行的压缩格式的相关特定扩展,只有支持了这种相关特定扩展才能支持相应的压缩格式列表。
例如,检查GL_EXT_texture_compression_s3tc 扩展是否支持,若支持就支持了进行下列压缩格式:
if(gltIsExtSupported("GL_EXT_texture_compression_s3tc")) 
GL_COMPRESSED_RGB_S3TC_DXT1   RGB数据被压缩,alpha值始终是1.0
GL_COMPRESSED_RGBA_S3TC_DXT1   RGB数据被压缩,alpha值是1.0或0.0
GL_COMPRESSED_RGBA_S3TC_DXT3   RGB数据被压缩,alpha值用4位存储
GL_COMPRESSED_RGBA_S3TC_DXT5   RGB数据被压缩,alpha值是一些8位值的加权平均值

加载压缩纹理

glGetCompressedTexImage函数能提取经过压缩的数据并把它存储到磁盘中,之后就可以直接加载压缩过的纹理来进一步提升纹理的加载速度。但这可能要硬件产生商遵循标准的硬件实现中才是可行的,不然可能还是属于未压缩的数据。
可通过如下函数进行加载压缩过的纹理。
void glCompressedTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLint border, GLsizei imageSize,  void *data);
void glCompressedTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize,  void *data);
void glCompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize,  void *data);

与glTexImage函数相同的参数,只有internalFormat参数必须指定一种受到支持的压缩纹理格式,例如支持扩展GL_EXT_texture_compression_s3tc时,则支持上面所说的一系列压缩格式。
此外还有个glCompressedTexSubImage函数用于更新部分或全部已经加载的纹理,与glTexSubImage函数功能类似,也是要求internalFormat参数的压缩格式被支持才可行。

压缩带来好处是更小的空间、网络传输速度更快、从磁盘加载速度更快、复制到图形内存也更快速,能允许更多的纹理加载到硬件中,使得纹理的启用更加快速。但在运行时会带来一定的性能开销,例如 GL_EXT_texture_compression_s3tc 是通过从每个纹理单元提取颜色数据进行工作的。对于某些纹理可能会导致图像质量的损失,但在具有大量细节的纹理没有实质性影响,纹理压缩方法的选择取决于底层图像的本质。

最后一个示例

SphereWorld球体世界:https://blog.csdn.net/qq_39574690/article/details/115281922

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值