大部分OpenGL教程都会在一开始就讲解VAO,但是该教程的作者认为这是很不合理的,因为要理解它的作用需要建立在我们此前学过的知识基础上。因此直到教程已经进行了一大半,作者才引入VAO这个概念。在我看来这也是非常合理和自然的。

先预览一下最终的代码逻辑:

准备工作

为了讲解后面的内容,我们对代码进行了更改(算是回退吧,改回到以前不使用Instancing的版本):

  • 去掉了sendDataToOpenGL()函数中关于实例化的部分代码
  • 把VertexShader中的MVP矩阵改回Uniform
  • 在paintGL()函数中直接提供MVP矩阵的信息,并改回使用glDrawElements函数绘制

更改以后的VetexShader代码:

 #version                            

 in layout(location=) vec3 position;
in layout(location=) vec3 color; uniform mat4 fullTransformMatrix;
out vec3 passingColor; void main()
{
gl_Position = fullTransformMatrix * vec4(position,);
passingColor= color;
}

MyGlWindow.cpp文件的更改请参考在应用实例化绘制之前的代码,这里就不再重复了。

另外我们在ShapeGenerator中添加了一种新的图形,四面体。

代码如下:

 ShapeData ShapeGenerator::makeTetrahedron()
{
ShapeData ret;
Vertex stackVerts[] =
{
glm::vec3(-0.289f, -0.408f, -0.500f), //
glm::vec3(+1.0f, 0.0f, 0.0f), //Color
glm::vec3(-0.289f, -0.408f, 0.500f), //
glm::vec3(0.0f, +1.0f, 0.0f), //Color
glm::vec3(0.577f, -0.408f, 0.000f), //
glm::vec3(0.0f, 0.0f, +1.0f), //Color
glm::vec3(0.000f, 0.408f, 0.000f), //
glm::vec3(+0.0f, +1.0f, +1.0f), //Color
}; ret.numVertices = NUM_ARRAY_ELEMENTS(stackVerts);
ret.vertices = new Vertex[ret.numVertices];
memcpy(ret.vertices, stackVerts, sizeof(stackVerts)); unsigned short stackIndices[] =
{
,,,
,,,
,,,
,,,
}; ret.numIndices = NUM_ARRAY_ELEMENTS(stackIndices);
ret.indices = new GLushort[ret.numIndices];
memcpy(ret.indices, stackIndices, sizeof(stackIndices));
return ret;
}

问题的提出

需求:我们需要同时绘制两个立方体,以及两个四面体

目前的解决方法

我们利用已有的知识可以这样去完成:

  1. 在sendDataToOpenGL()中生成立方体形状
  2. 在sendDataToOpenGL()中创建,绑定,设置立方体的VertexBuffer
  3. 在sendDataToOpenGL()中开启通道0,1
  4. 在sendDataToOpenGL()中设置通道0,1如何获取数据(glVertexAttribPointer)
  5. 在sendDataToOpenGL()中创建,绑定,设置立方体的IndexBuffer
  6. 在sendDataToOpenGL()中生成四面体形状
  7. 在sendDataToOpenGL()中创建,绑定,设置四面体的VertexBuffer
  8. 在sendDataToOpenGL()中开启通道0,1
  9. 在sendDataToOpenGL()中设置通道0,1如何获取数据(glVertexAttribPointer)
  10. 在sendDataToOpenGL()中创建,绑定,设置四面体的IndexBuffer
  11. 在paintGL()中再次绑定立方体的VertexBuffer
  12. 在paintGL()中再次设置0,1如何获取数据(glVertexAttribPointer)
  13. 在paintGL()中再次绑定立方体的IndexBuffer
  14. 在paintGL()中绘制立方体
  15. 在paintGL()中再次绑定四面体的VertexBuffer
  16. 在paintGL()中再次设置0,1如何获取数据(glVertexAttribPointer)
  17. 在paintGL()中再次绑定四面体的IndexBuffer
  18. 在paintGL()中绘制四面体

我们可以看到,在每次绘制中都要进行一起“切换”:

  • 先切换到立方体的状态,绑定VertexBuffer,设置数据格式,绑定IndexBuffer,绘制立方体
  • 再切换到四面体的状态,绑定VertexBuffer,设置数据格式,绑定IndexBuffer,绘制四面体

