粒子特效:Particles Effect,粒子特效本质上是通过一次或者多次渲染绘制出大量位置、形状或者颜色不同的物体(粒子),形成大量粒子运动的视觉效果。所以,粒子特效天然适合用OpenGL ES 实例化(Instancing)实现。
定义粒子,通常一个粒子有一个生命值,生命值结束该粒子消失,还有描述粒子在(x, y, z)三个方向的位置(偏移)和运动速度,以及粒子的颜色等属性。可以把粒子定义成一个结构体:
struct Particle {
GLfloat dxSpeed,dySpeed,dzSpeed;// 粒子速度
GLubyte r,g,b,a; //r,g,b,a //粒子颜色
GLfloat dx,dy,dz; // 粒子的位置
GLfloat life; //粒子出现的时长
GLfloat cameraDistance;
Particle(){
dxSpeed = 1.0f;
dySpeed = 1.0f;
dzSpeed = 1.0f;
dx = 0.0f;
dy = 0.0f;
dz = 0.0f;
r = static_cast(1.0f);
g = static_cast(1.0f);
b = static_cast(1.0f);
a = static_cast(1.0f);
life = 10.0f; //粒子的生命值
}
/*操作符重载 < */
bool operator<(const Particle& that) const {
return this->cameraDistance > that.cameraDistance;
}
};
#version 300 es
precision mediump float;
layout(location = 0) in vec3 a_vertex; //粒子顶点
layout(location = 1) in vec2 a_texCoord; //粒子材质
layout(location = 2) in vec3 a_offset; //粒子位置
layout(location = 3) in vec4 a_particlesColor; //粒子颜色
uniform mat4 u_mat; //MVP矩阵 观察矩阵*模型矩阵*透视矩阵
/*输出的材质和颜色*/
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_mat * vec4(a_vertex - vec3(0.0, 0.95, 0.0) + a_offset, 1.0);
v_texCoord = a_texCoord;
v_color = a_particlesColor;
}
属性a_offset
是粒子的位置偏移,最终确定粒子的位置,属性a_particlesColor
表示照在粒子表面光的颜色,这两个属性均为实例化数组,因为每个粒子有不同的位置和颜色。
将粒子相关的属性传进来,mvp矩阵传进来,对位置做简单的计算,并将颜色和材质传递给片元着色器
#version 300 es
precision mediump float;
in vec2 v_texCoord;
in vec4 v_color;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_TextureMap;
void main() {
outColor = texture(s_TextureMap, v_texCoord) * v_color;
// outColor = texture(s_TextureMap, v_texCoord) ;
}
如果粒子希望显示材质本身,可以直接将采样器得到的颜色赋值给outColor,不用再跟v_color相乘
glGenBuffers(1, &m_ParticlesVertexVboId);
glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesVertexVboId);
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data),
g_vertex_buffer_data,
GL_STATIC_DRAW);
glGenBuffers(1, &m_ParticlesPosVboId);
glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesPosVboId);
// Initialize with empty (NULL) buffer : it will be updated later, each frame.
glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 3 * sizeof(GLfloat),
NULL, GL_DYNAMIC_DRAW);
glGenBuffers(1, &m_ParticlesColorVboId);
glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesColorVboId);
// Initialize with empty (NULL) buffer : it will be updated later, each frame.
glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 4 * sizeof(GLubyte),
NULL, GL_DYNAMIC_DRAW);
创建VBO,注意:粒子的顶点是GL_STATIC_DRAW,位置和颜色会动态变化,所以是GL_DYNAMIC_DRAW
// Generate VAO Id
glGenVertexArrays(1, &m_VaoId);
glBindVertexArray(m_VaoId);
/*给shader传递数据*/
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesVertexVboId);
glVertexAttribPointer(
0,
3,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GLfloat),
(void *) 0
);
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesVertexVboId);
glVertexAttribPointer(
1,
2,
GL_FLOAT,
GL_FALSE,
5 * sizeof(GLfloat),
(const void *) (3 * sizeof(GLfloat))
);
glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesPosVboId);
glVertexAttribPointer(
2,
3,
GL_FLOAT,
GL_FALSE,
0,
(void *) 0
);
glEnableVertexAttribArray(3);
glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesColorVboId);
glVertexAttribPointer(
3,
4,
GL_UNSIGNED_BYTE,
GL_TRUE,
0,
(void *) 0
);
创建并绑定VAO
给shader中的变量a_vertex、a_texCoord、a_offset、a_particlesColor赋值
glVertexAttribDivisor(0, 0);
glVertexAttribDivisor(1, 0);
glVertexAttribDivisor(2, 1);
glVertexAttribDivisor(3, 1);
glVertexAttribDivisor(0, 0);
表示实例化绘制时对 index =0 的属性不更新;glVertexAttribDivisor(2, 1);
用于指定 index = 2 的属性为实例化数组,1 表示每绘制一个实例,更新一次数组中的元素。
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_BLEND); //关闭混合
// value += 1;
// value = value % 360;
// value = value % 360;
//转化为弧度角
float radiansX = static_cast(MATH_PI / 180.0f * value);
glm::mat4 modelMat = glm::mat4(1.0f); //模型矩阵
modelMat = glm::scale(modelMat, glm::vec3(0.8f, 0.8f, 0.8f));
modelMat = glm::rotate(modelMat, radiansX, glm::vec3(1.0f, 0.0f, 0.0f));
modelMat = glm::rotate(modelMat, radiansX, glm::vec3(0.0f, 1.0f, 0.0f));
modelMat = glm::translate(modelMat, glm::vec3(0.0f, 0.0f, 0.0f));
glm::mat4 projection =glm::perspective(45.0f, 0.5f, 0.1f, 100.f);
// viewMat matrix
glm::mat4 viewMat = glm::lookAt(
glm::vec3(0, 6, 0), // Camera is at (0,0,1), in World Space
glm::vec3(0, 0, 0), // and looks at the origin
glm::vec3(0, 0, 1) // Head is up (set to 0,-1,0 to look upside-down)
);
glm::mat4 mvpMatrix = projection * viewMat * modelMat;
int particleCount = UpdateParticles();
m_pOpenGLShader->Bind();
glBindVertexArray(m_VaoId);
m_pOpenGLShader->SetUniformValue("u_mat",mvpMatrix);
// Bind the RGBA map
m_pOpenGLShader->SetUniformValue("s_TextureMap",0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_texID[3]);
glDrawArraysInstanced(GL_TRIANGLES, 0, 36, particleCount);
void MSParticlesSample::GenerateNewParticle(Particle &particle) {
particle.life = 10.0f;
particle.cameraDistance = -1.0f;
particle.dx = (rand() % 2000 - 1000.0f) / 3000.0f;
particle.dy = (rand() % 2000 - 1000.0f) / 3000.0f;
particle.dz = (rand() % 2000 - 1000.0f) / 3000.0f;
float spread = 1.5f;
glm::vec3 maindir = glm::vec3(0.0f, 2.0f, 0.0f);
glm::vec3 randomdir = glm::vec3(
(rand() % 2000 - 1000.0f) / 1000.0f,
(rand() % 2000 - 1000.0f) / 1000.0f,
(rand() % 2000 - 1000.0f) / 1000.0f
);
glm::vec3 speed = maindir + randomdir * spread;
particle.dxSpeed = speed.x;
particle.dySpeed = speed.y;
particle.dzSpeed = speed.z;
particle.r = static_cast(rand() % 256);
particle.g = static_cast(rand() % 256);
particle.b = static_cast(rand() % 256);
particle.a = static_cast((rand() % 256) / 3);
}
新粒子的速度、偏移以及颜色都是随机生成的
/**
* 查找生命值结束的粒子
* @return
*/
int MSParticlesSample::FindUnusedParticle() {
for (int i = m_LastUsedParticle; i < MAX_PARTICLES; i++)
{
if (m_ParticlesContainer[i].life <= 0)
{
m_LastUsedParticle = i;
return i;
}
}
for (int i = 0; i < m_LastUsedParticle; i++)
{
if (m_ParticlesContainer[i].life <= 0)
{
m_LastUsedParticle = i;
return i;
}
}
return -1;
}
查找生命值结束的粒子
int MSParticlesSample::UpdateParticles() {
int newParticles = 300;
for (int i = 0; i < newParticles; i++)
{
int particleIndex = FindUnusedParticle();
if (particleIndex >= 0)
{
GenerateNewParticle(m_ParticlesContainer[particleIndex]);
}
}
int particlesCount = 0;
for (int i = 0; i < MAX_PARTICLES; i++)
{
Particle &p = m_ParticlesContainer[i]; // shortcut
if (p.life > 0.0f)
{
float delta = 0.1f;
glm::vec3 speed = glm::vec3(p.dxSpeed, p.dySpeed, p.dzSpeed), pos = glm::vec3(p.dx,
p.dy,
p.dz);
p.life -= delta;
if (p.life > 0.0f)
{
//模拟简单的物理:只有重力,没有碰撞
speed += glm::vec3(0.0f, 0.81f, 0.0f) * delta * 0.5f;
pos += speed * delta;
p.dxSpeed = speed.x;
p.dySpeed = speed.y;
p.dzSpeed = speed.z;
p.dx = pos.x;
p.dy = pos.y;
p.dz = pos.z;
m_pParticlesPosData[3 * particlesCount + 0] = p.dx;
m_pParticlesPosData[3 * particlesCount + 1] = p.dy;
m_pParticlesPosData[3 * particlesCount + 2] = p.dz;
m_pParticlesColorData[4 * particlesCount + 0] = p.r;
m_pParticlesColorData[4 * particlesCount + 1] = p.g;
m_pParticlesColorData[4 * particlesCount + 2] = p.b;
m_pParticlesColorData[4 * particlesCount + 3] = p.a;
}
particlesCount++;
}
}
glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesPosVboId);
glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 3 * sizeof(GLfloat), NULL,
GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, particlesCount * sizeof(GLfloat) * 3, m_pParticlesPosData);
glBindBuffer(GL_ARRAY_BUFFER, m_ParticlesColorVboId);
glBufferData(GL_ARRAY_BUFFER, MAX_PARTICLES * 4 * sizeof(GLubyte), NULL,
GL_DYNAMIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, particlesCount * sizeof(GLubyte) * 4,
m_pParticlesColorData);
return particlesCount;
}
更新粒子(更新粒子的位置、运动速度和生命值),然后更新实例化数组