OpenGL学习笔记(一):状态管理与绘制

来源:互联网 发布:2djgame 邀请码 淘宝 编辑:程序博客网 时间:2024/06/06 03:06

作者:yurunsun@gmail.com 新浪微博@孙雨润 新浪博客 CSDN博客

日期: 2013-6-11

本章内容:

  1. 用任意一种颜色清除窗口
  2. 在二维或三维空间绘制几何图元:点、直线、多边形
  3. 打开/关闭/查询状态
  4. 控制几何图元的显示:线、多边形
  5. 在实心物体表面适当位置指定法线向量
  6. 用顶点数组和缓冲区对象存储和访问几何数据
  7. 同时保存和恢复几个状态变量
  8. 显示列表

1. 绘图工具箱

  1. 清除窗口

    glClearColor(0.0, 0.0, 0.0, 0.0);glClearDepth(1.0);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  2. 指定颜色

    glColor3f(0.0, 1.0, 0.0);

2. 绘制点、直线、多边形

  1. 指定顶点

    glVertex3f(1.0, 1.0, 1.0);
  2. 几何图元

    把一组顶点放在一对glBegin()和glEnd()之间用来绘制几何图元:

    glBegin(GL_POLYGON)    glVertex3f(-1.0, -1.0, 0.0);    glVertex3f(1.0, -1.0, 0.0);    glVertex3f(1.0, 1.0, 0.0);    glVertex3f(-1.0, 1.0, 0.0);glEnd();

    GL_POLYGON以外的图元名称参见手册。

3. 打开/关闭/查询状态

    glEnable(GL_LIGHTING);    glDisable(GL_FOG);    glIsEnabled(GL_DEPTH_TEST);    glGetBooleanv(GL_BLEND, bBlend);    glGetIntegerv(GL_CURRENT_COLOR, colorArr);

4. 控制几何图元的显示

  1. 点的大小

    glPointSize(2.0);
  2. 直线的宽度、点画线

    glLineWidth(3.0);glLineStipple(1, 0x3F07);glEnable(GL_LINE_STIPPLE);
  3. 多边形的点、线、填充模式,正面与丢弃

    glPolygonMode(GL_FRONT, GL_FILL);glPolygonMode(GL_BACK, GL_LINE);glFrontFace(GL_CCW);    //逆时针顶点方向为正面glCullFace(GL_BACK);    //转换到屏幕坐标前丢弃背面glEnable(GL_CULL_FACE);

5. 法线

  1. 概念

    • 法线是一条垂直于某表面的方向向量
    • 平表面上每个点的垂直方向都相同,曲面上每个点的法线可能不同
    • OpenGL中既可以为每个多边形指定一条法线,也可以为多边形的每个顶点分别指定一条法线
    • 只能在顶点处分配法线
  2. 实现

    glBegin (GL_POLYGON);    glNormal3fv(n0); glVertex3fv(v0);    glNormal3fv(n1); glVertex3fv(v1);    glNormal3fv(n3); glVertex3fv(v3);glEnd();

    开启自动对法线归一化的功能:

    glEnable(GL_NORMALIZE);

6. 顶点数组(VA)

采用之前办法绘制20条边的多边形需要22个函数调用:指定20次顶点、一对glBegin/glEnd.绘制立方体需要为每个顶点重复指定3次。通过使用OpenGL的顶点数组,只需要一次调用。步骤如下:

  • 启用数组(同时最多8个)
  • 放入数据
  • 绘制图形:随机/系统/线性 访问

6.1. 启用数组

glEnableClientState(GL_NORMAL_ARRAY);glEnableClientState(GL_VERTEX_ARRAY);

例如关掉光照后禁用改变法线状态的值,则调用

glDisableClientState(GL_NORMAL_ARRAY);之所以使用新的函数名而不是`glEnable`,是因为顶点数组存储在client端,不能放入显示列表中。而`glEnable`需要能够放入server端显示列表

6.2. 放入数据

glColorPointer(3, GL_FLOAT, 0, colorArr);glVertexPointer(3, GL_FLOAT, 0, vertexArr);

6.3. 解引用并绘制图形

在解引用前数据一直保存在client端,内容很容易被修改。在此步骤中数组数据被发送到server端进行绘制。

