在 OpenGL 中使用点精灵进行深度测试

Depth test with point sprite in OpenGL

我正在尝试使用点精灵使用 OpenGL 3 制作粒子系统。

我使用带有 GL_STREAM_DRAW 的 VBO,我在其中放置每个粒子的坐标。 在每一帧中,我用新的粒子坐标更新 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);

但它只是导致什么都没有被绘制。

据我了解,解决此问题的一种方法是按深度对粒子重新排序,但此解决方案在 CPU 上对于许多粒子来说成本非常高,所以有没有办法进行适当的深度测试对于 GPU 上的点精灵?

用于绘制粒子的顶点着色器如下:

#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 - 深度缓冲区从未被清除,因为评论中提到的用户

2 - 顶点缓冲区中点大小的计算方式有误。透视矩阵仅应用于相机位置,而不应用于顶点位置。它应该是 dist = distance(cameraPos, position)); 而不是 dist = sqrt(dot(camera * cameraPos, position));