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

作者: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_BUFFER
GL_ELEMENT_ARRAY_BUFFER
GL_PIXEL_PACK_BUFFER
GL_PIXEL_UNPACK_BUFFER
GL_COPY_READ_BUFFER
GL_COPY_WRITE_BUFFER
GL_TRANSFORM_FEEDBACK_BUFFER
GL_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 2

GLuint buffers[NUM_BUFFERS];
GLfloat vertices[][3] = {...}
GLubyte indices[][4] = {...};

glGenBuffers(NUM_BUFFERS, buffers);                     // step 1
glBindBuffer(GL_ARRAY_BUFFER, buffers[VERTICES]);       // step 2
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // step 3
glVertexPointer(3, GL_FLOAT, 0, BUFFER_OFFSET(0));      // step 4
glEnableClientState(GL_VERTEX_ARRAY);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[INDICES]);// step 2
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// step3
glDrawElements(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个状态。


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值