天天看点

【OpenGL】绘制一个点

使用 glew

glew 全称是 OpengGL Extension Wrangler Library,它能够帮忙解决 OpengGL 不断扩展的问题。初始化 glew 之后,它将查询系统上所有可用的扩展功能并自动加载它们,然后提供一个头文件作为接口,我们直接通过头文件就可以使用这些扩展功能。

glew 的下载地址

http://glew.sourceforge.net/

下载之后进行解压,得到的目录结构

|-glew-2.1.0
    |-bin
    |-lib
    |-include
    |-doc
           

接下来开始配置环境,配置的方式有两种,第一种方式跟配置 glut 一样,系统级的配置,配置之后所有的 opengl 项目都不需要配置

  • 第一步,把 include 目录下的头文件放在 %VISUAL_STUDIO%\VC\include\gl 目录下
  • 第二步,把 lib 目录下的 lib 文件放在 %VISUAL_STUDIO%\VC\lib 目录下,然后在项目属性的 链接器 –> 输入 添加相应库的引用
  • 第三步,把 bin 目录下的 dll 文件放在 system32 目录下,同样的 64 位系统要放在 sysWOW64 目录下

这种方式虽然能够一劳永逸,但因为 win32 和 x64 的库文件名一样,所以无法做到兼容,即无法同时把 32 位的库文件和 64 位的库拷到系统路径;另外过多地把库文件拷到系统路径也不好,因此推荐使用第二种方式,即给每个创建的项目配置环境

  • 把头文件放在 $PROJECT_ROOT%\include\GL 目录下,然后在项目属性的 C/C++ –> 附加包含目录 中添加 .\include
  • 把静态库 lib 文件放在 $PROJECT_ROOT%\lib 目录下,然后在项目属性的 链接器 –> 常规 –> 附加库目录 中添加 .\lib,在 链接器 –> 输入 中添加相应库的引用
  • 把动态库 dll 文件放在可执行程序 exe 同级目录下

如果一个项目想同时编译 32 位和 64 位,则可以分别把 32 位的 lib 文件和 64 位的 lib 文件放在 .\lib\win32 和 .\lib\x64 目录下,然后分别修改附加库目录,再把相应的 dll 文件拷到编译后的 32 位程序和 64 位程序目录下。

GL context

在开始绘制图形之前,我们必须先了解 GL context 和 GL objects 这两个重要概念,参考文档 https://www.khronos.org/opengl/wiki/Main_Page

上一篇文章讲到 OpenGL 渲染是基于状态(state)的。OpenGL context 是一个重要的概念的,只有创建了 context,OpenGL 才存在,context 一旦被销毁了,OpenGL 就不存在了。context 存储了一个 OpenGL 实例的所有状态,类似于一个程序开辟的所有内存空间。context 可以看作进程在操作系统中的一个执行过程,一个进程可以创建多个 context,每一个 context 代表一个可视面,就像一个应用程序的一个界面一样。

简单来讲,context 保存了一个 OpenGL 实例的所有状态,在使用 OpenGL 之前必须先创建一个 context。

int main(int argc, char **argv)
{
    char *GL_version=(char *)glGetString(GL_VERSION);
    char *GL_vendor=(char *)glGetString(GL_VENDOR);
    char *GL_renderer=(char *)glGetString(GL_RENDERER);
    return ;
}
           

这里是想获取一些系统信息,但得到的结果却全是空,这里因为此时还没有创建 context,OpenGL 相当于不存在。

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

    glutInitWindowSize(, );
    glutInitWindowPosition(, );
    glutCreateWindow("Create Dot");

    //获取 OpenGL 版本号
    char *GL_version = (char *)glGetString(GL_VERSION);
    //获取本机提供 GL 支持的公司
    char *GL_vendor = (char *)glGetString(GL_VENDOR);
    //获取渲染器的名称
    char *GL_renderer = (char *)glGetString(GL_RENDERER);
    //获取着色器的版本号
    char* GL_shader_version =  (char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
    //获取本机硬件支持的最大顶点属性数
    GLint max_vertex_attrib;
    glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_vertex_attrib);

    return ;
}
           

这样就可以正常获取信息了,因为

glutCreateWindow

创建一个窗口的同时就相当于已经创建了一个 context。

GL objects

再重申一遍,OpenGL 基于状态来渲染,可以说 OpenGL 被定义成“状态机”,所有的 API 都是修改状态、查询状态或者使用状态来渲染。GL Object 是一些状态的集合,这点看起来跟 GL Context 有点像,也可以这样类比,但要知道 object 和 context 的 state 是相互独立的,context 有一套状态,每个 object 也会有自己的一些状态。只有把 object 绑定到 context 上,它的状态都会映射到 context 上,绑定之后修改 context 的状态,object 也会受影响;相反基于 context 状态的函数也可以使用 object 的状态。

对象可以分成两大类,regular objects 和 container objects。

regular object 包括 Buffer Objects,Query Objects,Renderbuffer Objects,Sampler Objects,Texture Objects。

