粒子渲染器在图形渲染中扮演着重要角色,它负责将粒子系统的数据转换成屏幕上的像素。本文将探讨粒子渲染器的设计思路、实现细节以及存在的问题,并展望未来的优化方向。
粒子渲染器的核心任务是将粒子系统的数据转化为图像。为了实现这一目标,定义了一个名为IParticleRenderer
的接口,它从粒子系统中获取数据,并在GPU上进行渲染。目前实现了一个基于OpenGL的渲染器GLParticleRenderer
。
这种“渲染-动画”分离的设计提供了极大的灵活性。例如,在进行性能测试时,创建了一个名为EmptyRenderer
的渲染器,它不需要修改任何代码就可以直接使用整个系统,尽管屏幕上不会显示任何像素,但可以收集到时间数据。同样的想法也可以应用于单元测试。
在C++中,IParticleRenderer
接口的定义如下:
class IParticleRenderer {
public:
IParticleRenderer() { }
virtual ~IParticleRenderer() { }
virtual void generate(ParticleSystem *sys, bool useQuads) = 0;
virtual void destroy() = 0;
virtual void update() = 0;
virtual void render() = 0;
};
参数useQuads
目前尚未使用。如果设置为true
,则意味着生成四边形而不是点,这将增加发送到GPU的内存量。
顶点着色器的代码如下:
#version 330
uniform mat4x4 matModelview;
uniform mat4x4 matProjection;
layout(location = 0) in vec4 vVertex;
layout(location = 1) in vec4 vColor;
out vec4 outColor;
void main() {
vec4 eyePos = matModelview * gl_Vertex;
gl_Position = matProjection * eyePos;
outColor = vColor;
float dist = length(eyePos.xyz);
float att = inversesqrt(0.1f * dist);
gl_PointSize = 2.0f * att;
}
上述顶点着色器使用颜色和位置数据,计算gl_Position
和gl_PointSize
。片段着色器相对简单,因此这里不再展示代码。
更新函数update()
的实现如下:
void GLParticleRenderer::update() {
const size_t count = m_system->numAliveParticles();
if (count > 0) {
glBindBuffer(GL_ARRAY_BUFFER, m_bufPos);
float *ptr = (float*)(m_system->finalData()->m_pos.get());
glBufferSubData(GL_ARRAY_BUFFER, 0, count*sizeof(float)*4, ptr);
glBindBuffer(GL_ARRAY_BUFFER, m_bufCol);
ptr = (float*)(m_system->finalData()->m_col.get());
glBufferSubData(GL_ARRAY_BUFFER, 0, count*sizeof(float)*4, ptr);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
}
如上所示,update()
函数获取所需数据并更新渲染器的缓冲区。
渲染函数render()
的实现如下:
void GLParticleRenderer::render() {
const size_t count = m_system->numAliveParticles();
if (count > 0) {
glBindVertexArray(m_vao);
glDrawArrays(GL_POINTS, 0, count);
glBindVertexArray(0);
}
}
加上整个上下文:
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, gParticleTexture);
glEnable(GL_PROGRAM_POINT_SIZE);
mProgram.use();
mProgram.uniformMatrix4f("matProjection", camera.projectionMatrix);
mProgram.uniformMatrix4f("matModelview", camera.modelviewMatrix);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
gpuRender.begin();
gCurrentEffect->render();
// << render()方法
gpuRender.end();
glDisable(GL_BLEND);
mProgram.disable();
OpenGL渲染器虽然简单且有效,但并非理想的生产就绪代码。以下是一些需要改进的地方:
textID
。这样,每个粒子都可以使用不同的纹理。useQuads
变量,但也许使用几何着色器生成四边形会更好。这是最初的假设和设计选择。知道,即使将CPU端优化到最高水平,也无法击败“仅GPU”的粒子系统。有很多灵活性,但性能上有所损失。