您的位置:首页 > Web前端

最简单的粒子特效(Transform Feedback ) 的关键代码示例【OpenGL】

2017-01-10 21:29 1351 查看

使用 Transform Feedback 的优点:

1.允许顶点着色器的输出存储在一个 buffer 对象上,之后其他 shader 程序可以读取 buffer 对象的数据进行绘制(本shader也可以读取它作为下一次顶点着色器的输入);
2.允许在GPU(顶点着色器)上实现粒子发射器,不用依赖CPU,即不用在CPU上利用随机数初始化粒子,而把该操作迁移至顶点着色器中。

最终效果图



从上图可以看出,粒子不是同时发射的,而且运动路径并不受限与起点和终点的线性插值

关键代码和注释:
void InitEmitParticles()
{
std::string vShaderStr = TextFileRead("emit_vs.glsl");
std::string fShaderStr = TextFileRead("emit_fs.glsl");
userData->emitProgramObject = InitShaders(
vShaderStr.c_str(),
fShaderStr.c_str());

{
const char *feedbackVaryings[5] =
{
"v_position",
"v_velocity",
"v_size",
"v_curtime",
"v_lifetime"
};

// ###################################################
// Set the vertex shader outputs as transform feedback varyings
// ☆ 将顶点着色器的输出作为 transform feedback 的 varyings
// 即指定存入 transform feedback buffers 中的值
glTransformFeedbackVaryings(userData->emitProgramObject, 5, feedbackVaryings, GL_INTERLEAVED_ATTRIBS);
// ###################################################

// Link program must occur after calling glTransformFeedbackVaryings
// 必须在 glTransformFeedbackVaryings 之后
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
userData->emitTimeLoc = glGetUniformLocation(userData->emitProgramObject, "u_time");
userData->emitEmissionRateLoc = glGetUniformLocation(userData->emitProgramObject, "u_emissionRate");
userData->emitNoiseSamplerLoc = glGetUniformLocation(userData->emitProgramObject, "s_noiseTex");
}
}
void EmitParticles(float deltaTime)
{
// ☆
GLuint srcVBO = userData->particleVBOs[userData->curSrcIndex];
GLuint dstVBO = userData->particleVBOs[(userData->curSrcIndex + 1) % 2];

glUseProgram(userData->emitProgramObject);

// ☆ 传入 srcVBO 保存的 varyings
SetupVertexAttributes(srcVBO);
// ###################################################
// Set transform feedback buffer
// glBindBuffer ( GL_TRANSFORM_FEEDBACK_BUFFER, dstVBO );
// 绑定一个 buffer object 到一个索引的 buffer target 上
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, dstVBO);
// ###################################################

// Turn off rasterization - we are not drawing
// 关闭光栅化,不再绘制屏幕
glEnable(GL_RASTERIZER_DISCARD);

// Set uniforms
glUniform1f(userData->emitTimeLoc, userData->time);
glUniform1f(userData->emitEmissionRateLoc, EMISSION_RATE);

// Bind the 3D noise texture
// 绑定三维噪声纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_3D, userData->noiseTextureId);
glUniform1i(userData->emitNoiseSamplerLoc, 0);

// ###################################################
// Emit particles using transform feedback
// 指定图元类型
glBeginTransformFeedback(GL_POINTS);
// 保存变换后的 varyings 至 dstVBO 中
glDrawArrays(GL_POINTS, 0, NUM_PARTICLES);
glEndTransformFeedback();
// ###################################################
#ifdef BLOCK
// Create a sync object to ensure transform feedback results are completed before the draw that uses them.
// 确保绘制命令在使用它们时,transform feedback 的结果已经完成
// Create a new sync object and insert it into the GL command stream
userData->emitSync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
#endif

// Restore state
// 恢复初始状态
glDisable(GL_RASTERIZER_DISCARD);
glUseProgram(0);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);	//
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_3D, 0);

// Ping pong the buffers
// 交换 buffers(将 dstVBO 用于屏幕绘制和下一次的 srcVBO)
userData->curSrcIndex = (userData->curSrcIndex + 1) % 2;
}

void Draw()
{
// -----------------------------
#ifdef BLOCK
// Block the GL server until transform feedback results are completed
// 阻塞 GL server,直到 transform feedback 完成(sync object 接到通知)
glWaitSync(userData->emitSync, 0, GL_TIMEOUT_IGNORED);
// 标记为删除,当结束阻塞时删除
glDeleteSync(userData->emitSync);
#endif
// -----------------------------

// Set the viewport
glViewport(0, 0, SCREEN_W, SCREEN_H);

// Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// Use the program object
// 使用 绘制 shader
glUseProgram(userData->drawProgramObject);

// Load the VBO and vertex attributes
// ☆ 注意是当前的 VBO(最新的 transform feedback 的结果)
SetupVertexAttributes(userData->particleVBOs[userData->curSrcIndex]);

// Set uniforms
glUniform1f(userData->drawTimeLoc, userData->time);
glUniform4f(userData->drawColorLoc, 1.0f, 1.0f, 1.0f, 1.0f);
glUniform2f(userData->drawAccelerationLoc, 0.0f, ACCELERATION);

// Blend particles
glEnable(GL_BLEND);
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
//glBlendFunc(GL_ONE, GL_ONE);

glDrawArrays(GL_POINTS, 0, NUM_PARTICLES);

glutSwapBuffers();
}


其余代码和注释:
#include "stdafx.h"
#include <GL/glew.h>
#include <GL/freeglut.h>

#include "Noise3D.hpp"
#include "Utils.hpp"

#include "UserData.h"

//#define BLOCK 1
#define SCREEN_W 640
#define SCREEN_H 320

#define EMISSION_RATE .2f
#define ACCELERATION -.5f