这个过程是必须的,如果不进行“切换”,会绘制错误的数据。

引入VAO的解决方案

VAO是顶点数组对象的简称,可以理解为一种“容器”,包含了绘制某种图形所需要的所有状态。

我们先给MyGlWindow类增加一个成员函数setupVertexArrays(),在initializeGL()函数中的sendDataToOpenGL()函数后面调用它。

 void MyGlWindow::initializeGL()
{
glewInit();
glEnable(GL_DEPTH_TEST);
sendDataToOpenGL();
setupVertexArrays();
installShaders();
}

在setupVertexArrays()函数中,我们分别为立方体和四面体创建了两个VAO,并分别把相关的设置都在VAO后准备好。

然后在paintGL()绘制时,只需要先绑定立方体的VAO,然后进行绘制,就会绘制立方体,再绑定四面体的VAO,然后进行绘制,就会绘制四面体。

看看完整代码:

MyGlWindow.h:

 #pragma once
#include <QtOpenGL\qgl.h>
#include <string>
#include "Camera.h" class MyGlWindow :public QGLWidget
{
protected:
void sendDataToOpenGL();
void installShaders();
void initializeGL();
void paintGL();
GLuint transformMatrixBufferID;
Camera camera;
std::string ReadShaderCode(const char* fileName);
void mouseMoveEvent(QMouseEvent*);
void keyPressEvent(QKeyEvent*);
void setupVertexArrays();
};

MyGlWindow.cpp:

 #include <gl\glew.h>