container objects 包括 Vertex Array Objects,Framebuffer Objects,Program Pipeline Objects,Transform Feedback Objects。

对象创建、销毁和绑定

使用 glGen* 函数给对象生成一个名字,就是创建对象了。

void glGen*(GLsizei n, GLuint *objects);
           

这个函数可以同时创建多个对象,只需要给定一个对象数组的指针;对象名是 Glsizei 类型,也就是一个 32 位无符号整型,这个整数并不是一个指针,只是对象的一个引用,用于标识这个对象。另外,整数 0 这个名字用于特殊对象,所以我们给对象起名时只能从 1 开始。

void glDelete*(GLsizei n, const GLuint *objects);
           

这个函数用于销毁对象,同样如果传进来的 object 是一个数组的话,可以批量销毁多个对象。关于对象销毁有几点要注意的

  • 如果对象已经绑定到 context ,则对象销毁后会自动解绑;但如果对象附加到另一个对象上,则这我种附加关系不会解除
  • 对象被 delete 之后并不会立即删除,它的名字还可以使用,但请不要用
void glBind*(GLenum target, GLuint object);
           

这个函数用于把对象绑定到 context。target 指明了对象的类型,有些对象可以绑定为多个类型,比如一个 buffer obejct 可以绑定为 array buffer,index buffer,pixel buffer,transform buffer 或者其它。

VAO && VBO

我们知道了 OpenGL 是一个状态机,使用 object 来保存数据和状态,钴绑定到 context,将 object 的状态和 context 的状态关联起来,然后使用 context 的状态进行渲染。接下来就着手准备绘制一个点,这里需要用到两个对象 VBO 和 VAO。

VAO 即 Vertex Array Object,它保存了所有顶点数据的状态,并没有保存顶点数据,而是保存了顶点数据的格式和所需 buffer 对象的引用。每一个状态属性都是可以开启和关闭的,使用下面两个函数

void glEnableVertexAttribArray(GLuint index);
void glDisableVertexAttribArray(GLuint index);
           

VBO,即 Vertex Buffer Object,在缓存区保存的就是顶点数据了。为了让 VAO 能使用 VBO 的数据,我们需要告诉 OpenGL,编号为 index 属性使用当前绑定到 GL_ARRAY_BUFFER 的 VBO

index 是第几个属性,像顶点的第 0 个属性就是位置;size 指定构成属性的分量个数,像顶点位置由 x,y,z 三个分量组成,所以 size=3;type 指定属性值的类型,像顶点位置为 GL_FLOAT;normalized 指属性在管线中使用之前是否需要被规范化;stride 指两个相同属性值之间间隔的字节数,只有一个属性时间隔为 0;pointer 指存储数据的偏移值,同样只有一个属性时偏移值为 0。

绘制一个点

#include <gl/glew.h>
#include <gl/glut.h>
#include <math3d.h>
#include <iostream>

using namespace std;

void init();
void renderPerFrame();

int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);

    glutInitWindowSize(, );
    glutInitWindowPosition(, );
    glutCreateWindow("Create Dot");

    char *GL_version = (char *)glGetString(GL_VERSION);
    char *GL_vendor = (char *)glGetString(GL_VENDOR);
    char *GL_renderer = (char *)glGetString(GL_RENDERER);

    GLenum res = glewInit();
    if (GLEW_OK != res)
    {
        cout << "glewInit failed: " << glewGetErrorString(res) << endl;
        system("pause");
        return ;
    }

    init();

    glutDisplayFunc(renderPerFrame);
    glClearColor(, , , );

    glutMainLoop();
}

void init()
{
    //创建 buffer 对象
    GLuint VBO;
    glGenBuffers(, &VBO);

    //绑定 buffer 对象
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    //定义数据
    M3DVector3f vertices[];
    vertices[][] = ;
    vertices[][] = ;
    vertices[][] = ;
    //填充 buffer 的值
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
}

void renderPerFrame()
{
    glClear(GL_COLOR_BUFFER_BIT);

    //开启顶点属性
    glEnableVertexAttribArray();
    //指定属性使用的 buffer
    glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, , );
    //绘制顶点 
    glDrawArrays(GL_POINTS, , );
    //关闭顶点属性
    glDisableVertexAttribArray();

    glutSwapBuffers();
}
           

总结一下这个过程:

首先,创建顶点缓冲对象,即 VBO;然后将顶点缓冲对象绑定到 context;最后给顶点缓冲对象赋顶点数据。

经过上面三步之后,在 context 中就已经有了一个顶点缓冲对象,这个对象保存了顶点数据。接下来就是在主回调函数中取顶点数据进行绘制,这里使用的是固定管线,所以不用 VAO,后面主要使用可编程管线。

绘制的时候是基于状态的,要绘制顶点,必须知道每个状态属性从哪里取数据,即把顶点对象(VAO)和顶点缓冲对象(VBO)关联起来。首先,要开启顶点对象的某个属性;然后指定该属性使用的 buffer 对象;然后开始绘制;最后关闭顶点对象属性。

【OpenGL】绘制一个点

继续阅读