OpenGL学习脚印: 基本图形绘制方式比较

写在前面

本节主要讨论OpenGL下基本图形(points, lines, triangles, polygons, quads, fans and strips)的绘制方式,比较传统和现代绘制方式的区别。本文整理自网络,保留了部分原文,参考资料部分列出了主要参考内容。本节示例代码在vs2012下测试通过,如果发现了错误请纠正我。转载需经过作者同意。

通过本节,可以了解到:

  • 传统立即模式Immediate Mode绘图
  • 传统显示列表Display List绘图
  • 顶点数组Vertex Arrays绘图
  • 现代的VBO VAO绘图
  • 现代结合Shader的绘图

1.传统方式绘制

传统绘制方式在OpenGL新版本中已经废弃,不过部分情况下还能工作。这里列出他们仅供学习。

1.1 立即模式Immediate Mode

传统的使用glBegin…glEnd方式制定绘制方式,在这两个函数对之间给出绘制的数据,这种方式成为立即模式。

立即模式绘图,示例代码如下所示:

[cpp] view plaincopyprint?
  1. //依赖库glew32.lib freeglut.lib
  2. //使用glBegin…glEnd绘制三角形(已过时,仅为学习目的)
  3. #  include <GL/glew.h>
  4. #  include <GL/freeglut.h>
  5. void userInit();
  6. void reshape(int w,int h);
  7. void display( void );
  8. void keyboardAction( unsigned char key, int x, int y );
  9. int main( int argc, char **argv )
  10. {
  11. glutInit(&argc, argv);
  12. glutInitDisplayMode( GLUT_RGBA|GLUT_SINGLE);
  13. glutInitWindowPosition(100,100);
  14. glutInitWindowSize( 512, 512 );
  15. glutCreateWindow( “Triangle demo” );
  16. glewInit();
  17. userInit();
  18. glutReshapeFunc(reshape);
  19. glutDisplayFunc( display );
  20. glutKeyboardFunc( keyboardAction );
  21. glutMainLoop();
  22. return 0;
  23. }
  24. //自定义初始化函数
  25. void userInit()
  26. {
  27. glClearColor( 0.0, 0.0, 0.0, 0.0 );
  28. glColor4f(1.0,1.0,0.0,0.0);
  29. }
  30. //调整窗口大小回调函数
  31. void reshape(int w,int h)
  32. {
  33. glViewport(0,0,(GLsizei)w,(GLsizei)h);
  34. }
  35. //绘制回调函数
  36. void display( void )
  37. {
  38. glClear( GL_COLOR_BUFFER_BIT);
  39. //使用传统的glBegin…glEnd绘制三角形
  40. glBegin(GL_TRIANGLES);
  41. glVertex3f(-0.5,-0.5,0.0);
  42. glVertex3f(0.5,0.0,0.0);
  43. glVertex3f(0.0,0.5,0.0);
  44. glEnd();
  45. glFlush();
  46. }
  47. //键盘按键回调函数
  48. void keyboardAction( unsigned char key, int x, int y )
  49. {
  50. switch( key )
  51. {
  52. case 033:  // Escape key
  53. exit( EXIT_SUCCESS );
  54. break;
  55. }
  56. }

本例及本文所有绘制效果都如下图所示:

1.2 显示列表Display List

显示列表是一组存储在一起的OpenGL函数,可以再以后执行。调用一个显示列表时,它所存储的函数就会按照顺序执行。显示列表通过存储OpenGL函数,可以提高性能。如果需要多次重复绘制同一个几何图形,或者如果有一些需要多次调用的用于更改状态的函数,就可以把他们存储在显示列表中。例如绘制三轮车的车轮的有效方法是,把绘制一个车轮的操作存储在显示列表中,并3次执行这个显示列表。每次在执行时,适当地做出坐标转换即可(参考自[2])。

显示列表,示例代码如下所示,执行效果同上图:

