在计算机图形学中,纹理映射是实现复杂表面效果的高效方法,即以较小的计算量就可以实现较为逼真的模芯效果。在GPGPU中,纹理映射也是一个至关重要的概念。由图形API实现经典GPGPU的原理可以总结为:用纹理映射实现的科学计算(computation by texturing)。
1、纹理映射的概念
在渲染对象过程中,最简单的方式是给各个对象表面显式地涂上各种颜色。但这样颜色会非常单一。同时,让设计者手动地给每个像素定义不同颜色显然也不可能。于是,纹理映射就成为一个生成较高质量三维表面地高效地这种方案。
纹理映射的原理:首先,由应用程序生成顶点组成的三维模型。然后这些顶点被网格化或三角化,变成若干相连的平面。这是,可以选择使用一些预定好的二维位图,在定义好模型后,将这些位图贴在对象表面。这个过程称为纹理映射。映射,也就指的是通过空间中的顶点坐标与纹理坐标之间的函数关系,用纹理图为顶点赋值。
2、几何图元
几何图元是组成人们熟知地三维模型地基本元素,如点、直线、三角形等,通常由一个顶点列表组成。为了标志顶点列表地起始和终止位置,需要使用函数glBegin()和glEnd()。glBegin()地形参是一个几何图元对象地名称。
glBegin(GL_POLYGON); //GL_POLYGON 是多边形图元地标识。这里表示一个边长为2的二维正方形 glVertex2f(-1.-, -1.0); glVertex2f(-1.0, 1.0); glVertex2f(1.0, 1.0); glVertext2f(1.0, -1.0); glEnd();
常用OpenGL几何图元类型
几何图元类型 | 注释 |
---|---|
GL_POINTS | 单个顶点集 |
GL_LINES | 多组双顶点线段 |
GL_POLYGON | 单个简单填充凸多边形 |
GL_TRIANGLES | 多组独立填充三角形 |
GL_QUADS | 多组独立填充四边形 |
GL_LINE_STRIP | 不闭合折线 |
GL_LINE_LOOP | 闭合折线 |
GL_TRIANGLE_STRIP | 线性连续填充三角形串 |
GL_TRIANGLE_FAN | 扇形连续填充三角形串 |
GL_QUAD_STRIP | 连续填充四边形串 |
同时如果我们给同一个图元不同顶点指定了不同颜色,OpenGL默认对策是对图元进行平滑着色,即根据顶点颜色对其他部分线性插值。纹理坐标也是每个顶点的属性,可以使用函数glTexCoor()指定。
几何图元可以分为填充图元和非填充图元两类。直线是非填充图元,其不具备“内部”。二维多边形是一种填充图元,其“内部”可以定义。OpenGL中,填充图元有三种方式,即顶点方式、边线方式和填充方式。顶点方式是用顶点组成的点集来绘制;边线方式是仅绘制多边形的边线,其“内部”没有定义。填充方式是对多边形进行填充,此时边线在填充时也是内部的一部分。
3、位图与流水线
位图是另一种基本图元,也称为离散图元。它是一个由向量组成的矩阵。向量的元素数就是位图的通道数,比如彩色位图通常是RGB,或者加入透明通道为RGBA。
与几何图元一样,位图也是图形应用程序可以生成的数据形式。同样会进入图像流水线。但是,位图已经是可以存储在帧缓存里的二维离散图元,它不用流经顶点处理单元,而是从另一条并行的流水线流入,在片段处理阶段和流过顶点处理单元的数据汇合。
OpenGL对像素的读写,具体有三种操作:
- 把像素块从帧缓存读到住存储器中,对应OpenGL函数是glReadPixels()
- 把像素块从主存储器写入光栅化器中,对应OpenGL函数是glDrawPixels()
- 把像素块从帧缓存复制到光栅化器中,对应OpenGL函数是glCopyPixels()
基本流程如图:
注意,像素块在OpenGL中的存储方式可能和在主存储器中的不同,如像素中各分量的排列顺序。如果想要将像素块从帧缓存的一部分转移到另一部分,就需要先读出像素,然后在另一处写入。可以使用glReadPixels和glDrawPixels,但频繁在主机与设备间传输数据过于低效,推荐使用glCopyPixels。
4、纹理图
可以将纹理图看成一张颜色查找表,根据每个顶点的纹理坐标可以从纹理图上查到该顶点的颜色。通常纹理图和帧缓存中的位图一样,都是由离散的像素构成。为了区分,我们将纹理图上的一个像素称为纹理元。事实上,由于纹理坐标都是经过插值和采样计算得到的,所以在纹理图中查找颜色并不是连三的。而是根据相邻纹理元的颜色插值或最近邻得到的。因此可以将纹理图看成连续的数组,它的二维坐标都是在实数域内得到定义的。
OpenGL中默认的纹理图都是边长为1的正方形。这样避免了使用明确坐标,用户就可以在不必知道纹理图尺寸的情况下使用纹理。但对GPGPU编程却产生了不便。如,需要知道一个长度为512的数组的第100个元素,用C语言查找只需要使用下标99即可,但OpenGL需要使用100.0/512.0=0.1953125.
OpenGL中设置纹理图的函数为glTexImage2D(),一个指定4个分量、每个分量为1个字节的二维纹理图:
#define nImageWidth 64 #define nImageHeight 64 static Glubyte ubImage[nImageHeight][nImageWidth][4]; //填充数组 glEnable(GL_TEXTURE_2D); glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,nImageWidth,nImageHeigght,0,GL_RGBA,GL_UNSIGNED_BYTE, ubImage);
当不需要对整幅纹理图进行操作时,可以使用函数glTexSubImage2D()来定义一幅子纹理图。
当使用glTexImage2D时,OpenGL就会在显卡上分配一块纹理缓存,把纹理图从内存转移到纹理缓存中。如果已经调用过glTexImage2D,更新纹理图最好使用glTexSubImage2D,这样就不用在显卡上重新分配存储空间,如果改动较小也不用将整个纹理图传输到显卡上,以提高效率。这也是GPGPU的典型做法。
5、纹理坐标
将纹理图映射到三维表面是通过为每个顶点定义纹理坐标实现的。与顶点坐标一样,是一个四维向量[s,t,r,q].除第一个分量外(使用时,用户至少需要使用一维纹理坐标,因而s一定由用户设定),其他分量的默认值为:t=0,r=0,q=1。设置纹理坐标函数为glTexCoord()。
6、纹理参数
在纹理映射前,还需要对一些参数进行设置。
1. 越界取值:当指定的纹理坐标值大于实际的取值范围时,即超出纹理图的边界时,GL_TEXTURE_WRAP系列参数用来指定这种情况下,OpenGL采取的措施。总的来说,OpenGL一般有两种策略。一种是用钳位算法(clamp)将坐标值限制在某个区间内,即大于该范围的取值就钳定在区间上限,小于时就钳定在区间下限。另一种时在边界以外重复边界内的取值。
2. 放大/缩小纹理图
7、映射参数
此外,还需要确定映射过程中纹理图与表面的相互作用,即处理与表面已有颜色的相互关系。通过glTexEnv进行。
8、纹理对象
如果用户同时使用多块纹理,则频繁使用glTexImage来加载过于低效。OpenGL提供了纹理对象来管理纹理,这样多块纹理可以在纹理缓存中并存。纹理缓存不足时,OpenGL会按照优先级管理纹理,使加载纹理次数尽可能少。
首先,需要调用glGenTextures()来建立一个纹理对象。其会返回n个有效的整数纹理标识符。这些整数被保存在textureNames数组中。这些返回的纹理表示符都是目前OpenGL未被占用的,不一定是连续的整数。0是OpenGL预留的纹理标识符,不会被分配。分配到的纹理对象的标识符,只表示该标识符有效,而纹理暂时还是无效的。使用前,用户需要将它与某种类型的纹理绑定起来glBindTexture()。同时相关程序结束后,可以使用glDeleteTextures()删除。
9、纹理单元
纹理单元与多重纹理映射息息相关。在图形任务中,有时需要将多块纹理映射到同一表面,映射的结果是多重纹理融合的效果。OpenGl使用纹理单元来管理多重纹理映射中使用的不同纹理图。一个纹理单元就是一个独立的纹理,除了纹理图本身外,它还保存了纹理坐标和纹理参数等一切使用该纹理需要的信息。同一纹理图也可以被多个纹理单元使用。
多重纹理映射时,可以使用OpenGL常量GL_TEXTUREi来选择使用哪个纹理单元,其中i是0到31的整数。
版权声明:本文为CSDN博主「koibiki」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/koibiki/article/details/80350966