OpenGL 扩展
许多高级特性,如那些要在GPU上进行普通浮点运算的功能,都不是OpenGL内核的一部份。因此,OpenGL Extensions通过对OpenGL API的扩展, 为我们提供了一种可以访问及使用硬件高级特性的机制。OpenGL扩展的特点:不是每一种显卡都支持该扩展,即便是该显卡在硬件上支持该扩展,但不同版本的显卡驱动,也会对该扩展的运算能力造成影响,因为OpenGL扩展设计出来的目的,就是为了最大限度地挖掘显卡运算的能力,提供给那些在该方面有特别需求的程序员来使用。在实际编程的过程中,我们必须小心检测当前系统是否支持该扩展,如果不支持的话,应该及时把错误信息返回给软件进行处理。当然,为了降低问题的复杂性,本教程的代码跳过了这些检测步骤。
OpenGL Extension Registry OpenGL扩展注册列表中,列出了几乎所有的OpenGL可用扩展,有需要的朋友可能的查看一下。
当我们要在程序中使用某些高级扩展功能的时候,我们必须在程序中正确引入这些扩展的扩展函数名。有一些小工具可以用来帮助我们检测一下某个给出的扩展函数是否被当前的硬件及驱动所支持,如:glewinfo, OpenGL extension viewer等等,甚至OpenGL本身就可以(在上面的连接中,就有一个相关的例子)。
如何获取这些扩展函数的入口指针,是一个比较高级的问题。下面这个例子,我们使用GLEW来作为扩展载入函数库,该函数库把许多复杂的问题进行了底层的封装,给我们使用高级扩展提供了一组简洁方便的访问函数。
[cpp] view plaincopyvoid initGLEW (void) {
// init GLEW, obtain function pointers
int err = glewInit();
// Warning: This does not check if all extensions used
// in a given implementation are actually supported.
// Function entry points created by glewInit() will be
// NULL in that case!
if (GLEW_OK != err) {
printf((char*)glewGetErrorString(err));
exit(ERROR_GLEW);
}
}
OpenGL离屏渲染的准备工作
在传统的GPU渲染流水线中,每次渲染运算的最终结束点就是帧缓冲区。所谓帧缓冲区,其实是显卡内存中的一块,它特别这处在于,保存在该内存区块中的图像数据,会实时地在显示器上显示出来。根据显示器设置的不同,帧缓冲区最大可以取得32位的颜色深度,也就是说红、绿、蓝、alpha四个颜色通道共享这32位的数据,每个通道占8位。当然用32位来记录颜色,如果加起来的话,可以表示160万种不同的颜色,这对于显示器来说可能是足够了,但是如果我们要在浮点数字下工作,用8位来记录一个浮点数,其数学精度是远远不够的。另外还有一个问题就是,帧缓存中的数据最大最小值会被限定在一个范围内,也就是 [0/255; 255/255]
如何解决以上的一些问题呢?一种比较苯拙的做法就是用有符号指数记数法,把一个标准的IEEE 32位浮点数映射保存到8位的数据中。不过幸运的是,我们不需要这样做。首先,通过使用一些OpenGL的扩展函数,我们可以给GPU提供32位精度的浮点数。另外有一个叫EXT_framebuffer_object 的OpenGL的扩展, 该扩展允许我们把一个离屏缓冲区作为我们渲染运算的目标,这个离屏缓冲区中的RGBA四个通道,每个都是32位浮点的,这样一来, 要想GPU上实现四分量的向量运算就比较方便了,而且得到的是一个全精度的浮点数,同时也消除了限定数值范围的问题。我们通常把这一技术叫FBO,也就是Frame Buffer Object的缩写。
要使用该扩展,或者说要把传统的帧缓冲区关闭,使用一个离屏缓冲区作我们的渲染运算区,只要以下很少的几行代码便可以实现了。有一点值得注意的是:当我用使用数字0,来绑定一个FBO的时候,无论何时,它都会还原window系统的特殊帧缓冲区,这一特性在一些高级应用中会很有用,但不是本教程的范围,有兴趣的朋友可能自已研究一下。
[cpp] view plaincopyGLuint fb;
void initFBO(void) {
// create FBO (off-screen framebuffer)
glGenFramebuffersEXT(1, &fb);
// bind offscreen buffer
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
}
Back to top
GPGPU 概念
1: 数组 = 纹理
一维数组是本地CPU最基本的数据排列方式,多维的数组则是通过对一个很大的一维数组的基准入口进行坐标偏移来访问的(至少目前大多数的编译器都是这样做的)。一个小例子可以很好说明这一点,那就是一个MxN维的数组 a[i][j] = a[i*M+j];我们可能把一个多维数组,映射到一个一维数组中去。这些数组我开始索引都被假定为0;
而对于GPU,最基本的数据排列方式,是二维数组。一维和三维的数组也是被支持的,但本教程的技术不能直接使用。数组在GPU内存中我们把它叫做纹理或者是纹理样本。纹理的最大尺寸在GPU中是有限定的。每个维度的允许最大值,通过以下一小段代码便可能查询得到,这些代码能正确运行,前提是OpenGL的渲染上下文必须被正确初始化。
[cpp] view plaincopyint maxtexsize;
glGetIntegerv(GL_MAX_TEXTURE_SIZE,&maxtexsize);
printf(“GL_MAX_TEXTURE_SIZE, %d ”,maxtexsize);
就目前主流的显卡来说,这个值一般是2048或者4096每个维度,值得提醒大家的就是:一块显卡,虽然理论上讲它可以支持4096*4096*4096的三维浮点纹理,但实际中受到显卡内存大小的限制,一般来说,它达不到这个数字。
在CPU中,我们常会讨论到数组的索引,而在GPU中,我们需要的是纹理坐标,有了纹理坐标才可以访问纹理中每个数据的值。而要得到纹理坐标,我们又必须先得到纹理中心的地址。
传统上讲,GPU是可以四个分量的数据同时运算的,这四个分量也就是指红、绿、蓝、alpha(RGBA)四个颜色通道。稍后的章节中,我将会介绍如何使用显卡这一并行运算的特性,来实现我们想要的硬件加速运算。
在CPU上生成数组
让我们来回顾一下前面所要实现的运算:也就是给定两个长度为N的数组,现在要求两数组的加权和y=y+alpha*x,我们现在需要两个数组来保存每个浮点数的值,及一个记录alpha值的浮点数。
[cpp] view plaincopyfloat* dataY = (float*)malloc(N*sizeof(float)); float* dataX = (float*)malloc(N*sizeof(float)); float alpha;
虽然我们的实际运算是在GPU上运行,但我们仍然要在CPU上分配这些数组空间,并对数组中的每个元素进行初始化赋值。
在GPU上生成浮点纹理
这个话题需要比较多的解释才行,让我们首先回忆一下在CPU上是如何实现的,其实简单点来说,我们就是要在GPU上建立两个浮点数组,我们将使用浮点纹理来保存数据。
有许多因素的影响,从而使问题变得复杂起来。其中一个重要的因素就是,我们有许多不同的纹理对像可供我们选择。即使我们排除掉一些非本地的目标,以及限定只能使用2维的纹理对像。我们依然还有两个选择,GL_TEXTURE_2D是传统的OpenGL二维纹理对像,而ARB_texture_rectangle则是一个OpenGL扩展,这个扩展就是用来提供所谓的texture rectangles的。对于那些没有图形学背景的程序员来说,选择后者可能会比较容易上手。texture2Ds 和 texture rectangles 在概念上有两大不同之处。我们可以从下面这个列表来对比一下,稍后我还会列举一些例子。
另外一个重要的影响因素就是纹理格式,我们必须谨慎选择。在GPU中可能同时处理标量及一到四分量的向量。本教程主要关注标量及四分量向量的使用。比较简单的情况下我们可以在中纹理中为每个像素只分配一个单精度浮点数的储存空间,在OpenGL中,GL_LUMNANCE就是这样的一种纹理格式。但是如果我们要想使用四个通道来作运算的话,我们就可以采用GL_RGBA这种纹理格式。使用这种纹理格式,意味着我们会使用一个像素数据来保存四个浮点数,也就是说红、绿、蓝、alpha四个通道各占一个32位的空间,对于LUMINANCE格式的纹理,每个纹理像素只占有32位4个字节的显存空间,而对于RGBA格式,保存一个纹理像素需要的空间是4*32=128位,共16个字节。
接下来的选择,我们就要更加小心了。在OpenGL中,有三个扩展是真正接受单精度浮点数作为内部格式的纹理的。分别是:NV_float_buffer,ATI_texture_float 和ARB_texture_float.每个扩展都就定义了一组自已的列举参数及其标识,如:(GL_FLOAT_R32_NV) ,( 0x8880),在程序中使用不同的参数,可以生成不同格式的纹理对像,下面会作详细描述。
在这里,我们只对其中两个列举参数感兴趣,分别是GL_FLOAT_R32_NV和GL_FLOAT_RGBA32_NV. 前者是把每个像素保存在一个浮点值中,后者则是每个像素中的四个分量分别各占一个浮点空间。这两个列举参数,在另外两个扩展(ATI_texture_float andARB_texture_float )中也分别有其对应的名称:GL_LUMINANCE_FLOAT32_ATI,GL_RGBA_FLOAT32_ATI 和 GL_LUMINANCE32F_ARB,GL_RGBA32F_ARB 。在我看来,他们名称不同,但作用都是一样的,我想应该是多个不同的参数名称对应着一个相同的参数标识。至于选择哪一个参数名,这只是看个人的喜好,因为它们全部都既支持NV显卡也支持ATI的显卡。
最后还有一个要解决的问题就是,我们如何把CPU中的数组元素与GPU中的纹理元素一一对应起来。这里,我们采用一个比较容易想到的方法:如果纹理是LUMINANCE格式,我们就把长度为N的数组,映射到一张大小为sqrt(N) x sqrt(N)和纹理中去(这里规定N是刚好能被开方的)。如果采用RGBA的纹理格式,那么N个长度的数组,对应的纹理大小就是sqrt(N/4) x sqrt(N/4),举例说吧,如果N=1024^2,那么纹理的大小就是512*512 。
以下的表格总结了我们上面所讨论的问题,作了一下分类,对应的GPU分别是: NVIDIA GeForce FX (NV3x), GeForce 6 and 7 (NV4x, G7x) 和 ATI.
(*) Warning: 这些格式作为纹理是被支持的,但是如果作为渲染对像,就不一定全部都能够得到良好的支持(seebelow)。
讲完上面的一大堆基础理论这后,是时候回来看看代码是如何实现的。比较幸运的是,当我们弄清楚了要用那些纹理对像、纹理格式、及内部格式之后,要生成一个纹理是很容易的。
[cpp] view plaincopy// create a new texture name
GLuint texID;
glGenTextures (1, &texID);
// bind the texture name to a texture target
glBindTexture(texture_target,texID);
// turn off filtering and set proper wrap mode
// (obligatory for float textures atm)
glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP);
// set texenv to replace instead of the default modulate
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
// and allocate graphics memory
glTexImage2D(texture_target, 0, internal_format,
texSize, texSize, 0, texture_format, GL_FLOAT, 0);
让我们来消化一下上面这段代码的最后那个OpenGL函数,我来逐一介绍一下它每个参数:第一个参数是纹理对像,上面已经说过了;第二个参数是0,是告诉GL不要使用多重映像纹理。接下来是内部格式及纹理大小,上面也说过了,应该清楚了吧。第六个参数是也是0,这是用来关闭纹理边界的,这里不需要边界。接下来是指定纹理格式,选择一种你想要的格式就可以了。对于参数GL_FLOAT,我们不要被它表面的意思迷惑,它并不会影响我们所保存在纹理中的浮点数的精度。其实它只与CPU方面有关系,目的就是要告诉GL稍后将要传递过去的数据是浮点型的。最后一个参数还是0,意思是生成一个纹理,但现在不给它指定任何数据,也就是空的纹理。该函数的调用必须按上面所说的来做,才能正确地生成一个合适的纹理。上面这段代码,和CPU里分配内存空间的函数malloc(),功能上是很相像的,我们可能用来对比一下。
最后还有一点要提醒注意的:要选择一个适当的数据排列映射方式。这里指的就是纹理格式、纹理大小要与你的CPU数据相匹配,这是一个非常因地制宜的问题,根据解决的问题不同,其相应的处理问题方式也不同。从经验上看,一些情况下,定义这样一个映射方式是很容易的,但某些情况下,却要花费你大量的时间,一个不理想的映射方式,甚至会严重影响你的系统运行。
数组索引与纹理坐标的一一对应关系
在后面的章节中,我们会讲到如何通过一个渲染操作,来更新我们保存在纹理中的那些数据。在我们对纹理进行运算或存取的时候,为了能够正确地控制每一个数据元素,我们得选择一个比较特殊的投影方式,把3D世界映射到2D屏幕上(从世界坐标空间到屏幕设备坐标空间),另外屏幕像素与纹理元素也要一一对应。这种关系要成功,关键是要采用正交投影及合适的视口。这样便能做到几何坐标(用于渲染)、纹理坐标(用作数据输入)、像素坐标(用作数据输出)三者一一对应。有一个要提醒大家的地方:如果使用texture2D,我们则须要对纹理坐标进行适当比例的缩放,让坐标的值在0到1之间,前面有相关的说明。
为了建立一个一一对应的映射,我们把世界坐标中的Z坐标设为0,把下面这段代码加入到initFBO()这个函数中
[cpp] view plaincopy// viewport for 1:1 pixel=texel=geometry mapping
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0, texSize, 0.0, texSize);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(0, 0, texSize, texSize);
使用纹理作为渲染对像
其实一个纹理,它不仅可以用来作数据输入对像,也还可以用作数据输出对像。这也是提高GPU运算效率和关键所在。通过使用 framebuffer_object这个扩展,我们可以把数据直接渲染输出到一个纹理上。但是有一个缺点:一个纹理对像不能同时被读写,也就是说,一个纹理,要么是只读的,要么就是只写的。显卡设计的人提供这样一个解释:GPU在同一时间段内会把渲染任务分派到几个通道并行运行, 它们之间都是相互独立的(稍后的章节会对这个问题作详细的讨论)。如果我们允许对一个纹理同时进行读写操作的话,那我们需要一个相当复杂的逻辑算法来解决读写冲突的问题, 即使在芯片逻辑上可以做到,但是对于GPU这种没有数据安全性约束的处理单元来说,也是没办法把它实现的,因为GPU并不是基von Neumann的指令流结构,而是基于数据流的结构。因此在我们的程序中,我们要用到3个纹理,两个只读纹理分别用来保存输入数组x,y。一个只写纹理用来保存运算结果。用这种方法意味着要把先前的运算公式:y = y + alpha * x 改写为:y_new = y_old + alpha * x.
FBO 扩展提供了一个简单的函数来实现把数据渲染到纹理。为了能够使用一个纹理作为渲染对像,我们必须先把这个纹理与FBO绑定,这里假设离屏帧缓冲已经被指定好了。
[cpp] view plaincopyglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, texture_target, texID, 0);
第一个参数的意思是很明显的。第二个参数是定义一个绑定点(每个FBO最大可以支持四个不同的绑定点,当然,不同的显卡对这个最大绑定数的支持不一样,可以用GL_MAX_COLOR_ATTACHMENTS_EXT来查询一下)。第三和第四个参数应该清楚了吧,它们是实际纹理的标识。最后一个参数指的是使用多重映像纹理,这里没有用到,因此设为0。
为了能成功绑定一纹理,在这之前必须先用glTexImage2D()来对它定义和分配空间。但不须要包含任何数据。我们可以把FBO想像为一个数据结构的指针,为了能够对一个指定的纹理直接进行渲染操作,我们须要做的就调用OpenGL来给这些指针赋以特定的含义。
不幸的是,在FBO的规格中,只有GL_RGB和GL_RGBA两种格式的纹理是可以被绑定为渲染对像的(后来更新这方面得到了改进),LUMINANCE这种格式的绑定有希望在后继的扩展中被正式定义使用。在我定本教程的时候,NVIDIA的硬件及驱动已经对这个全面支持,但是只能结会对应的列举参数NV_float_buffer一起来使用才行。换句话说,纹理中的浮点数的格式与渲染对像中的浮点数格式有着本质上的区别。
下面这个表格对目前不同的显卡平台总结了一下,指的是有哪些纹理格式及纹理对像是可能用来作为渲染对像的,(可能还会有更多被支持的格式,这里只关心是浮点数的纹理格式):
列表中最后一行所列出来的格式在目前来说,不能被所有的GPU移植使用。如果你想采用LUMINANCE格式,你必须使用ractangles纹理,并且只能在NVIDIA的显卡上运行。想要写出兼容NVIDIA及ATI两大类显卡的代是可能的,但只支持NV4x以上。幸运的是要修改的代码比较少,只在一个switch开关,便能实现代码的可移植性了。相信随着ARB新版本扩展的发布,各平台之间的兼容性将会得到进一步的提高,到时候各种不同的格式也可能相互调用了。
把数据从CPU的数组传输到GPU的纹理
为了把数据传输到纹理中去,我们必须绑定一个纹理作为纹理目标,并通过一个GL函数来发送要传输的数据。实际上就是把数据的首地址作为一个参数传递给该涵数,并指定适当的纹理大小就可以了。如果用LUMINANCE格式,则意味着数组中必须有texSize x texSize个元数。而RGBA格式,则是这个数字的4倍。注意的是,在把数据从内存传到显卡的过程中,是全完不需要人为来干预的,由驱动来自动完成。一但传输完成了,我们便可能对CPU上的数据作任意修改,这不会影响到显卡中的纹理数据。 而且我们下次再访问该纹理的时候,它依然是可用的。在NVIDIA的显卡中,以下的代码是得到硬件加速的。
[cpp] view plaincopyglBindTexture(texture_target, texID);
glTexSubImage2D(texture_target,0,0,0,texSize,texSize,
texture_format,GL_FLOAT,data);
这里三个值是0的参数,是用来定义多重映像纹理的,由于我们这里要求一次把整个数组传输一个纹理中,不会用到多重映像纹理,因此把它们都关闭掉。
以上是NVIDIA显卡的实现方法,但对于ATI的显卡,以下的代码作为首选的技术。在ATI显卡中,要想把数据传送到一个已和FBO绑定的纹理中的话,只需要把OpenGL的渲染目标改为该绑定的FBO对像就可以了。
glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);glRasterPos2i(0,0);glDrawPixels(texSize,texSize,texture_format,GL_FLOAT,data);
第一个函数是改变输出的方向,第二个函数中我们使用了起点作为参与点,因为我们在第三个函数中要把整个数据块都传到纹理中去。
两种情况下,CPU中的数据都是以行排列的方式映射到纹理中去的。更详细地说,就是:对于RGBA格式,数组中的前四个数据,被传送到纹理的第一个元素的四个分量中,分别与R,G,B,A分量一一对应,其它类推。而对于LUMINANCE 格式的纹理,纹理中第一行的第一个元素,就对应数组中的第一个数据。其它纹理元素,也是与数组中的数据一一对应的。
把数据从GPU纹理,传输到CPU的数组
这是一个反方向的操作,那就是把数据从GPU传输回来,存放在CPU的数组上。同样,有两种不同的方法可供我们选择。传统上,我们是使用OpenGL获取纹理的方法,也就是绑定一个纹理目标,然后调用glGetTexImage()这个函数。这些函数的参数,我们在前面都有见过。
glBindTexture(texture_target,texID);glGetTexImage(texture_target,0,texture_format,GL_FLOAT,data);
但是这个我们将要读取的纹理,已经和一个FBO对像绑定的话,我们可以采用改变渲染指针方向的技术来实现。
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);glReadPixels(0,0,texSize,texSize,texture_format,GL_FLOAT,data);
由于我们要读取GPU的整个纹理,因此这里前面两个参数是0,0。表示从0起始点开始读取。该方法是被推荐使用的。
一个忠告:比起在GPU内部的传输来说,数据在主机内存与GPU内存之间相互传输,其花费的时间是巨大的,因此要谨慎使用。由其是从CPU到GPU的逆向传输。
在前面“ 当前显卡设备运行的问题” 中 提及到该方面的问题。
一个简单的例子
[cpp] view plaincopy#include 《stdio.h》
#include 《stdlib.h》
#include 《GL/glew.h》
#include 《GL/glut.h》
int main(int argc, char **argv) {
// 这里声明纹理的大小为:teSize;而数组的大小就必须是texSize*texSize*4
int texSize = 2;
int i;
// 生成测试数组的数据
float* data = (float*)malloc(4*texSize*texSize*sizeof(float));
float* result = (float*)malloc(4*texSize*texSize*sizeof(float));
for (i=0; i《texSize*texSize*4; i++)
data[i] = (i+1.0)*0.01F;
// 初始化OpenGL的环境
glutInit (&argc, argv);
glutCreateWindow(“TEST1”);
glewInit();
// 视口的比例是 1:1 pixel=texel=data 使得三者一一对应
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0.0,texSize,0.0,texSize);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glViewport(0,0,texSize,texSize);
// 生成并绑定一个FBO,也就是生成一个离屏渲染对像
GLuint fb;
glGenFramebuffersEXT(1,&fb);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);
// 生成两个纹理,一个是用来保存数据的纹理,一个是用作渲染对像的纹理
GLuint tex,fboTex;
glGenTextures (1, &tex);
glGenTextures (1, &fboTex);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,fboTex);
// 设定纹理参数
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_T, GL_CLAMP);
// 这里在显卡上分配FBO纹理的贮存空间,每个元素的初始值是0;
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,0,GL_RGBA32F_ARB,
texSize,texSize,0,GL_RGBA,GL_FLOAT,0);
// 分配数据纹理的显存空间
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_RECTANGLE_ARB,
GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR,GL_DECAL);
glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,0,GL_RGBA32F_ARB,
texSize,texSize,0,GL_RGBA,GL_FLOAT,0);
//把当前的FBO对像,与FBO纹理绑定在一起
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT,
GL_COLOR_ATTACHMENT0_EXT,
GL_TEXTURE_RECTANGLE_ARB,fboTex,0);
// 把本地数据传输到显卡的纹理上。
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);
glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB,0,0,0,texSize,texSize,
GL_RGBA,GL_FLOAT,data);
//--------------------begin-------------------------
//以下代码是渲染一个大小为texSize * texSize矩形,
//其作用就是把纹理中的数据,经过处理后,保存到帧缓冲中去,
//由于用到了离屏渲染,这里的帧缓冲区指的就是FBO纹理。
//在这里,只是简单地把数据从纹理直接传送到帧缓冲中,
//没有对这些流过GPU的数据作任何处理,但是如果我们会用CG、
//GLSL等高级着色语言,对显卡进行编程,便可以在GPU中
//截获这些数据,并对它们进行任何我们所想要的复杂运算。
//这就是GPGPU技术的精髓所在。问题讨论:www.physdev.com
glColor4f(1.00f,1.00f,1.00f,1.0f);
glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);
glEnable(GL_TEXTURE_RECTANGLE_ARB);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0);
glVertex2f(0.0, 0.0);
glTexCoord2f(texSize, 0.0);
glVertex2f(texSize, 0.0);
glTexCoord2f(texSize, texSize);
glVertex2f(texSize, texSize);
glTexCoord2f(0.0, texSize);
glVertex2f(0.0, texSize);
glEnd();
//--------------------end------------------------
// 从帧缓冲中读取数据,并把数据保存到result数组中。
glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);
glReadPixels(0, 0, texSize, texSize,GL_RGBA,GL_FLOAT,result);
// 显示最终的结果
printf(“Data before roundtrip: ”);
for (i=0; i《texSize*texSize*4; i++)
printf(“%f ”,data[i]);
printf(“Data after roundtrip: ”);
for (i=0; i《texSize*texSize*4; i++)
printf(“%f ”,result[i]);
// 释放本地内存
free(data);
free(result);
// 释放显卡内存
glDeleteFramebuffersEXT (1,&fb);
glDeleteTextures (1,&tex);
glDeleteTextures(1,&fboTex);
return 0;
}
现在是时候让我们回头来看一下前面要解决的问题,我强烈建议在开始一个新的更高级的话题之前,让我们先弄一个显浅的例子来实践一下。下面通过一个小的程序,尝试着使用各种不同的纹理格式,纹理对像以及内部格式,来把数据发送到GPU,然后再把数据从GPU取回来,保存在CPU的另一个数组中。在这里,两个过程都没有对数据作任何运算修该,目的只是看一下数据GPU和CPU之间相互传输,所需要使用到的技术及要注意的细节。也就是把前面提及到的几个有迷惑性的问题放在同一个程序中来运行一下。在稍后的章节中将会详细讨论如何来解决这些可能会出现的问题。
由于赶着要完成整个教程,这里就只写了一个最为简单的小程序,采用rectangle纹理、ARB_texture_float作纹理对像并且只能在NVIDIA的显卡上运行。
你可以在这里下载到为ATI显卡写的另一个版本。
以上代码是理解GPU编程的基础,如果你完全看得懂,并且能对这代码作简单的修改运用的话,那恭喜你,你已经向成功迈进了一大步,并可以继续往下看,走向更深入的学习了。但如看不懂,那回头再看一编吧。
Back to top
评论
查看更多