[cpp] view plaincopyprint?
  1. //依赖库glew32.lib freeglut.lib
  2. //使用顶点列表绘制三角形(已过时,仅为学习目的)
  3. #  include <GL/glew.h>
  4. #  include <GL/freeglut.h>
  5. void userInit();
  6. void reshape(int w,int h);
  7. void display( void );
  8. void keyboardAction( unsigned char key, int x, int y );
  9. //显示列表句柄
  10. GLuint displayListId;
  11. int main( int argc, char **argv )
  12. {
  13. glutInit(&argc, argv);
  14. glutInitDisplayMode( GLUT_RGBA|GLUT_SINGLE);
  15. glutInitWindowPosition(100,100);
  16. glutInitWindowSize( 512, 512 );
  17. glutCreateWindow( “Triangle demo” );
  18. glewInit();
  19. userInit();
  20. glutReshapeFunc(reshape);
  21. glutDisplayFunc( display );
  22. glutKeyboardFunc( keyboardAction );
  23. glutMainLoop();
  24. return 0;
  25. }
  26. //自定义初始化函数
  27. void userInit()
  28. {
  29. glClearColor( 0.0, 0.0, 0.0, 0.0 );
  30. glColor4f(1.0,1.0,0.0,0.0);
  31. //创建显示列表
  32. displayListId = glGenLists(1);
  33. glNewList(displayListId,GL_COMPILE);
  34. glBegin(GL_TRIANGLES);
  35. glVertex3f(-0.5,-0.5,0.0);
  36. glVertex3f(0.5,0.0,0.0);
  37. glVertex3f(0.0,0.5,0.0);
  38. glEnd();
  39. glEndList();
  40. }
  41. //调整窗口大小回调函数
  42. void reshape(int w,int h)
  43. {
  44. glViewport(0,0,(GLsizei)w,(GLsizei)h);
  45. }
  46. //绘制回调函数
  47. void display( void )
  48. {
  49. glClear( GL_COLOR_BUFFER_BIT);
  50. //利用显示列表,绘制三角形
  51. glCallList(displayListId);
  52. glFlush();
  53. }
  54. //键盘按键回调函数
  55. void keyboardAction( unsigned char key, int x, int y )
  56. {
  57. switch( key )
  58. {
  59. case 033:  // Escape key
  60. exit( EXIT_SUCCESS );
  61. break;
  62. }
  63. }

2.现代方式绘制

2.1 顶点数组绘图

使用顶点数组方式,需要利用glEnableClientState开启一些特性,这里开启顶点数组特性使用glEnableClientState(GL_VERTEX_ARRAY)

使用顶点数组时,用户定义好存储顶点的数据,在调用glDrawArrays、glDrawElements之类的函数时,通过glVertexPointer设定的指针,传送数据到GPU。当调用完glDrawArrays后,GPU中已经有了绘图所需数据,用户可以释放数据空间。(参考自[3])

顶点数组方式绘图示例代码如下所示:

