Я пытаюсь сделать систему частиц с OpenGL 3, используя точечный спрайт.
Я использую VBO с GL_STREAM_DRAW, где я помещаю координаты каждой частицы.
В течение каждого кадра я обновляю VBO с новой координатой частицы. Частица просто визуализируется с помощью GL_POINTS, используя GL_VERTEX_PROGRAM_POINT_SIZE.
Я заметил, что некоторые частицы были наложены друг на друга, несмотря на то, что они должны были быть ближе к камере.
Точечный спрайт фактически рисуется по порядку вызова отрисовки, а не по глубине, что создает такую ситуацию:
Здесь самая дальняя частица рисуется первой, а замыкающая частица — второй. Как и ожидалось, частицы шкафа полностью покрывают тот, что за ним.
Здесь порядок прорисовки меняется на обратный, в результате чего самая дальняя частица видна.
Я пытаюсь использовать глубину тестирования OpenGL с помощью
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LEQUAL);
glDepthRange(0.f, 1.f);
glEnable(GL_DEPTH_CLAMP);
Но это просто не приводит к ничьей.
Насколько я понимаю, один из способов решения этой проблемы — переупорядочить частицы по глубине, но для многих частиц это решение будет очень дорогостоящим для ЦП, поэтому есть ли способ провести надлежащее тестирование глубины для точечного спрайта на графическом процессоре?
Использование вершинного шейдера для рисования частицы следующее:
#version 330
layout(location = 0) in vec4 position;
uniform float time;
uniform mat4 camera;
smooth out float dist;
void main()
{
vec4 cameraPos = position + vec4(0.0, 0.0, -1.0, 0.0);
gl_Position = camera * cameraPos;
dist = sqrt(dot(camera * cameraPos, position));
gl_PointSize = 15.0/dist;
}
Фрагмент шейдера:
#version 330
out vec4 colour;
uniform float time;
smooth in float dist;
float map(float value, float inMin, float inMax, float outMin, float outMax) {
return outMin + (outMax - outMin) * (value - inMin) / (inMax - inMin);
}
void main()
{
// colour = vec4(pos.x, pos.y, 1.0, 1.0);
if(dot(gl_PointCoord-0.5,gl_PointCoord-0.5)>0.25)
discard;
else {
float g = (dot(gl_PointCoord-0.5,gl_PointCoord-0.5) > 0.22 ? 0.6 : map(dot(gl_PointCoord-0.5,gl_PointCoord-0.5), 0.0, 0.21, 0.0, 0.6));
colour = vec4(g, g*sin(time)*sin(time)*cos(time), sin(dist), 1.0);
}
}
Полный код (без некоторого стандартного кода):
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/vec4.hpp>
#include <glm/mat4x4.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/trigonometric.hpp>
#include <algorithm>
#include <string>
#include <iostream>
#include <vector>
#include <random>
#include <cmath>
#include "tools.h"#include "shader.h"#include "data.h"
#define BENCHMARK 230000
#define MAX_POINT 2
#define TTL 100
void init_program(GLuint* program)
{
std::vector<GLuint> shaders;
shaders.push_back(create_shader(GL_VERTEX_SHADER, read_file("data/particle.vs")));
shaders.push_back(create_shader(GL_FRAGMENT_SHADER, read_file("data/particle.fs")));
*program = create_program(shaders);
std::for_each(shaders.begin(), shaders.end(), glDeleteShader);
}
bool first=true;
void create_new_point(Point* p)
{
// Testing draw order
if(first)
p->pos = glm::vec4(0.f, 0.f, 0.f, 1.f);
else
p->pos = glm::vec4(0.f, 0.f, 0.8, 1.f);
p->dir = glm::vec4(0.f, 0.f, 0.f, 0.f);
p->ttl = TTL+(TTL*(distrib(gen)/2.0));
first = false;
}
void update_point(Point* p, double dt)
{
if((p->ttl - dt) <= 0)
create_new_point(p);
else
{
glm::vec4 speed(dt/2.0);
p->pos += (p->dir*speed);
p->ttl = p->ttl - dt;
}
}
void vbo_point(std::vector<Point>& points, float* data, GLuint* vbo, bool update)
{
for(size_t n=0; n<points.size(); ++n)
{
if(update)
{
data[n*4] = points[n].pos.x;
data[n*4+1] = points[n].pos.y;
data[n*4+2] = points[n].pos.z;
data[n*4+3] = points[n].pos.w;
}
else
{
data[n*4] = 0;
data[n*4+1] = 0;
data[n*4+2] = 0;
data[n*4+3] = 0;
}
}
glBindBuffer(GL_ARRAY_BUFFER, *vbo);
if(update)
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float)*4*points.size(), data);
else
glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*points.size(), data, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
int main(void)
{
GLFWwindow* window;
if (!glfwInit())
return -1;
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
window = glfwCreateWindow(1280, 768, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);
// Init data
GLuint vbo, vao, program;
glGenBuffers(1, &vbo);
init_program(&program);
// VAO
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
/*
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
glDepthFunc(GL_LEQUAL);
glDepthRange(0.f, 1.f);
glEnable(GL_DEPTH_CLAMP);
glEnable(GL_BLEND) ;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
*/
// Time data
double prev = 0.0;
double curr = 0.0;
double frameTime = 0.0;
// Init Points
std::vector<Point> points;
for(size_t n=0; n<MAX_POINT; ++n)
{
Point tmp = {glm::vec4(0), glm::vec4(0), 0};
points.push_back(tmp);
}
float* data = new float[4*points.size()];
for(size_t n=0; n<points.size(); ++n)
update_point(&points[n], 0);
vbo_point(points, data, &vbo, false);
glfwSwapInterval(1);
GLint time = glGetUniformLocation(program, "time");
GLint camera_location = glGetUniformLocation(program, "camera");
glm::mat4 camera_matrix = glm::perspective(glm::radians(45.f), 1.33f, 0.1f, 10.f);
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
curr = glfwGetTime();
/* Render here */
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1.f, 1.f, 1.f, 0.f);
glUseProgram(program);
glUniform1f(time, glfwGetTime());
glUniformMatrix4fv(camera_location, 1, GL_FALSE, glm::value_ptr(camera_matrix));
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_POINTS, 0, points.size());
glDisableVertexAttribArray(0);
glUseProgram(0);
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
for(size_t n=0; n<points.size(); ++n)
update_point(&points[n], frameTime);
vbo_point(points, data, &vbo, true);
std::cout << std::fixed;
std::cout.precision(8);
std::cout << "\rfps: " << 1.f/frameTime << " | Point drawed :" << points.size()
<< " | TTL1: " << points[0].ttl;
prev = glfwGetTime();
frameTime = prev-curr;
}
delete[] data;
glfwTerminate();
return 0;
}
Проблема была на самом деле связана с 2 проблемы:
1 — Буфер глубины никогда не очищался как пользователь derhass упоминается в комментарии.
2 — Размер точки в буфере вершин был вычислен неверно. Перспективная матрица была применена только к положению камеры, а не к положению вершины. Так должно быть dist = distance(cameraPos, position));
вместо dist = sqrt(dot(camera * cameraPos, position));
Других решений пока нет …