我们使用15节学到的知识来绘制14节的立方体。

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

而15节我们知道了glDrawElementsInstanced函数可以支持批量绘制。

我们需要绘制的多个立方体唯一不同的只是转换矩阵,为了达到这个目的,我们只需要定义一组不同的变换矩阵,采用glDrawElementsInstanced即可达到目的。

构建矩阵的更简便写法

在进行之前,先介绍一个创建平移和旋转矩阵的更简单的写法:

我们此前构建平移和旋转矩阵都是使用如下的语法:

glm::translate(glm::mat4(), glm::vec3(0.0f,0.0f,3.0f)); //平移矩阵

glm::rotate(glm::mat4(), 125.0f, glm::vec3(1.0f, 0.0f,0.0f)); //旋转矩阵

发现第一个参数都需要一个矩阵,这个写法不利于我们本节的实践。

有一个简化的方法:

引入头文件<glm\gtx\transform.hpp>

然后上述两个矩阵的构建都可以省略掉第一个参数。

glm::translate(glm::vec3(0.0f,0.0f,3.0f)); //平移矩阵

glm::rotate(125.0f, glm::vec3(1.0f, 0.0f,0.0f)); //旋转矩阵

准备数据

先从修改sendDataToOpenGL()函数开始修改:

 void MyGlWindow::sendDataToOpenGL()
{ ShapeData shape = ShapeGenerator::makeCube(); GLuint vertexBufferID;
glGenBuffers(, &vertexBufferID);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
glBufferData(GL_ARRAY_BUFFER, shape.vertexBufferSize(), shape.vertices, GL_STATIC_DRAW); glEnableVertexAttribArray();
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(GLfloat) * , ); GLuint indexBufferID;
glGenBuffers(, &indexBufferID);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, shape.indexBufferSize(), shape.indices, GL_STATIC_DRAW); glEnableVertexAttribArray();
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(GLfloat) * , (char*)(sizeof(GLfloat) * )); numIndices = shape.numIndices;
shape.cleanUp(); //instancing
glm::mat4 projectionMatrix = glm::perspective(30.0f, ((float)width()) / height(), 0.1f, 10.0f); glm::mat4 fullTransforms[] =
{
projectionMatrix * glm::translate(glm::vec3(0.0f, 0.0f, -3.0f)) * glm::rotate(54.0f,glm::vec3(1.0f, 0.0f, 0.0f)),
projectionMatrix * glm::translate(glm::vec3(2.0f, 0.0f, -4.0f)) * glm::rotate(126.0f, glm::vec3(0.0f, 1.0f, 0.0f))
}; GLuint transformMatrixBufferID;
glGenBuffers(, &transformMatrixBufferID);
glBindBuffer(GL_ARRAY_BUFFER, transformMatrixBufferID);
glBufferData(GL_ARRAY_BUFFER, sizeof(fullTransforms), fullTransforms, GL_STATIC_DRAW);
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * ));
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * ));
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * ));
glVertexAttribPointer(, , GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * ));
glEnableVertexAttribArray();
glEnableVertexAttribArray();
glEnableVertexAttribArray();
glEnableVertexAttribArray();
glVertexAttribDivisor(, );
glVertexAttribDivisor(, );
glVertexAttribDivisor(, );
glVertexAttribDivisor(, );
}

从27行起是新增内容,27行定义了一个projectionMatrix。

29-33行定义了一个“变换矩阵数组”,包含两个元素。它们的形式都是 projectionMatrix * translationMatrix * rotationMatrix。注意这里的tranlationMatrix和rotationMatrix都使用了前面介绍的简化方法去定义。

37行再次绑定transformMatrixBuffer到GL_ARRAY_BUFFER上,可能会有疑问:之前给GL_ARRAY_BUFFER绑定了Vertex Buffer(8行),这里又绑定其他的东西,会把之前绑定的数据破坏掉吗?

答案是不会,只要在这之前使用了glAttribPointer函数(12行),之前绑定的数据就是安全的。

39-50行和上节学习的内容是相似的,但是这里用到了4组命令,原因是一个mat4要被当做四个float对待。

还要修改paintGL():

 void MyGlWindow::paintGL()
{
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
glViewport(, , width(), height());
glDrawElementsInstanced(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, , );
}

我们看到14节中paintGL()中的很多代码都不见了,原因是这些工作都在sendDataToOpenGL()中准备了。这里只需要使用一个glDrawElementsInstanced函数去进行批量绘制即可。

