OpenGL纹理详解

现实生活中,纹理最通常的作用是装饰我们的物体模型,它就像是贴纸一样贴在物体表面,使得物体表面拥有图案。但实际上在OpenGL中,纹理的作用不仅限于此,它可以用来存储大量的数据,一个典型的例子就是利用纹理存储地形信息。

纹理对象和参数

现代OpenGL中,纹理对象和顶点数组对象(VAO)及缓存对象(VBO)一样,需要调用glGenTextures函数生成。

生成纹理对象

同样的,纹理对象需要进行绑定。OpenGL中纹理可以分为1D,2D和3D纹理,我们在绑定纹理对象的时候需要指定纹理的种类。由于本文将以一张图片为例,因此我们为我们的纹理对象绑定一个GL_TEXTURE_2D的纹理。

2D纹理绑定

纹理坐标

在OpenGL中,我们通常将纹理中的像素将按照纹理坐标进行编址,纹理坐标系是一个空间直角坐标系,横轴为S轴,纵轴为T轴,垂直于屏幕的坐标轴为R轴。在我们的2D纹理中,由于没有R轴,我们也可以将横轴称为U 纵轴称为V轴,也就是我们所说的UV坐标系。但和OpenGL坐标系所不同的是:纹理坐标系的(0,0)点位于纹理的左下角,而(1,1)点位于纹理的右上角。

2D纹理坐标系

采样

通过纹理坐标获取像素颜色信息的过程称为采样,而采样的结果会根据纹理参数设置的不同而千差万别。OpenGL中设置纹理参数的API接口为glTextureParameter,我们所有的纹理参数都由这个接口设置,下面我们介绍几种常用的纹理参数的配置。

Wrapping

纹理坐标的范围与OpenGL的屏幕坐标范围一样,是0-1。超出这一范围的坐标将被OpenGL根据GL_TEXTURE_WRAP参数的值进行处理:

GL_REPEAT: 超出纹理范围的坐标整数部分被忽略,形成重复效果。

GL_MIRRORED_REPEAT: 超出纹理范围的坐标整数部分被忽略,但当整数部分为奇数时进行取反,形成镜像效果。

GL_CLAMP_TO_EDGE:超出纹理范围的坐标被截取成0和1,形成纹理边缘延伸的效果。

GL_CLAMP_TO_BORDER: 超出纹理范围的部分被设置为边缘色。

这四种模式所产生的纹理效果如下:

2D纹理的4种wrapping模式

我们可以为纹理坐标系中每条坐标轴设定不同的wrapping模式,例如这里我们将S和T轴的wrapping模式设定为GL_REPEAT:

wrapping模式的设置

需要注意的是,如果我们将wrapping模式设置为GL_CLAMP_TO_BORDER,我们需要单独设置另一属性----边界颜色,具体方法也是利用glTextureParameter方法,只不过将属性名称替换为GL_TEXTURE_BORDER_COLOR:

纹理边界颜色的设置

这两行代码执行后,纹理的边界颜色将被设置为红色。

过滤

由于纹理坐标和我们当前的屏幕分辨率是无关的,所以当我们为一个模型贴纹理时,往往会遇到纹理尺寸与模型尺寸不符的情况,这时,纹理会因为缩放而失真。处理这一失真的过程我们称为过滤,在OpenGL中我们有如下几种常用的过滤手段:

GL_NEAREST: 最临近过滤,获得最靠近纹理坐标点的像素。

GL_LINEAR: 线性插值过滤,获取坐标点附近4个像素的加权平均值。

GL_NEAREST_MIPMAP_NEAREST:用于mipmap,下节将详细介绍。

GL_LINEAR_MIPMAP_NEAREST:

GL_NEAREST_MIPMAP_LINEAR:

GL_LINEAR_MIPMAP_LINEAR:

在我们讨论mipmap之前,我们来看看前两种过滤算法在纹理图像面前会产生怎样的过滤效果(原始图片大小和模型大小相差16倍):

两种插值过滤算法过滤效果

从这里我们可以看出,线性插值算法的过滤效果似乎更加平滑,但并不表示这种算法适合所有的场合,如果我们需要对图像进行马赛克处理或模拟一个8位游戏机的游戏画面时,最临近过滤算法是最好的选择。

