天天看点

GPU深度发掘(四)::Render to Vertexbuffer in OpenGL

  GPU深度发掘(四):: Render to Vertexbuffer in OpenGL 作者:华文广 更新:2007/5/10 www.physdev.com

要想实现GPU编程,需要比较好的相关理论基础才行。如果你以前没有这方面的基础,请先学习一下相关的知识,推荐看一下文章《GPGPU::数学基础教程》

概貌:

PBO: Pixel buffer object

FBO: Frame buffer object

VBO: Vertex buffer object

以下介绍两种不同的实现方法,随着显卡的不断发展,可能会有更好的解决方法,但以下两种方法是目前比较常用的。

方法 1: 在顶点着色其间读取纹理,也就是(Vertex Texture Fetch). 可以看一下以下论文: http://developer.nvidia.com/object/using_vertex_textures.html

优点:

  • 不用拷贝到VBO。
  • 灵活的纹理数据读取
  • 消耗比较少的显存资源

缺点:

  • 只有NVidia的N3.0以上的显卡才支持
  • 只支持少量的浮点纹理格式(GL_RGBA_FLOAT32_ATI)
  • 老的显卡(GFX系列之前)不支持,GF6以后的才可以。
  • 稍为要慢一点,GF6600GT的读取纹理的速度是30M/秒,如果该纹理要先经过FBO作处理的话,则会更慢一些。当然,这里的慢是相对于后面将要绍介的第二种方法而言,但如果与把纹理读回到CPU内存的速度相比,这个方法要快得多。

方法 2: 拷贝到像素缓冲对像(PBO)

PBO指的是: pixel buffer object,PBO能直接转换为VBO,用作顶点数组的渲染。

优点:

  • 支技多种PBO的数据格式,而不紧紧是浮点的RGBA。
  • 更快。在GF6600GT显卡上,渲染FBO + 拷贝FBO到PBO + 渲染VBO,速度是58M/秒。据说现在新的G8显卡,可以省去拷贝这一步,也就是直接渲染到VBO,那速度就更快了。

缺点:

  • 比较高的显存消耗。
  • 须要额外的拷贝动作。
  • 使用多个FBO渲染对像时,每个对像必许用同一种数据格式。例如:如果我们想在一个渲染通道中,把position信息渲染到浮点RGB缓冲区块,然后把normal信息渲染到BYTE-RGB格式的缓冲区,这样做是不行的。

注意:老的GFX显卡不支持多渲染对像。

实现:

方法 1: 在顶点着色其间读取纹理.

步骤 1: 生成 FBO

生成一个FBO,然后把一个用来保存顶点数据的纹理绑定到这个FBO上。

步骤 2: 生成 VBO

生成一个顶点缓冲对像(VBO),用来保保存纹理坐标信息,主要的作用就是为了在顶点着色期间能正确定位并读取到FBO纹理中的数据。

Create a vertex buffer object (VBO) holding texture coordinates for

referencing the FBO texture (e.g. a position VBO with

2 float-coordinates per vertex)

( see further down the text how to create )

步骤 3: 渲染 FBO

( 详细说明请看后面的内容 )

步骤 4: 渲染 VBO

这里,我们先从VBO得到顶点坐标,然后用该坐标来访问FBO纹理,并取得纹理数据。

以下例子是一个顶点程序的代码:

Code:

// vertex.position is our  
    // index to the real vertex array  
   
   
    !!ARBvp1.0   
   
   
    OPTION NV_vertex_program3; 
   
   
    PARAM mvp[4] = { state.matrix.mvp }; 
   
   
    TEMP real_position; 
   
   
    TEX real_position, vertex.position, texture[0], 2D; 
   
   
    DP4 result.position.x, mvp[0], real_position; 
   
   
    DP4 result.position.y, mvp[1], real_position; 
   
   
    DP4 result.position.z, mvp[2], real_position; 
   
   
    DP4 result.position.w, mvp[3], real_position; END  ;
         