再看修改后的VertexShader:

 #version                            

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

第五行虽然只有一个location=2,但是对应的sendDataToOpenGL()中却有2,3,4,5 四个通道,原因是我们也要把这里的mat4当做四个float对待,可以想象成这样的情景:

 in layout(location=) vec4 fullTransformMatrix_part1;
in layout(location=) vec4 fullTransformMatrix_part2;
in layout(location=) vec4 fullTransformMatrix_part3;
in layout(location=) vec4 fullTransformMatrix_part4;

编译运行后得到的结果和第14节是一样的。

这样做的好处是扩展起来非常容易,例如我们需要再多绘制几个立方体,只需要向sendDataToOpenGL()函数中的fullTransforms数组中增加元素,并修改paintLG()函数中第5行的最后一个参数即可。

3D Computer Grapihcs Using OpenGL - 16 使用DrawElementsInstanced绘制立方体的更多相关文章

  1. 3D Computer Grapihcs Using OpenGL - 19 Vertex Array Object(顶点数组对象)

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

  2. 3D Computer Grapihcs Using OpenGL - 15 Draw Element Instanced

    友情提示:继续本节之前,需要保存此前的代码,本节为了试验,会对代码做一些修改,但后续的修改需要我们把代码返回之前的进度. OpenGL内置支持Instancing,有专门的函数来处理这件事情. 为了方 ...

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

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

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

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

  5. 3D Computer Grapihcs Using OpenGL - 03 OpenGL Buffer Data

    本节绘制一个三角形,并讲解Buffer Object-缓冲对象 OpenGL的窗口坐标 屏幕中心为坐标原点,横向朝右为x正方向,纵向朝上为y正方向,最大值最小值分别为1,-1. Buffer Obje ...

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

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

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

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

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

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

  9. 3D Computer Grapihcs Using OpenGL - 05 EBO

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

随机推荐

  1. http请求跨域问题分析

    http请求跨域问题分析 个人认为可能涉及很多http的知识,本人才疏学浅不敢妄自揣测,只是做个笔记为了以后对比理解,从原生fetch说起吧,前提假设有个后端服务,我用express来模拟,如下: v ...

  2. [codeforces#592Div2]C-G题

    题目链接 感觉这场难度迷茫,个人觉得难度排序为$A<B<D=E=G<C<F$ C题: 比赛结束1500+pp,结果出分900+fst,我就是fst的睿智Orz. 题意为给出$n ...

  3. POJ - 1287 Networking (最小生成树&并查集

    You are assigned to design network connections between certain points in a wide area. You are given ...

  4. 洛谷 P3834 卢卡斯定理 题解

    题面 首先你需要知道这条定理: C(n,m)=C(n%p,m%p)*C(n/p,m/p); 这样可以递归实现: 注意坑点:是C(n+m,m),并不是C(n,m); #include <bits/ ...

  5. java按值传递?

    原文链接:https://blog.csdn.net/scholar_man/article/details/80900212 在Java中,参数都是按值传递的.被传递到方法中的拷贝值,要不就是一个引 ...

  6. $().click()和$(document).on('click','要选择的元素',function(){})的不同(转https://www.cnblogs.com/sqh17/p/7746418.html)

    $(document).on();用于动态绑定事件 jQuery的出现,大大简化了对dom的操作,但是如果不是仔细阅读api和进行操作,就不知道其中最大的优点和使用方式.就拿$().click()和$ ...

  7. Python time strptime()方法 时间操作

    描述 Python time strptime() 函数根据指定的格式把一个时间字符串解析为时间元组. 语法 strptime()方法语法: time.strptime(string[, format ...

  8. postgresql 相关操作

    1.root 用户,执行 service postgresql restart service postgresql start  --启动 2.查看数据库状态 /etc/init.d/postgre ...

  9. ElasticSearch实战系列四: ElasticSearch理论知识介绍

    前言 在前几篇关于ElasticSearch的文章中,简单的讲了下有关ElasticSearch的一些使用,这篇文章讲一下有关 ElasticSearch的一些理论知识以及自己的一些见解. 虽然本人是 ...

  10. java实现spark常用算子之countbykey

    import org.apache.spark.SparkConf;import org.apache.spark.api.java.JavaPairRDD;import org.apache.spa ...