我们可以单独为纹理缩放指定不同的过滤算法,这两种情况下纹理参数设置分别对应为:GL_TEXTURE_MIN_FILTER和GL_TEXTURE_MAG_FILTER.

分别为纹理缩放指定线性过滤算法

Mipmaps

正如前面所提到的,还有一种过滤纹理的方法---mipmaps。如下图所示: Mipmaps 将纹理按照1/2的比例缩小并生成副本,以此类推直到纹理只有1x1大小(这里是纹理坐标,不是真实像素大小)。采样时我们根据当前缩小比率来选择合适大小的纹理,按照线性过滤或最邻近过滤来采样。比如,我们要用做贴图的纹理大小为 64x32,对它做下采样生成32x16,16x8,8x4,4x2,2x1,1x1的纹理,如果要渲染的区域大小为14x6,那么我们要么选16x8的纹理,要么选16x8与8x4的两块纹理做加权平均。

Mipmaps实例

Mipmaps是一个功能强大的纹理技术,它可以提高渲染的性能以及提升场景的视觉质量。它可以用来解决使用一般的纹理贴图会出现的两个常见的问题:

1.  闪烁,当屏幕上被渲染区域与它所应用的纹理图像相比显得非常小时,就会出现闪烁。尤其当视口和物体在移动的时候,这种负面效果更容易被看到。

2. 性能问题。如果我们的贴纹理的区域离我们非常远,远到在屏幕中只有一个像素那么大小时,纹理的所有纹素都集中在这一像素中。这时,我们无论做邻近过滤还是做线性过滤时都不得不将纹理的所有纹素计算在内,这种计算效率将大大影响我们的采样效率,而纹理的数据量越大,对效率的影响就会更大。

使用Mipmaps技术就可以解决上面那两个问题。当加载纹理的同时预处理生成一系列从大到小的纹理,使用时只需选择合适大小的纹理加载就行了。这样虽然会增加一些额外的内存(一个正方形纹理将额外占用约30%的内存),但将大大提高了我们的采样效率和采样质量。

生成mipmaps的过程很简单,只需要在加载纹理后执行下面一行代码:

生成mipmaps

使用mipmaps也很简单,只需设置过滤参数为以下4种中的任意一种:

GL_NEAREST_MIPMAP_NEAREST:选择最邻近的mip层,并使用最邻近过滤。

GL_NEAREST_MIPMAP_LINEAR:对两个mip层使用最邻近过滤后的采样结果进行加权平均。

GL_LINEAR_MIPMAP_NEAREST:选择最邻近的mip层,使用线性插值算法进行过滤。

GL_LINEAR_MIPMAP_LINEAR:对两个mip层使用线性插值过滤后的采样结果进行加权平均,又称三线性mipmap。

在选择这几种过滤方法时,我们需要考虑的是效率和质量,线性过滤往往更加平滑,但随之而来的是更多的采样次数;而临近过滤减少了采样次数,但最终视觉效果会比较差。

到这里,我们的纹理单元已生成完毕,纹理参数也已配置就绪,在下一篇中,我们将继续讨论如何加载和使用纹理的问题。

OpenGL基本概念入门——纹理贴图

1. 创建纹理图像

OpenGL要求纹理的高度和宽度都必须是2的n次方大小,只有满足这个条件,这个纹理图片才是有效的。     一旦获取了像素值,我们就可以将这些数据传给OpenGL,让OpenGL生成一个纹理贴图:

glGenTextures(1,@Texture);
    glBindTexture(GL_TEXTURE_2D,Texture);
    glTexImage2D(GL_TEXTURE_2D,0,3,Bit.Width,Bit.Height,0,GL_RGB,GL_UNSIGNED_BYTE,Pixels);

glGenTextures和glBindTexture函数用于创建和绑定纹理对象,glTexImage2D函数将Pixels数组中的像素值传给当前绑定的纹理对象,于是便创建了纹理。glTexImage函数的参数分别是纹理的类型,纹理的等级,每个像素的字节数,纹理图像的宽度和高度,边框大小,像素数据的格式,像素值的数据类型,像素数据。

2. OpenGL中的贴图方式

