学习OpenGL系列二 图元

xiaoxiao2021-02-28  110

学习OpenGL 图元

1.GLEW

    GLEW(the OpenGL Extension Wrangler Library),它是一个跨平台的C++扩展库,基于OpenGL图形接口GLEW为我们提供了非常便捷的集成,只要包含一个glew.h头文件,就能使用gl,glu,glext,wgl,glx的全部函数。从本章开始,将会引入这个库,具体库文件已在第一章给出。

  值得注意的是,使用GLEW,需要将glew.h置于 freeglut.h 之前。

  同时对GLEW的初始化glewInit() 需要在glut初始化与建立窗口之后。

 

2.图元表达

 为了描述一个图元,我们需要使用一些有意义的数据来表达他。例如使用顶点信息或者使用像素信息。这些数据会被存储在OpenGL的缓存,即缓存对象(Buffer Object)当中。在OpenGL中,有着8种不同类型的缓存对象。不同表达形式,会存储在不同的缓存对象中。

 

3.创建缓存对象

 在OpenGL中创建一个缓存对象主要分三步:

 使用glGenBuffers()生成新缓存对象。

 使用glBindBuffer()绑定缓存对象。

 使用glBufferData()将数据拷贝到缓存对象中。

 

4.关于Gen Bind 的理解

 在OpenGL中经常会出现行如glGen*的函数,他们的作用是返回n个未使用的对象句柄(或叫对象名称),却不分配任何内存。而诸如glBind*的函数则是将该对象句柄绑定到相应的缓存上。一开始看着很奇怪,可以这样理解的,OpenGL使用了一个容器比如栈来统一管理其中的对象,因此需要先在栈中分配一个位置。而glBind* 则是对全局变量的一种赋值操作,这样OpenGL程序内部就可以通过调用固定的全局变量,来达到控制不同对象的效果。状态机也类同。

 

5.顶点缓存对象

 顶点缓存对象(VBO Vertex Buffer Object)是缓冲对象的一种,顾名思义,VBO用来存储顶点的,VBO是在GPU中加载顶点最有效的方式。在使用VBO时,缓冲区必须绑定到GL_ARRAY_BUFFER,下面给出一个例子:

 

#include <stdio.h> #include <iostream> #include <GL/glew.h> #include <GL/freeglut.h> using namespace std; static void DisplayCallback() { glClear(GL_COLOR_BUFFER_BIT); int index = 0; glEnableVertexAttribArray(index); //启用顶点数组 glVertexAttribPointer(index, 3, GL_FLOAT, GL_FALSE, 0, 0); //设置顶点属性访问数据量 glDrawArrays(GL_POINTS, 0, 1); //绘制图元序列 (点) glDisableVertexAttribArray(index); //禁用顶点数组 glFlush(); } static void Init() { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //缓存对象三部曲 GLuint VBO; //对象句柄 GLfloat Vertices[] = {0.0f, 0.0f ,0.0f}; //顶点数据 glGenBuffers(1, &VBO); //分配句柄 glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定缓存 glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), //添加数据 Vertices, GL_STATIC_DRAW); } static void Display() { glutDisplayFunc(DisplayCallback); } int main(int argc, char** argv) { //1.GLUT初始化 glutInit(&argc, argv); //2.创建窗口 glutInitDisplayMode(GLUT_RGBA); glutInitWindowSize(1024, 768); glutInitWindowPosition(100, 100); glutCreateWindow("Hello Primitive"); //3.GLEW初始化 注意这里的位置 GLenum res = glewInit(); if (res != GLEW_OK) { fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res)); system("pause"); return 1; } //4.数据初始化 Init(); //5.注册绘制回调函数 Display(); //6.进入主循环 glutMainLoop(); return 0; }

6.1代码函数分析

1.glewInit

  函数原型:

    GLenum glewInit();

 在使用glew之前需要使用该函数对其初始化,其使用位置要在GLUT初始化和创建窗口之后。

 返回值为错误代码,GLEW_OK为成功初始化,可以使用glewGetErrorString获取相应的错误信息。

 

2.glGenBuffers

 函数原型:

    void glGenBuffers(GLsizei n,GLuint* buffers);

 返回n个当前未使用的缓存对象名称,并保存到buffers数组中。返回到buffers中的名称不一定是连续的。

 

3.glBindBuffer

 函数原型:

    void glBindBuffer(GLenum target,GLuint buffer);

 将缓存对象绑定到相应的缓存上,target 可以是很多种缓存,目标名GL_ARRAY_BUFFER 是buffer将存储一个顶点数组。其他类型下文有所介绍。

 

4.glBufferData

 函数原型:

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

 绑定了我们的对象后,可以使用该函数在OpenGL服务端内存中分配size个存储单元,用于向其中存储数据或索引。

    Note:如果当前绑定的对象已经存在了关联的数据,那么首先会删除这些数据。

 参数 size 为待传递数据大小。如果所需size大小超过了能够分配的额度,那么glBufferData()将会产生一个GL_OUT_OF_MEMORY的错误。

 参数 data 为待传递数据的指针。如果data为NULL,则仅仅预留给定数据大小的内存空间。

 参数 target常用设置: (from 红宝书)

   顶点属性数据:GL_ARRAY_BUFFER

   索引属性:GL_ELEMENT_ARRAY_BUFFER

   像素数据:GL_PIXEL_UNPACK_BUFFER

   从OpenGL获取的像素数据:GL_PIXEL_PACK_BUFFER

   缓存间复制数据:GL_COPY_READ_BUFFER、GL_COPY_WIRTE_BUFFER

   纹理缓存存储数据:GL_TEXTURE_BUFFER

        TransForm FeedBack着色器获得的结果:GL_TRANSFORM_FEEDBACK_BUFFER

   一致变量:GL_UNIFORM_BUFFER

 还有更多,但红宝书中提到的这些会在后文中慢慢出现。

 参数usage用于设置分配数据之后的读取和写入方式。方式包括:

    GL_STATIC_DRAW,GL_STATIC_READ,GL_STATIC_COPY

    GL_DYNAMIC_DRAW,GL_DYNAMIC_READ,GL_DYNAMIC_COPY

    GL_STREAM_DRAW,GL_STREAM_READ,GL_STREAM_COPY

 其中,static 表示缓冲对象的数据将不会被改动,dynamic 表示数据将会被频繁改动,stream 表示每帧数据都要改变。draw read copy 则代表行为,绘制,读取,复制。

 