[cpp] view plaincopyprint?
  1. //依赖库glew32.lib freeglut.lib
  2. //使用Vertex Arrays顶点数组绘制三角形(不推荐使用)
  3. #  include <GL/glew.h>
  4. #  include <GL/freeglut.h>
  5. void userInit();
  6. void reshape(int w,int h);
  7. void display( void );
  8. void keyboardAction( unsigned char key, int x, int y );
  9. //定义一个包含3个float的结构体
  10. //为了保持简单,暂时未引入c++类概念
  11. struct vec3f {
  12. GLfloat x, y, z;
  13. };
  14. int main( int argc, char **argv )
  15. {
  16. glutInit(&argc, argv);
  17. glutInitDisplayMode( GLUT_RGBA|GLUT_SINGLE);
  18. glutInitWindowPosition(100,100);
  19. glutInitWindowSize( 512, 512 );
  20. glutCreateWindow( “Triangle demo” );
  21. glewInit();
  22. userInit();
  23. glutReshapeFunc(reshape);
  24. glutDisplayFunc( display );
  25. glutKeyboardFunc( keyboardAction );
  26. glutMainLoop();
  27. return 0;
  28. }
  29. //自定义初始化函数
  30. void userInit()
  31. {
  32. glClearColor( 0.0, 0.0, 0.0, 0.0 );
  33. glColor4f(1.0,1.0,0.0,0.0);
  34. }
  35. //调整窗口大小回调函数
  36. void reshape(int w,int h)
  37. {
  38. glViewport(0,0,(GLsizei)w,(GLsizei)h);
  39. }
  40. //绘制回调函数
  41. void display( void )
  42. {
  43. glClear( GL_COLOR_BUFFER_BIT);
  44. //利用顶点数组,绘制三角形
  45. const int num_indices = 3;
  46. //创建保存顶点的结构体数组
  47. vec3f *vertices = new vec3f[num_indices];
  48. // 顶点1
  49. vertices[0].x = -0.5f;
  50. vertices[0].y = -0.5f;
  51. vertices[0].z = 0.0f;
  52. // 顶点2
  53. vertices[1].x = 0.5f;
  54. vertices[1].y = 0.0f;
  55. vertices[1].z = 0.0f;
  56. //顶点3
  57. vertices[2].x = 0.0f;
  58. vertices[2].y = 0.5f;
  59. vertices[2].z = 0.0f;
  60. // 启用vertex arrays
  61. glEnableClientState(GL_VERTEX_ARRAY);
  62. //定义顶点数组
  63. glVertexPointer(
  64. 3,         // 每个顶点的维度
  65. GL_FLOAT,  // 顶点数据类型
  66. 0,         // 连续顶点之间的间隙,这里为0
  67. vertices   //指向第一个顶点的第一个坐标的指针
  68. );
  69. glDrawArrays(GL_TRIANGLES, 0, num_indices);
  70. glDisableClientState(GL_VERTEX_ARRAY);
  71. //释放内存空间
  72. delete[] vertices;
  73. glFlush();
  74. }
  75. //键盘按键回调函数
  76. void keyboardAction( unsigned char key, int x, int y )
  77. {
  78. switch( key )
  79. {
  80. case 033:  // Escape key
  81. exit( EXIT_SUCCESS );
  82. break;
  83. }
  84. }

2.2 现代VBO VAO绘图

首先了解下VBO和VAO。

根据文[4]所述:

A Vertex Array Object (VAO) is an object which contains one or more Vertex Buffer Objects and is designed to store the information for a complete rendered object. In our example this is a diamond consisting of four vertices as well as a color for each vertex.

A Vertex Buffer Object (VBO) is a memory buffer in the high speed memory of your video card designed to hold information about vertices. In our example we have two VBOs, one that describes the coordinates of our vertices and another that describes the color associated with each vertex. VBOs can also store information such as normals, texcoords, indicies, etc.

VAO即Vertex Array Object ,是一个包含一个或多个VBO的对象,被设计用来存储一个完整被渲染对象所需的信息。

VBO即Vertex Buffer Object,是一个在高速视频卡中的内存缓冲,用来保存顶点数据,也可用于包含诸如归一化向量、纹理和索引等数据。

根据[1]中所述:

VBO stores actual vertex data. The most important thing about a VBO is not that it stores data, though it is its primary function, but where it is stored. A VBO object resides on GPU, the graphics processing unit. This means it is very fast, it is stored in memory on the graphics card itself. How cool is that? Storing data on the computer processor or RAM is slow mostly because it needs to be transferred to the GPU, and this transfer can be costly.

VAO represents properties, rather than actual data. But these properties do describe the objects actually stored in theVBO.VAO can be thought of as anadvanced memory pointer to objects. Similar to C-language pointers, they do a whole lot more tracking than just the address. They are very sophisticated.

VAOs are a lot like helpers, rather than actual data storage. That’s what they’re for. They also keep track of properties to be used in current rendering process. Finally, they describe properties of objects, rather than the raw data of those objects that is by all means already stored in a VBO.

VAOs are not directly related to VBOs, although it may seem that way at first. VAOs simply save time to enable a certain application state needed to be set. Without VAO, you would have to call a bunch of gl* commands to do the same thing.

VBO存储了实际的数据,真正重要的不是它存储了数据,而是他将数据存储在GPU中。这意味着VBO它会很快,因为存在RAM中的数据需要被传送到GPU中,因此这个传送是有代价的。

VAO代表的是一些描述存储在VBO中对象的属性。VAO可以被视为指向对象的高级内存指针,有点类似于C语言指针,但比地址多了跟多的跟踪作用。他们很复杂。

VAO很像是辅助对象,而不是实际的数据存储对象。他们记录了当前绘制过程中的属性,描述对象的属性,而不是已经存储在VBO中原始数据。

