目录
一、说明
二、实例化
三、实例化数组
四、小行星带
五、完整代码
六、结论
一、说明
实例化渲染,是用少数数据做模板,实现海量物体渲染的手段方法。用实例化渲染,需要对每个实例产生一定描述数据。如何实现?请看本文下文。
二、实例化
假设您有一个场景,其中绘制了许多模型,其中大多数模型包含相同的顶点数据集,但具有不同的世界变换。想象一个充满草叶的场景:每片草叶都是一个仅由几个三角形组成的小模型。您可能想要绘制其中的很多片,并且您的场景最终可能会有数千片甚至数万片草叶,您需要渲染每一帧。由于每片叶子只有几个三角形,因此叶子几乎可以立即渲染。但是,您必须进行的数千次渲染调用将大大降低性能。
如果我们实际上要渲染如此大量的对象,它在代码中看起来会有点像这样:
for(unsigned int i = 0; i < amount_of_models_to_draw; i++)
{
DoSomePreparations(); // bind VAO, bind textures, set uniforms etc.
glDrawArrays(GL_TRIANGLES, 0, amount_of_vertices);
}
当绘制许多实例像这样渲染模型,由于多次绘制调用,很快就会出现性能瓶颈。与渲染实际顶点相比,使用以下函数告诉 GPU 渲染顶点数据绘制数组或者绘制元素这会消耗相当多的性能,因为 OpenGL 必须做好必要的准备才能绘制顶点数据(例如告诉 GPU 从哪个缓冲区读取数据、在哪里找到顶点属性,所有这些都通过相对较慢的 CPU 到 GPU 总线进行)。因此,尽管渲染顶点非常快,但向 GPU 发出渲染命令却不是。
如果我们可以将数据一次性发送到 GPU,然后通过一次绘制调用告诉 OpenGL 使用此数据绘制多个对象,那么会方便得多。输入实例化。
实例化是一种技术,我们可以通过一次渲染调用一次绘制多个(相等网格数据)对象,从而节省了每次需要渲染对象时的所有 CPU -> GPU 通信。要使用实例化进行渲染,我们需要做的就是更改渲染调用绘制数组和绘制元素到绘制数组实例化和绘制元素实例化这些经典渲染函数的实例版本采用一个称为实例数它设置了我们要渲染的实例数量。我们将所有必需的数据一次性发送给 GPU,然后通过一次调用告诉 GPU 应该如何绘制所有这些实例。然后 GPU 会渲染所有这些实例,而无需持续与 CPU 通信。
这个函数本身没什么用。渲染同一个物体一千次对我们来说毫无用处,因为每个渲染的物体都是完全相同的,因此也位于相同的位置;我们只会看到一个物体!因此,GLSL 在顶点着色器中添加了另一个内置变量,称为gl_InstanceID。
使用实例渲染调用之一进行绘制时,gl_InstanceID会从 开始为要渲染的每个实例递增0
。例如,如果我们要渲染第 43 个实例,gl_InstanceID将在顶点着色器中具有值42
。每个实例都有一个唯一值意味着我们现在可以索引大量位置值,以将每个实例定位在世界上的不同位置。
为了让您了解实例化绘制,我们将演示一个简单的示例,该示例仅使用一次渲染调用即可在规范化设备坐标中渲染一百个 2D 四边形。我们通过索引一个均匀的100
偏移向量数组来唯一地定位每个实例化四边形来实现这一点。结果是一个整齐排列的四边形网格,填满了整个窗口:
每个四边形由 2 个三角形组成,总共有 6 个顶点。每个顶点包含一个 2D NDC 位置向量和一个颜色向量。以下是本示例使用的顶点数据 - 当有 100 个三角形时,三角形足够小,可以正确适合屏幕:
float quadVertices[] = {
// positions // colors
-0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
-0.05f, -0.05f, 0.0f, 0.0f, 1.0f,
-0.05f, 0.05f, 1.0f, 0.0f, 0.0f,
0.05f, -0.05f, 0.0f, 1.0f, 0.0f,
0.05f, 0.05f, 0.0f, 1.0f, 1.0f
};
四边形在片段着色器中被着色,片段着色器从顶点着色器接收颜色向量并将其设置为其输出:
#version 330 core
out vec4 FragColor;
in vec3 fColor;
void main()
{
FragColor = vec4(fColor, 1.0);
}
到目前为止还没有什么新东西,但在顶点着色器上它开始变得有趣:
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
out vec3 fColor;
uniform vec2 offsets[100];
void main()
{
vec2 offset = offsets[gl_InstanceID];
gl_Position = vec4(aPos + offset, 0.0, 1.0);
fColor = aColor;
}
这里我们定义了一个名为offsets的统一数组,其中包含总共的100
偏移向量。在顶点着色器中,我们通过使用gl_InstanceID索引偏移数组来检索每个实例的偏移向量。如果我们现在使用实例绘制来绘制四边形,我们将得到位于不同位置的四边形。 100
100
我们确实需要在进入渲染循环之前实际设置在嵌套 for 循环中计算的偏移位置:
glm::vec2 translations[100];
int index = 0;
float offset = 0.1f;
for(int y = -10; y < 10; y += 2)
{
for(int x = -10; x < 10; x += 2)
{
glm::vec2 translation;
translation.x = (float)x / 10.0f + offset;
translation.y = (float)y / 10.0f + offset;
translations[index++] = translation;
}
}
这里我们创建了一组100
平移向量,其中包含 10x10 网格中所有位置的偏移向量。除了生成翻译数组之外,我们还需要将数据传输到顶点着色器的统一数组:
shader.use();
for(unsigned int i = 0; i < 100; i++)
{
shader.setVec2(("offsets[" + std::to_string(i) + "]")), translations[i]);
}
在这段代码中,我们将 for 循环计数器i转换为细绳动态创建位置字符串,用于查询统一位置。然后,我们为偏移统一数组中的每个项目设置相应的平移向量。
现在所有准备工作都已完成,我们可以开始渲染四边形了。要通过实例渲染进行绘制,我们调用绘制数组实例化或者绘制元素实例化。由于我们没有使用元素索引缓冲区,因此我们将调用绘制数组版本:
glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);
的参数绘制数组实例化与绘制数组除了最后一个参数设置我们要绘制的实例数。因为我们想100
在 10x10 网格中显示四边形,所以我们将其设置为。运行代码现在应该会给你熟悉的彩色四边形 100
图像。100
三、实例化数组
虽然之前的实现对于这个特定的用例来说很好,但每当我们渲染的实例数量远远超过100
实例数量时(这很常见),我们最终会达到可以发送到着色器的统一数据量的上限。一个替代方案是实例数组。实例数组被定义为顶点属性(允许我们存储更多数据),该属性按实例而不是按顶点进行更新。
对于顶点属性,在每次运行顶点着色器时,GPU 都会检索属于当前顶点的下一组顶点属性。但是,当将顶点属性定义为实例化数组时,顶点着色器只会更新每个实例的顶点属性内容。这使我们能够使用标准顶点属性来存储每个顶点的数据,并使用实例化数组来存储每个实例唯一的数据。
为了给出一个实例化数组的示例,我们将采用前面的示例并将偏移统一数组转换为实例化数组。我们必须通过添加另一个顶点属性来更新顶点着色器:
#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;
out vec3 fColor;
void main()
{
gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
fColor = aColor;
}
我们不再使用gl_InstanceID,可以直接使用偏移属性,而无需先索引到大型统一数组。
因为实例化数组是顶点属性,就像位置和颜色变量一样,我们需要将其内容存储在顶点缓冲区对象中并配置其属性指针。我们首先将翻译数组(来自上一节)存储在一个新的缓冲区对象中:
unsigned int instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
然后我们还需要设置它的顶点属性指针,并启用顶点属性:
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribDivisor(2, 1);
这段代码的有趣之处在于最后一行,我们调用glVertexAttribDivisor。此函数告诉 OpenGL何时将顶点属性的内容更新为下一个元素。其第一个参数是所讨论的顶点属性,第二个参数是属性除数默认情况下,属性除数为0
,它告诉 OpenGL 在顶点着色器每次迭代时更新顶点属性的内容。通过将此属性设置为,1
我们告诉 OpenGL,当我们开始渲染新实例时,我们想要更新顶点属性的内容。通过将其设置为,2
我们将每 2 个实例更新一次内容,依此类推。通过将属性除数设置为,1
我们实际上告诉 OpenGL,属性位置处的顶点属性2
是一个实例数组。
如果我们现在再次使用绘制数组实例化我们将获得以下输出:
这与前面的示例完全相同,但现在使用了实例数组,这使我们能够将更多的数据(只要内存允许)传递给顶点着色器进行实例绘制。
为了好玩,我们可以再次使用gl_InstanceID 将每个四边形从右上到左下慢慢缩小,为什么不呢?
void main()
{
vec2 pos = aPos * (gl_InstanceID / 100.0);
gl_Position = vec4(pos + aOffset, 0.0, 1.0);
fColor = aColor;
}
结果是,四边形的第一个实例绘制得非常小,随着实例绘制的进行,gl_InstanceID越来越接近,因此四边形恢复到原始大小的程度也越来越大。像这样 100
将实例数组与gl_InstanceID一起使用是完全合法的。
如果您仍然不太清楚实例渲染的工作原理,或者想了解一切是如何组合在一起的,您可以在此处找到该应用程序的完整源代码。
虽然这些示例很有趣,但它们并不是实例化的真正好例子。是的,它们确实让您轻松了解实例化的工作原理,但实例化在绘制大量类似对象时发挥了最大作用。因此,我们将进入太空。
四、小行星带
想象一下这样一个场景:一颗大行星位于一颗巨大的小行星环的中心。这样的小行星环可能包含数千或数万个岩层,并且很快就会无法在任何像样的显卡上渲染。这种情况对于实例渲染特别有用,因为所有小行星都可以用一个模型来表示。然后,每颗小行星都会从其独有的变换矩阵中获得其变化。
为了演示实例化渲染的影响,我们首先要渲染一个小行星围绕行星盘旋的场景,而无需实例化渲染。该场景将包含一个可从此处下载的大型行星模型,以及我们正确放置在行星周围的大量小行星岩石。小行星岩石模型可在此处下载。
在代码示例中,我们使用之前在模型加载章节 中定义的模型加载器来加载模型。
为了实现我们想要的效果,我们将为每个小行星生成一个模型变换矩阵。变换矩阵首先将岩石平移到小行星环中的某个位置 - 然后我们将在偏移量中添加一个小的随机位移值,以使环看起来更自然。从那里我们还应用随机比例和随机旋转。结果是一个变换矩阵,它将每个小行星平移到行星周围的某个位置,同时使其与其他小行星相比看起来更自然、更独特。
unsigned int amount = 1000;
glm::mat4 *modelMatrices;
modelMatrices = new glm::mat4[amount];
srand(glfwGetTime()); // initialize random seed
float radius = 50.0;
float offset = 2.5f;
for(unsigned int i = 0; i < amount; i++)
{
glm::mat4 model = glm::mat4(1.0f);
// 1. translation: displace along circle with 'radius' in range [-offset, offset]
float angle = (float)i / (float)amount * 360.0f;
float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float x = sin(angle) * radius + displacement;
displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float y = displacement * 0.4f; // keep height of field smaller compared to width of x and z
displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float z = cos(angle) * radius + displacement;
model = glm::translate(model, glm::vec3(x, y, z));
// 2. scale: scale between 0.05 and 0.25f
float scale = (rand() % 20) / 100.0f + 0.05;
model = glm::scale(model, glm::vec3(scale));
// 3. rotation: add random rotation around a (semi)randomly picked rotation axis vector
float rotAngle = (rand() % 360);
model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));
// 4. now add to list of matrices
modelMatrices[i] = model;
}
这段代码看起来可能有点令人生畏,但我们基本上沿着半径由 radius 定义的圆变换小行星的 x 和 z 位置,并随机地将每个小行星在圆周上按-offset和offset进行一点位移。我们给位移施加较小的影响,以创建更平坦的小行星环。然后我们应用缩放和旋转变换,并将生成的变换矩阵存储在大小为amount的modelMatricesy
中。在这里我们生成模型矩阵,每个小行星一个。 1000
加载行星和岩石模型并编译一组着色器后,渲染代码看起来有点像这样:
// draw planet
shader.use();
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f));
model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));
shader.setMat4("model", model);
planet.Draw(shader);
// draw meteorites
for(unsigned int i = 0; i < amount; i++)
{
shader.setMat4("model", modelMatrices[i]);
rock.Draw(shader);
}
首先,我们绘制行星模型,对其进行平移和缩放以适应场景,然后绘制与之前生成的变换数量相等的岩石模型。然而,在绘制每块岩石之前,我们首先在着色器中设置相应的模型变换矩阵。
结果就是出现了一个类似太空的场景,我们可以看到围绕行星的自然的小行星环:
此场景每帧总共包含1001
渲染调用,其中1000
涉及岩石模型。您可以在此处找到此场景的源代码。
一旦我们开始增加这个数字,我们很快就会注意到场景不再流畅运行,我们每秒能够渲染的帧数急剧减少。一旦我们将数量设置为接近某个值,2000
场景在 GPU 上的运行速度就会变得非常慢,以至于很难移动。
现在让我们尝试渲染相同的场景,但这次使用实例渲染。我们首先需要稍微调整一下顶点着色器:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in mat4 instanceMatrix;
out vec2 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
gl_Position = projection * view * instanceMatrix * vec4(aPos, 1.0);
TexCoords = aTexCoords;
}
我们不再使用模型统一变量,而是声明一个mat4作为顶点属性,因此我们可以存储变换矩阵的实例数组。但是,当我们将数据类型声明为大于向量4情况有点不同。顶点属性允许的最大数据量等于向量4. 因为mat4基本上是 4向量4s,我们必须为这个特定矩阵保留 4 个顶点属性。因为我们为其分配了 的位置3
,所以矩阵的列将具有3
、4
、5
和的顶点属性位置6
。
然后我们必须设置这些4
顶点属性的每个属性指针,并将它们配置为实例数组:
// vertex buffer object
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);
for(unsigned int i = 0; i < rock.meshes.size(); i++)
{
unsigned int VAO = rock.meshes[i].VAO;
glBindVertexArray(VAO);
// vertex attributes
std::size_t vec4Size = sizeof(glm::vec4);
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)0);
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(1 * vec4Size));
glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(2 * vec4Size));
glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(3 * vec4Size));
glVertexAttribDivisor(3, 1);
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1);
glBindVertexArray(0);
}
注意,我们作弊了一点,声明了网作为公共变量而不是私有变量,这样我们就可以访问它的顶点数组对象。这不是最干净的解决方案,而只是一个简单的修改以适合这个例子。除了小技巧之外,这段代码应该很清楚。我们基本上是在声明 OpenGL 应该如何解释矩阵的每个顶点属性的缓冲区,以及每个顶点属性都是一个实例数组。
接下来我们再次获取网格的VAO ,这次使用绘制元素实例化:
// draw meteorites
instanceShader.use();
for(unsigned int i = 0; i < rock.meshes.size(); i++)
{
glBindVertexArray(rock.meshes[i].VAO);
glDrawElementsInstanced(
GL_TRIANGLES, rock.meshes[i].indices.size(), GL_UNSIGNED_INT, 0, amount
);
}
这里我们绘制了与上一个示例相同数量的小行星,但这次使用的是实例渲染。结果应该完全相同,但一旦我们增加数量,您就会真正开始看到实例渲染的威力。没有实例渲染,我们能够平滑地渲染1000
小行星1500
。使用实例渲染,我们现在可以将此值设置为100000
。由于岩石模型有576
顶点,因此每帧绘制大约57
一百万个顶点,而不会出现明显的性能下降;并且只有 2 次绘制调用!
100000
该图像由半径为150.0f
且偏移量等于 的小行星 渲染而成。您可以在此处25.0f
找到实例渲染演示的源代码。
五、完整代码
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <stb_image.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <learnopengl/shader.h>
#include <learnopengl/camera.h>
#include <learnopengl/model.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
// camera
Camera camera(glm::vec3(0.0f, 0.0f, 155.0f));
float lastX = (float)SCR_WIDTH / 2.0;
float lastY = (float)SCR_HEIGHT / 2.0;
bool firstMouse = true;
// timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;
int main()
{
// glfw: initialize and configure
// ------------------------------
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
// glfw window creation
// --------------------
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
// tell GLFW to capture our mouse
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
// glad: load all OpenGL function pointers
// ---------------------------------------
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// configure global opengl state
// -----------------------------
glEnable(GL_DEPTH_TEST);
// build and compile shaders
// -------------------------
Shader asteroidShader("10.3.asteroids.vs", "10.3.asteroids.fs");
Shader planetShader("10.3.planet.vs", "10.3.planet.fs");
// load models
// -----------
Model rock(FileSystem::getPath("resources/objects/rock/rock.obj"));
Model planet(FileSystem::getPath("resources/objects/planet/planet.obj"));
// generate a large list of semi-random model transformation matrices
// ------------------------------------------------------------------
unsigned int amount = 100000;
glm::mat4* modelMatrices;
modelMatrices = new glm::mat4[amount];
srand(static_cast<unsigned int>(glfwGetTime())); // initialize random seed
float radius = 150.0;
float offset = 25.0f;
for (unsigned int i = 0; i < amount; i++)
{
glm::mat4 model = glm::mat4(1.0f);
// 1. translation: displace along circle with 'radius' in range [-offset, offset]
float angle = (float)i / (float)amount * 360.0f;
float displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float x = sin(angle) * radius + displacement;
displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float y = displacement * 0.4f; // keep height of asteroid field smaller compared to width of x and z
displacement = (rand() % (int)(2 * offset * 100)) / 100.0f - offset;
float z = cos(angle) * radius + displacement;
model = glm::translate(model, glm::vec3(x, y, z));
// 2. scale: Scale between 0.05 and 0.25f
float scale = static_cast<float>((rand() % 20) / 100.0 + 0.05);
model = glm::scale(model, glm::vec3(scale));
// 3. rotation: add random rotation around a (semi)randomly picked rotation axis vector
float rotAngle = static_cast<float>((rand() % 360));
model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));
// 4. now add to list of matrices
modelMatrices[i] = model;
}
// configure instanced array
// -------------------------
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &modelMatrices[0], GL_STATIC_DRAW);
// set transformation matrices as an instance vertex attribute (with divisor 1)
// note: we're cheating a little by taking the, now publicly declared, VAO of the model's mesh(es) and adding new vertexAttribPointers
// normally you'd want to do this in a more organized fashion, but for learning purposes this will do.
// -----------------------------------------------------------------------------------------------------------------------------------
for (unsigned int i = 0; i < rock.meshes.size(); i++)
{
unsigned int VAO = rock.meshes[i].VAO;
glBindVertexArray(VAO);
// set attribute pointers for matrix (4 times vec4)
glEnableVertexAttribArray(3);
glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)0);
glEnableVertexAttribArray(4);
glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(glm::vec4)));
glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(2 * sizeof(glm::vec4)));
glEnableVertexAttribArray(6);
glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(3 * sizeof(glm::vec4)));
glVertexAttribDivisor(3, 1);
glVertexAttribDivisor(4, 1);
glVertexAttribDivisor(5, 1);
glVertexAttribDivisor(6, 1);
glBindVertexArray(0);
}
// render loop
// -----------
while (!glfwWindowShouldClose(window))
{
// per-frame time logic
// --------------------
float currentFrame = static_cast<float>(glfwGetTime());
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;
// input
// -----
processInput(window);
// render
// ------
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// configure transformation matrices
glm::mat4 projection = glm::perspective(glm::radians(45.0f), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 1000.0f);
glm::mat4 view = camera.GetViewMatrix();
asteroidShader.use();
asteroidShader.setMat4("projection", projection);
asteroidShader.setMat4("view", view);
planetShader.use();
planetShader.setMat4("projection", projection);
planetShader.setMat4("view", view);
// draw planet
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, glm::vec3(0.0f, -3.0f, 0.0f));
model = glm::scale(model, glm::vec3(4.0f, 4.0f, 4.0f));
planetShader.setMat4("model", model);
planet.Draw(planetShader);
// draw meteorites
asteroidShader.use();
asteroidShader.setInt("texture_diffuse1", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, rock.textures_loaded[0].id); // note: we also made the textures_loaded vector public (instead of private) from the model class.
for (unsigned int i = 0; i < rock.meshes.size(); i++)
{
glBindVertexArray(rock.meshes[i].VAO);
glDrawElementsInstanced(GL_TRIANGLES, static_cast<unsigned int>(rock.meshes[i].indices.size()), GL_UNSIGNED_INT, 0, amount);
glBindVertexArray(0);
}
// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
// -------------------------------------------------------------------------------
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
camera.ProcessKeyboard(FORWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
camera.ProcessKeyboard(LEFT, deltaTime);
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
camera.ProcessKeyboard(RIGHT, deltaTime);
}
// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// make sure the viewport matches the new window dimensions; note that width and
// height will be significantly larger than specified on retina displays.
glViewport(0, 0, width, height);
}
// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
float xpos = static_cast<float>(xposIn);
float ypos = static_cast<float>(yposIn);
if (firstMouse)
{
lastX = xpos;
lastY = ypos;
firstMouse = false;
}
float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
lastX = xpos;
lastY = ypos;
camera.ProcessMouseMovement(xoffset, yoffset);
}
// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
camera.ProcessMouseScroll(static_cast<float>(yoffset));
}
在不同的机器上,小行星的数量100000
可能有点太高,因此请尝试调整值直到达到可接受的帧速率。
六、结论
如您所见,在正确的环境中,实例化渲染可以极大地改善应用程序的渲染能力。因此,实例化渲染通常用于草地、植物、粒子和类似的场景 - 基本上任何具有许多重复形状的场景都可以从实例化渲染中受益。