OpenGL为我们提供了三种纹理——GL_TEXTURE_1D、GL_TEXTURE_2D和GL_TEXTURE_3D。它们分别表示1维纹理、2维纹理和3维纹理。无论是哪一中纹理,使用方法都是相同的:即先创建一个纹理对象和一个存储纹理数据的n维数组,在调用glTexImageN D函数来传入相应的纹理数据。除此之外,我们可以一些函数来设置纹理的其他特性。

2.1 设置贴图模式

OpenGL提供了3种不同的贴图模式:GL_MODULATE,GL_DECAL和GL_BLEND。默认情况下,贴图模式是GL_MODULATE,在这种模式下,OpenGL会根据当前的光照系统调整物体的色彩和明暗。第二种模式是GL_DECAL,在这种模式下所有的光照效果都是无效的,OpenGL将仅依据纹理贴图来绘制物体的表面。最后是GL_BLEND,这种模式允许我们使用混合纹理。在这种模式下,我们可以把当前纹理同一个颜色混合而得到一个新的纹理。我们可以调用glTexEnvi函数来设置当前贴图模式:

glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,TextureMode);

其中TextureMode就是想要设置的纹理模式,可以为GL_MODULATE,GL_DECAL和GL_BLEND中的任何一种。

另外,对于GL_BLEND模式,我们可以调用

glTexEnvfv(GL_TEXUTRE_ENV,GL_TEXTURE_ENV_COLOR,@ColorRGBA);

其中,ColorRGBA为一个表示RGBA颜色的4维数组。

2.2 纹理滤镜

在纹理映射的过程中,如果图元的大小不等于纹理的大小,OpenGL便会对纹理进行缩放以适应图元的尺寸。我们可以通过设置纹理滤镜来决定OpenGL对某个纹理采用的放大、缩小的算法。

调用glTexParameter来设置纹理滤镜。如:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILETER, MagFilter);//设置放大滤镜
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, MinFilter); //设置缩小滤镜

上述调用中,第一个参数表明是针对何种纹理进行设置,第二个参数表示要设置放大滤镜还是缩小滤镜。第三个参数表示使用的滤镜。可以为下面的值之一:

表6.3-1 可使用的纹理滤镜
滤镜 描述
GL_NEAREST 取最邻近像素
GL_LINEAR 线性内部插值
GL_NEAREST_MIPMAP_NEAREST 最近多贴图等级的最邻近像素
GL_NEAREST_MIPMAP_LINEAR 在最近多贴图等级的内部线性插值
GL_LINEAR_MIPMAP_NEAREST 在最近多贴图等级的外部线性插值
GL_LINEAR_MIPMAP_LINEAR 在最近多贴图等级的外部和内部线性插值

3 纹理映射

3.1 纹理坐标

要使用当前的纹理绘制图元,我们必须在绘制每个顶点之前为该顶点指定纹理坐标。只需调用

glTexCoord2d(s:Double;t:Double);

函数即可。其中,s、t是对于2D纹理而言的s、t坐标。对于任何纹理,它的纹理坐标都如同图6.4-1所示的那样:

对于任何纹理,无论纹理的真正大小如何,其顶端(左上角)的纹理坐标恒为(0,0),右下角的纹理坐标恒为(1,1)。也就是说,纹理坐标应是一个介于0到1之间的一个小数。

例如,下面的代码将使用当前纹理绘制一个三角形:

glBindTexture(Tex);
      glBegin(GL_TRIANGLES);
        glTexCoord2d(0,0);
        glVertex3f(-10,-10,0);
        glTexCoord2d(0,1);
        glVertex3f(-10,10,0);

glTexCoord2d(1,1);
        glVertex3f(10,10,0);

glEnd();

3.2 纹理缠绕

前面提到,纹理坐标应位于0-1之间。那么当纹理坐标大于这个值会出现什么情况呢?

我们可以对OpenGL进行设置,以决定当纹理坐标不位于这一区间时应采取的操作。我们可以指定两种操作:GL_CLAMP和GL_REPEAT。对于GL_CLAMP,超出纹理坐标的区域会使用纹理图像的边界颜色来代替,如图6.4-2所示。

而GL_REPEAT方式则是对纹理坐标进行重置而得到重复的图像。观察图6.4-3,你就能很容易地发现这一点。

 