enum ATTR_ENUM
{
ATTRIBUTE_POSITION = 0,
ATTRIBUTE_VELOCITY = 1,
ATTRIBUTE_SIZE = 2,
ATTRIBUTE_CURTIME = 3,
ATTRIBUTE_LIFETIME = 4
};

UserData *userData = NULL;

// 初始化 Shader Program,返回 Program ID
GLuint InitShaders(const char *vs, const char *fs)
{
GLint vertCompiled, fragCompiled, linked;

// Shaders
GLint v = glCreateShader(GL_VERTEX_SHADER);
GLint f = glCreateShader(GL_FRAGMENT_SHADER);

glShaderSource(v, 1, &vs, NULL);
glShaderSource(f, 1, &fs, NULL);

glCompileShader(v);
glGetShaderiv(v, GL_COMPILE_STATUS, &vertCompiled); // Debug
if (vertCompiled != GL_TRUE)
{
printf("Vertex Shader compied error! \n");
}

glCompileShader(f);
glGetShaderiv(f, GL_COMPILE_STATUS, &fragCompiled);
if (fragCompiled != GL_TRUE)
{
printf("Fragment Shader compied error! \n");
}

//Program:
GLuint p = glCreateProgram();
glAttachShader(p, v);
glAttachShader(p, f);

glLinkProgram(p);
glGetProgramiv(p, GL_LINK_STATUS, &linked); // Debug
if (linked != GL_TRUE)
{
printf("Program linked error! \n");
}

return p;
}

void InitEmitParticles()
{
...
}

///
// Initialize the shader and program object
//
int Init()
{
Particle particleData[NUM_PARTICLES];

int i;
// ☆
InitEmitParticles();

std::string vShaderStr = TextFileRead("draw_vs.glsl");
std::string fShaderStr = TextFileRead("draw_fs.glsl");
// Load the shaders and get a linked program object
userData->drawProgramObject = InitShaders(vShaderStr.c_str(), fShaderStr.c_str());

// Get the uniform locations
userData->drawTimeLoc = glGetUniformLocation(userData->drawProgramObject, "u_time");
userData->drawColorLoc = glGetUniformLocation(userData->drawProgramObject, "u_color");
userData->drawAccelerationLoc = glGetUniformLocation(userData->drawProgramObject, "u_acceleration");

userData->time = 0.0f;
userData->curSrcIndex = 0;

glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

// Create a 3D noise texture for random values
// 创建 3D 噪声纹理 (128*128)
userData->noiseTextureId = Create3DNoiseTexture(128, 50.0);

// Initialize particle data
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;
}

// Create the particle VBOs
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); // ❤ 意味着该buffer会被频繁写入
}

return TRUE;
}

void SetupVertexAttributes(GLuint vboID)
{
glBindBuffer(GL_ARRAY_BUFFER, vboID);
glVertexAttribPointer(ATTRIBUTE_POSITION, 2, GL_FLOAT,
GL_FALSE, sizeof(Particle),
(const void *)NULL);

glVertexAttribPointer(ATTRIBUTE_VELOCITY, 2, GL_FLOAT,
GL_FALSE, sizeof(Particle),
(const void *)offsetof(Particle, velocity[0]));

glVertexAttribPointer(ATTRIBUTE_SIZE, 1, GL_FLOAT,
GL_FALSE, sizeof(Particle),
(const void *)offsetof(Particle, size));

glVertexAttribPointer(ATTRIBUTE_CURTIME, 1, GL_FLOAT,
GL_FALSE, sizeof(Particle),
(const void *)offsetof(Particle, curtime));

glVertexAttribPointer(ATTRIBUTE_LIFETIME, 1, GL_FLOAT,
GL_FALSE, sizeof(Particle),
(const void *)offsetof(Particle, lifetime));

glEnableVertexAttribArray(ATTRIBUTE_POSITION);
glEnableVertexAttribArray(ATTRIBUTE_VELOCITY);
glEnableVertexAttribArray(ATTRIBUTE_SIZE);
glEnableVertexAttribArray(ATTRIBUTE_CURTIME);
glEnableVertexAttribArray(ATTRIBUTE_LIFETIME);
}

void EmitParticles(float deltaTime)
{
...
}

///
// Update time-based variables
//
void Update(float deltaTime)
{
userData->time += deltaTime;
//printf("time = %lf\n", userData->time);
// ☆
EmitParticles(deltaTime);
}

///
// Draw a triangle using the shader pair created in Init()
//
void Draw()
{
...
}

///
// Cleanup
//
void ShutDown()
{
// Delete program object
glDeleteProgram(userData->drawProgramObject);
glDeleteProgram(userData->emitProgramObject);

glDeleteBuffers(2, &userData->particleVBOs[0]);
}

// 键盘响应事件
void ProcessNormalKeys(unsigned char key, int x, int y)
{
// Esc
if (key == 27)
{
ShutDown();
exit(0);
}
}

void Display(int value)
{
static float delta_time = 0.005f;
Update(delta_time);

Draw();
glutTimerFunc(value, &Display, value);
}

int main(int argc, char* argv[])
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB /*| GLUT_STENCIL | GLUT_DEPTH*/);
glutInitWindowPosition(100, 100);
glutInitWindowSize(SCREEN_W, SCREEN_H);
glutCreateWindow("Hello Transform Feedback !");

GLenum err = glewInit();
if (err != GLEW_OK)
{
fprintf(stderr, "Error: %s\n", glewGetErrorString(err));
exit(-2);
}

userData = new UserData;

if (!Init())
{
return GL_FALSE;
}

glutDisplayFunc(Draw);
glutTimerFunc(10, &Display, 10);
glutKeyboardFunc(ProcessNormalKeys);

glutMainLoop();

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  opengl
相关文章推荐