Внедрение миллионов объектов в OpenGL: улучшение кадров в секунду

Моя конечная цель — сделать 1 миллион сфер разных размеров и цветов с частотой 60 кадров в секунду. Я хочу иметь возможность перемещать камеру по экрану.

Я изменил код на эту страницу учебника я изучаю попытаться создать экземпляр многих сфер. Тем не менее, я считаю, что всего в 64 сферах мой fps падает ниже 60, а в 900 сферах мой fps — жалкое 4. Мое понимание инстансинга наивно, но я считаю, что я должен получать больше кадров в секунду, чем этот. 60 кадров в секунду должны быть достижимы только с 64 сферами. Я считаю, что я каким-то образом заставляю CPU и GPU взаимодействовать чаще, чем нужно. Итак, мой вопрос: как я могу создать так много объектов (в идеале миллионы) без снижения частоты кадров (в идеале 60 кадров в секунду)?

Я рассчитываю FPS путем расчета (10 / time_elapsed) каждые 10 кадров, где time_elapsed время, прошедшее с момента последнего вызова fps. Я печатаю это, используя printf в строке 118 моего кода.

Я изучал OpenGL через этот урок и поэтому я использую 32-битный GLEW а также 32-битный GLFW в Visual Studio 2013. У меня 8 ГБ ОЗУ в 64-разрядной операционной системе (Windows 7) с Процессор с частотой 2,30 ГГц.

Я попытался написать собственный пример на основе учебника выше. Исходный код:

(установите в строке № 2 количество сфер, которые нужно создать. Убедитесь, что строка № 2 имеет квадратный корень из целых чисел. Установите в строке 4 детали сфер, самое низкое значение, которое она может пройти, — 0. Более высокое число = более подробный.)

// Make sure NUM_INS is a square number
#define NUM_INS 1

// Detail up to 4 is probably good enough
#define SPHERE_DETAIL 4

#include <vector>

// GLEW
#define GLEW_STATIC
#include <GL/glew.h>

// GLFW
#include <GLFW/glfw3.h>

// GL includes
#include "Shader.h"
// GLM Mathemtics
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// Properties
GLuint screenWidth = 800, screenHeight = 600;

// Function prototypes
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
std::vector<GLfloat> create_sphere(int recursion);

// The MAIN function, from here we start our application and run the Game loop
int main()
{
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);

GLFWwindow* window = glfwCreateWindow(screenWidth, screenHeight, "LearnOpenGL", nullptr, nullptr); // Windowed
glfwMakeContextCurrent(window);

// Set the required callback functions
glfwSetKeyCallback(window, key_callback);

// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
glewInit();

// Define the viewport dimensions
glViewport(0, 0, screenWidth, screenHeight);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // Comment to remove wireframe mode

// Setup OpenGL options
glEnable(GL_DEPTH_TEST);

// Setup and compile our shader(s)
Shader shader("core.vs", "core.frag");

// Generate a list of 100 quad locations/translation-vectors
std::vector<glm::vec2> translations(NUM_INS);
//glm::vec2 translations[NUM_INS];
int index = 0;
GLfloat offset = 1.0f / (float)sqrt(NUM_INS);
for (GLint y = -(float)sqrt(NUM_INS); y < (float)sqrt(NUM_INS); y += 2)
{
for (GLint x = -(float)sqrt(NUM_INS); x < (float)sqrt(NUM_INS); x += 2)
{
glm::vec2 translation;
translation.x = (GLfloat)x / (float)sqrt(NUM_INS) + offset;
translation.y = (GLfloat)y / (float)sqrt(NUM_INS) + offset;
translations[index++] = translation;
}
}

// Store instance data in an array buffer
GLuint instanceVBO;
glGenBuffers(1, &instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * NUM_INS, &translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);

// create 12 vertices of a icosahedron
std::vector<GLfloat> vv = create_sphere(SPHERE_DETAIL);

