Depth test with point sprite in OpenGL

1k views Asked by At

I am trying to do a particle system with OpenGL 3 using point sprite.

I use a VBO with GL_STREAM_DRAW where I put the coordinate of every particle. During each frame, I update the VBO with the new particle coordinate. The particle are simply rendered with GL_POINTS, using GL_VERTEX_PROGRAM_POINT_SIZE.

I noticed that some particles where overlayed by other despite the fact that they were supposed to be closer to the camera.

The point sprite are actually drawn by order of draw call and not by depth, which create situation like this :

The farthest particle is drawn first, the closes particle is drawn second.

Here the farthest particle is drawn first, the closes particle is drawn second. As expected, the closet particles completely cover the one behind it.

This time the order is reversed.

Here, the draw order is reversed resulting in the farthest particle being visible.

I try using OpenGL depth testing using

glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_LEQUAL);
    glDepthRange(0.f, 1.f);
glEnable(GL_DEPTH_CLAMP);

But it just result in nothing being drawn.

As I understand it, one way to resolve this problem is to reorder the particles by depth but this solution would be very costly on the CPU for many particle so is there a way to have proper depth testing for point sprite on the GPU ?

The vertex shader use for drawing the particle is the following :

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

The fragment shader :

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

The full code (minus some boilerplate code):

#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;
}
1

There are 1 answers

0
Maeln On BEST ANSWER

The problem was actually tied to 2 issue :

1 - The Depth Buffer was never cleared as user derhass mentionned in the comment.

2 - The point size in the vertex buffer was computed in the wrong way. The perspective matrix was applied only to the camera position and not to the vertex position. It should be dist = distance(cameraPos, position)); instead of dist = sqrt(dot(camera * cameraPos, position));