可以调用glTexParameter设置缠绕方式:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,WrapMode);//在s方向上的缠绕方式
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,WrapMode);//在t方向上的缠绕方式

其中,WrapMode可取GL_CLAMP或者GL_REPEAT。

4.纹理对象

创建和使用文理对象

在OpenGL中,我们使用glGenTextures创建纹理对象:

glGenTextures(Count:Integer;TexObjs:Pointer);

其中,Count是我们要创建的纹理数目,当我们只想创建一个纹理时,只需调用

var Texture:GLUint;
    ...
    glGenTextures(1,@Texture);

这样,Texture变量中就存储了我们创建的纹理的ID号。

创建之后,我们使用glBindTexture将创建的纹理绑定到当前纹理。这样所有的纹理函数都将针对当前纹理。

glBindTexture(Texture:GLUint);

这样,我们就可以调用glTexParameter、glTexImage2D等函数来设置这个纹理对象了。

删除纹理对象

在纹理资源使用完毕后(一般是程序退出或场景转换时),一定要删除纹理对象,释放资源。

调用

glDeleteTextures(Count:Integer;TexObj:Pointer);

来删除纹理对象。例如

glDeleteTextures(1,@Texture);

5. 多贴图纹理

多贴图纹理(Mip Mapping)为一个纹理对象生成不同尺寸的图像。在需要时,根据绘制图形的大小来决定采用的纹理等级或者在不同的纹理等级之间进行线性内插。使用多贴图纹理的好处在于消除纹理躁动。这种情况在所绘制的景物离观察者较远时常常发生(如图6.6-1和6.6-2)。由于多贴图纹理现在的渲染速度已经很快,以至于和普通纹理没有什么区别,我们现在一般都使用多贴图纹理。

使用多贴图纹理并不麻烦。首先,我们需要创建不同等级(尺寸)的纹理图片。我们需要调用n次glTexImage2D函数,生成不同等级的纹理贴图。例如:
    glTexImage2D(GL_TEXTURE_2D,0,3,8,8,0,GL_RGB,GL_UNSIGNED_BYTE,Pixels);
    glTexImage2D(GL_TEXTURE_2D,1,3,4,4,0,GL_RGB,GL_UNSIGNED_BYTE,Pixels);
    glTexImage2D(GL_TEXTURE_2D,2,3,2,2,0,GL_RGB,GL_UNSIGNED_BYTE,Pixels);
    glTexImage2D(GL_TEXTURE_2D,3,3,1,1,0,GL_RGB,GL_UNSIGNED_BYTE,Pixels);

这些函数调用的第二个参数表示当前纹理的等级。0级的分辨率最大。之后,每一级的分辨率是上一级分辨率的一半。这样的函数调用应一直进行下去,直至图像的高度和宽度都为1。

但有时候,这样做总并不是很方便。我们可以借助一个glu函数帮我们自动生成这些多贴图纹理。只需要把生成纹理图像的函数调用由glTexImage2D改为gluBuild2DMipMaps即可:

gluBuild2DMipMaps(GL_TEXTURE_2D,3,Bit.Width,Bit.Height,0,GL_RGB,GL_UNSIGNED_BYTE,Pixels);

此外,还必须把纹理的滤镜改为MIP_MAP滤镜。例如:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

