• 3D Computer Grapihcs Using OpenGL


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

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

    准备工作

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

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

    更改以后的VetexShader代码:

     1 #version 430                           
     2                                        
     3 in layout(location=0) vec3 position;   
     4 in layout(location=1) vec3 color;
     5 
     6 uniform mat4 fullTransformMatrix;
     7 out vec3 passingColor;
     8                                        
     9 void main()                            
    10 {  
    11   gl_Position = fullTransformMatrix * vec4(position,1);
    12   passingColor= color;           
    13 }

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

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

    代码如下:

     1 ShapeData ShapeGenerator::makeTetrahedron()
     2 {
     3     ShapeData ret;
     4     Vertex stackVerts[] =
     5     {
     6         glm::vec3(-0.289f, -0.408f, -0.500f), //0
     7         glm::vec3(+1.0f, 0.0f, 0.0f),   //Color
     8         glm::vec3(-0.289f, -0.408f, 0.500f), //1
     9         glm::vec3(0.0f, +1.0f, 0.0f),    //Color
    10         glm::vec3(0.577f, -0.408f, 0.000f), //2
    11         glm::vec3(0.0f, 0.0f, +1.0f), //Color
    12         glm::vec3(0.000f, 0.408f, 0.000f), //3
    13         glm::vec3(+0.0f, +1.0f, +1.0f), //Color        
    14     };
    15 
    16     ret.numVertices = NUM_ARRAY_ELEMENTS(stackVerts);
    17     ret.vertices = new Vertex[ret.numVertices];
    18     memcpy(ret.vertices, stackVerts, sizeof(stackVerts));
    19 
    20     unsigned short stackIndices[] =
    21     {
    22         0,1,2,
    23         0,1,3,
    24         1,2,3,
    25         2,0,3,
    26     };
    27 
    28     ret.numIndices = NUM_ARRAY_ELEMENTS(stackIndices);
    29     ret.indices = new GLushort[ret.numIndices];
    30     memcpy(ret.indices, stackIndices, sizeof(stackIndices));
    31     return ret;
    32 }

    问题的提出

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

    目前的解决方法

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

    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()函数后面调用它。

    1 void MyGlWindow::initializeGL()
    2 {
    3     glewInit();
    4     glEnable(GL_DEPTH_TEST);
    5     sendDataToOpenGL();
    6     setupVertexArrays();
    7     installShaders();
    8 }

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

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

    看看完整代码:

    MyGlWindow.h:

     1 #pragma once
     2 #include <QtOpenGLqgl.h>
     3 #include <string>
     4 #include "Camera.h"
     5 
     6 class MyGlWindow :public QGLWidget
     7 {
     8 protected:
     9     void sendDataToOpenGL();
    10     void installShaders();
    11     void initializeGL();
    12     void paintGL();
    13     GLuint transformMatrixBufferID;
    14     Camera camera;
    15     std::string ReadShaderCode(const char* fileName);
    16     void mouseMoveEvent(QMouseEvent*);
    17     void keyPressEvent(QKeyEvent*);
    18     void setupVertexArrays();
    19 };

    MyGlWindow.cpp:

      1 #include <glglew.h>
      2 #include "MyGlWindow.h"
      3 #include <iostream>
      4 #include <fstream>
      5 #include <glmgtcmatrix_transform.hpp>
      6 #include <glmgtx	ransform.hpp>
      7 #include <ShapeGenerator.h>
      8 #include <Qt3DInputqmouseevent.h>
      9 #include <Qt3DInputqkeyevent.h>
     10 
     11 
     12 GLuint programID;
     13 
     14 //立方体的索引数组长度
     15 GLuint cubeNumIndices;
     16 //立方体的VAO ID
     17 GLuint cubeVertexArrayObjectID;
     18 //立方体的VertexBufferID
     19 GLuint cubeVertexBufferID;
     20 //立方体的IndexBuffer的ID
     21 GLuint cubeIndexBufferID;
     22 
     23 
     24 
     25 //四面体的索引数组长度
     26 GLuint tetraNumIndices;
     27 //四面体的VAO ID
     28 GLuint tetraVertexArrayObjectID;
     29 //四面体的BufferID
     30 GLuint tetraVertexBufferID;
     31 //四面体的IndexBufferID
     32 GLuint tetraIndexBufferID;
     33 
     34 
     35 
     36 GLuint fullTransformUniformLocation;
     37 
     38 void MyGlWindow::sendDataToOpenGL()
     39 {
     40     //创建Cube
     41     ShapeData cube = ShapeGenerator::makeCube();
     42 
     43     //创建和设置VertexBuffer
     44     glGenBuffers(1, &cubeVertexBufferID);
     45     glBindBuffer(GL_ARRAY_BUFFER, cubeVertexBufferID);
     46     glBufferData(GL_ARRAY_BUFFER, cube.vertexBufferSize(), cube.vertices, GL_STATIC_DRAW);
     47 
     48     //创建和设置IndexBuffer
     49     glGenBuffers(1, &cubeIndexBufferID);
     50     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIndexBufferID);
     51     glBufferData(GL_ELEMENT_ARRAY_BUFFER, cube.indexBufferSize(), cube.indices, GL_STATIC_DRAW);
     52 
     53     cubeNumIndices = cube.numIndices;
     54     cube.cleanUp();
     55 
     56     //创建四面体
     57     ShapeData tetra = ShapeGenerator::makeTetrahedron();
     58 
     59     //创建和设置VertexBuffer
     60     glGenBuffers(1, &tetraVertexBufferID);
     61     glBindBuffer(GL_ARRAY_BUFFER, tetraVertexBufferID);
     62     glBufferData(GL_ARRAY_BUFFER, tetra.vertexBufferSize(), tetra.vertices, GL_STATIC_DRAW);
     63 
     64     //创建和设置IndexBuffer
     65     glGenBuffers(1, &tetraIndexBufferID);
     66     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tetraIndexBufferID);
     67     glBufferData(GL_ELEMENT_ARRAY_BUFFER, tetra.indexBufferSize(), tetra.indices, GL_STATIC_DRAW);
     68 
     69     tetraNumIndices = tetra.numIndices;
     70     tetra.cleanUp();
     71 
     72 }
     73 
     74 void MyGlWindow::setupVertexArrays()
     75 {
     76     //设置绘制Cube的VAO
     77     //生成VAO
     78     glGenVertexArrays(1, &cubeVertexArrayObjectID);
     79     //绑定VAO,后续的一系列状态和设置都会存储在这个VAO里。
     80     glBindVertexArray(cubeVertexArrayObjectID);
     81 
     82     //开启通道1(位置)
     83     glEnableVertexAttribArray(0);
     84     //开启通道2(颜色)
     85     glEnableVertexAttribArray(1);
     86 
     87     //绑定顶点数据ID到绑定点
     88     glBindBuffer(GL_ARRAY_BUFFER, cubeVertexBufferID);
     89     //设置通道1如何获取数据
     90     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, 0);
     91     //设置通道2如何获取数据
     92     glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (char*)(sizeof(float) * 3));
     93 
     94     //绑定索引数据ID到绑定点
     95     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cubeIndexBufferID);
     96 
     97 
     98     //设置绘制四面体的VAO
     99     glGenVertexArrays(1, &tetraVertexArrayObjectID);
    100     glBindVertexArray(tetraVertexArrayObjectID);
    101 
    102     //开启通道1(位置)
    103     glEnableVertexAttribArray(0);
    104     //开启通道2(颜色)
    105     glEnableVertexAttribArray(1);
    106 
    107     //绑定顶点数据ID到绑定点
    108     glBindBuffer(GL_ARRAY_BUFFER, tetraVertexBufferID);
    109     //设置通道1如何获取数据
    110     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, 0);
    111     //设置通道2如何获取数据
    112     glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (char*)(sizeof(float) * 3));
    113 
    114     //绑定索引数据ID到绑定点
    115     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, tetraIndexBufferID);
    116 
    117 
    118 }
    119 
    120 void MyGlWindow::installShaders()
    121 {
    122     GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER);
    123     GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
    124 
    125     std::string tmp = ReadShaderCode("VertexShaderCode.glsl");
    126     const char* vertexShaderCode = tmp.c_str();
    127     glShaderSource(vertexShaderID, 1, &vertexShaderCode, 0);
    128 
    129     tmp = ReadShaderCode("FragmentShaderCode.glsl");
    130     const char* fragmentShaderCode = tmp.c_str();    
    131     glShaderSource(fragmentShaderID, 1, &fragmentShaderCode, 0);
    132 
    133     glCompileShader(vertexShaderID);
    134     glCompileShader(fragmentShaderID);
    135 
    136     programID = glCreateProgram();
    137     glAttachShader(programID, vertexShaderID);
    138     glAttachShader(programID, fragmentShaderID);
    139 
    140     glLinkProgram(programID);
    141 
    142     glUseProgram(programID);
    143 }
    144 
    145 void MyGlWindow::initializeGL()
    146 {
    147     glewInit();
    148     glEnable(GL_DEPTH_TEST);
    149     sendDataToOpenGL();
    150     setupVertexArrays();
    151     installShaders();
    152 }
    153 
    154 void MyGlWindow::paintGL()
    155 {
    156     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    157     glViewport(0, 0, width(), height());
    158 
    159     //绑定cube的VAO,下面绘制的都是立方体--------------------------------------
    160     glBindVertexArray(cubeVertexArrayObjectID);
    161 
    162     glm::mat4 fullTransformMatrix;
    163     glm::mat4 viewToProjectionMatrix = glm::perspective(30.0f, ((float)width()) / height(), 0.1f, 10.0f);
    164     glm::mat4 worldToViewMatrix = camera.getWorldToViewMatrix();
    165     glm::mat4 worldToProjectionMatrix = viewToProjectionMatrix * worldToViewMatrix;
    166 
    167     //绘制Cube1
    168     glm::mat4 cube1ModelToWorldMatrix =
    169         glm::translate(glm::vec3(-1.0f, 0.0f, -3.0f))*
    170         glm::rotate(36.0f, glm::vec3(1.0f, 0.0f, 0.0f));
    171 
    172     fullTransformMatrix = worldToProjectionMatrix * cube1ModelToWorldMatrix;
    173     glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]);
    174     glDrawElements(GL_TRIANGLES, cubeNumIndices, GL_UNSIGNED_SHORT, 0);
    175 
    176     //绘制Cube2
    177     glm::mat4 cube2ModelToWorldMatrix = 
    178         glm::translate(glm::vec3(1.0f, 0.0f, -3.75f))*
    179         glm::rotate(36.0f, glm::vec3(0.0f, 1.0f, 0.0f));
    180     fullTransformMatrix = worldToProjectionMatrix * cube2ModelToWorldMatrix;
    181     glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]);
    182     glDrawElements(GL_TRIANGLES, cubeNumIndices, GL_UNSIGNED_SHORT, 0);
    183 
    184     //绑定Tetra的VAO,下面绘制的都是四面体--------------------------------------
    185     glBindVertexArray(tetraVertexArrayObjectID);
    186 
    187     //绘制Tetra1
    188     glm::mat4 tetra1ModelToWorldMatrix =
    189         glm::translate(glm::vec3(1.0f, -2.0f, -3.75f))*
    190         glm::rotate(36.0f, glm::vec3(0.0f, 1.0f, 0.0f));
    191     fullTransformMatrix = worldToProjectionMatrix * tetra1ModelToWorldMatrix;
    192     glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]);
    193     glDrawElements(GL_TRIANGLES, tetraNumIndices, GL_UNSIGNED_SHORT, 0);
    194 
    195     glm::mat4 tetra2ModelToWorldMatrix =
    196         glm::translate(glm::vec3(-3.0f, -2.0f, -3.75f))*
    197         glm::rotate(36.0f, glm::vec3(1.0f, 1.0f, 0.0f));
    198     fullTransformMatrix = worldToProjectionMatrix * tetra2ModelToWorldMatrix;
    199     glUniformMatrix4fv(fullTransformUniformLocation, 1, GL_FALSE, &fullTransformMatrix[0][0]);
    200     glDrawElements(GL_TRIANGLES, tetraNumIndices, GL_UNSIGNED_SHORT, 0);
    201 }
    202 
    203 
    204 std::string MyGlWindow::ReadShaderCode(const char* fileName)
    205 {
    206     std::ifstream myInput(fileName);
    207     if (!myInput.good())
    208     {
    209         std::cout << "File failed to load..." << fileName;
    210         exit(1);
    211     }
    212     return std::string(
    213         std::istreambuf_iterator<char>(myInput),
    214         std::istreambuf_iterator<char>());
    215 }
    216 
    217 void MyGlWindow::mouseMoveEvent(QMouseEvent * e)
    218 {
    219     camera.mouseUpdate(glm::vec2(e->x(), e->y()));
    220     repaint();
    221 }
    222 
    223 void MyGlWindow::keyPressEvent(QKeyEvent * e)
    224 {
    225     switch (e->key())
    226     {
    227     case Qt::Key::Key_W:
    228         camera.moveForward();
    229         break;
    230     case Qt::Key::Key_S:
    231         camera.moveBackward();
    232         break;
    233     case Qt::Key::Key_A:
    234         camera.strafeLeft();
    235         break;
    236     case Qt::Key::Key_D:
    237         camera.strafeRight();
    238         break;
    239     case Qt::Key::Key_Q:
    240         camera.moveUp();
    241         break;
    242     case Qt::Key::Key_E:
    243         camera.moveDown();
    244         break;
    245 
    246     default:
    247         break;
    248     }
    249     repaint();
    250 }

    总结一下加入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

  • 相关阅读:
    001-Go JSON处理
    cpu高占用,线程堆栈,jstack,pstack,jmap, kill -3 pid,java(weblogic,tomcat)
    jQuery插入,复制、替换和删除节点
    jquery 控制css样式
    10分钟掌握XML、JSON及其解析
    阻止跳转的四种方式,你知道吗?
    jQuery事件绑定和委托
    响应式Web设计的9项基本原则
    网友写的验证码生成方案,可防止绝大多数机械识别。
    7个高性能JavaScript代码高亮插件
  • 原文地址:https://www.cnblogs.com/AnKen/p/8409237.html
Copyright © 2020-2023  润新知