以下列出的是一引些可以被支持的纹理格式。

vertex shader:

Quote:

Originally Posted by Nvidia Documentation 顶点纹理的调用会有许多的限制,必须使用GL_TEXTURE_2D的纹理对像,目前只支持GL_LUMINANCE_FLOAT32_ATI 和 GL_RGBA_FLOAT32_ATI 两种数据格式,这两种格式都表示只支持32-bit的浮点数据,前者是单通道,后者是四通道。值得注意的是:如果使用其它的纹理格式,或用了一些不被支持的过滤模式,会造成一些问题,显卡驱动可能会退回到软件模式下进行顶点处理。 以下是一个正确的代码写法。

GLuint vertex_texture;

glGenTextures(1, &vertex_texture);

glBindTexture(GL_TEXTURE_2D, vertex_texture);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);

glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_FLOAT32_ATI, width, height, 0,GL_LUMINANCE, GL_FLOAT, data);

方法2. 拷贝到像素缓冲对像 (PBO).

步骤 1: 生成一个用作像素缓冲对像的VBO:

示例:

Code:

GLuint vbo_points_handle; glGenBuffersARB(1, &vbo_vertices_handle); glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, vbo_vertices_handle); glBufferDataARB(GL_PIXEL_PACK_BUFFER_EXT, vbo_points.size()*4*
    sizeof(
    float ),
   
   
    	NULL, GL_DYNAMIC_DRAW_ARB );
         

步骤 2: 生成一个 FBO.

多个渲染对像可以帮助我们实现同一时间写入顶点/法线/副法线。以下是一个生成FBO的示例:

Code:

GLuint fb_handle; glGenFramebuffersEXT(1,&fb_handle);  fbo_tex_vertices = NewFloatTex(tex_width,tex_height,0); fbo_tex_normals = NewFloatTex(tex_width,tex_height,0);
         

这段代码是演示如何生成浮点数据的纹理。

Code:

/** * Sets up a floating point texture with NEAREST filtering. 
   
   
    * (mipmaps etc. are unsupported for floating point textures) 
   
   
    */ 
   
   
    void setupTexture (
    const GLuint texID,
    int texSize_w,
    int texSize_h) { 
   
   
    	// make active and bind 
   
   
    	glBindTexture(textureParameters.texTarget,texID); 
   
   
    	// turn off filtering and wrap modes 
   
   
    	glTexParameteri(textureParameters.texTarget, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
   
   
    	glTexParameteri(textureParameters.texTarget, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 
   
   
    	glTexParameteri(textureParameters.texTarget, GL_TEXTURE_WRAP_S, GL_CLAMP); 
   
   
    	glTexParameteri(textureParameters.texTarget, GL_TEXTURE_WRAP_T, GL_CLAMP); 
   
   	
    // define texture with floating point format 
   
   
    	glTexImage2D(textureParameters.texTarget,0,textureParameters.texInternalFormat,
   
   
    		texSize_w,texSize_h,0,textureParameters.texFormat,GL_FLOAT,0); 
   
   	
    // check if that worked 
   
   	
    if (glGetError() != GL_NO_ERROR) 
   
   
    	{ 
   
   
    		printf("glTexImage2D():			 [FAIL]  "); 
   
   		
    // PAUSE(); 
   
   		
    exit (ERROR_TEXTURE); 
   
   
    	} 
   
   	
    else if (mode == 0) 
   
   
    	{ 
   
   
    		printf("glTexImage2D():			 [PASS]  "); 
   
   
    	} 
   
   	
    // printf("Created a %i by %i floating point texture.  ",texSize,texSize); 
   
   
    }
         

注意:即使我们可以生成RGB的纹理,但它内部格式可能还是RGBA的,用glReadPixels()来读取数据时由于要进行格式转换,可能会减慢运行的速度。因此,多数情况下,我们尽量使用RGBA的格式。

步骤 3: 渲染 FBO

输入纹理中包含有必要的数据(如:顶点位置、法线等),经过运算之后,把数据保存到输出纹理中。

下面代码是绑定FBO缓冲区:

Code:

//glBindFramebufferEXT(GL_attach two textures to FBO
   
   
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachmentpoints[0], 
   
   
    	textureParameters.texTarget, outTexID, 0); 
   
   
    // check if that worked 
   
   
    if (!checkFramebufferStatus()) 
   
   
    { 
   
   
    printf("glFramebufferTexture2DEXT():	 [FAIL]  "); 
   
   
    // PAUSE(); 
   
   
    exit (ERROR_FBOTEXTURE); 
   
   
    } 
   
   
    else if (mode == 0)
   
   
     { printf("glFramebufferTexture2DEXT():	 [PASS]  "); 
   
   
    }
         

下面代码通过渲染一个四边形来触发FBO的运算。

Code:

// make quad filled to hit every pixel/texel 
   
   
    // (should be default but we never know) 
   
   
    glPolygonMode(GL_FRONT,GL_FILL); 
   
   
    if (textureParameters.texTarget == GL_TEXTURE_2D) 
   
   
    { 
   
   	
    // render with normalized texcoords 
   
   
    	glBegin(GL_QUADS); 
   
   
    	glTexCoord2f(0.0, 0.0); 
   
   
    	glVertex2f(0.0, 0.0); 
   
   
    	glTexCoord2f(1.0, 0.0); 
   
   
    	glVertex2f(outTexSizeW, 0.0); 
   
   
    	glTexCoord2f(1.0, 1.0); 
   
   
    	glVertex2f(outTexSizeW, outTexSizeH); 
   
   
    	glTexCoord2f(0.0, 1.0); 
   
   
    	glVertex2f(0.0, outTexSizeH); 
   
   
    	glEnd(); 
   
   
    } 
   
   
    else 
   
   
    { 
   
   	
    // render with unnormalized texcoords 
   
   
    	glBegin(GL_QUADS); 
   
   
    	glTexCoord2f(0.0, 0.0); 
   
   
    	glVertex2f(0.0, 0.0); 
   
   
    	glTexCoord2f(outTexSizeW, 0.0); 
   
   
    	glVertex2f(outTexSizeW, 0.0); 
   
   
    	glTexCoord2f(outTexSizeW, outTexSizeH); 
   
   
    	glVertex2f(outTexSizeW, outTexSizeH); 
   
   
    	glTexCoord2f(0.0, outTexSizeH); 
   
   
    	glVertex2f(0.0, outTexSizeH); 
   
   
    	glEnd(); 
   
   
    } 
   
   
     
         

gluOrtho2D是必须要有的! 如果没有, glReadPixels运行时会出错

Code:

/** 
   
   
    * Creates framebuffer object, binds it to reroute rendering operations 
   
   
    * from the traditional framebuffer to the offscreen buffer 
   
   
    */ 
   
   
    void initFBO(void) 
   
   
    { 
   
   
    // create FBO (off-screen framebuffer) 
   
   
    glGetIntegerv(GL_DRAW_BUFFER, &_currentDrawbuf); 
   
   
    // Save the current Draw buffer 
   
   
    glGenFramebuffersEXT(1, &fb); 
   
   
    // bind offscreen framebuffer (that is, skip the window-specific render target) 
   
   
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb); 
   
   
    // viewport for 1:1 pixel=texture mapping 
   
   
    glMatrixMode(GL_PROJECTION); 
   
   
    glLoadIdentity(); 
   
   
    gluOrtho2D(0.0, outTexSizeW, 0.0, outTexSizeH); 
   
   
    glMatrixMode(GL_MODELVIEW); glLoadIdentity(); 
   
   
    glViewport(0, 0, outTexSizeW, outTexSizeH);
   
   
     }
         

