【OpenGL手册14】实例化

目录

一、说明

二、实例化

三、实例化数组

四、小行星带

五、完整代码

六、结论


一、说明

        实例化渲染,是用少数数据做模板,实现海量物体渲染的手段方法。用实例化渲染,需要对每个实例产生一定描述数据。如何实现?请看本文下文。

二、实例化

        假设您有一个场景,其中绘制了许多模型,其中大多数模型包含相同的顶点数据集,但具有不同的世界变换。想象一个充满草叶的场景:每片草叶都是一个仅由几个三角形组成的小模型。您可能想要绘制其中的很多片,并且您的场景最终可能会有数千片甚至数万片草叶,您需要渲染每一帧。由于每片叶子只有几个三角形,因此叶子几乎可以立即渲染。但是,您必须进行的数千次渲染调用将大大降低性能。

        如果我们实际上要渲染如此大量的对象,它在代码中看起来会有点像这样:


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偏移向量数组来唯一地定位每个实例化四边形来实现这一点。结果是一个整齐排列的四边形网格,填满了整个窗口:

通过 OpenGL 实例绘制 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索引偏移数组来检索每个实例的偏移向量。如果我们现在使用实例绘制来绘制四边形,我们将得到位于不同位置的四边形。 100100

        我们确实需要在进入渲染循环之前实际设置在嵌套 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是一个实例数组。

        如果我们现在再次使用绘制数组实例化我们将获得以下输出:

与 OpenGL 实例四边形相同的图像,但这次使用实例数组。

        这与前面的示例完全相同,但现在使用了实例数组,这使我们能够将更多的数据(只要内存允许)传递给顶点着色器进行实例绘制。

        为了好玩,我们可以再次使用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一起使用是完全合法的。

使用实例数组在 OpenGL 中绘制的实例四边形图像

        如果您仍然不太清楚实例渲染的工作原理,或者想了解一切是如何组合在一起的,您可以在此处找到该应用程序的完整源代码。

        虽然这些示例很有趣,但它们并不是实例化的真正好例子。是的,它们确实让您轻松了解实例化的工作原理,但实例化在绘制大量类似对象时发挥了最大作用。因此,我们将进入太空。

四、小行星带

        想象一下这样一个场景:一颗大行星位于一颗巨大的小行星环的中心。这样的小行星环可能包含数千或数万个岩层,并且很快就会无法在任何像样的显卡上渲染。这种情况对于实例渲染特别有用,因为所有小行星都可以用一个模型来表示。然后,每颗小行星都会从其独有的变换矩阵中获得其变化。

        为了演示实例化渲染的影响,我们首先要渲染一个小行星围绕行星盘旋的场景,而无需实例化渲染。该场景将包含一个可从此处下载的大型行星模型,以及我们正确放置在行星周围的大量小行星岩石。小行星岩石模型可在此处下载。

在代码示例中,我们使用之前在模型加载章节 中定义的模型加载器来加载模型。

        为了实现我们想要的效果,我们将为每个小行星生成一个模型变换矩阵。变换矩阵首先将岩石平移到小行星环中的某个位置 - 然后我们将在偏移量中添加一个小的随机位移值,以使环看起来更自然。从那里我们还应用随机比例和随机旋转。结果是一个变换矩阵,它将每个小行星平移到行星周围的某个位置,同时使其与其他小行星相比看起来更自然、更独特。


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 zdisplacement = (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.25ffloat 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 vectorfloat rotAngle = (rand() % 360);model = glm::rotate(model, rotAngle, glm::vec3(0.4f, 0.6f, 0.8f));// 4. now add to list of matricesmodelMatrices[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);
}  

        首先,我们绘制行星模型,对其进行平移和缩放以适应场景,然后绘制与之前生成的变换数量相等的岩石模型。然而,在绘制每块岩石之前,我们首先在着色器中设置相应的模型变换矩阵。

        结果就是出现了一个类似太空的场景,我们可以看到围绕行星的自然的小行星环:

使用 OpenGL 绘制的小行星带图像

        此场景每帧总共包含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,所以矩阵的列将具有345和的顶点属性位置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 attributesstd::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 次绘制调用!