VAO并不与VBO直接相关,进过初看起来如此。VAOs节省了设置程序所需的状态的时间。如果没有VAO,你需要调用一堆类似gl*之类的命令。这里从songho[5]文的用户反馈列表中找到一个示例解释了VAO节省时间的例子:

提问:How do Vertex Buffer Objects relate to Vertex Array Objects?

songho回答:

The name, VAO (Vertex Array Object) looks somewhat related to VBO, but it is not. VAO is for encapsulating vertex array states/functions into it. Therefore, you can replace the multiple OpenGL calls to a single call of glBindVertexArray(), in order to setup various vertex array states and attributes before drawing.
The following example gives a better sense of VAO purpose;

[cpp] view plaincopyprint?
  1. // draw with VAO
  2. glBindVertexArray(vaoId); // bind vao
  3. glDrawElements(…);
  4. glBindVertexArray(0);     // unbind vao
  5. // draw without VAO
  6. // need to set many states before drawing
  7. glEnableClientState(GL_VERTEX_ARRAY); // enable client states
  8. glEnableClientState(GL_NORMAL_ARRAY);
  9. glBindBuffer(GL_ARRAY_BUFFER, vboId); // bind vbo
  10. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iboId);
  11. glVertexPointer(3, GL_FLOAT, 0, 0); // vertex attributes
  12. glNormalPointer(GL_FLOAT, 0, offset); // normal attributes
  13. glDrawElements(…);
  14. glBindBuffer(GL_ARRAY_BUFFER, 0); // unbind vbo
  15. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
  16. glDisableClientState(GL_VERTEX_ARRAY);
  17. glDisableClientState(GL_NORMAL_ARRAY);</span></span>

You can dramatically reduce the function call overhead and make the code much simpler with VAO.

But the overall performance gain is very minimal.
从这里可以看出使用VAO方式的好处,更多关于VAO、VBO讨论不在此处展开。

单独使用VBO执行绘制的示例代码如下:

(这里并没有使用VAO,因此仍然要使用glEnableClientState(GL_VERTEX_ARRAY),稍后会给出VAO与VBO结合例子)

[cpp] view plaincopyprint?
  1. //依赖库glew32.lib freeglut.lib
  2. //使用VBO绘制三角形(现代OpenGL方式)
  3. #  include <GL/glew.h>
  4. #  include <GL/freeglut.h>
  5. void userInit();
  6. void reshape(int w,int h);
  7. void display( void );
  8. void keyboardAction( unsigned char key, int x, int y );
  9. //VBO句柄
  10. GLuint vboId;
  11. int main( int argc, char **argv )
  12. {
  13. glutInit(&argc, argv);
  14. glutInitDisplayMode( GLUT_RGBA|GLUT_SINGLE);
  15. glutInitWindowPosition(100,100);
  16. glutInitWindowSize( 512, 512 );
  17. glutCreateWindow( “Triangle demo” );
  18. glewInit();
  19. userInit();
  20. glutReshapeFunc(reshape);
  21. glutDisplayFunc( display );
  22. glutKeyboardFunc( keyboardAction );
  23. glutMainLoop();
  24. return 0;
  25. }
  26. //自定义初始化函数
  27. void userInit()
  28. {
  29. glClearColor( 0.0, 0.0, 0.0, 0.0 );
  30. glColor4f(1.0,1.0,0.0,0.0);
  31. //创建顶点数据
  32. GLfloat vertices[] = {
  33. -0.5,-0.5,0.0,
  34. 0.5,0.0,0.0,
  35. 0.0,0.5,0.0
  36. };
  37. //分配vbo句柄
  38. glGenBuffersARB(1,&vboId);
  39. //GL_ARRAY_BUFFER_ARB表示作为顶点数组解析
  40. glBindBufferARB(GL_ARRAY_BUFFER_ARB,vboId);
  41. //拷贝数据
  42. glBufferDataARB(GL_ARRAY_BUFFER_ARB,sizeof(vertices),
  43. vertices,GL_STATIC_DRAW_ARB);
  44. glBindBufferARB(GL_VERTEX_ARRAY,0);
  45. }
  46. //调整窗口大小回调函数
  47. void reshape(int w,int h)
  48. {
  49. glViewport(0,0,(GLsizei)w,(GLsizei)h);
  50. }
  51. //绘制回调函数
  52. void display( void )
  53. {
  54. glClear( GL_COLOR_BUFFER_BIT);
  55. glBindBufferARB(GL_ARRAY_BUFFER_ARB, vboId);//绑定vbo
  56. glEnableClientState(GL_VERTEX_ARRAY);//启用顶点数组属性
  57. glVertexPointer(3, GL_FLOAT, 0, 0);//如何解析vbo中数据
  58. glDrawArrays(GL_TRIANGLES, 0, 3);
  59. glDisableClientState(GL_VERTEX_ARRAY);
  60. glBindBufferARB(GL_ARRAY_BUFFER_ARB,0);//解除绑定
  61. glFlush();
  62. }
  63. //键盘按键回调函数
  64. void keyboardAction( unsigned char key, int x, int y )
  65. {
  66. switch( key )
  67. {
  68. case 033:  // Escape key
  69. exit( EXIT_SUCCESS );
  70. break;
  71. }
  72. }

