使用OpenGL 3.3实例化似乎很慢

Instancing with OpenGL 3.3 seems very slow

本文关键字:实例化 OpenGL 使用      更新时间:2023-10-16

我用c++写了一个最小的代码示例,它渲染10000种颜色屏幕上有四边形。我使用"实例化",所以只更新每一帧的模型矩阵。6个顶点的数据存储在单独的VBO中,并且将一直被重用。投影矩阵(正字法)在程序启动时被注入通过统一的。模型矩阵在CPU上使用库GLM进行计算。我测量了渲染时间,我得到的平均FPS只有52。我认为这是多到少,但我找不到错误/瓶颈在我的小样本程序。

经过分析,这3个计算似乎是用GLM完成的都很慢。我做错什么了吗?例如,如果移除旋转计算后,我的FPS提升了10帧!也许你可以帮我找出,我在这里可以做得更好的地方,以及如何做我可以优化我的样本。对我来说很重要的是,每个四元组在运行时都是单独可配置的,所以我决定使用实例化。将矩阵计算移动到GPU似乎是另一种选择,但我真的很困惑,为什么CPU在计算10000时有这么多问题model-matrices !好吧,我的CPU非常糟糕(Athlon 2 Core-Duo M300, GPU是ATI Mobility Radeon 4100),但它应该在没有可测量的时间内完成这个任务,或者?

这是一个最小的,完全工作的,可编译的例子(如果你有GLFW和GLM)。也许有人有时间可以帮我一下:)