5.glEnableVertexAttribArray

 函数原型:

    void glEnableVertexAttribArray(GLuint index);

 该函数会启用一个与index相关联的顶点属性数组。在后续顶点着色器的使用中,可以使用这个函数来传递顶点属性。在这里启用后,在后边会被一些顶点数组命令访问,例如glDrawArrays()。当再次调用不同index的该函数时,后者会覆盖前者。

 

6.glDisableVertexAttribArray

 函数原型:

    void glDisableVertexAttribArray(GLuint index);

 用于取消对index顶点属性数组的启用。默认为全部禁用。

 

7.glVertexAttribPointer

 函数原型:

    void glVertexAttribPointer(GLuint index,GLint size,GLenum type,GLboolean normalized,GLsizei stride,const GLvoid* pointer);

 用于告诉管线怎样解析buffer中的数据。在这里,我们会解析GL_ARRAY_BUFFER中的数据。

 参数index 定义了顶点属性的索引,我们会对该顶点属性进行修改。

 参数size 为属性中的元素个数,比如现在的3为X,Y,Z 如果是RGBA就为4

 参数type 为每个元素的数据类型。

 参数normalized 是否归一化。在unity中normalize会取模为1,这里应该相同。

 参数stride 表示数组中两个连续元素之间的间隔字节数。如果为0,那么内存中各个数据就是紧密结合的。

 参数 pointer 为内存偏移值。

8.glDrawArrays

 函数原型:

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

 该函数用于绘制几何图形。该函数为最简单的draw call ,cpu在这才真正进行工作,将整合收集到的参数和状态数据绘制到屏幕上。在这里使用顺序绘制,就是按照顺序一个个处理顶点,较为简单。还有一种叫做索引绘制,主要用于共用顶点绘图。

 参数 mode 为渲染图元的类型,比如点GL_POINTS ,三角 GL_TRAINGLES

 参数 first 指定启用数组中的起始索引。

 参数 count 指定要渲染的索引数。

 

6.2 绘制三角形

 

#include <stdio.h> #include <iostream> #include <GL/glew.h> #include <GL/freeglut.h> using namespace std; static void DisplayCallback() { glClear(GL_COLOR_BUFFER_BIT); int index = 0; glEnableVertexAttribArray(index); //启用顶点数组 glVertexAttribPointer(index, 3, GL_FLOAT, GL_FALSE, 0, 0); //设置顶点属性访问数据量 glDrawArrays(GL_TRIANGLES, 0, 3); //绘制图元序列 (三角形) glDisableVertexAttribArray(index); //禁用顶点数组 glFlush(); } static void Init() { glClearColor(0.0f, 0.0f, 0.0f, 0.0f); //缓存对象三部曲 GLuint VBO; //对象句柄 GLfloat Vertices[3][3] = { //顶点数据 { -1.0f, -1.0f, 0.0f }, { 1.0f, -1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } }; glGenBuffers(1, &VBO); //分配句柄 glBindBuffer(GL_ARRAY_BUFFER, VBO); //绑定缓存 glBufferData(GL_ARRAY_BUFFER, sizeof(Vertices), //分配空间 NULL, GL_STATIC_DRAW); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Vertices[0]), Vertices[0]); //填充数据 0-2 glBufferSubData(GL_ARRAY_BUFFER, sizeof(Vertices[0]), sizeof(Vertices[0]), Vertices[1]); //填充数据 3-5 glBufferSubData(GL_ARRAY_BUFFER, sizeof(Vertices[0])*2, sizeof(Vertices[0]), Vertices[2]); //填充数据 6-8 } static void Display() { glutDisplayFunc(DisplayCallback); } int main(int argc, char** argv) { //1.GLUT初始化 glutInit(&argc, argv); //2.创建窗口 glutInitDisplayMode(GLUT_RGBA); glutInitWindowSize(1024, 768); glutInitWindowPosition(100, 100); glutCreateWindow("Hello Triangle"); //3.GLEW初始化 注意这里的位置 GLenum res = glewInit(); if (res != GLEW_OK) { fprintf(stderr, "Error: '%s'\n", glewGetErrorString(res)); system("pause"); return 1; } //4.数据初始化 Init(); //5.注册绘制回调函数 Display(); //6.进入主循环 glutMainLoop(); return 0; }

在这里使用了glBufferData()数据为空,同时glBufferSubData()来进行添加数据。

 

参考文献

OpenGL编程指南》

http://www.songho.ca/opengl/gl_vbo.html

http://ogldev.atspace.co.uk/index.html

http://blog.csdn.net/column/details/13062.html 中文

http://www.cnblogs.com/hefee/p/3824300.html 中文

 

 Dore写于南京。

转载请注明原文地址: https://www.6miu.com/read-73338.html

最新回复(0)