#include "MyGlWindow.h"
#include <iostream>
#include <fstream>
#include <glm\gtc\matrix_transform.hpp>
#include <glm\gtx\transform.hpp>
#include <ShapeGenerator.h>
#include <Qt3DInput\qmouseevent.h>
#include <Qt3DInput\qkeyevent.h> GLuint programID; //立方体的索引数组长度
GLuint cubeNumIndices;
//立方体的VAO ID
GLuint cubeVertexArrayObjectID;
//立方体的VertexBufferID
GLuint cubeVertexBufferID;
//立方体的IndexBuffer的ID
GLuint cubeIndexBufferID; //四面体的索引数组长度
GLuint tetraNumIndices;
//四面体的VAO ID
GLuint tetraVertexArrayObjectID;
//四面体的BufferID
GLuint tetraVertexBufferID;
//四面体的IndexBufferID
GLuint tetraIndexBufferID; GLuint fullTransformUniformLocation; void MyGlWindow::sendDataToOpenGL()
{
//创建Cube
ShapeData cube = ShapeGenerator::makeCube(); //创建和设置VertexBuffer
glGenBuffers(, &cubeVertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, cubeVertexBufferID);
glBufferData(GL_ARRAY_BUFFER, cube.vertexBufferSize(), cube.vertices, GL_STATIC_DRAW); //创建和设置IndexBuffer
glGenBuffers(, &cubeIndexBufferID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIndexBufferID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, cube.indexBufferSize(), cube.indices, GL_STATIC_DRAW); cubeNumIndices = cube.numIndices;
cube.cleanUp(); //创建四面体
ShapeData tetra = ShapeGenerator::makeTetrahedron(); //创建和设置VertexBuffer
glGenBuffers(, &tetraVertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, tetraVertexBufferID);
glBufferData(GL_ARRAY_BUFFER, tetra.vertexBufferSize(), tetra.vertices, GL_STATIC_DRAW); //创建和设置IndexBuffer
glGenBuffers(, &tetraIndexBufferID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tetraIndexBufferID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, tetra.indexBufferSize(), tetra.indices, GL_STATIC_DRAW); tetraNumIndices = tetra.numIndices;
tetra.cleanUp(); } void MyGlWindow::setupVertexArrays()
{
//设置绘制Cube的VAO
//生成VAO
glGenVertexArrays(, &cubeVertexArrayObjectID);
//绑定VAO,后续的一系列状态和设置都会存储在这个VAO里。
glBindVertexArray(cubeVertexArrayObjectID); //开启通道1(位置)
glEnableVertexAttribArray();
//开启通道2(颜色)
glEnableVertexAttribArray(); //绑定顶点数据ID到绑定点
glBindBuffer(GL_ARRAY_BUFFER, cubeVertexBufferID);
//设置通道1如何获取数据
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(GLfloat) * , );
//设置通道2如何获取数据
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(GLfloat) * , (char*)(sizeof(float) * )); //绑定索引数据ID到绑定点
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIndexBufferID); //设置绘制四面体的VAO
glGenVertexArrays(, &tetraVertexArrayObjectID);
glBindVertexArray(tetraVertexArrayObjectID); //开启通道1(位置)
glEnableVertexAttribArray();
//开启通道2(颜色)
glEnableVertexAttribArray(); //绑定顶点数据ID到绑定点
glBindBuffer(GL_ARRAY_BUFFER, tetraVertexBufferID);
//设置通道1如何获取数据
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(GLfloat) * , );
//设置通道2如何获取数据
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(GLfloat) * , (char*)(sizeof(float) * )); //绑定索引数据ID到绑定点
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tetraIndexBufferID); } void MyGlWindow::installShaders()
{
GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); std::string tmp = ReadShaderCode("VertexShaderCode.glsl");
const char* vertexShaderCode = tmp.c_str();
glShaderSource(vertexShaderID, , &vertexShaderCode, ); tmp = ReadShaderCode("FragmentShaderCode.glsl");
const char* fragmentShaderCode = tmp.c_str();
glShaderSource(fragmentShaderID, , &fragmentShaderCode, ); glCompileShader(vertexShaderID);
glCompileShader(fragmentShaderID); programID = glCreateProgram();
glAttachShader(programID, vertexShaderID);
glAttachShader(programID, fragmentShaderID); glLinkProgram(programID); glUseProgram(programID);
} void MyGlWindow::initializeGL()
{
glewInit();
glEnable(GL_DEPTH_TEST);
sendDataToOpenGL();
setupVertexArrays();
installShaders();
} void MyGlWindow::paintGL()
{
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glViewport(, , width(), height()); //绑定cube的VAO,下面绘制的都是立方体--------------------------------------
glBindVertexArray(cubeVertexArrayObjectID); glm::mat4 fullTransformMatrix;
glm::mat4 viewToProjectionMatrix = glm::perspective(30.0f, ((float)width()) / height(), 0.1f, 10.0f);
glm::mat4 worldToViewMatrix = camera.getWorldToViewMatrix();
glm::mat4 worldToProjectionMatrix = viewToProjectionMatrix * worldToViewMatrix; //绘制Cube1
glm::mat4 cube1ModelToWorldMatrix =
glm::translate(glm::vec3(-1.0f, 0.0f, -3.0f))*
glm::rotate(36.0f, glm::vec3(1.0f, 0.0f, 0.0f)); fullTransformMatrix = worldToProjectionMatrix * cube1ModelToWorldMatrix;
glUniformMatrix4fv(fullTransformUniformLocation, , GL_FALSE, &fullTransformMatrix[][]);
glDrawElements(GL_TRIANGLES, cubeNumIndices, GL_UNSIGNED_SHORT, ); //绘制Cube2
glm::mat4 cube2ModelToWorldMatrix =
glm::translate(glm::vec3(1.0f, 0.0f, -3.75f))*
glm::rotate(36.0f, glm::vec3(0.0f, 1.0f, 0.0f));
fullTransformMatrix = worldToProjectionMatrix * cube2ModelToWorldMatrix;
glUniformMatrix4fv(fullTransformUniformLocation, , GL_FALSE, &fullTransformMatrix[][]);
glDrawElements(GL_TRIANGLES, cubeNumIndices, GL_UNSIGNED_SHORT, ); //绑定Tetra的VAO,下面绘制的都是四面体--------------------------------------
glBindVertexArray(tetraVertexArrayObjectID); //绘制Tetra1
glm::mat4 tetra1ModelToWorldMatrix =
glm::translate(glm::vec3(1.0f, -2.0f, -3.75f))*
glm::rotate(36.0f, glm::vec3(0.0f, 1.0f, 0.0f));
fullTransformMatrix = worldToProjectionMatrix * tetra1ModelToWorldMatrix;
glUniformMatrix4fv(fullTransformUniformLocation, , GL_FALSE, &fullTransformMatrix[][]);
glDrawElements(GL_TRIANGLES, tetraNumIndices, GL_UNSIGNED_SHORT, ); glm::mat4 tetra2ModelToWorldMatrix =
glm::translate(glm::vec3(-3.0f, -2.0f, -3.75f))*
glm::rotate(36.0f, glm::vec3(1.0f, 1.0f, 0.0f));
fullTransformMatrix = worldToProjectionMatrix * tetra2ModelToWorldMatrix;
glUniformMatrix4fv(fullTransformUniformLocation, , GL_FALSE, &fullTransformMatrix[][]);
glDrawElements(GL_TRIANGLES, tetraNumIndices, GL_UNSIGNED_SHORT, );
} std::string MyGlWindow::ReadShaderCode(const char* fileName)
{
std::ifstream myInput(fileName);
if (!myInput.good())
{
std::cout << "File failed to load..." << fileName;
exit();
}
return std::string(
std::istreambuf_iterator<char>(myInput),
std::istreambuf_iterator<char>());
} void MyGlWindow::mouseMoveEvent(QMouseEvent * e)
{
camera.mouseUpdate(glm::vec2(e->x(), e->y()));
repaint();
} void MyGlWindow::keyPressEvent(QKeyEvent * e)
{
switch (e->key())
{
case Qt::Key::Key_W:
camera.moveForward();
break;
case Qt::Key::Key_S:
camera.moveBackward();
break;
case Qt::Key::Key_A:
camera.strafeLeft();
break;
case Qt::Key::Key_D:
camera.strafeRight();
break;
case Qt::Key::Key_Q:
camera.moveUp();
break;
case Qt::Key::Key_E:
camera.moveDown();
break; default:
break;
}
repaint();
}