2.3 结合Shader绘图

结合现代Shader的绘图,不再此处展开,请参见另外一篇博客《OpenGL学习脚印: 顶点数据传送和着色器处理1》。

3.总结几种方式利弊

  • 使用立即模式的缺点很明显,数据量大一点的话,代码量增加,而且数据发送到服务端需要开销;
  • 使用显示列表,显示列表是一个服务端函数,因此它免除了传送数据的额外开销。但是,显示列表一旦编译后,其中的数据无法修改。
  • 使用顶点数组,可以减少函数调用和共享顶点数据的冗余。但是,使用顶点数组时,顶点数组相关函数是在客户端,因此数组中数据在每次被解引用时必须重新发送到服务端,额外开销不可忽视。
  • 使用VBO在服务端创建缓存对象,并且提供了访问函数来解引用数组;例如在顶点数组中使用的函数如 glVertexPointer(), glNormalPointer(), glTexCoordPointer()。同时,VBO内存管理会根据用户提示,”target”  和”usage”模式,将缓存对象放在最佳地方。因此内存管理会通过在系统内存、AGP内存和视频卡内存(system, AGP and video memory)这3中内存见平衡来优化缓存。另外,不像显示列表,VBO中数据可以通过映射到客户端内存空间而被用户读取和更新。VBO的另外一个优势是它像显示列表和纹理一样,能和多个客户端共享缓存对象。可见使用VBO优势很明显。(参考自:[5])

4.参考资料

[1]:  Learn to draw OpenGL primitives

[2]: 《OpenGL编程指南》 红宝书 第七版

[3]:   When does glVertexPointer() copy data?

[4] :    Tutorial2: VAOs, VBOs, Vertex and Fragment Shaders (C / SDL)

[5] :  OpenGL Vertex Buffer Object (VBO)

var html = document.getElementById("artContent").innerHTML; document.getElementById("artContent").innerHTML = html;

转载源出处:
http://www.360doc.com/content/14/1028/10/19175681_420522404.shtml