GLuint quadVAO, quadVBO;
glGenVertexArrays(1, &quadVAO);
glGenBuffers(1, &quadVBO);
glBindVertexArray(quadVAO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, vv.size() * sizeof(GLfloat), &vv[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(2 * sizeof(GLfloat)));
// Also set instance data
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (GLvoid*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glVertexAttribDivisor(2, 1); // Tell OpenGL this is an instanced vertex attribute.
glBindVertexArray(0);

// For printing frames-per-second
float counter = 0;
double get_time = 0;
double new_time;

// Game loop
while (!glfwWindowShouldClose(window))
{
// Print fps by printing (number_of_frames / time_elapsed)
counter += 1;
if (counter > 10) {
counter -= 10;
new_time = glfwGetTime();
printf("fps: %.2f ", (10/(new_time - get_time)));
get_time = new_time;
}

// Check and call events
glfwPollEvents();

// Clear buffers
//glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Draw 100 instanced quads
shader.Use();

glm::mat4 model;
model = glm::rotate(model, 0.0f, glm::vec3(1.0f, 0.0f, 0.0f));
// Camera/View transformation
glm::mat4 view;
GLfloat radius = 10.0f;
GLfloat camX = sin(glfwGetTime()) * radius;
GLfloat camZ = cos(glfwGetTime()) * radius;
view = glm::lookAt(glm::vec3(camX, 0.0f, camZ), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
// Projection
glm::mat4 projection;
projection = glm::perspective(45.0f, (GLfloat)screenWidth / (GLfloat)screenHeight, 0.1f, 100.0f);
// Get the uniform locations
GLint modelLoc = glGetUniformLocation(shader.Program, "model");
GLint viewLoc = glGetUniformLocation(shader.Program, "view");
GLint projLoc = glGetUniformLocation(shader.Program, "projection");
// Pass the matrices to the shader
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(projLoc, 1, GL_FALSE, glm::value_ptr(projection));

glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, vv.size() / 3, NUM_INS); // 100 triangles of 6 vertices each
glBindVertexArray(0);

// Swap the buffers
glfwSwapBuffers(window);
}

glfwTerminate();
return 0;
}

// Is called whenever a key is pressed/released via GLFW
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
}

std::vector<GLfloat> add_color(std::vector<GLfloat> sphere) {
// Add color
std::vector<GLfloat> colored_sphere;
for (GLint i = 0; i < sphere.size(); i+=9) {
colored_sphere.push_back(sphere[i]);
colored_sphere.push_back(sphere[i+1]);
colored_sphere.push_back(sphere[i+2]);
colored_sphere.push_back(0.0f);
colored_sphere.push_back(0.0f);
colored_sphere.push_back(0.0f);
colored_sphere.push_back(sphere[i+3]);
colored_sphere.push_back(sphere[i+4]);
colored_sphere.push_back(sphere[i+5]);
colored_sphere.push_back(0.0f);
colored_sphere.push_back(0.0f);
colored_sphere.push_back(0.0f);
colored_sphere.push_back(sphere[i+6]);
colored_sphere.push_back(sphere[i+7]);
colored_sphere.push_back(sphere[i+8]);
colored_sphere.push_back(0.0f);
colored_sphere.push_back(0.0f);
colored_sphere.push_back(0.0f);
}

return colored_sphere;
}