总结一下加入VAO以后的绘制流程

1. 初始化数据sendDataToOpenGL():

  • 创建/绑定/设置内容  立方体的VertexBuffer
  • 创建/绑定/设置内容  立方体的IndexBuffer
  • 为四面体重复上述步骤

2. 设置VAO, setupVertexArrays():

  • 创建/绑定立方体的VAO
  • 开启通道0,1
  • 绑定VertexBuffer
  • 设置通道0,1的获取数据格式
  • 绑定IndexBuffer
  • 为四面体重复上述步骤

3. 绘制,paintGL()

  • 绑定立方体的VAO
  • 绘制两个立方体
  • 绑定四面体的VAO
  • 绘制两个四面体

从代码中我们可以发现OpenGL中“绑定”这一操作的模式:“绑定"以后相当于进入了一种环境,之后我们可以对其进行设置,再次“绑定”相当于又重新进入了之前设置好的环境。这一种模式对VAO, VBO都是适用的。

最终效果:

代码下载: https://mrart.coding.net/p/3DGraphics/d/3DGraphics/git/releases, 找到VAO这个Release

3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(顶点数组对象)的更多相关文章

  1. 3D Computer Grapihcs Using OpenGL - 06 Vertex and Fragment Shaders

    从这里就接触到了可编程图形渲染管线. 下面介绍使用Vertex Shader (顶点着色器)和 Fragment Shader(像素着色器)的方法. 我们的目标是使用这两个着色器给三角形填充绿色. 添 ...

  2. 3D Computer Grapihcs Using OpenGL - 11 Model View Projection Matrices

    本节我们将绘制一个3维物体,立方体. 如果要渲染3D物体,我们需要了解MVP(Model View Projection),它表示三个转换矩阵.实际上这个名字不够明确,更加确切的释义如下: Model ...

  3. 3D Computer Grapihcs Using OpenGL - 07 Passing Data from Vertex to Fragment Shader

    上节的最后我们实现了两个绿色的三角形,而绿色是直接在Fragment Shader中指定的. 这节我们将为这两个三角形进行更加自由的着色——五个顶点各自使用不同的颜色. 要实现这个目的,我们分两步进行 ...

  4. 3D Computer Grapihcs Using OpenGL - 20 结合Buffer

    在上一节的案例中,我们使用了四个Buffer Object,立方体的VertexBuffer,立方体的索引Buffer,四面体的VertexBuffer,四面体的索引Buffer. 我们这节尝试把两个 ...

  5. 3D Computer Grapihcs Using OpenGL - 05 EBO

    本节将采用两种方法绘制两个三角形. 先看第一种方法的代码 MyGlWindow.cpp #include <gl\glew.h> #include "MyGlWindow.h&q ...

  6. 3D Computer Grapihcs Using OpenGL - 10 Color Buffer

    本节我们将尝试利用三角形制作一个“走马灯”效果. 一个三角形如图示方式,从左向右依次移动. 先看一下代码: MyGlWindow.cpp #include <gl\glew.h> #inc ...

  7. 3D Computer Grapihcs Using OpenGL - 09 Enable Depth Test

    启用Depth Test OpenGL是个3D绘图API,也就是说不只有xy坐标轴,还有第三个坐标轴z,z轴的方向是垂直于屏幕,指向屏幕内. 靠近人眼的方向是负方向,标准化设备坐标的最小值是-1, 最 ...

  8. 3D Computer Grapihcs Using OpenGL - 16 使用DrawElementsInstanced绘制立方体

    我们使用15节学到的知识来绘制14节的立方体. 在第14节我们使用了两次glDrawElements实现了OpenGL实例化,发现这样仍然不太方便,如果需要绘制成千上万的立方体,就需要手写成千上万次的 ...

  9. 3D Computer Grapihcs Using OpenGL - 14 OpenGL Instancing

    如果我们需要绘制两个(或者多个)一样的立方体(或者物体),只是位置.缩放.旋转不一样,那么我们可以不需要多次将这个物体的顶点信息.颜色信息等发送到显卡,而是发送一次,绘制多次,仅仅是每次绘制之前应用不 ...

