【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.rhkb.cn/news/334436.html

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

相关文章

微软Copilot+ PC:Phi-Silica

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调重新阅读。而最新科技&#xff08;Mamba&#xff0c;xLSTM,KAN&#xff09;则提供了大模…

视创云展「VR直播」是什么?有哪些功能和应用场景?

视创云展「VR直播」通过“3D沉浸式展厅直播高互动感”的创新玩法&#xff0c;使企业随时随地举办一场低成本、高互动、能获客的元宇宙直播活动成为可能。「VR直播」能实现3D展厅内VR场景漫游&#xff0c;更结合音视频交互、同屏互动等新功能&#xff0c;为用户带来更沉浸的虚拟…

[nextjs]推荐几个很好看的模板网站

最近在做网站,折腾了 vue 框架,然后发现了 nextjs 框架,感觉这个做出来的网站配色很好看,然后又开始研究这个 网站配色好看是因为用的 tailwindcss,找网站过程中,发现了几个很好看的模板网站,在这里推荐下,或许你也能用得上 推荐第一个网站是: https://tailspark.co/ 有组件,也…

fastadmin 树状菜单展开,合并;简要文件管理系统界面设计与实现

一&#xff0c;菜单合并效果图 源文件参考&#xff1a;fastadmin 子级菜单展开合并、分类父级归纳 - FastAdmin问答社区 php服务端&#xff1a; public function _initialize() {parent::_initialize();$this->model new \app\admin\model\auth\Filetype;$this->admin…

【PROXYCHAINS】Kali Linux 上配置NAT PROXYCHAINS保姆级教程

kali linux配置agent 在博主配置kali 的时候遇到了一些小问题&#xff0c;主要就是连接一直报错超时。 方法一&#xff1a;优点&#xff1a;免费&#xff0c;但是agent很不稳定 搜索免费ip,在Google搜索free proxy list 检查可用ip 连接成功 cd /etcls |grep redsnano reds…

检索模型预训练方法:RetroMAE

论文title&#xff1a;https://arxiv.org/pdf/2205.12035RetroMAE: Pre-Training Retrieval-oriented Language Models Via Masked Auto-Encoder 论文链接&#xff1a;https://arxiv.org/pdf/2205.12035 摘要 1.一种新的MAE工作流&#xff0c;编码器和解器输入进行了不同的掩…

go语言初识别(五)

本博客内容涉及到&#xff1a;切片 切片 1. 切片的概念 首先先对数组进行一下回顾&#xff1a; 数组定义完&#xff0c;长度是固定的&#xff0c;例如&#xff1a; var num [5]int [5]int{1,2,3,4,5}定义的num数组长度是5&#xff0c;表示只能存储5个整形数字&#xff0c…

SCI一区 | Matlab实现PSO-TCN-LSTM-Attention粒子群算法优化时间卷积长短期记忆神经网络融合注意力机制多变量时间序列预测

SCI一区 | Matlab实现PSO-TCN-LSTM-Attention粒子群算法优化时间卷积长短期记忆神经网络融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现PSO-TCN-LSTM-Attention粒子群算法优化时间卷积长短期记忆神经网络融合注意力机制多变量时间序列预测预测效果基本介绍程序设…

Docker 常用命令大全!!

Docker 常用命令 一、启动类1. 启动 docker2. 关闭 docker3. 重新启动 docker4. docker 设置自启动5. 查看 docker 运行状态6. 查看 docker 版本号等信息7. docker 帮助 二、 镜像类1. 查看镜像2. 搜索镜像3. 拉取镜像4. 运行镜像5. 删除镜像6. 加载镜像7. 保存镜像 三、容器类…

【css3】02-css3新特性之选择器篇

目录 1 属性选择器 2 结构伪类选择器 3 其他选择器 :target和::selection ::first-line和::first-letter 4 伪类和伪元素的区别 伪类&#xff08;Pseudo-classes&#xff09; 伪元素&#xff08;Pseudo-elements&#xff09; 伪类和伪元素的区别 1 属性选择器 ☞ 属性选…