std::vector<GLfloat> tesselate(std::vector<GLfloat> shape, int recursion) {

if (recursion > 0) {
std::vector<GLfloat> new_sphere = {};
for (GLint i = 0; i < shape.size(); i += 9) {

// 1.902113 approximately
GLfloat radius = sqrt(1.0f + pow((1.0f + sqrt(5.0f)) / 2.0f, 2));

// Every 9 points is a triangle.  Take 1 triangle and turn it into 4 triangles.
GLfloat p_one[] = {shape[i], shape[i + 1], shape[i + 2]};
GLfloat p_two[] = {shape[i + 3], shape[i + 4], shape[i + 5]};
GLfloat p_thr[] = {shape[i + 6], shape[i + 7], shape[i + 8]};
GLfloat p_one_two[] = { (p_one[0] + p_two[0]) / 2.0f, (p_one[1] + p_two[1]) / 2.0f, (p_one[2] + p_two[2]) / 2.0f };
GLfloat p_one_thr[] = { (p_one[0] + p_thr[0]) / 2.0f, (p_one[1] + p_thr[1]) / 2.0f, (p_one[2] + p_thr[2]) / 2.0f };
GLfloat p_two_thr[] = { (p_two[0] + p_thr[0]) / 2.0f, (p_two[1] + p_thr[1]) / 2.0f, (p_two[2] + p_thr[2]) / 2.0f };

GLfloat r_one_two = sqrt((p_one_two[0]*p_one_two[0]) + (p_one_two[1]*p_one_two[1]) + (p_one_two[2]*p_one_two[2]));
GLfloat r_one_thr = sqrt((p_one_thr[0]*p_one_thr[0]) + (p_one_thr[1]*p_one_thr[1]) + (p_one_thr[2]*p_one_thr[2]));
GLfloat r_two_thr = sqrt((p_two_thr[0]*p_two_thr[0]) + (p_two_thr[1]*p_two_thr[1]) + (p_two_thr[2]*p_two_thr[2]));

GLfloat t_one_two[] = { radius * p_one_two[0] / r_one_two, radius * p_one_two[1] / r_one_two, radius * p_one_two[2] / r_one_two };
GLfloat t_one_thr[] = { radius * p_one_thr[0] / r_one_thr, radius * p_one_thr[1] / r_one_thr, radius * p_one_thr[2] / r_one_thr };
GLfloat t_two_thr[] = { radius * p_two_thr[0] / r_two_thr, radius * p_two_thr[1] / r_two_thr, radius * p_two_thr[2] / r_two_thr };

// Triangle 1:
new_sphere.push_back(p_one[0]);
new_sphere.push_back(p_one[1]);
new_sphere.push_back(p_one[2]);

new_sphere.push_back(t_one_two[0]);
new_sphere.push_back(t_one_two[1]);
new_sphere.push_back(t_one_two[2]);

new_sphere.push_back(t_one_thr[0]);
new_sphere.push_back(t_one_thr[1]);
new_sphere.push_back(t_one_thr[2]);

// Triangle 2:
new_sphere.push_back(p_two[0]);
new_sphere.push_back(p_two[1]);
new_sphere.push_back(p_two[2]);

new_sphere.push_back(t_one_two[0]);
new_sphere.push_back(t_one_two[1]);
new_sphere.push_back(t_one_two[2]);

new_sphere.push_back(t_two_thr[0]);
new_sphere.push_back(t_two_thr[1]);
new_sphere.push_back(t_two_thr[2]);

// Triangle 3:
new_sphere.push_back(p_thr[0]);
new_sphere.push_back(p_thr[1]);
new_sphere.push_back(p_thr[2]);

new_sphere.push_back(t_one_thr[0]);
new_sphere.push_back(t_one_thr[1]);
new_sphere.push_back(t_one_thr[2]);

new_sphere.push_back(t_two_thr[0]);
new_sphere.push_back(t_two_thr[1]);
new_sphere.push_back(t_two_thr[2]);

// Center Triangle:

new_sphere.push_back(t_one_two[0]);
new_sphere.push_back(t_one_two[1]);
new_sphere.push_back(t_one_two[2]);

new_sphere.push_back(t_one_thr[0]);
new_sphere.push_back(t_one_thr[1]);
new_sphere.push_back(t_one_thr[2]);

new_sphere.push_back(t_two_thr[0]);
new_sphere.push_back(t_two_thr[1]);
new_sphere.push_back(t_two_thr[2]);
}
return tesselate(new_sphere, recursion - 1);
}

printf("number of vertices to be rendered: %d || ", shape.size());
return shape;
}