OpenGL纹理详解相关推荐

  1. Android openGl开发详解(二)

    https://zhuanlan.zhihu.com/p/35192609 Android openGl开发详解(二)--通过SurfaceView,TextureView,GlSurfaceView ...

  2. Android openGl开发详解(二)——通过SurfaceView,TextureView,GlSurfaceView显示相机预览(附Demo)

    最近公司在做自定义相机这一块,之前使用的是第三方,后来需求变更,第三方不支持添加动态贴纸,所以只能自己扩展.当然网上有很多例子,但是关于添加动态贴纸的例子几乎找不到,反正我是没找到(欲哭无泪).当然, ...

  3. OpenGL粒子系统详解及编程实现

    OpenGL粒子系统详解及编程实现 标签: opengl编程 2016-08-23 14:23  1114人阅读  评论(0)  收藏  举报   分类: OSG(6)  版权声明:本文为博主原创文章 ...

  4. OPENGL ES 2.0 知识串讲 (10) ——OPENGL ES 详解IV(纹理优化)

    上节回顾 上一节学习了如何从一张原始图片中,获取生成纹理所需要的信息,然后根据这些信息,通过OpenGL ES API在GPU内存中生成了一张纹理,并且还介绍了纹理属性,知道了如何通过纹理坐标将纹理映 ...

  5. 【OpenGL】详解第一个OpenGL程序

    写在前面 OpenGL能做的事情太多了!很多程序也看起来很复杂.很多人感觉OpenGL晦涩难懂,原因大多是被OpenGL里面各种语句搞得头大,一会gen一下,一会bind一下,一会又active一下. ...

  6. opengl 深度详解_一步步学OpenGL(23) -《阴影贴图1》

    教程 23 阴影贴图1 原文: http://ogldev.atspace.co.uk/www/tutorial23/tutorial23.html CSDN完整版专栏: https://blog.c ...

  7. opengl 深度详解_一步步学OpenGL(1) -《打开一个窗口》

    注意本教程中需要使用的是freeGLUT(GLUT太老会有潜在危险)窗口库和GLEW扩展库. vs2013配置freeGLUT3.0:vs2013 配置 freeglut3.0(opengl的窗口系统 ...

  8. OPENGL ES 2.0 知识串讲 (8) ——OPENGL ES 详解II(传入绘制信息)

    上节回顾 上一节讲述了如何通过 OpenGL ES 给 GPU 关联一套可以使用的 shader,这 一套 shader 是被放在一个 program 中当作一个整体供 GPU 使用的.那么 GPU ...

  9. OpenGL ES 详解纹理生成和纹理映射步骤以及函数

    通常一个纹理映射的步骤是: 创建纹理对象.就是获得一个新的纹理句柄 ID. 指定纹理.就是将数据赋值给 ID 的纹理对象,在这一步,图像数据正式加载到了 ID 的纹理对象中. 设定过滤器.定义了ope ...

最新文章

  1. su mysql bash 4.1_故障案例: 进入到心创建的用户中,系统会提示-bash-4.1$,请问如何解决?...
  2. qt opencv cmake配置 单纯小白
  3. NO--14 微信小程序,左右联动二
  4. MybatisPlus入门Lombok的使用
  5. Redis架构及分片管理
  6. 数据丢失引起宕机怎么办?
  7. [转载] 使用Python编写打字训练小程序
  8. 简述python常用的函数模块_Python中常用的Python time模块常用函数
  9. CGVAE -> Delinker -> DeepCoy
  10. 【渝粤教育】国家开放大学2019年春季 2328仓储与配送管理 参考试题
  11. Fences -让你的桌面图标分组显示,成块状化
  12. ASP:Panel控件(容器控件)
  13. 中国钢铁物流行业发展策略分析及投资建议咨询报告2021-2027年
  14. Use ulog to monitor ports logs (by quqi99)
  15. 如何在win7系统上装苹果雪豹操作系统
  16. DDOS攻击土味解读
  17. XP下IIS相关问题
  18. linux 显卡 卡死,linux服务器显卡崩溃解决方案
  19. 详解【java实现】编写一个程序,从键盘读入一段英文(多行),找出其中所有的英文单词,统计每个单词出现的次数,并按照单词出现次数由大到小排序后输出。
  20. ​互链半年报 | 区块链平台应用落地哪家强?阿里系第一,迅雷、平安紧随

热门文章

  1. 点云分割训练哪家强?监督,弱监督,无监督还是半监督?
  2. Linux系统下7个好用的PDF编辑器推荐
  3. AI Studio 精品项目 | 基于Few-shot Learning实现中文科学文献学科分类
  4. 倚天摘星:四季度反复筑底成为定局!
  5. qt.network.monitor: Could not get the INetworkConnection instance for the adapter GUID.QT关闭时程序异常结束
  6. 礼物帮手-论文(不全)
  7. 注意前方,有月亮出现
  8. python生成二维码_使用python生成二维码
  9. 第2.1章 scrapy之国内高匿代理IP爬取
  10. 罗彻斯特大学计算机科学系专业排名,罗切斯特大学优势专业及优势专业排名(USNEWS美国大学排名)...