glArrayElement(GLint ith);

表示获取当前所有已开启数组的第ith顶点的数据。分别调用:

glEdgeFlag3fv();glTexCoord3fv();glColor3fv();glSecondaryColor3fv();glIndexfv();glNormal3fv();glFogCoordfv();glVertex3fv();  

例如:

glEnableClientState(GL_NORMAL_ARRAY);glEnableClientState(GL_VERTEX_ARRAY);glColorPointer(3, GL_FLOAT, 0, colorArr);glVertexPointer(3, GL_FLOAT, 0, vertexArr);glBegin(GL_TRIANGLES);    glArrayElement(2);    glArrayELement(3);    glArrayELement(5);glEnd();

与下面效果相同:

glBegin(GL_TRIANGLES);    glColor3fv(colorArr + 2*3);    glVertex3fv(vertexArr + 2*3);    glColor3fv(colorArr + 2*3);    glVertex3fv(vertexArr + 2*3);       glColor3fv(colorArr + 2*5);    glVertex3fv(vertexArr + 2*5);glEnd();

6.4 解引用数组元素的一个list

glArrayElement()类似的函数还有:

glDrawElements();glMultiDrawElements();glDrawRangeElements();
  • glDrawElements()

    void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices);

    相当于

    glBegin(mode);for (int i = 0; i < count; ++i) {    glArrayElement(indices[i]);}

    注意:不要把glDrawElements()放在glBegin()/glEnd()之间

  • glMultiDrawElements()

    void glMultiDrawElements(GLenum mode, GLsizei* count, GLenum type, const GLvoid** indices, GLsizei primcount);

    相当于

    for (int i = 0; i < premcount; ++i) {    if (count[i] > 0) {        glDrawElements(mode, count[i], type, indices[i]);    }}    
  • glDrawRangElements()

    glDrawElements()基础上加入start/end`的限制,其外的顶点会被丢弃。

6.5 解引用数组元素的一个sequence

上述函数均能随机存储,glDrawArrays()只能按顺序访问:

void glDrawArrays(GLenum mode, GLint first, GLsizei count);

相当于:

glBegin (mode);    for (int i = 0; i < count; ++i) {        glArrayElement(first + i);    }glEnd();而`glMultiDrawArrays()`则提供类似的组合调用功能。

下面两章VBO VAO的内容不被opengl 1.1支持 仅供了解

7. 缓存对象(BO)

我们向OpenGL发送大量顶点、向量等数据,这种传输可能是简单的从内存copy到显卡;但OpenGL是按照C/S模式设计的,在OpenGL需要数据的任何时候都必须把数据从client端内存send到server端显卡。如果分布式渲染,数据传输效率很低。因此引入buffer object, 允许程序显示指定哪些数据缓存在server端。

  • v1.5支持顶点数据缓存
  • v2.1支持像素数据缓存
  • v3.1支持统一缓存对象(uniform buffer object),存储成块的用于着色器的统一变量数据

7.1 创建缓存对象

void glGenBuffers(GLsizei n, GLuint* buffers);

在buffer数组中返回n个当前未使用过的名称,表示缓存对象。

7.2 激活缓冲区对象

void glBindBuffer(GLenum target, GLuint buffer);

为激活缓存对象首先需要将其绑定,表示选择未来的操作将影响哪个缓存对象,可选值有:

GL_ARRAY_BUFFERGL_ELEMENT_ARRAY_BUFFERGL_PIXEL_PACK_BUFFERGL_PIXEL_UNPACK_BUFFERGL_COPY_READ_BUFFERGL_COPY_WRITE_BUFFERGL_TRANSFORM_FEEDBACK_BUFFERGL_UNIFORM_BUFFER

7.3 赋值/初始化缓存对象

void glBufferData(GLenum target, GLsizeiptre size, const GLvoid* data, GLenum usage);

分配size各字节的server端内存用于存储顶点数据或索引。如果data是NULL则纯粹分配内存,如果是指向client端内存的指针则会讲数据copy到server端。

7.4 更新缓存对象

两种更新方法:

  1. 直接替换内存

    void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid *data);
  2. 获取内存指针,灵活修改

    GLvoid *glMapBuffer(GLenum target, GLenum access);GLboolean glUnmapBuffer(GLenum target);

7.5 缓存对象之间copy data

V3.1之前分两步:

  1. Data从缓存对象copy到client端内存
  2. 绑定到新的缓存对象

V3.1使用新函数:

void glCopyBufferSubData(GLenum readbuffer, GLenum writebuffer, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);

7.6 清除缓存对象

void glDeleteBuffers(GLsizei n, const GLuint *buffers);

7.7 使用缓存对象存储顶点数组

步骤如下:

  1. 生成缓存对象id
  2. 绑定缓存对象,确定用于存储顶点数据还是索引
  3. 请求数据内存
  4. 指定相对于缓存起始位置的偏移
  5. 绑定缓存对象用于渲染
  6. 使用顶点数组渲染函数

完整示例如下:

#define VERTICES 0#define INDICES 1#define NUM_BUFFERS 2GLuint buffers[NUM_BUFFERS];GLfloat vertices[][3] = {...}GLubyte indices[][4] = {...};glGenBuffers(NUM_BUFFERS, buffers);                     // step 1glBindBuffer(GL_ARRAY_BUFFER, buffers[VERTICES]);       // step 2glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // step 3glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));      // step 4glEnableClientState(GL_VERTEX_ARRAY);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[INDICES]);// step 2glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// step3glDrawElements(GL_QUADS, 24, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); // step6

8. 顶点数组对象(VAO)

大数据量可能要求在每个帧的多组顶点数组间切换,glVertexPointer()这样的函数调用次数会随之增大。顶点数组对象则绑定了调用的集合,用来设置顶点数组的状态。

void glGenVertexArrays(GLsizei n, GLuint *arrays);void gBindVertexArray(GLuint array);void glDeleteVertexArrays(GLsizei n, GLuint *arrays);

9. 显示列表

opengl采用CS模式,每次绘制需要将顶点等数据从client发送到server。使用显示列表将绘图命令保存在server端,同时还能避免重复计算。

但是显示列表中的值不能在以后进行修改,需要删除显示列表并创建一个新的列表。

显示列表在以下领域中优化效果最明显:

  1. 矩阵操作
  2. 对位图和图像的光栅化
  3. 光源、材料属性和光照模型

9.1 命名、创建、删除

GLuint glGenLists(GLsizei range);void glNewList(GLuint list, GLenum mode);void glEndList(void);GLboolean glIsList(GLuint list);void glDeleteLists(GLuint list, GLsizei range);  GL_COMPILE;                 // 不立即执行GL_COMPILE_AND_EXECUTE      // 立即执行

设置client端的函数以及用于提取状态值的函数无法存储在显示列表中。

9.2 执行

  1. 一般形式

    void glCallList(GLuint list);
  2. 层次式显示列表

    glNewList(listIndex,GL_COMPILE);    glCallList(handlebars);    glCallList(frame);    glTranslatef(1.0, 0.0, 0.0);    glCallList(wheel);    glTranslatef(3.0, 0.0, 0.0);    glCallList(wheel);glEndList();
  3. 执行多个

    void glListBase(GLuint base);   

    这个函数指定一个偏移量,将与glCallLists()函数中显示列表索引相加,以获得最终的显示列表索引。默认为0.此函数对于glCallList()/glNewList()函数没有效果

    void glCallLists(GLsizein, GLenum type, const GLvoid *lists);

9.3 用显示列表管理状态变量

如果将渲染命令中包含状态改变的命令,这些状态会在执行时被修改并继续保持,但有时候我们希望执行显示列表之后恢复原来的状态。可以使用glPushAttrib()函数保存一组状态变量,并用glPopAttrib()函数在需要时恢复这些值。

OpenGL将相关的状态变量进行归组,称为属性组,例如GL_LINE_BIT属性包括宽度、点画、抗锯齿等共5个状态,使用glPushAttrib()/glPopAttrib()可以同时保存和恢复全部这5个状态。


  • 如果这篇文章对您有帮助,请到CSDN博客留言;
  • 转载请注明:来自雨润的技术博客 http://blog.csdn.net/sunyurun
原创粉丝点击