std::vector<GLfloat> create_sphere(int recursion) {

// Define the starting icosahedron
GLfloat t_ = (1.0f + sqrt(5.0f)) / 2.0f;

std::vector<GLfloat> icosahedron = {
-1.0f,  t_,  0.0f, -t_,  0.0f,  1.0f, 0.0f,  1.0f,  t_,
-1.0f,  t_,  0.0f, 0.0f,  1.0f,  t_, 1.0f,  t_,  0.0f,
-1.0f,  t_,  0.0f, 1.0f,  t_,  0.0f, 0.0f,  1.0f, -t_,
-1.0f,  t_,  0.0f, 0.0f,  1.0f, -t_, -t_,  0.0f, -1.0f,
-1.0f,  t_,  0.0f, -t_,  0.0f, -1.0f, -t_,  0.0f,  1.0f,

1.0f,  t_,  0.0f, 0.0f,  1.0f,  t_,  t_,  0.0f,  1.0f,
0.0f,  1.0f,  t_, -t_,  0.0f,  1.0f,  0.0f, -1.0f,  t_,
-t_,  0.0f,  1.0f, -t_,  0.0f, -1.0f, -1.0f, -t_,  0.0f,
-t_,  0.0f, -1.0f,  0.0f,  1.0f, -t_,  0.0f, -1.0f, -t_,
0.0f,  1.0f, -t_,  1.0f,  t_,  0.0f,  t_,  0.0f, -1.0f,

1.0f, -t_,  0.0f, t_,  0.0f,  1.0f, 0.0f, -1.0f,  t_,
1.0f, -t_,  0.0f, 0.0f, -1.0f,  t_,-1.0f, -t_,  0.0f,
1.0f, -t_,  0.0f,-1.0f, -t_,  0.0f, 0.0f, -1.0f, -t_,
1.0f, -t_,  0.0f, 0.0f, -1.0f, -t_, t_,  0.0f, -1.0f,
1.0f, -t_,  0.0f, t_,  0.0f, -1.0f, t_,  0.0f,  1.0f,

0.0f, -1.0f,  t_, t_,  0.0f,  1.0f, 0.0f,  1.0f,  t_,
-1.0f, -t_,  0.0f, 0.0f, -1.0f,  t_,-t_,  0.0f,  1.0f,
0.0f, -1.0f, -t_,-1.0f, -t_,  0.0f,-t_,  0.0f, -1.0f,
t_,  0.0f, -1.0f, 0.0f, -1.0f, -t_, 0.0f,  1.0f, -t_,
t_,  0.0f,  1.0f, t_,  0.0f, -1.0f, 1.0f,  t_,  0.0f,
};

// Tesselate the icososphere the number of times recursion
std::vector<GLfloat> colorless_sphere = tesselate(icosahedron, recursion);

// Add color and return
return add_color(colorless_sphere);
}

Вершинный шейдер: (называется core.vs)

#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 offset;

out vec3 fColor;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
gl_Position = projection * view * model * vec4(position.x + offset.x, position.y + offset.y, position.z, 1.0f);
fColor = color;
}

Фрагмент шейдера: (названный core.frag)

#version 330 core
in vec3 fColor;
out vec4 color;

void main()
{
color = vec4(fColor, 1.0f);
}

Класс шейдеров: (названный Shader.h)

#ifndef SHADER_H
#define SHADER_H

#include <string>
#include <fstream>
#include <sstream>
#include <iostream>

#include <GL/glew.h>