#define GLEW_STATIC
#define GLM_FORCE_INLINE
#define GLM_FORCE_SSE2
#include "glew.h"
#include "glfw3.h"
#include "glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include <conio.h>
#include <cstdlib>
#include <iostream>
#include <ctime>
GLuint buildShader()
{
    std::string strVSCode = 
    "#version 330 coren"
    "in vec3 vertexPosition;n"
    "in mat4 modelMatrix;n"
    "uniform mat4 projectionMatrix;n"
    "out vec4 m_color;n"
    "void main() {n"
    "   vec4 vecVertex = vec4(vertexPosition, 1);n"
    "   gl_Position = projectionMatrix * modelMatrix * vecVertex;n"
    "   m_color = gl_Position;n"
    "}n";
    std::string strFSCode = "#version 330 coren"
    "out vec4 frag_colour;n"
    "in vec4 m_color;n"
    "void main() {n"
    "   frag_colour = vec4(m_color.x, m_color.y, m_color.z, 0.5f);n"
    "}n";
    GLuint gluiVertexShaderId = glCreateShader(GL_VERTEX_SHADER);
    char const * VertexSourcePointer = strVSCode.c_str();
    glShaderSource(gluiVertexShaderId, 1, &VertexSourcePointer, NULL);
    glCompileShader(gluiVertexShaderId);
    GLuint gluiFragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER);
    char const * FragmentSourcePointer = strFSCode.c_str();
    glShaderSource(gluiFragmentShaderId, 1, &FragmentSourcePointer, NULL);
    glCompileShader(gluiFragmentShaderId);
    GLuint gluiProgramId = glCreateProgram();
    glAttachShader(gluiProgramId, gluiVertexShaderId);
    glAttachShader(gluiProgramId, gluiFragmentShaderId);
    glLinkProgram(gluiProgramId);
    glDeleteShader(gluiVertexShaderId);
    glDeleteShader(gluiFragmentShaderId);
    return gluiProgramId;
}
struct Sprite
{
    glm::vec3 position, dimension;
    float speed, rotation, rx, ry;
};
struct Vertex
{
    float x, y, z;
    Vertex(){};
    Vertex(float x, float y, float z) : x(x), y(y), z(z) {}
};
int main(int arc, char **argv)
{
    // GLFW init
    int displayResWith   = 1366; //modify this here
    int displayResHeight = 768;  //modify this here
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_RED_BITS, 8);
    glfwWindowHint(GLFW_GREEN_BITS, 8);
    glfwWindowHint(GLFW_BLUE_BITS, 8);
    glfwWindowHint(GLFW_ALPHA_BITS, 8);
    glfwWindowHint(GLFW_DEPTH_BITS, 32);
    glfwWindowHint(GLFW_STENCIL_BITS, 32);
    GLFWwindow* window = glfwCreateWindow(displayResWith, displayResHeight,"Instancing", glfwGetPrimaryMonitor(),NULL);
    int width, height;
    glfwMakeContextCurrent(window);
    glfwSwapInterval(0);
    glfwGetFramebufferSize(window, &width, &height);
    //GLEW init
    glewExperimental = GL_TRUE;
    glewInit();
    const GLubyte* renderer = glGetString(GL_RENDERER);
    const GLubyte* version = glGetString(GL_VERSION);
    std::cout << "Renderer: " << renderer << std::endl;
    std::cout << "OpenGL supported version: " << version << std::endl;
    //OpenGL init
    glEnable(GL_CULL_FACE); 
    glCullFace(GL_BACK);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glClearColor(255.0f, 255.0f, 255.0f, 255.0f);
    //Shader
    GLuint programID = buildShader();
    //VBO vertexBuffer
    GLuint vertexBuffer;
    glGenBuffers(1, &vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    Vertex VertexBufferData[6];
    VertexBufferData[0] = Vertex(-0.5f, 0.5f, 0.0f);    //Links oben
    VertexBufferData[1] = Vertex(-0.5f, -0.5f, 0.0f);   //Links unten
    VertexBufferData[2] = Vertex(0.5f, -0.5f, 0.0f);    //Rechts unten
    VertexBufferData[3] = VertexBufferData[2];          //Rechts unten
    VertexBufferData[4] = Vertex(0.5f, 0.5f, 0.0f);     //Rechts oben
    VertexBufferData[5] = VertexBufferData[0];          //Links oben
    glBufferData(GL_ARRAY_BUFFER, sizeof(Vertex)*6, VertexBufferData, GL_STATIC_DRAW);
    //VBO instanceBuffer
    GLuint instanceBuffer;
    glGenBuffers(1, &instanceBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, instanceBuffer);
    int iMaxInstanceCount = 30000;
    glm::mat4 *ptrInstanceBufferData = new glm::mat4[iMaxInstanceCount];
    glBufferData(GL_ARRAY_BUFFER, iMaxInstanceCount * sizeof(glm::mat4), NULL, GL_STREAM_DRAW);
    //VAO - Start
    GLuint vertexArrayObject;
    glGenVertexArrays(1, &vertexArrayObject);
    glBindVertexArray(vertexArrayObject);
        //For VBO vertexbuffer
        glEnableVertexAttribArray(glGetAttribLocation(programID, "vertexPosition"));
        glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
        glVertexAttribPointer(
            glGetAttribLocation(programID, "vertexPosition"),
            3,                                                  
            GL_FLOAT,                                           
            GL_FALSE,                                           
            sizeof(Vertex),                                     
            (void*)0                                            
            );
        glVertexAttribDivisor(0, 0);
        //For VBO instanceBuffer
        int pos = glGetAttribLocation(programID, "modelMatrix");
        int pos1 = pos + 0;
        int pos2 = pos + 1;
        int pos3 = pos + 2;
        int pos4 = pos + 3;
        glEnableVertexAttribArray(pos1);
        glEnableVertexAttribArray(pos2);
        glEnableVertexAttribArray(pos3);
        glEnableVertexAttribArray(pos4);
        glBindBuffer(GL_ARRAY_BUFFER, instanceBuffer);
        glVertexAttribPointer(pos1, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4 * 4, (void*)(0));
        glVertexAttribPointer(pos2, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4 * 4, (void*)(sizeof(float) * 4));
        glVertexAttribPointer(pos3, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4 * 4, (void*)(sizeof(float) * 8));
        glVertexAttribPointer(pos4, 4, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4 * 4, (void*)(sizeof(float) * 12));
        glVertexAttribDivisor(pos1, 1);
        glVertexAttribDivisor(pos2, 1);
        glVertexAttribDivisor(pos3, 1);
        glVertexAttribDivisor(pos4, 1);
    glBindVertexArray(0); //VAO - End
    //Matrix vars
    glm::mat4 Projection, Rotating, Scaling, Translation, Identity;
    glm::vec3 ZRotateVec(0.0f, 0.0f, 1.0f);
    //Calc projection-matrix and put shader (uniform)
    Projection = glm::ortho(0.0f, (float)width, 0.0f, (float)height, 0.0f, 1.0f);
    glUseProgram(programID);
    glUniformMatrix4fv(glGetUniformLocation(programID, "projectionMatrix"), 1, GL_FALSE, &Projection[0][0]);
    //Creating sprites
    std::srand(static_cast<unsigned int>(std::time(0)));
    int iActInstanceCount = 10000;
    Sprite *ptrSprites = new Sprite[iActInstanceCount];
    for (int i = 0; i < iActInstanceCount; ++i)
    {
        ptrSprites[i].dimension = glm::vec3(16, 16, 1.0f);
        ptrSprites[i].position = glm::vec3(std::rand()%(width-32),std::rand()%(height-32),-1.0f *((std::rand()%256)/256.0f));
        ptrSprites[i].rotation = rand() % 360 + 0.0f;
        ptrSprites[i].rx = static_cast<float>(std::rand() % 2);
        ptrSprites[i].ry = static_cast<float>(std::rand() % 2);
        ptrSprites[i].speed = (std::rand() % 100) + 1.0f;
        if (ptrSprites[i].speed < 1.0f) ptrSprites[i].speed = 1.0f;
    }
    //FPS init
    double fFramesRendered = 0.0f;
    double fFrameMeasurementStart = 0.0f;
    double fFPS = 0.0f;
    double fCurrentTime = 0.0f;
    glfwSetTime(0);
    //Main-loop (also renderloop)
    while (!glfwWindowShouldClose(window))
    {
        //application-logic
        if (glfwGetKey(window, GLFW_KEY_ESCAPE)== GLFW_PRESS)
            glfwSetWindowShouldClose(window, GL_TRUE);
        const double fNewTime = glfwGetTime();
        double fDeltaTime = fNewTime - fCurrentTime;
        fCurrentTime = fNewTime;
        for (int i = 0; i < iActInstanceCount; ++i)
        {
            float fSpeed = ptrSprites[i].speed * static_cast<float>(fDeltaTime);
            ptrSprites[i].rotation += fSpeed;
            if (ptrSprites[i].rotation >= 360.0f) ptrSprites[i].rotation = 0.0f;
            if (ptrSprites[i].rx == 1)  ptrSprites[i].position.x = ptrSprites[i].position.x + fSpeed;
            if (ptrSprites[i].rx == 0)  ptrSprites[i].position.x = ptrSprites[i].position.x - fSpeed;
            if (ptrSprites[i].ry == 1)  ptrSprites[i].position.y = ptrSprites[i].position.y + fSpeed;
            if (ptrSprites[i].ry == 0)  ptrSprites[i].position.y = ptrSprites[i].position.y - fSpeed;
            if (ptrSprites[i].position.x <= 0) ptrSprites[i].rx = 1;
            if (ptrSprites[i].position.x + ptrSprites[i].dimension.x >= width) ptrSprites[i].rx = 0;
            if (ptrSprites[i].position.y <= 0) ptrSprites[i].ry = 1;
            if (ptrSprites[i].position.y + ptrSprites[i].dimension.y >= height) ptrSprites[i].ry = 0;
            //matrix-calculations (saved in local buffer)
            Translation = glm::translate(Identity, ptrSprites[i].position + glm::vec3(ptrSprites[i].dimension.x / 2.0f, ptrSprites[i].dimension.y / 2.0f, 0.0f));
            Scaling = glm::scale(Translation, ptrSprites[i].dimension);
            ptrInstanceBufferData[i] = glm::rotate(Scaling, ptrSprites[i].rotation, ZRotateVec);
        }
        //render-call
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glUseProgram(programID);
        glBindVertexArray(vertexArrayObject);
        glBindBuffer(GL_ARRAY_BUFFER, instanceBuffer);
        glBufferData(GL_ARRAY_BUFFER, iMaxInstanceCount * sizeof(glm::mat4), NULL, GL_STREAM_DRAW); // Buffer orphaning
        glBufferSubData(GL_ARRAY_BUFFER, 0, iActInstanceCount * sizeof(glm::mat4), ptrInstanceBufferData);
        glDrawArraysInstanced(GL_TRIANGLES, 0, 6, iActInstanceCount);
        glBindVertexArray(0);
        glfwSwapBuffers(window);
        glfwPollEvents();

        //FPS-stuff
        ++fFramesRendered;
        if ((fCurrentTime*1000.0f) >= (fFrameMeasurementStart*1000.0f) + 1000.0f)
        {
            fFPS = ((fCurrentTime*1000.0f) - (fFrameMeasurementStart*1000.0f)) / 1000.0f * fFramesRendered;
            fFrameMeasurementStart = fCurrentTime;
            fFramesRendered = 0;
            std::cout << "FPS: " << fFPS << std::endl;
        }
    }
    //Termination and cleanup
    glDeleteBuffers(1, &vertexBuffer);
    glDeleteBuffers(1, &instanceBuffer);
    glDeleteVertexArrays(1, &vertexArrayObject);
    glDeleteProgram(programID);
    glfwDestroyWindow(window);
    glfwTerminate();
    return _getch();
}

嗯,在我的机器上测试之后,它肯定是CPU有限的,所以您对OGL所做的任何事情都不会产生太大的影响。在GCC至少为- 0的情况下,我得到了大约300fps,但在- 0的情况下只有~80 fps。我的CPU非常快(i7 2600k, 4.7ghz),但我的GPU相当慢(GT 520)。我也在Ubuntu上。

一些快速的想法,可能会加快一点:

  • 将顶点位置放在顶点着色器的数组中,并使用gl_VertexID来访问它们
  • 使用GL_TRIANGLE_STRIP代替gl_triangle
  • 使用弧度表示角度,否则GLM必须转换它们

这些都不太可能产生任何影响,真的。只要确保编译器设置正确,可能就没有太多要做的了。