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

六、结论

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue3实战笔记(36)—粒子特效完成炫酷的404

文章目录 前言404特效总结 前言 昨天介绍了一个粒子特效小例子&#xff0c;不够直观&#xff0c;下面直接实战在自己的项目中实现一个好玩滴。 404特效 更改之前创建好的404.vue: <template><div class"container"><vue-particles id"tspartic…

EXCEL如何自动根据上行内容填充到空格

接上篇文章&#xff0c;经过宏命令后会有空格出现&#xff0c;那么如何自动根据上行内容填充到空格呢&#xff1f; 请看步骤~ ctrl G 选择空值–》定位 -》 按 -》然后等于上一行 -》ctrl enter 一。全选表格&#xff0c; ctrl G 调出界面&#xff0c;选择空值按下定位 …

二分答案思想下的二进制问题

序列合并 题目描述 给定一个长度为 n n n 的非负整数序列 { a n } \{a_n\} {an​}&#xff0c;你可以进行 k k k 次操作&#xff0c;每次操作你选择两个相邻的数&#xff0c;把它们合并成它们的按位或。 形式化地&#xff0c;一次操作中&#xff0c;你选择一个下标 i i …

Transformer模型架构笔记

0. 简介 Transformer是一种用于自然语言处理&#xff08;NLP&#xff09;和其他序列到序列&#xff08;sequence-to-sequence&#xff09;任务的深度学习模型架构&#xff0c;它在2017年由Vaswani等人首次提出。Transformer架构引入了自注意力机制&#xff08;self-attention …

windows中每日定时执行python脚本,解决问题

由于需要一个每天定时执行的任务&#xff0c;所以需要定时启动&#xff0c;网上看了很多方法&#xff0c;感觉不能在python脚本种写个while true 定时执行&#xff0c;占资源不说还不可靠。 最后考虑通过系统工具定时启动&#xff0c;发现linux中有crontab&#xff0c;windows…

JMH304-剑侠情缘2网络版+2017纹饰端+翅膀+单机+外网整理+各种副本

资源介绍&#xff1a; 藏剑-太虚-梁山-杀手堂种树地宫师门纹饰装备长流云阳套等等———– 做登录器联系站长 资源截图&#xff1a; 下载地址

2023、2024国赛web复现wp

2023 Unzip 类型&#xff1a;任意文件上传漏洞 主要知识点&#xff1a;软链接 随便上传一个一句话木马文件&#xff0c;得到一串php代码 根据代码上传zip文件发现进入后还是此页面 代码审计&#xff1a; <?php error_reporting(0); highlight_file(__FILE__);$finfo fin…

Mac免费软件推荐

1. iTerm2 - 功能强大的终端 iTerm2 是一个功能强大且灵活的终端仿真器&#xff08;可替代系统默认终端&#xff09;&#xff0c;适合需要在 macOS 上进行大量终端操作的用户。其丰富的功能和高可定制性使得 iTerm2 成为许多开发者和系统管理员的首选工具。无论是处理多个会话…

基于MyBatisPlus表结构维护工具

SuperTable表结构维护工具 一、简述 用于同步表实体与数据库表结构&#xff0c;同步建表、删改字段、索引&#xff0c;种子数据的工具… 一、开发环境 JDK&#xff1a;JDK8SpringBoot&#xff1a;2.7.2MyBatisPlus: 3.5.6MySQL: 5.7其他依赖&#xff1a;略 二、特性 表结…

5G工业数采网关的功能及工业应用-天拓四方

随着5G技术的不断发展&#xff0c;其在工业领域的应用日益广泛。5G工业数采网关作为连接工业设备与网络的重要枢纽&#xff0c;具备多种功能&#xff0c;为工业自动化、智能制造和智慧工厂提供了强大的支持。本文将详细解析5G工业数采网关的功能&#xff0c;并探讨其在工业领域…

【调试笔记-20240528-Linux-用 OpenWrt-23.05 SDK 编译 frp 软件包】

调试笔记-系列文章目录 调试笔记-20240528-Linux-用 OpenWrt-23.05 SDK 编译 frp 软件包 文章目录 调试笔记-系列文章目录调试笔记-20240528-Linux-用 OpenWrt-23.05 SDK 编译 frp 软件包 前言一、调试环境操作系统&#xff1a;Ubuntu 22.04.4 LTS编译环境调试目标 二、调试步…

剖析【C++】——类与对象(中)——小白篇—超详解

目录 1.类的6个默认成员函数&#xff1a; 1. 默认构造函数&#xff08;Default Constructor&#xff09; 2. 析构函数&#xff08;Destructor&#xff09; 3. 拷贝构造函数&#xff08;Copy Constructor&#xff09; 4. 拷贝赋值运算符&#xff08;Copy Assignment Operato…

【RK3288 Android10 T8pro usb hid-multitouch idc配置】

【RK3288 Android10 T8pro usb hid-multitouch idc配置】 文章目录 【RK3288 Android10 T8pro usb hid-multitouch idc配置】背景代码分析1. 读取配置文件2. 标志内外置屏幕3. 设置输入设备4. findviewport()5. 根据对应的viewport来计算相应的mapping的参数 结论 背景 T8pro …

C++网络编程——socket

在服务器中&#xff0c;需要建立一个socket套接字才能对外提供一个网络通信接口&#xff0c;在Linux系统中套接字仅是一个文件描述符&#xff0c;也就是一个int类型的值 socket概念 socket 的原意是“插座”&#xff0c;在计算机通信领域&#xff0c;socket 被翻译为“套接字…

骆驼大赛

目录 一&#xff0c;主版图 二&#xff0c;骰子 三&#xff0c;初始设置 四&#xff0c;核心规则 五&#xff0c;结算 这是适合5-8人玩的一个概率推理类的回合制桌游。 一&#xff0c;主版图 赛道由16个格子组成&#xff0c;编号为1-16。 一共7个骆驼&#xff0c;其中正…

python如何巧妙地利用内置函数与列表切片组织舞会派对

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、问题分析 三、解决方案 1. 利用内置函数创建参会人员名单 2. 利用列表切片…

【教学类-58-06】黑白三角拼图06(1页3张彩色黑点卡片,一种宫格36张,适合一个班级一次操作)

作品展示 背景需求 【教学类-58-05】黑白三角拼图05&#xff08;2-10宫格&#xff0c;每个宫格随机1张-6张&#xff0c;带空格纸&#xff0c;1页3张黑白3张白卡&#xff09;-CSDN博客文章浏览阅读343次&#xff0c;点赞10次&#xff0c;收藏6次。【教学类-58-05】黑白三角拼图…

基于深度强化学习的无人车自适应速度规划

论文&#xff1a;Adaptive speed planning for Unmanned Vehicle Based on Deep Reinforcement Learning 编辑&#xff1a;东岸因为一点人工一点智能 基于深度强化学习的无人车自适应速度规划本文对无人车辆的速度规划部分进行了一些改进。首先&#xff0c;将车辆速度与车辆与…

Excel中怎样将第一行建立好的规则套用到每一行?

考虑使用条件格式来完成&#xff0c;有两种方式可以尝试&#xff1a; 一、一次性创建条件格式 1.选中需要设置条件格式的区域&#xff0c;如果是不连续的区域&#xff0c;可以按住Ctrl键&#xff0c;然后用鼠标依次选中需要的数据区域 2.点击 开始选项卡&#xff0c;条件格式…

探索python循环逻辑的魅力:从无限到有限

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;循环逻辑的初步认识 二、无限循环&#xff1a;持续运转的引擎 三、有…