class Shader
{
public:
GLuint Program;
// Constructor generates the shader on the fly
Shader(const GLchar* vertexPath, const GLchar* fragmentPath)
{
// 1. Retrieve the vertex/fragment source code from filePath
std::string vertexCode;
std::string fragmentCode;
std::ifstream vShaderFile;
std::ifstream fShaderFile;
// ensures ifstream objects can throw exceptions:
vShaderFile.exceptions(std::ifstream::badbit);
fShaderFile.exceptions(std::ifstream::badbit);
try
{
// Open files
vShaderFile.open(vertexPath);
fShaderFile.open(fragmentPath);
std::stringstream vShaderStream, fShaderStream;
// Read file's buffer contents into streams
vShaderStream << vShaderFile.rdbuf();
fShaderStream << fShaderFile.rdbuf();
// close file handlers
vShaderFile.close();
fShaderFile.close();
// Convert stream into string
vertexCode = vShaderStream.str();
fragmentCode = fShaderStream.str();
}
catch (std::ifstream::failure e)
{
std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;
}
const GLchar* vShaderCode = vertexCode.c_str();
const GLchar * fShaderCode = fragmentCode.c_str();
// 2. Compile shaders
GLuint vertex, fragment;
GLint success;
GLchar infoLog[512];
// Vertex Shader
vertex = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex, 1, &vShaderCode, NULL);
glCompileShader(vertex);
// Print compile errors if any
glGetShaderiv(vertex, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertex, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// Fragment Shader
fragment = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment, 1, &fShaderCode, NULL);
glCompileShader(fragment);
// Print compile errors if any
glGetShaderiv(fragment, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragment, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// Shader Program
this->Program = glCreateProgram();
glAttachShader(this->Program, vertex);
glAttachShader(this->Program, fragment);
glLinkProgram(this->Program);
// Print linking errors if any
glGetProgramiv(this->Program, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(this->Program, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// Delete the shaders as they're linked into our program now and no longer necessery
glDeleteShader(vertex);
glDeleteShader(fragment);

}
// Uses the current shader
void Use()
{
glUseProgram(this->Program);
}
};

#endif

3

Решение

Моя конечная цель — сделать 1 миллион сфер разных размеров и цветов с частотой 60 кадров в секунду.

Это необоснованное ожидание.

Допустим, что каждая сфера состоит из 50 треугольников. Вроде маленький для хорошей формы сферы, но давайте предположим, что они такие маленькие.

1 миллион сфер при 50 трис на сферу — это 50 миллионов треугольников за кадр. При 60 FPS это 3 миллиард треугольники в секунду.

Ни один коммерчески доступный графический процессор не достаточно хорош для этого. И это просто сфера из 50 треугольников; ваш 4x мозаичный икосаэдр закончится 5000 треугольники.

Теперь да, рисование 60 таких сфер составляет всего ~ 300 000 треугольников на кадр. Но даже это при 60 FPS составляет ~ 18 миллионов треугольников в секунду. Существует аппаратное обеспечение, которое может обрабатывать столько треугольников, но это очень много. И вы определенно не собираюсь получить 1 миллион из них.

Это не связано с обменом данными между процессором или процессором. Вы просто тратите больше работы на свой графический процессор, чем он мог бы справиться. Возможно, вам удастся улучшить кое-что здесь и там, но ничто не даст вам даже одну десятую того, что вы хотите.

По крайней мере, не с этим общим подходом.


Для вашего конкретного случая, когда вы хотите нарисовать миллионы сфер, я бы использовал самозванцы с трассировкой лучей, а не реальную геометрию сфер. То есть вы рисуете четырехугольники, чьи позиции генерирует вершинный (или геометрический) шейдер. Вы генерируете квад на сферу, так что квад ограничивает сферу. Затем шейдер фрагментов выполняет простой тест пересечения лучевой сферы, чтобы определить, попадает ли рассматриваемый фрагмент (со стороны обзора камеры) в сферу или нет. Если луч не попадает в сферу, вы отбрасываете фрагмент.

Вам также необходимо изменить gl_FragDepth чтобы дать самозванцу правильное значение глубины, чтобы пересекающиеся сферы могли работать.

4

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]