Particle Use Case
创建粒子并加入场景的demo示例:
auto particle = ParticleSystemQuad::create("dc/atom.plist");
particle->setPosition(size.width / 2, size.height / 2);
this->addChild(particle);
Particle源码解析
顺着上边的代码,我们最直接的就会找到
bool ParticleSystem::initWithFile(const std::string& plistFile){
// 从文件中读取字典数据,并进行初始化
ValueMap dict = FileUtils::getInstance()->getValueMapFromFile(_plistFile);
ret = this->initWithDictionary(dict, listFilePath);
}
bool ParticleSystem::initWithDictionary(ValueMap& dictionary, const std::string& dirname){
int maxParticles = dictionary["maxParticles"].asInt();
if(this->initWithTotalParticles(maxParticles)){
}
}
bool ParticleSystem::initWithTotalParticles(int numberOfParticles)
{
// 这里初始化_particleData
if( !_particleData.init(_totalParticles) )
if (_batchNode)// 批处理节点,类似SpriteBatchNode
{
for (int i = 0; i < _totalParticles; i++)
{
_particleData.atlasIndex[i] = i;
}
}
}
bool ParticleData::init(int count)
{
maxCount = count;
// 申请了最大粒子数量的内存空间,用来存放每个粒子的数据,通过offset来确定粒子的具体数据
posx= (float*)malloc(count * sizeof(float));
posy= (float*)malloc(count * sizeof(float));
// ...
}
| particle变量 | plist key | 解释 |
|---|---|---|
| _totalParticles | maxParticles | 粒子的最大数量 |
| _life | particleLifespan | 粒子的生命周期时间 |
剩下的逻辑就是靠update驱动
void ParticleSystem::update(float dt)
{
if (_isActive && _emissionRate && _AllActiveState)
{
// _life = dictionary["particleLifespan"].asFloat();
// _emissionRate = _totalParticles / _life;
float rate = 1.0f / _emissionRate; // 排放速率 = 粒子声明周期 / 粒子数量
addParticles(emitCount);
// 每一个粒子的生命递减,
// 将末尾最后一个生命周期还存在的数据,copy到开头第一个生命周期结束的位置
// 就是将死去的粒子的位置,用末尾还存在的粒子代替
// 保证所有存在的粒子都是排在数组的前边,方便后续遍历
// 接着就是计算各种属性,size,pos,angle,color等
// 更新渲染数据
updateParticleQuads()
// only update gl buffer when visible
if (_visible && ! _batchNode)
{
postStep();
}
}
}
// 当添加粒子的时候,因为_particleData已经提前申请好了内存,此时通过下标填充数据即可
void ParticleSystem::addParticles(int count)
{
//life
for (int i = start; i < _particleCount ; ++i)
{
float theLife = _life + _lifeVar * RANDOM_M11(&RANDSEED);
_particleData.timeToLive[i] = MAX(0, theLife);
}
// positon
// color
}
void ParticleSystemQuad::updateParticleQuads(){
// 这里面是纯粹在操作_quads数据,将_particleData正确的填充到_quads里面
}
V3F_C4B_T2F_Quad *_quads; // quads to be rendered
struct CC_DLL V3F_C4B_T2F_Quad
{
/// top left
V3F_C4B_T2F tl;
/// bottom left
V3F_C4B_T2F bl;
/// top right
V3F_C4B_T2F tr;
/// bottom right
V3F_C4B_T2F br;
};
void ParticleSystemQuad::postStep()
{
// ↓ 需要关注下这个buffer的来源:ParticleSystemQuad::setupVBOandVAO里面有具体的数据绑定操作等行为
glBindBuffer(GL_ARRAY_BUFFER, _buffersVBO[0]);
// ↓之前操作的粒子数据
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(_quads[0])*_totalParticles, _quads);
glBindBuffer(GL_ARRAY_BUFFER, 0);
CHECK_GL_ERROR_DEBUG();
}
void ParticleSystemQuad::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
//quad command
if(_particleCount > 0)
{
_quadCommand.init(_globalZOrder,
_texture, // _texture:对应粒子使用的纹理
getGLProgramState(), _blendFunc,
_quads, // 粒子的数据
_particleCount, // 粒子的个数
transform, flags
);
renderer->addCommand(&_quadCommand);
}
}
- 使用到的shader: ShaderPositionTextureColor_noMVP
void main()
{
gl_FragColor = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
}
void main()
{
gl_Position = CC_PMatrix * a_position;
v_fragmentColor = a_color;
v_texCoord = a_texCoord;
}
因为QuadCommand也是TrianglesCommand,所以也会参与合批
CCParticleBatchNode
void ParticleSystem::setBatchNode(ParticleBatchNode* batchNode)
{
if( _batchNode != batchNode ) {
_batchNode = batchNode; // weak reference
if( batchNode ) {
//each particle needs a unique index
for (int i = 0; i < _totalParticles; i++)
{
_particleData.atlasIndex[i] = i;
}
}
}
}
// 在场景中添加一个ParticleBatchNode,这个ParticleBatchNode接管了渲染,
// ParticleBatchNode.addChild(particle) 将顶点数据复制到BatchNode
// ParticleNode需要和ParticleBatchNode使用同样的纹理
void ParticleBatchNode::draw(Renderer* renderer, const Mat4 & /*transform*/, uint32_t flags)
{
_batchCommand.init(_globalZOrder, getGLProgram(), _blendFunc,
_textureAtlas, // 这个图集是需要由Texture2D进行初始化
_modelViewTransform, flags
);
renderer->addCommand(&_batchCommand);
CC_PROFILER_STOP("CCParticleBatchNode - draw");
}
bool ParticleBatchNode::initWithTexture(Texture2D *tex, int capacity)
{
// 这个纹理必须是粒子纹理,只有多个相同的粒子
_textureAtlas = new (std::nothrow) TextureAtlas();
_textureAtlas->initWithTexture(tex, capacity);
}
// batchCommand的渲染
void BatchCommand::execute()
{
// Set material
_shader->use();
_shader->setUniformsForBuiltins(_mv);
GL::bindTexture2D(_textureID);
GL::blendFunc(_blendType.src, _blendType.dst);
// Draw
_textureAtlas->drawQuads();
}
优化
粒子的数学运算会消耗一定的性能,这个无法避免。
理论上,只要扩展下Particle.texture使其支持从TextureAtlas里面获取纹理,也能完美实现Particle和Sprite合批,因为底层都是TrianglesCommand,只需要注意下blend混合模式即可。
实现方式,可以考虑追加一个textureAtlas解析,现在是支持textureImageData、textureFileName的解析,因为studio不支持,所以只能单独开发工具做支撑了。
ParticleBatchNode对性能优化意义不大,因为项目中很少有粒子是连着在一起,不连续就会断批。
ParticleBatchNode会将add进去的Particle的顶点数据放在一个大的buffer里面,这样在填充到renderbuffer里面的时候,就避免了碎片化拷贝内存,会带来一定的性能提升,这和拷贝压缩包比碎文件快,都是一样的道理