[√]cocos2dx particleSystem渲染过程解读、优化的思考

简介: [√]cocos2dx particleSystem渲染过程解读、优化的思考

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解析,现在是支持textureImageDatatextureFileName的解析,因为studio不支持,所以只能单独开发工具做支撑了。

ParticleBatchNode对性能优化意义不大,因为项目中很少有粒子是连着在一起,不连续就会断批。

ParticleBatchNode会将add进去的Particle的顶点数据放在一个大的buffer里面,这样在填充到renderbuffer里面的时候,就避免了碎片化拷贝内存,会带来一定的性能提升,这和拷贝压缩包比碎文件快,都是一样的道理

目录
相关文章
|
JavaScript Android开发
AutoJs4.1.0实战教程---js文件打包发布成APK文件
AutoJs4.1.0实战教程---js文件打包发布成APK文件
1767 0
AutoJs4.1.0实战教程---js文件打包发布成APK文件
|
NoSQL Redis 数据安全/隐私保护
redis设置密码
redis设置密码
1294 1
|
测试技术 开发工具 文件存储
Git Stash
【8月更文挑战第27天】
359 6
|
设计模式 存储 人工智能
深度解析Unity游戏开发:从零构建可扩展与可维护的游戏架构,让你的游戏项目在模块化设计、脚本对象运用及状态模式处理中焕发新生,实现高效迭代与团队协作的完美平衡之路
【9月更文挑战第1天】游戏开发中的架构设计是项目成功的关键。良好的架构能提升开发效率并确保项目的长期可维护性和可扩展性。在使用Unity引擎时,合理的架构尤为重要。本文探讨了如何在Unity中实现可扩展且易维护的游戏架构,包括模块化设计、使用脚本对象管理数据、应用设计模式(如状态模式)及采用MVC/MVVM架构模式。通过这些方法,可以显著提高开发效率和游戏质量。例如,模块化设计将游戏拆分为独立模块。
778 3
|
存储 数据采集 数据可视化
基于Python flask+MySQL+echart的电影数据分析可视化系统
该博客文章介绍了一个基于Python Flask框架、MySQL数据库和ECharts库构建的电影数据分析可视化系统,系统功能包括猫眼电影数据的爬取、存储、展示以及电影评价词云图的生成。
710 1
|
存储 监控 数据挖掘
ERP系统中的客户满意度调查与反馈处理解析
【7月更文挑战第25天】 ERP系统中的客户满意度调查与反馈处理解析
1024 0
|
缓存 JavaScript
报错:cannot read properties of undefined “reading url“
报错:cannot read properties of undefined “reading url“
426 6
|
存储 缓存 安全
Android系统 应用存储路径与权限
Android系统 应用存储路径与权限
1779 0
Android系统 应用存储路径与权限
|
JSON JavaScript 数据格式
【深入探究C++ JSON库】解析JSON元素的层级管理与遍历手段
【深入探究C++ JSON库】解析JSON元素的层级管理与遍历手段
1975 2