【OpenGL】绘制一个点

  • Post author:
  • Post category:其他


使用 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 0;
}

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

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

    glutInitWindowSize(400, 400);
    glutInitWindowPosition(100, 100);
    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 0;
}

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

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

glVertexAttribPointer(index, size, type, normalized, stride, pointer);

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(400, 400);
    glutInitWindowPosition(100, 100);
    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 1;
    }

    init();

    glutDisplayFunc(renderPerFrame);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);

    glutMainLoop();
}

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

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

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

void renderPerFrame()
{
    glClear(GL_COLOR_BUFFER_BIT);

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

    glutSwapBuffers();
}

总结一下这个过程:

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

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

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

绘制一个点



版权声明:本文为xingxinmanong原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。