BIO/NIO学习

在传送文件的时候常常出现这么一个问题&#xff0c;就是当客户端的文件全部传送完了之后&#xff0c;服务器没有接收到客户端那边传过的停止信号&#xff0c;所以服务器也就跟着客户端停止运行了&#xff0c;我们可以使用 try {socket.shutdownOutput();} catch (IOException e…

OrangePi AIpro开发板,使用了310B,昇腾310B较于昇腾310有何性能提升?

OrangePi AIpro开发板 他们对应的模组分别是&#xff1a;Atlas 200 AI和Atlas 200I A2 310&#xff1a;基本规格 - Atlas 200 AI加速模块 用户指南 14 - 华为 (huawei.com) 310B&#xff1a;基本规格 - Atlas 200I A2 加速模块 用户指南 04 - 华为 (huawei.com)

栈的特性及代码实现(C语言)

目录 栈的定义 栈的结构选取 链式储存结构和顺序栈储存结构的差异 栈的代码实现 "stack.h" "stack.c" 总结 栈的定义 栈&#xff1a;栈是限定仅在表尾进行插入和删除操作的线性表。 我们把运行插入的和删除的一段叫做栈顶&#xff08;TOP&#xff…

vmware hostd占用443端口解决方法

原因&#xff1a;VMware 准备弃用的虚拟机共享功能&#xff0c;目前仍然存在该进程启动&#xff0c;并且占用443端口&#xff01; 解决&#xff1a; 1.临时解决 在任务管理器中结束名为“VMware hostd”进程 2.永久生效 打开VMware &#xff0c;编辑——首选项——共享虚拟机—…

鸿蒙ArkUI-X跨平台开发:【资源分类与访问】

资源分类与访问 应用开发过程中&#xff0c;经常需要用到颜色、字体、间距、图片等资源&#xff0c;在不同的设备或配置中&#xff0c;这些资源的值可能不同。 应用资源&#xff1a;借助资源文件能力&#xff0c;开发者在应用中自定义资源&#xff0c;自行管理这些资源在不同…

【NumPy】全面解析NumPy的bitwise_xor函数:高效按位异或操作指南

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

【全开源】二手车置换平台系统小程序(FastAdmin+ThinkPHP+Uniapp)

二手车置换平台系统 特色功能&#xff1a; 车辆评估&#xff1a;系统提供车辆状况、性能和价值的评估功能&#xff0c;通过拍照、上传图片等方式自动识别车辆信息并给出估价建议&#xff0c;帮助买家和卖家更准确地了解车辆价值。 在线交易&#xff1a;平台提供在线购车、售车…

二十九、openlayers官网示例DeclutterGroup解析——避免矢量图层的文字重叠

官网demo地址&#xff1a; Declutter Group 这篇说的是如何设置矢量图层上多数据点文字不重叠。 主要是属性declutter &#xff0c;用于处理矢量图层上重叠的标注和符号&#xff0c;为true时启用去重叠功能。所有矢量特征的标注和符号都会被处理以避免重叠。false则与之相反。…

【从零开始学习RabbitMQ | 第二篇】如何确保MQ的可靠性和消费者可靠性

目录 前言&#xff1a; MQ可靠性&#xff1a; 数据持久化&#xff1a; Lazy Queue&#xff1a; 消费者可靠性&#xff1a; 消费者确认机制&#xff1a; 消费失败处理&#xff1a; MQ保证幂等性&#xff1a; 方法一&#xff1a; 总结&#xff1a; 前言&#xff1a; …

【UE5.1 角色练习】06-角色发射火球-part2

目录 效果 步骤 一、火球生命周期 二、添加可被伤害的NPC 三、添加冲量 在上一篇&#xff08;【UE5.1 角色练习】06-角色发射火球-part1&#xff09;基础上继续实现角色发射火球相关功能 效果 步骤 一、火球生命周期 为了防止火球没有命中任何物体而一直移动下去&#…