【OpenGL手册14】实例化

news2025/4/9 6:01:03

目录

一、说明

二、实例化

三、实例化数组

四、小行星带

五、完整代码

六、结论


一、说明

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

二、实例化

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

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


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 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);
}  

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

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

使用 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 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 次绘制调用!

使用实例渲染在 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 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可能有点太高,因此请尝试调整值直到达到可接受的帧速率。

六、结论

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

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

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

相关文章

攻击同学网络,让同学断网

技术介绍&#xff1a;ARP欺骗 ARP欺骗&#xff08;ARP spoofing&#xff09;是一种网络攻击技术&#xff0c;它通过伪造ARP&#xff08;地址解析协议&#xff09;响应包来欺骗目标设备&#xff0c;使其将网络流量发送到攻击者指定的位置。具体操作步骤如下&#xff1a; 攻击者…

你的手机是如何控制你的手表之广播篇

前言 要让手机能够控制手表&#xff0c;第一步当然要让手机能够“看见”手表&#xff0c;人类作为上帝视角&#xff0c;我们是能够通过眼睛直接看见手机和手表的&#xff0c;但要让手机“看见”手表&#xff0c;就需要一端把自己的信息通过电磁波的形式发往空中&#xff0c;另…

ADIL简单测试实例

参考资料&#xff1a;https://blog.csdn.net/geyichongchujianghu/article/details/130045373这个连接是Java的代码&#xff0c;我根据它的链接写了一个kotlin版本的。 AIDL&#xff08;Android Interface Definition Language&#xff09;是Android平台上用于进程间通信&…

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

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

电力电子技术03 (1)---电路稳态分析方法

学习来源&#xff08;只用于个人学习笔记&#xff0c;建议对着老师视频学习理解更深入&#xff09;&#xff1a;2.2稳态分析的基本方法_哔哩哔哩_bilibili 一、Buck降压电路 Buck电路&#xff0c;也称为降压转换器&#xff0c;是一种DC-DC电压转换器&#xff0c;用于将输入电…

7个靠谱的副业赚钱方法,宝妈,上班族,学生党可以做的兼职副业

你是否也曾面临过这样的困境&#xff1a;生活费紧张&#xff0c;想要找份兼职来补贴家用或是满足自己的小心愿&#xff1f;别担心&#xff0c;今天我将带领你踏入这个丰富多彩的兼职世界&#xff0c;助你轻松达成月入过千的小目标&#xff01; 在我漫长的兼职探索旅程中&#…

Java GC问题排查的一些个人总结和问题复盘

个人博客 Java GC问题排查的一些个人总结和问题复盘 | iwts’s blog 是否存在GC问题判断指标 有的比较明显&#xff0c;比如发布上线后内存直接就起飞了&#xff0c;这种也是比较好排查的&#xff0c;也是最多的。如果单纯从优化角度&#xff0c;看当前应用是否需要优化&…

C++的算法:枚举法的原理及应用

在C++编程中,枚举法,又称穷举法或暴力法,是一种非常基础和常见的算法解题方法。它的核心思想在于:通过遍历所有可能的情况,然后逐一检查每个情况是否满足问题的要求,从而找到问题的解决方案。 一、枚举法的原理 枚举法的核心在于穷举和验证。它将问题的所有可能性一一列举…

高铁VR虚拟全景展示提升企业实力和形象

步入VR的神奇世界&#xff0c;感受前所未有的汽车展示体验。VR虚拟现实技术以其独特的沉浸式模拟&#xff0c;让你仿佛置身于真实展厅之中&#xff0c;尽情探索汽车的每一处细节。 一、定制化展示&#xff0c;随心所欲 VR汽车虚拟展厅打破空间束缚&#xff0c;让汽车制造商能够…

官宣|HelpLook现已入驻钉钉应用市场,助力企业知识管理知识