OpenG绘图方式比较相关推荐

  1. H5中canvas和svg绘图方式介绍

    在HTML5中包括了两种绘图方式,canvas和svg(矢量呈现),而与canvas不同的是,svg是一种XML标记语言,它既可以单独保存以".svg"为后缀的文件在浏览器中打开显 ...

  2. python绘画_python绘图骚操作之plotly(一)——plotly的基本绘图方式

    python进阶教程 机器学习 深度学习 长按二维码关注 进入正文 Plotly基础内容介绍 目录 一 plotly简介 二 plotly安装 2.1 安装方式 三 plotly的绘图方式 四 plo ...

  3. iOS绘图详解-多种绘图方式、裁剪、滤镜、移动、CTM

    iOS绘图详解 摘要: Core Graphics Framework是一套基于C的API框架,使用了Quartz作为绘图引擎.它提供了低级别.轻量级.高保真度的2D渲染.该框架可以用于基于路径的 绘 ...

  4. 计算机辅助绘图方式,计算机辅助绘图技巧

    简介: 目前常用的计算机绘图软件是AutoCAD R14,以及在此平台上开发的"天正建筑CAD"."天正结构CAD"等.还有一些计算机辅助设计软件如" ...

  5. Matplotlib数据可视化实操--基础知识、使用PyLab模块和Pyplot模块基础绘图方式

    文章目录 一.Matplotlib数据可视化是什么? 二.Matplotlib.pyplot接口汇总 1.绘图类型 2.Image函数 3.Axis函数 4.Figure函数 三.使用Pyplot模块 ...

  6. 为子控件添加自定义绘图方式

    在MFC应用程序中,有时会遇到需要让指定的控件实现自绘.但是看该控件的事件,没有一个像是能承担这种责任的. 我们都知道控件也是窗口,也都有消息循环.所以: 方案一:写个新类,继承自某个窗口类,在它的W ...

  7. %matplotlib和%pylab的绘图的方式及使用

    版权声明:转载请注明作者(独孤尚良dugushangliang)出处:https://blog.csdn.net/dugushangliang/article/details/120053533 %m ...

  8. Python Qt GUI设计:QPainter、QPen、QBrush和QPixmap窗口绘图类(基础篇—17)

    目录 1.QPainter绘图类 2.QPen绘图类 3.QBrush绘图类 4.QPixmap绘图类 本篇博文主要介绍如何实现在窗口中绘图,在 PyQt5中,一般可以通过QPainter.QPen. ...

  9. python入门指南bl-Python Matplotlib 绘图使用指南 (附代码)

    雷锋网(公众号:雷锋网)按:本文为雷锋字幕组编译的技术博客,原标题 Matplotlib Plotting Guide, 作者为 Prince Grover. 翻译 | 李振 于志鹏 整理 | 凡江 ...

  10. python画图程序-编程入门06:Python海龟绘图

    现在让我们开始尝试"图形用户界面"(GUI)程序的编写--Python标准库中有个turtle模块,可以生成标准的应用程序窗口进行图形绘制.turtle的绘图方式非常简单直观--想 ...

最新文章

  1. Prototype Pattern
  2. reddit高赞资源:20h系统性深度学习强化学习课程,视频、PPT、代码全都有 | 免费...
  3. 【学习笔记】【oc】类和对象及类的三大基本特征
  4. 跨平台 C/C++ memcached 客户端 memcacheclient 介绍
  5. mysql time_limit_mysql ---- limit使用方式
  6. matlab 测量矩阵,急求一个测量矩阵采用分块多项式矩阵时怎样引用的代码!!!
  7. 用碧海潮声制作的宋体(雅黑宋体)替换Windows7原生的火柴棍式的宋体
  8. aix磁盘挂载到linux,AIX下文件系统挂载点相互调换方案
  9. 数据产品-数据分析和可视化工具Excel基础使用
  10. Android开发基础(四大组件及Intent)
  11. leetcode 206 如何原地反转单链表?
  12. 毕业设计 基于java的贴吧论坛_java毕业设计_springboot框架的论坛贴吧
  13. Jackson修改字段名和自定义命名策略
  14. 分享一款国产并口PSRAM存储芯片EMI164NA16LM
  15. 什么是AVIF?如何在你的网站上使用AV1格式图像
  16. 爬取双色球的中奖号码
  17. HDU 4147 KFC -Z+W
  18. arduino按钮控制led,按一次亮,再按灭
  19. 工作一年之后的记录与总结
  20. #博学谷it学习技术支持#黑马头条遇到问题及解决1

热门文章

  1. java计算机毕业设计高考填报信息系统源码+数据库+系统+lw文档+部署
  2. 12,jesd204b实战操作笔记
  3. html js实现搜索框提示功能,js实现智能提示搜索框
  4. 如何把空间数据从CGCS2000转换到WGS84和BD09 ——JAVA语言实现
  5. rxbus 源码_RxBus 这个 RxBus 稳如老狗 @codeKK Android开源站
  6. 如何设计微信公众号的封面图?教你设计自己的专属公众号封面
  7. 平面变压器的设计(翻译)(1)
  8. 新手小白如何从0到1学会电商运营,这3个方法带你月入五位数
  9. JAVA数据库增删改查
  10. CRC校验的计算和原理(包括对模2除法的说明)