步骤 4: 拷贝 FBO 的数据到 PBO

Example:

Code:

/** 
   
   
    *Copy from FBO to PBO 
   
   
    * 
   
   
    */ 
   
   
    void copyFromTextureToPBO(GLuint pboID,
    int texSize_w,
    int texSize_h) 
   
   
    { 
   
   
    	glReadBuffer(attachmentpoints[0]); 
   
   
    	glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, pboID); 
   
   
    	glReadPixels(0, 0, texSize_w,texSize_h,
   
   
    		textureParameters.texFormat,GL_FLOAT, 0);
   
   
    	glReadBuffer(GL_NONE); 
   
   
    	glBindBufferARB(GL_PIXEL_PACK_BUFFER_EXT, 0 ); 
   
   
    }
         

( vbo_vertices.size() == tex_width * tex_height )

步骤 5: 渲染 VBO:

Code:

glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo_vertices_handle); 
   
   
    glEnableClientState(GL_VERTEX_ARRAY); 
   
   
    glVertexPointer  ( 4, GL_FLOAT,4*
    sizeof(
    float), (
    char *) 0);  
   
   
    glBindBufferARB(GL_ARRAY_BUFFER_ARB, vbo_normals_handle); 
   
   
    glEnableClientState(GL_NORMAL_ARRAY);
   
   
    glNormalPointer(GL_FLOAT, 4*
    sizeof(
    float), (
    char *) 0 ); 
   
   
    glDrawArrays( GL_TRIANGLES, 0,vbo_vertices.size() );  
   
   
    glDisableClientState(GL_NORMAL_ARRAY); 
   
   
    glDisableClientState(GL_VERTEX_ARRAY);
         

示例说明

本文章所带的例子,实现了在GPU中计算B样条曲线的功能,用到的技术有:VBO,FBO,Render to vertex,CG,B-spline.实现过程如图所示:

GPU深度发掘(四)::Render to Vertexbuffer in OpenGL

主要分为三个阶段:

第一阶段:GPU片段着色运算,生成FBO顶点数据。

  • 把样条控制点的数据发送到GPU的一个输入纹理(控制点纹理)。
  • 在片段处理单元中读取控“制点纹理”中的数据,使用B样条插值函数,计算插值顶点,把结果保存到FBO所绑定的输出纹理(插值纹理)中。

    第二阶段:FBO拷贝到PBO

    把插值纹理通过使用glReadPixels()函数,拷贝到PBO中。

    第三阶段:渲染VBO

    使用glDrawArrays(); 来渲染样条曲线。当然这里我们要把前面生成的PBO数据指定为一个VBO对像。

    整个过程的插值运算及数据拷贝,都是在GPU中进行,最终的顶点数据直接用顶点数组来作渲染,数据没有返回到CPU中因此速度会非常快。

    结论

    声明:

    本译文可以自由转载,要求保留原作者信息并注明文章出自物理开发网:www.physdev.com

    本文所提供的代码在NV6600的显卡测试通过,如果如果你在其它显卡上测试有问题,可以到物理开发网(www.physdev.com)的GPGPU/CUDA论坛上发表留言进行反溃。

    下一步计划将要写一个GUP粒子系统示例,GUP布料系统示例,GPU头发系统示例,敬请关注。

  • 参考

    http://oss.sgi.com/projects/ogl-sample/registry/EXT/pixel_buffer_object.txt

    http://developer.nvidia.com/object/using_vertex_textures.html

    http://wiki.delphigl.com/index.php/GLSL_Partikel

    http://www.mathematik.uni-dortmund.de/~goeddeke/gpgpu/tutorial.html#arrays3

    http://download.developer.nvidia.com/developer/SDK/Individual_Samples/samples.html

    示例代码下载:http://www.physdev.com/phpbb/viewtopic.php?t=144

继续阅读