对象同步变换反馈粒子系统:Transform FeedBack Particle System & Sync - MartinRGB/GLES30_ProgrammingGuide_NDK GitHub Wiki
同步对象
让 GPU 在 某个操作执行完之前先等待,然后将更多命令送入执行队列。
变换反馈
变换反馈允许顶点着色器的输出保存在 VBO 中,可以做到,一个 VBO 准备生成,另外一个 VBO 区域绘制,需要 2 组着色器
粒子系统中的综合运用
这个案例中的构思是:分配两个 VBO 保存粒子数据,算法在两个 VBO 中切换,步骤如下:
1.每一 frame 中,选择某一个 VBO 作为输入,绑定为 GL_ARRAY_BUFFER
,输出绑定为 GL_TRANSFORM_FEEDBACK_BUFFER
。
2.启用 GL_RASTERIZER_DISCARD
,不绘制片段
3.顶点着色器生成新粒子到 变换反馈 VBO 中,将现有粒子原封不动复制到其中。
4.禁用 GL_RASTERIZER_DISCARD
,开始绘制
5.粒子渲染到 FBO
6.下一帧中,I/O VBO 进行交换。
下面为工程中的流程简单分析
1.粒子的生成的初始化
粒子生成部分,利用传入的 3D 噪音生成随机数值,设置速度、位置、大小、当前时间、寿命,并设置为变换反馈属性,注意:输出变量必须在链接程序对象之前标记为用于变量反馈
UserData *userData = esContext->userData;
...
userData->emitProgramObject = esLoadProgram ( vShaderStr, fShaderStr );
{
// 用于变换反馈的变量
const char *feedbackVaryings[5] =
{
"v_position",
"v_velocity",
"v_size",
"v_curtime",
"v_lifetime"
};
// 将顶点着色器的输出作为变换反馈的变量,GL_INTERLEAVED_ATTRIBS制定了输出变量在缓冲区对象中交叉存取
glTransformFeedbackVaryings ( userData->emitProgramObject,
5,
feedbackVaryings,
GL_INTERLEAVED_ATTRIBS );
// 设置完变换反馈后,链接到程序对象
glLinkProgram ( userData->emitProgramObject );
// Get the uniform locations - this also needs to happen after glLinkProgram is called again so
// that the uniforms that output to varyings are active
...
}
2.粒子绘制初始化
完成粒子绘制部分,初始化变换反馈 index,初始化粒子所有属性,读取粒子的材质图,读取 3D 噪音作为随机量,并且创建两块 FBO
Particle particleData[ NUM_PARTICLES ];
UserData *userData = ( UserData * ) esContext->userData;
int i;
// Write Shader Code Here
InitEmitParticles ( esContext );
// Load the shaders and get a linked program object
userData->drawProgramObject = esLoadProgram ( vShaderStr, fShaderStr );
// Get the uniform locations
...
userData->time = 0.0f;
//初始化当前缓冲区 index 为 0
userData->curSrcIndex = 0;
glClearColor ( 0.0f, 0.0f, 0.0f, 0.0f );
userData->textureId = LoadTexture ( esContext->platformData, "smoke.tga" );
...
// 生成一段 3D 噪音图像,以便求随机值
userData->noiseTextureId = Create3DNoiseTexture ( 128, 50.0 );
// 粒子属性数据的初始化
for ( i = 0; i < NUM_PARTICLES; i++ )
{
Particle *particle = &particleData[i];
particle->position[0] = 0.0f;
particle->position[1] = 0.0f;
particle->velocity[0] = 0.0f;
particle->velocity[1] = 0.0f;
particle->size = 0.0f;
particle->curtime = 0.0f;
particle->lifetime = 0.0f;
}
// 创建两块帧缓冲区
glGenBuffers ( 2, &userData->particleVBOs[0] );
for ( i = 0; i < 2; i++ )
{
glBindBuffer ( GL_ARRAY_BUFFER, userData->particleVBOs[i] );
glBufferData ( GL_ARRAY_BUFFER, sizeof ( Particle ) * NUM_PARTICLES, particleData, GL_DYNAMIC_COPY );
}
return TRUE;
3.粒子生成过程
使用 glBindBufferBase
绑定到 GL_TRANSFORM_FEEDBACK_BUFFER
目标。
禁用光栅化来不绘制任何片段,只是执行着色器流程,把 vertex shader 中的属性输出到 VBO,这一过程中。
调用 glBeginTransformFeedback(GL_POINTS)
启动变换反馈渲染,直到 glEndTransformFeedback
为了结果完成后再传入,我们使用了同步对象 glFence()
...
//Source
GLuint srcVBO = userData->particleVBOs[ userData->curSrcIndex ];
//Destination
GLuint dstVBO = userData->particleVBOs[ ( userData->curSrcIndex + 1 ) % 2 ];
glUseProgram ( userData->emitProgramObject );
// 源缓冲区设置 Vertex 属性
SetupVertexAttributes ( esContext, srcVBO );
// Set transform feedback buffer 设置 变换反馈 的缓冲区
glBindBuffer ( GL_TRANSFORM_FEEDBACK_BUFFER, dstVBO );
glBindBufferBase ( GL_TRANSFORM_FEEDBACK_BUFFER, 0, dstVBO );
// Turn off rasterization - we are not drawing 关闭光栅化
glEnable ( GL_RASTERIZER_DISCARD );
// ### 粒子的生成流程,设置 Uniform,绑定 3D噪音Texture
...
// Emit particles using transform feedback
glBeginTransformFeedback ( GL_POINTS );
glDrawArrays ( GL_POINTS, 0, NUM_PARTICLES );
glEndTransformFeedback();
// 创建同步对象,确保上面的生成完成 Create a sync object to ensure transform feedback results are completed before the draw that uses them.
userData->emitSync = glFenceSync ( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 );
// 清空各种绑定
...
// 生成完成后,更替 index
userData->curSrcIndex = ( userData->curSrcIndex + 1 ) % 2;
然后再 update 方法中进行生成更新
4.粒子绘制过程
等待同步对象生成的完成,然后采用标准绘制流程
// 阻塞GL服务器端,直到变换反馈完成 Block the GL server until transform feedback results are completed
glWaitSync ( userData->emitSync, 0, GL_TIMEOUT_IGNORED );
glDeleteSync ( userData->emitSync );
// ### 标准绘制流程
...
5.着色器分析
生成部分
vert:
每当粒子寿命到期,着色器将其作为新活跃粒子的潜在候选,此着色器为粒子随机生成属性
//根据传入的 3D noise texture,通过 gl_VertexID 内建变量 为每个粒子选择不同的纹理坐标
float randomValue( inout float seed )
{
float vertexId = float( gl_VertexID ) / float( NUM_PARTICLES );
vec3 texCoord = vec3( u_time, vertexId, seed );
seed += 0.1;
return texture( s_noiseTex, texCoord ).r;
}
void main()
{
float seed = u_time;
float lifetime = a_curtime - u_time;
//随机生成速度,设置变换反馈的输出变量
if( lifetime <= 0.0 && randomValue(seed) < u_emissionRate )
{
v_position = vec2( 0.0, -1.0 );
v_velocity = vec2( randomValue(seed) * 2.0 - 1.00,
randomValue(seed) * 1.4 + 1.0 );
v_size = randomValue(seed) * 20.0 + 60.0;
v_curtime = u_time;
v_lifetime = 2.0;
}
else
{
v_position = a_position;
v_velocity = a_velocity;
v_size = a_size;
v_curtime = a_curtime;
v_lifetime = a_lifetime;
}
gl_Position = vec4( v_position, 0.0, 1.0 );
}
frag:
设置纯色白色
绘制部分
vert:
粒子的年龄根据生成时,属性中保存的时间戳计算,速度和位置据此更新
void main()
{
float deltaTime = u_time - a_curtime;
if ( deltaTime <= a_lifetime )
{
vec2 velocity = a_velocity + deltaTime * u_acceleration;
vec2 position = a_position + deltaTime * velocity;
gl_Position = vec4( position, 0.0, 1.0 );
gl_PointSize = a_size * ( 1.0 - deltaTime / a_lifetime );
}
else
{
gl_Position = vec4( -1000, -1000, 0, 0 );
gl_PointSize = 0.0;
}
};
frag:
设置texture和颜色