前一阵子OpenAI公司最新的GPT-4o技术震撼发布&#xff0c;人工智能的实际应用前景再次引起行业瞩目&#xff0c;或者被GPT4o的数据分析等特色功能折服。如您正寻求将AI技术融入企业知识管理&#xff0c;不要错过HelpLook&#xff01;HelpLook AI知识库已经正式入驻钉钉应用市场…

go语言初识别(五)

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

轻松拿捏C语言——自定义类型之【结构体】

&#x1f970;欢迎关注 轻松拿捏C语言系列&#xff0c;来和 小哇 一起进步&#xff01;✊ &#x1f389;创作不易&#xff0c;请多多支持&#x1f389; &#x1f308;感谢大家的阅读、点赞、收藏和关注&#x1f495; &#x1f339;如有问题&#xff0c;欢迎指正 1. 结构体类型的…

《当微服务遇上Ribbon:一场负载均衡的华丽舞会》

在微服务的厨房里&#xff0c;如何确保每一道服务都恰到好处&#xff1f;揭秘Spring Cloud Ribbon如何像大厨一样精心调配资源&#xff0c;让负载均衡变得像烹饪艺术一样简单&#xff01; 文章目录 Spring Cloud Ribbon 详解1. 引言微服务架构中的负载均衡需求Spring Cloud Rib…

【chagpt】广泛使用API之前:考虑成本和数据隐私

文章目录 一. 定价和标记限制二. 安全和隐私 在广泛使用API之前&#xff0c;应该考虑两个重要因素&#xff1a;成本和数据隐私。 一. 定价和标记限制 OpenAI在Pricing页面上列出了模型的定价。请注意&#xff0c;OpenAI不一定及时更新该页面上的定价信息&#xff0c;因此实际…

爱岗敬业短视频:成都科成博通文化传媒公司

爱岗敬业短视频&#xff1a;传递正能量&#xff0c;塑造职场新风尚 在当今社会&#xff0c;短视频以其独特的传播方式和广泛的受众群体&#xff0c;成为了信息传播的重要渠道。在众多短视频内容中&#xff0c;以“爱岗敬业”为主题的短视频尤为引人注目&#xff0c;成都科成博…

链表mark

什么是链表&#xff0c;链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff0c;一个是数据域一个是指针域&#xff08;存放指向下一个节点的指针&#xff09;&#xff0c;最后一个节点的指针域指向null&#xff08;空指针的意思&#xff09;。…

《TCP/IP网络编程》(第十一章)进程间通信

进程间通信意味着两个不同的进程间可以交换数据&#xff0c;它使得不同的进程能够协同工作&#xff0c;实现复杂的系统功能。 1.通过管道实现进程间通信 下图是基于 管道&#xff08;PIPE&#xff09; 的进程间通信结构模型 管道不属于进程的资源&#xff0c;属于操作系统的资…

怎么简单的把图片缩小?图片在线改大小的方法

在日常工作中经常需要在网上上传图片&#xff0c;但是一般网上不同的平台对上传的图片大小和尺寸都会有限定的要求&#xff0c;不符合要求无法正常上传使用。所以当遇到图片太大的问题时&#xff0c;该如何快速修改图片大小&#xff0c;有很多的小伙伴都很关注这个问题的解决方…

qmt量化交易策略小白学习笔记第7期【qmt策略之股票快照指标】

qmt策略之股票快照指标 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 感谢关注&#xff0c;需免费开通量化回测与咨询实盘权限&#xff0c;可以和博主联系&#xff01; 股票快照指标 提供标…

趣店集团golang一面要个20K,Channel什么情况下会出现死锁,有遇到过吗?

结束后面试官加了VX&#xff0c;并询问方便二面的时间&#xff0c;一直还没回复&#xff0c;拖着拖着给忘啦... 面试题 1、自我介绍 2、你在团队里头负责哪一块&#xff0c;这个物流开放平台流量多大 3、为什么今年3月份被从物流开放团队转到了finance财务部门&#xff0c;感…