使用实例渲染在 OpenGL 中绘制的小行星带图像

  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 mouseglfwSetInputMode(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 seedfloat 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 zdisplacement = (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.25ffloat 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 vectorfloat 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 matricesmodelMatrices[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 matricesglm::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 planetglm::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 meteoritesasteroidShader.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 toplastX = 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可能有点太高,因此请尝试调整值直到达到可接受的帧速率。

六、结论

        如您所见,在正确的环境中,实例化渲染可以极大地改善应用程序的渲染能力。因此,实例化渲染通常用于草地、植物、粒子和类似的场景 - 基本上任何具有许多重复形状的场景都可以从实例化渲染中受益。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/17949.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

docker-compose 映射端口失败! docker端口映射失败 ,docker映射只能使用老端口,映射无法使用

1. 现象 使用docker-compose 启动项目&#xff0c;发现映射端口出现问题&#xff0c;不能映射端口&#xff01; 如图&#xff1a; 使用原来端口是可以使用的 2. 问题原因&#xff1a; 使用了docker-mode 为host模式&#xff0c;所以不能换端口&#xff0c;只能写为"8086:…

SpringBoot——基于Spring Task实现定时任务

目录 定时任务 项目总结 新建一个SpringBoot项目 pom.xml无需引入依赖 SpringTaskDemo SpringbootSpringtaskApplication启动类 定时任务 在日常的项目开发中&#xff0c;往往会涉及一些需要做到定时执行的代码&#xff0c;例如自动将超过24小时的未付款的订单改为取消状…

【调试笔记-20240522-Windows-WSL 修改已安装发行版名称】

调试笔记-系列文章目录 调试笔记-20240522-Windows-WSL 修改已安装发行版名称 文章目录 调试笔记-系列文章目录调试笔记-20240522-Windows-WSL 修改已安装发行版名称 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调试环境调试目标 二、调试步骤方法一&#xff1a;修…

数字化校园的特征

"数字化校园"是校园信息化进入高级阶段的表现形式&#xff0c;信息技术与教育教育的交融应该更深化。因而&#xff0c;数字化校园应该具以下特征&#xff1a; 1.互联网络高速发展 网络是信息时代的根基&#xff0c;没有网络就无法完成教育信息化的绝大部分作业。数字…

Go Redis 实现邮件群发

一、安装 go get github.com/go-redis/redis/v8二、邮箱服务配置,以QQ邮箱为例 三、示例代码 package mainimport ("context""fmt"redis "github.com/go-redis/redis/v8""gopkg.in/gomail.v2""gopkg.in/ini.v1"&quo…

总结 HTTP 协议的基本格式

一、HTTP 是什么 HTTP ( 全称为 " 超文本传输协议 ") 是一种应用非常广泛的 应用层协议 . HTTP 诞生与 1991 年 . 目前已经发展为最主流使用的一种应用层协议 . HTTP 协议目前有三个大版本: HTTP / 1 和 HTTP / 2 都是基于TCP 传输控制协议传输数据。最新版本的…

60. UE5 RPG 使用场景查询系统(EQS,Environment Query System)实现远程敌人寻找攻击位置

UE的Environment Query System&#xff08;EQS&#xff09;是环境查询系统&#xff0c;它是UE4和UE5中用于AI决策制定过程中的数据采集和处理的一个强大工具。EQS可以收集场景中相关的数据&#xff0c;利用生成器&#xff08;Generator&#xff09;针对用户的测试&#xff08;T…

基于SpringBoot的旅游管理系统

基于SpringBoot的旅游管理系统 旅游管理系统开发技术功能模块代码结构数据库设计运行截图源码获取 旅游管理系统 开发技术 技术&#xff1a;SpringBoot、MyBatis-Plus、MySQL、Beetl、Layui。 框架&#xff1a;基于开源框架Snowy-Layui开发。 工具&#xff1a;IDEA、Navicat等…

Python 读取.shp文件并生成图幅编号

代码适用于需要处理和分析地理空间数据的场景&#xff0c;如城市规划、环境监测或自然资源管理&#xff0c;其中它可以帮助用户读取特定区域的Shapefile文件&#xff0c;确定其地理边界&#xff0c;并基于这些边界计算出按照经纬度5度间隔的图幅编号&#xff0c;进而用于地图制…

django中,无法跳转到请求的html页面?

出现错误&#xff1a; You’re seeing this error because you have DEBUG True in your Django settings file. Change that to False, and Django will display a standard 404 page. 在urls中&#xff0c;注释了系统的默认配置&#xff0c;这时就需要在setting配置文件中&…

Android Studio 版本升级后 Gradle project sync failed(Android V 应用升级)

问题及解决方案 更新到蜥蜴 Android Studio Iguana 后&#xff0c;出现Gradle project sync failed的问题&#xff08;IDE更新版本的常态了&#xff09;。 背景&#xff1a;对应用进行Android V版本升级&#xff08;SDK35&#xff0c;gradle插件版本要 8.4.0&#xff09; 1、…

Java实现对PDF、纵向、横向页面添加自定义水印功能

Java实现对PDF、纵向、横向页面添加自定义水印 效果图 -- 纵向 页面PDF使用到JAR Maven依赖版本效果图 -- 横向页面PDF 效果图 – 纵向 页面PDF 代码如下&#xff1a; 使用到JAR Maven依赖版本 <dependency><groupId>org.apache.pdfbox</groupId><artifa…

redis 主从复制薪火相传 哨兵sentinel配置以及底层原理

薪火相传 我们知道redis的主从复制还有一个常见的架构 ---薪火相传 使用这种结构可以有效减轻master节点的复制数据同步压力 注意这里的6380节点仍然是slave节点 可以理解为一个中间节点,仍然是不可以写只可以读取的 我们只需要使用 slaveof ip port 这里可能访问节点的时候出…

yolov系列

学习一个深度学习网络&#xff0c;就看三点&#xff0c;1. 网络架构 2.输入输出 3.损失函数 yolov1 2015年诞生的YOLOv1创造性地使用端到端结构完成了物体检测任务&#xff0c;把检测问题转换成了回归问题&#xff0c;直接预测物体的类别和位置。 每个grid有30维&#xff0c;…

修改了vue3 <script setup>留言板

Лунная ночь <template><button class"edit_view_checkbox"><input type"checkbox" v-model"editshowInput" value"编辑" /></button><div class"editshowInput" v-if"editshowI…

Spring MVC+mybatis 项目入门:旅游网(二) dispatcher与controller与Spring MVC

个人博客&#xff1a;Spring MVCmybatis 项目入门:旅游网&#xff08;二&#xff09;dispatcher与controller与Spring MVC | iwtss blog 先看这个&#xff01; 这是18年的文章&#xff0c;回收站里恢复的&#xff0c;现阶段看基本是没有参考意义的&#xff0c;技术老旧脱离时代…

若依ruoyi-vue element-ui 横向滚动条 动态横向滚动条

动态横向滚动条 因为每次横向滑动都要到底部&#xff0c;引入插件 https://github.com/mizuka-wu/el-table-horizontal-scroll //动态横向滚动条移入样式 .el-table-horizontal-scrollbar :hover{//高度 变大10%transform: scaleY(1.5) translateY(-10%);//百分之八十亮度&a…

【Spring-01】BeanFactory和ApplicationContext

【Spring-01】BeanFactory和ApplicationContext 1. 容器接口1.1 什么是 BeanFactory1.2 BeanFactory 能做什么&#xff1f; 1. 容器接口 以 SpringBoot 的启动类为例&#xff1a; /*** BeanFactory 与 ApplicationContext的区别*/ SpringBootApplication public class Spring…

Grafana详解

目录 ​编辑 一、Grafana的主要特点 二、Grafana的基本功能 三、Grafana的使用方法 Grafana是一款开源的数据可视化工具&#xff0c;主要用于大规模指标数据的可视化展现。下面将详细介绍Grafana的特点、功能以及基本使用方法。 一、Grafana的主要特点 跨平台性&#xff…

牛客ONT45 距离是K的二叉树节点【中等 宽度优先遍历 Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/e280b9b5aabd42c9b36831e522485622 思路 图&#xff0c;队列 构件图&#xff0c;直接从target出发&#xff0c;扩展到第k层就是答案Java代码 import java.util.*;/** public class TreeNode {* int val 0;* …