在 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));
我正在尝试使用点精灵使用 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));