随机推荐

  1. Python流程控制与while 循环(day01)

    一:流程控制 假如把写程序比做走路,那我们到现在为止,一直走的都是直路,还没遇到过分叉口,想象现实中,你遇到了分叉口,然后你决定往哪拐必然是有所动机的.你要判断哪条叉路是你真正要走的路,如果我们想让 ...

  2. 组合&多态&封装

    目录 组合&多态&封装 一.组合 1.1什么是组合 1.2 为什么要用组合 1.3 如何使用组合 1.4 继承和组合都在什么时候用 二.多态与多态性 2.1 什么是多态 2.2 如何用 ...

  3. 将数据库模型放入到.Net Core的类库中

    一.前提概要 今年某天突然无聊,就决定学习.net core,此时的版本已经是.net core 1.1了.之前一直是用.net framework做项目,一直对Html.EditFor()等Html ...

  4. hadoop最简伪分布式安装

    本次安装运行过程使用的是Ubuntu16.04 64位+Hadoop2.5.2+jdk1.7.0_75 Notice: Hadoop2.5.2版本默认只支持64位系统 使用的jdk可以为1.7和1.8 ...

  5. Linux Shell交互式自动化运维程序

    Expect是Linux操作系统下的一个用来处理交互操作,系统默认是没有安装expect库,所以执行expect会提示找不到命令,需要手动安装,其它安装也比较简单,可以通过二进制源码包编译配置进行安装 ...

  6. 安装wordpress的过程

    1 首先安装lamp.在安装php时,由于ubuntu16.04源中自带的是php7,所以需要直接安装 apt-get install phpapt-get install libapache2-mo ...

  7. 003-基于impi zabbix监控r720 测试过程

    1.F2进入服务器bios 修改network  使这台服务器能够被远程访问. 2.在远程的centos 7 服务器上安装  impitool工具包 #ipmitool -I lanplus -H X ...

  8. SpringBoot02——A Simple SpringBoot Project&Hot Deployment

    1.简单的Controller映射 1.新建一个controller包,包一定在启动器的下一层级 2.可以在application.properties中进行调整端口和context-path参数 s ...

  9. 在vue下引入jquery bootstrap

    在vue 项目中引入jquery bootstrap 引入jquery npm install jquery --save-dev 在项目根目录下的build/webpack.base.conf.js ...

  10. Python之常用模二(时间、序列号等等)

    一.time模块 表示时间的三种方式: 时间戳:数字(计算机能认识的) 时间字符串:t='2012-12-12' 结构化时间:time.struct_time(tm_year=2017, tm_mon ...