现在的位置: 首页 > 综合 > 正文

Cocos2d-x2.0 粒子系统深入分析三部曲(二)

2013年10月13日 ⁄ 综合 ⁄ 共 17623字 ⁄ 字号 评论关闭

[Cocos2d-x相关教程来源于红孩儿的游戏编程之路CSDN博客地址:http://blog.csdn.net/honghaier]

                           红孩儿Cocos2d-X学习园地QQ2群:44208467加群写:Cocos2d-x
                           红孩儿Cocos2d-X学习园地QQ群:249941957加群写:Cocos2d-x

    

           Cocos2d-x2.0 粒子系统深入分析三部曲(二)

上一节我们了解了粒子系统的原理,也学习了Cocos2d-x中的两个有关粒子系统的类:

(1)      CCParticleSystem :粒子系统的基类,提供对粒子的创建和更新管理。

(2)      CCParticleBatchNode:粒子系统的批次结点,用于将使用相同纹理的粒子系统进行同批次渲染优化处理。

 

在学习CCParticleSystem时,我们留下了一些疑问,什么时候调用setBatchNode?以及做为基类,CCParticleSystem提供了两个供子类重载的纯虚函数postStep()和updateQuadWithParticle,它们的具体用法是什么?我们仍然有一些迷茫。

 

我们今天来了解一下CCParticleSystemQuad,这个类是CCParticleSystem的子类。它将解开我们的这些疑惑。

        

            打开CCParticleSystemQuad.h:

class CC_DLL CCParticleSystemQuad : public CCParticleSystem
{
protected:
	   //如果当前粒子系统未使用批次结点,则需要为粒子系统创建单独的顶点缓冲及索引缓冲以及OPENGL进行渲染的一些相关物件。
    ccV3F_C4B_T2F_Quad    *m_pQuads;    // OPENGL渲染图形所用的四边形顶点缓冲。
    GLushort            *m_pIndices;    // OPENGL渲染图形所用的索引缓冲。
//这里有宏判断当前OPENGL版本是否支持使用VAO处理顶点缓冲(VAO是什么?这个问题问的好,VAO是OPENGL3.X以上引入的新特性,VBO是Vertex Buffer Object, VAO是Vertex Array Object。 VAO是OpenGL 3.0以后才引入的新东西,但是在2.0版本中做为扩展接口。VBO其实就是显卡中的显存,为了提高渲染速度,可以将要绘制的顶点数据缓存在显存中,这样就不需要将要绘制的顶点数据重复从CPU发送到GPU, 浪费带宽资源。而VAO则是一个容器,可以包括多个VBO, 它类似于以前的call list, 由于它进一步将VBO容于其中,所以绘制效率将在VBO的基础上更进一步。)
#if CC_TEXTURE_ATLAS_USE_VAO
    GLuint                m_uVAOname;	   		//VAO的句柄。
#endif
	   GLuint                m_pBuffersVBO[2];  //VBO的两个句柄,第一个句柄对应顶点缓冲,第二个句柄对应索引缓冲。
public:
	   //构造函数。
    CCParticleSystemQuad();
	   //析构函数。
    virtual ~CCParticleSystemQuad();

    //创建函数,参为为PLIST,内部调用create实现。
    CC_DEPRECATED_ATTRIBUTE static CCParticleSystemQuad * particleWithFile(const char *plistFile);

    //上面函数的create实现。
    static CCParticleSystemQuad * create(const char *plistFile);

    //初始化索引缓冲。
    void setupIndices();

    //初始化方理坐标。
    void initTexCoordsWithRect(const CCRect& rect);

    //设置显示一个精灵帧。
    void setDisplayFrame(CCSpriteFrame *spriteFrame);

    //设置使用纹理对象上指定的矩形图像区域做为粒子系统的贴图。
    void setTextureWithRect(CCTexture2D *texture, const CCRect& rect);
    // 重载基类粒子系统的相应函数。
	   //初始化粒子数量。
    virtual bool initWithTotalParticles(unsigned int numberOfParticles);
	   //设置所用的纹理对象指针
    virtual void setTexture(CCTexture2D* texture);
	   //上一篇留下的疑问,虽然明显是更新粒子顶点缓冲中的位置数据。但上一篇为什么没有实现?
    virtual void updateQuadWithParticle(tCCParticle* particle, const CCPoint& newPosition);
	   //上一篇留下的疑问,不知道是做什么,咱们到CPP中看吧。
    virtual void postStep();
	   //渲染处理。
    virtual void draw();
	   //设置批次结点。
    virtual void setBatchNode(CCParticleBatchNode* batchNode);
	   //设置总的粒子数量。
    virtual void setTotalParticles(unsigned int tp);
    //监听响应当前结点的EVNET_COME_TO_FOREGROUND事件的回调函数。
    void listenBackToForeground(CCObject *obj);

    //创建一个当前实例结点,内部调用create实现。
    CC_DEPRECATED_ATTRIBUTE static CCParticleSystemQuad * node();
	   //上面的create实现。
    static CCParticleSystemQuad * create();
private:
		//如果使用VAO
#if CC_TEXTURE_ATLAS_USE_VAO
	   //初始化VAO和VBO
    void setupVBOandVAO();
#else
    //初始化VBO
    void setupVBO();
#endif
	   //申请内存。
    bool allocMemory();
};

对应的实现:

//重载粒子系统基类的初始化函数,创建相应数量的粒子。
bool CCParticleSystemQuad::initWithTotalParticles(unsigned int numberOfParticles)
{
    //调用基类的相应函数。
    if( CCParticleSystem::initWithTotalParticles(numberOfParticles) ) 
    {
        // 创建顶点和索引缓冲,如果失败释放并返回。
        if( ! this->allocMemory() ) {
            this->release();
            return false;
        }
		//填充索引缓冲。
        setupIndices();
		//如果当前OPENGL版本支持VAO,就创建VAO,如果不支持,只创建VBO。
#if CC_TEXTURE_ATLAS_USE_VAO
        setupVBOandVAO();
#else
        setupVBO();
#endif
//设置使用顶点格式为“位置+纹理+顶点色”的顶点格式组合。
setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTextureColor));

//告诉通知中心,注册函数listenBackToForeground用来响应当前结点的EVNET_COME_TO_FOREGROUND事件。这个事件的意义是程序将由后面返回到前台。这个事件响应时可做资源的重新载入。

CCNotificationCenter::sharedNotificationCenter()->addObserver(this,
                                                                      callfuncO_selector(CCParticleSystemQuad::listenBackToForeground),
                                                                      EVNET_COME_TO_FOREGROUND,
                                                                      NULL);
        
        return true;
    }
    return false;
}
//构造函数。
CCParticleSystemQuad::CCParticleSystemQuad()
:m_pQuads(NULL)
,m_pIndices(NULL)
#if CC_TEXTURE_ATLAS_USE_VAO
,m_uVAOname(0)
#endif
{
    memset(m_pBuffersVBO, 0, sizeof(m_pBuffersVBO));
}
//析构函数。
CCParticleSystemQuad::~CCParticleSystemQuad()
{
	//对所创建的顶点缓冲,索引缓冲,以及VBO,VA0进行释放。
    if (NULL == m_pBatchNode)
    {
        CC_SAFE_FREE(m_pQuads);
        CC_SAFE_FREE(m_pIndices);
        glDeleteBuffers(2, &m_pBuffersVBO[0]);
#if CC_TEXTURE_ATLAS_USE_VAO
        glDeleteVertexArrays(1, &m_uVAOname);
#endif
    }
    
//注销对通知管理器注册的相应事件的响应处理函数。
CCNotificationCenter::sharedNotificationCenter()->removeObserver(this, EVNET_COME_TO_FOREGROUND);
}

// 静态创建函数。由PLIST文件创建相应的当前实例对象,内部调用create实现。
CCParticleSystemQuad * CCParticleSystemQuad::particleWithFile(const char *plistFile)
{
    return CCParticleSystemQuad::create(plistFile);
}
//上面的create实现。
CCParticleSystemQuad * CCParticleSystemQuad::create(const char *plistFile)
{
	//创建一个CCParticleSystemQuad实例对象,进行初始化后交由内存管理器进行引用计数器的管理。
    CCParticleSystemQuad *pRet = new CCParticleSystemQuad();
    if (pRet && pRet->initWithFile(plistFile))
    {
        pRet->autorelease();
        return pRet;
    }
	//如果失败,删除并置空,返回NULL。
    CC_SAFE_DELETE(pRet);
    return pRet;
}

// 初始化纹理坐标。
void CCParticleSystemQuad::initTexCoordsWithRect(const CCRect& pointRect)
{
    // 创建出相应的矩形。
    CCRect rect = CCRectMake(
        pointRect.origin.x * CC_CONTENT_SCALE_FACTOR(),
        pointRect.origin.y * CC_CONTENT_SCALE_FACTOR(),
        pointRect.size.width * CC_CONTENT_SCALE_FACTOR(),
        pointRect.size.height * CC_CONTENT_SCALE_FACTOR());
	//默认使用的是批次结点,以批次结点的纹理对设置所用的图像区域矩形宽高。
    GLfloat wide = (GLfloat) pointRect.size.width;
    GLfloat high = (GLfloat) pointRect.size.height;
	//如果使用单纹理对象,取得纹理的宽高。
    if (m_pTexture)
    {
        wide = (GLfloat)m_pTexture->getPixelsWide();
        high = (GLfloat)m_pTexture->getPixelsHigh();
    }
	//此宏是为了解决精灵边缘黑线而做的纹理坐标的微调。
#if CC_FIX_ARTIFACTS_BY_STRECHING_TEXEL
    GLfloat left = (rect.origin.x*2+1) / (wide*2);
    GLfloat bottom = (rect.origin.y*2+1) / (high*2);
    GLfloat right = left + (rect.size.width*2-2) / (wide*2);
    GLfloat top = bottom + (rect.size.height*2-2) / (high*2);
#else
    GLfloat left = rect.origin.x / wide;
    GLfloat bottom = rect.origin.y / high;
    GLfloat right = left + rect.size.width / wide;
    GLfloat top = bottom + rect.size.height / high;
#endif 

    //将top与bottom交换一下,因为在Cococs2d-x中坐标系Y轴是向上为正,这里处理一下后面可以做为顶点位置数据。
    CC_SWAP( top, bottom, float);
	//根据是否使用批次结点来取得相应的矩形顶点缓冲数组。
    ccV3F_C4B_T2F_Quad *quads = NULL;
    unsigned int start = 0, end = 0;
    if (m_pBatchNode)
    {	//如果使用批次结点,这里取得批次结点中所有粒子所对应的矩形顶点数组。
        quads = m_pBatchNode->getTextureAtlas()->getQuads();
		//取得起始和结束的粒子所对应的矩形索引。
        start = m_uAtlasIndex;
        end = m_uAtlasIndex + m_uTotalParticles;
    }
    else
    {	//如果使用单纹理对象,则起始矩形索引就是0,结束矩形索引就是最大粒子数量。
        quads = m_pQuads;
        start = 0;
        end = m_uTotalParticles;
    }
	//遍历所有的矩形顶点。
    for(unsigned int i=start; i<end; i++) 
    {
        // 设置四个顶点的纹理坐标。
        quads[i].bl.texCoords.u = left;
        quads[i].bl.texCoords.v = bottom;
        quads[i].br.texCoords.u = right;
        quads[i].br.texCoords.v = bottom;
        quads[i].tl.texCoords.u = left;
        quads[i].tl.texCoords.v = top;
        quads[i].tr.texCoords.u = right;
        quads[i].tr.texCoords.v = top;
    }
}
//设置使用纹理对象上指定的矩形图像区域做为粒子系统的贴图。
void CCParticleSystemQuad::setTextureWithRect(CCTexture2D *texture, const CCRect& rect)
{
    // 如果当前尚无纹理对或者使用的纹理与参数指定的纹理不同。则设置使用参数指定的纹理。
    if( !m_pTexture || texture->getName() != m_pTexture->getName() )
    {
        CCParticleSystem::setTexture(texture);
    }
	//设置指定的矩形图像区域做为贴图计算粒子系统的纹理坐标.
    this->initTexCoordsWithRect(rect);
}
//设置使用的纹理对象。
void CCParticleSystemQuad::setTexture(CCTexture2D* texture)
{
	//取得纹理的大小。
    const CCSize& s = texture->getContentSize();
	//设置使用纹理对象上指定的矩形图像区域做为粒子系统的贴图。    
	this->setTextureWithRect(texture, CCRectMake(0, 0, s.width, s.height));
}
//设置使用精灵帧中的纹理。
void CCParticleSystemQuad::setDisplayFrame(CCSpriteFrame *spriteFrame)
{
	//有效性判断。
    CCAssert(spriteFrame->getOffsetInPixels().equals(CCPointZero), 
             "QuadParticle only supports SpriteFrames with no offsets");

    // 如果当前尚无纹理对或者使用的纹理与参数指定的纹理不同。则设置使用参数指定的纹理。
    if ( !m_pTexture || spriteFrame->getTexture()->getName() != m_pTexture->getName())
    {
		//设置使用精灵帧中的纹理.
        this->setTexture(spriteFrame->getTexture());
    }
}
//填充索引缓冲。
void CCParticleSystemQuad::setupIndices()
{
	//遍历粒子数目计算索引缓冲值。
    for(unsigned int i = 0; i < m_uTotalParticles; ++i)
    {
        const unsigned int i6 = i*6;
        const unsigned int i4 = i*4;
        m_pIndices[i6+0] = (GLushort) i4+0;
        m_pIndices[i6+1] = (GLushort) i4+1;
        m_pIndices[i6+2] = (GLushort) i4+2;

        m_pIndices[i6+5] = (GLushort) i4+1;
        m_pIndices[i6+4] = (GLushort) i4+2;
        m_pIndices[i6+3] = (GLushort) i4+3;
    }
}
//更新指定粒子的顶点缓冲中的位置数据。
void CCParticleSystemQuad::updateQuadWithParticle(tCCParticle* particle, const CCPoint& newPosition)
{
	//定义临时指针变量用于取得相应的粒子所对应的矩形顶点缓冲。
    ccV3F_C4B_T2F_Quad *quad;
	//如果使用了批次结点。
    if (m_pBatchNode)
    {
		//取得批次结点中对应的矩形顶点缓冲数组指针。
        ccV3F_C4B_T2F_Quad *batchQuads = m_pBatchNode->getTextureAtlas()->getQuads();
		//通过索引取得相应的矩形顶点缓冲。
        quad = &(batchQuads[m_uAtlasIndex+particle->atlasIndex]);
    }
    else
    {
		//如果没有使用批次结点,直接取得相应的粒子的矩形顶点缓冲。
        quad = &(m_pQuads[m_uParticleIdx]);
    }
	//根据是否由ALPHA值来设定RGB取得相应的颜色值。
    ccColor4B color = (m_bOpacityModifyRGB)
        ? ccc4( particle->color.r*particle->color.a*255, particle->color.g*particle->color.a*255, particle->color.b*particle->color.a*255, particle->color.a*255)
        : ccc4( particle->color.r*255, particle->color.g*255, particle->color.b*255, particle->color.a*255);
	//填真顶点缓冲中的颜色信息。
    quad->bl.colors = color;
    quad->br.colors = color;
    quad->tl.colors = color;
    quad->tr.colors = color;

    // 设置顶点的位置信息
    GLfloat size_2 = particle->size/2;
	//判断是否进行旋转。
    if (particle->rotation) 
    {
		//定义临时变量来存放以size_2为半径的矩形外圆上的四个顶点。
        GLfloat x1 = -size_2;
        GLfloat y1 = -size_2;

        GLfloat x2 = size_2;
        GLfloat y2 = size_2;
		//定义临时变量来存放圆心。
        GLfloat x = newPosition.x;
        GLfloat y = newPosition.y;
		//求得旋转角度。
        GLfloat r = (GLfloat)-CC_DEGREES_TO_RADIANS(particle->rotation);
		//通过sin,cos来计算旋转后的矩形外圆上的四个角的顶点位置。
        GLfloat cr = cosf(r);
        GLfloat sr = sinf(r);
        GLfloat ax = x1 * cr - y1 * sr + x;
        GLfloat ay = x1 * sr + y1 * cr + y;
        GLfloat bx = x2 * cr - y1 * sr + x;
        GLfloat by = x2 * sr + y1 * cr + y;
        GLfloat cx = x2 * cr - y2 * sr + x;
        GLfloat cy = x2 * sr + y2 * cr + y;
        GLfloat dx = x1 * cr - y2 * sr + x;
        GLfloat dy = x1 * sr + y2 * cr + y;

        // 填充计算旋转后的顶点位置信息。
        quad->bl.vertices.x = ax;
        quad->bl.vertices.y = ay;
        quad->br.vertices.x = bx;
        quad->br.vertices.y = by;
        quad->tl.vertices.x = dx;
        quad->tl.vertices.y = dy;
        quad->tr.vertices.x = cx;
        quad->tr.vertices.y = cy;
    } 
    else 
    {
        //如果不旋转,直接填充位置信息。
        quad->bl.vertices.x = newPosition.x - size_2;
        quad->bl.vertices.y = newPosition.y - size_2;
        quad->br.vertices.x = newPosition.x + size_2;
        quad->br.vertices.y = newPosition.y - size_2;
        quad->tl.vertices.x = newPosition.x - size_2;
        quad->tl.vertices.y = newPosition.y + size_2;
        quad->tr.vertices.x = newPosition.x + size_2;
        quad->tr.vertices.y = newPosition.y + size_2;                
    }
}
//我们一直想知道在CCParticleSystem中每次update时,如果粒子批次结点为空时为什么要调用postStep?它倒底是干什么的,看完下面的代码,就很清楚了。
void CCParticleSystemQuad::postStep()
{
	//绑定顶点缓冲区对象。
    glBindBuffer(GL_ARRAY_BUFFER, m_pBuffersVBO[0] );
	//用m_pQuads中数据更新绑定的缓冲区数据。
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(m_pQuads[0])*m_uParticleCount, m_pQuads);
    //取消绑定缓冲区对象。
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    CHECK_GL_ERROR_DEBUG();
	//清楚了,原来这个函数是针对不使用批次结点时的VBO顶点缓冲的更新。
}

// 绘制粒子系统。
void CCParticleSystemQuad::draw()
{
    //如果当前在粒子的批次结点有值,则draw()不应该被调用!为什么呢?因为有批次结点的话,渲染交给批次结点的draw()函数而不是当前粒子系统的draw()函数来处理。
    CCAssert(!m_pBatchNode,"draw should not be called when added to a particleBatchNode");
	//使用相应的Shader
    CC_NODE_DRAW_SETUP();
	//绑定所用的纹理对象。
    ccGLBindTexture2D( m_pTexture->getName() );
	//设定所用的ALPHA混合方案。
    ccGLBlendFunc( m_tBlendFunc.src, m_tBlendFunc.dst );
	//判断当前粒子索引是否为粒子总数,也就是判断是否已经update完成未出错。
    CCAssert( m_uParticleIdx == m_uParticleCount, "Abnormal error in particle quad");
	//如果使用VAO。
#if CC_TEXTURE_ATLAS_USE_VAO
    //使用VAO绑定的顶点数组
    glBindVertexArray( m_uVAOname );
	//绑定索引数组
#if CC_REBIND_INDICES_BUFFER
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_pBuffersVBO[1]);
#endif
	//渲染调用
    glDrawElements(GL_TRIANGLES, (GLsizei) m_uParticleIdx*6, GL_UNSIGNED_SHORT, 0);
	//渲染完取消绑定索引数组
#if CC_REBIND_INDICES_BUFFER
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
#endif
	//取消绑定顶点数组
    glBindVertexArray( 0 );

#else
    //
    // Using VBO without VAO
    //
	//
    #define kQuadSize sizeof(m_pQuads[0].bl)
	//设置使用相应的顶点格式为:位置+颜色+纹理坐标
    ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex );
	// 绑定顶点缓冲
    glBindBuffer(GL_ARRAY_BUFFER, m_pBuffersVBO[0]);
    // 设置顶点缓冲中位置数据的描述
    glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof( ccV3F_C4B_T2F, vertices));
    //设置顶点缓冲中颜色数据的描述
    glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof( ccV3F_C4B_T2F, colors));
    //设置顶点缓冲中纹理坐标数据的描述
    glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof( ccV3F_C4B_T2F, texCoords));
    //绑定索引缓冲
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_pBuffersVBO[1]);
	//渲染调用
    glDrawElements(GL_TRIANGLES, (GLsizei) m_uParticleIdx*6, GL_UNSIGNED_SHORT, 0);
	//取消绑定
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

#endif
	//渲染调用计数器加一
    CC_INCREMENT_GL_DRAWS(1);
    CHECK_GL_ERROR_DEBUG();
}
//设置粒子数量
void CCParticleSystemQuad::setTotalParticles(unsigned int tp)
{
    // 如果要申请内存的粒子数量大于之前已经申请内存的粒子数量。
    if( tp > m_uAllocatedParticles )
    {
        // 计算要申请的内存大小。
		//粒子信息结构数组大小。 
        size_t particlesSize = tp * sizeof(tCCParticle);
		//顶点缓冲的大小
        size_t quadsSize = sizeof(m_pQuads[0]) * tp * 1;
		//索引缓冲的大小
        size_t indicesSize = sizeof(m_pIndices[0]) * tp * 6 * 1;
		//在m_pParticles指定的内存位置申请相应大小的内存用于存储粒子信息结构数组。
        tCCParticle* particlesNew = (tCCParticle*)realloc(m_pParticles, particlesSize);
		//在m_pQuads指定的内存位置申请相应大小的内存用于填充顶点缓冲。
        ccV3F_C4B_T2F_Quad* quadsNew = (ccV3F_C4B_T2F_Quad*)realloc(m_pQuads, quadsSize);
		//在m_pIndices指定的内存位置申请相应大小的内存用于填充索引缓冲。
        GLushort* indicesNew = (GLushort*)realloc(m_pIndices, indicesSize);
		//如果申请都成功。
        if (particlesNew && quadsNew && indicesNew)
        {
            // 将内存地址传值给成员指针。
            m_pParticles = particlesNew;
            m_pQuads = quadsNew;
            m_pIndices = indicesNew;

            // 内存清零
            memset(m_pParticles, 0, particlesSize);
            memset(m_pQuads, 0, quadsSize);
            memset(m_pIndices, 0, indicesSize);
			//记录申请内存的粒子数量。
            m_uAllocatedParticles = tp;
        }
        else
        {
            // 如果失败,记录成功申请的内存地址打印出错LOG。
            if (particlesNew) m_pParticles = particlesNew;
            if (quadsNew) m_pQuads = quadsNew;
            if (indicesNew) m_pIndices = indicesNew;

            CCLOG("Particle system: out of memory");
            return;
        }
		//更新粒子数量。
        m_uTotalParticles = tp;

        // 根据是否使用批次结点来设定每个粒子对应的矩形顶点块的索引。
        if (m_pBatchNode)
        {
            for (unsigned int i = 0; i < m_uTotalParticles; i++)
            {
                m_pParticles[i].atlasIndex=i;
            }
        }
		//填充索引缓冲。
        setupIndices();
		//初始化VAO
#if CC_TEXTURE_ATLAS_USE_VAO
        setupVBOandVAO();
#else
		//初始化VBO
        setupVBO();
#endif
    }
    else
    {
		//如果要申请内存的粒子数量小于原来申请的粒子数量,直接修改一下最大粒子数量就OK。
        m_uTotalParticles = tp;
    }
}
//如果当前OPENGL版本支持VAO。
#if CC_TEXTURE_ATLAS_USE_VAO
//初始化VBO与VAO
void CCParticleSystemQuad::setupVBOandVAO()
{	//初始化1个顶点数组对象,产生VAO对象的句柄
    glGenVertexArrays(1, &m_uVAOname);
	//绑定VAO。
    glBindVertexArray(m_uVAOname);

#define kQuadSize sizeof(m_pQuads[0].bl)
	//创建2个VBO缓冲区对象,产生两个句柄填充到句柄数组中。
    glGenBuffers(2, &m_pBuffersVBO[0]);
	//绑定第一个VBO缓冲区对象。 
    glBindBuffer(GL_ARRAY_BUFFER, m_pBuffersVBO[0]);
	//将顶点数据拷贝到绑定的缓冲区。
    glBufferData(GL_ARRAY_BUFFER, sizeof(m_pQuads[0]) * m_uTotalParticles, m_pQuads, GL_DYNAMIC_DRAW);

    //设置使用位置数据
    glEnableVertexAttribArray(kCCVertexAttrib_Position);
	//设置位置数据在顶点缓冲中的描述
    glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof( ccV3F_C4B_T2F, vertices));

    //设置使用颜色数据
    glEnableVertexAttribArray(kCCVertexAttrib_Color);
	//设置颜色数据在顶点缓冲中的描述
    glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof( ccV3F_C4B_T2F, colors));

    //设置使用纹理坐标数据
    glEnableVertexAttribArray(kCCVertexAttrib_TexCoords);
	//设置纹理坐标数据在顶点缓冲中的描述
    glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof( ccV3F_C4B_T2F, texCoords));

	//绑定VBO的第二个缓冲区
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_pBuffersVBO[1]);
	//将索引缓冲区数据拷贝到绑定的缓冲区
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(m_pIndices[0]) * m_uTotalParticles * 6, m_pIndices, GL_STATIC_DRAW);
	//取消绑定
    glBindVertexArray(0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    CHECK_GL_ERROR_DEBUG();
}
#else
//否则只使用VBO
void CCParticleSystemQuad::setupVBO()
{
	//创建2个VBO缓冲区对象,产生两个句柄填充到句柄数组中。
    glGenBuffers(2, &m_pBuffersVBO[0]);
	//绑定第一个VBO缓冲区对象并填充数据。
    glBindBuffer(GL_ARRAY_BUFFER, m_pBuffersVBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(m_pQuads[0]) * m_uTotalParticles, m_pQuads, GL_DYNAMIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
	//绑定第二个VBO缓冲区对象并填充数据。
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_pBuffersVBO[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(m_pIndices[0]) * m_uTotalParticles * 6, m_pIndices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
	
    CHECK_GL_ERROR_DEBUG();
}

#endif
//响应当前结点的EVNET_COME_TO_FOREGROUND事件。这个事件的意义是程序将由后面返回到前台。这个事件响应时可做资源的重新载入。
void CCParticleSystemQuad::listenBackToForeground(CCObject *obj)
{
	//重新初始化VBO和VAO。
#if CC_TEXTURE_ATLAS_USE_VAO
        setupVBOandVAO();
#else
        setupVBO();
#endif
}
//创建顶点缓冲和索引缓冲区。
bool CCParticleSystemQuad::allocMemory()
{
	//如果已经申请则中断。
    CCAssert( ( !m_pQuads && !m_pIndices), "Memory already alloced");
    CCAssert( !m_pBatchNode, "Memory should not be alloced when not using batchNode");
	//释放顶点与索引缓冲
    CC_SAFE_FREE(m_pQuads);
    CC_SAFE_FREE(m_pIndices);
	//为顶点与索引缓冲申请内存。
    m_pQuads = (ccV3F_C4B_T2F_Quad*)malloc(m_uTotalParticles * sizeof(ccV3F_C4B_T2F_Quad));
    m_pIndices = (GLushort*)malloc(m_uTotalParticles * 6 * sizeof(GLushort));
    //如果出现失败,则提示LOG并释放置空返回。
    if( !m_pQuads || !m_pIndices) 
    {
        CCLOG("cocos2d: Particle system: not enough memory");
        CC_SAFE_FREE(m_pQuads);
        CC_SAFE_FREE(m_pIndices);

        return false;
    }
	//如果成功,置零操作返回true.
    memset(m_pQuads, 0, m_uTotalParticles * sizeof(ccV3F_C4B_T2F_Quad));
    memset(m_pIndices, 0, m_uTotalParticles * 6 * sizeof(GLushort));

    return true;
}
//设置CCParticleSystemQuad使用的粒子批次结点。 
void CCParticleSystemQuad::setBatchNode(CCParticleBatchNode * batchNode)
{
	//如果当前使用的粒子批次结点与参数不同,则进行更换处理。
    if( m_pBatchNode != batchNode ) 
    {
		//先记录一下当前使用的。
        CCParticleBatchNode* oldBatch = m_pBatchNode;
		//将后调用基类相应函数,设置当前使用的粒子批次结点。
        CCParticleSystem::setBatchNode(batchNode);

        // 如果参数值为空,代表不使用批次结点。则进行顶点缓冲的相关初始化。
        if( ! batchNode ) 
        {
			//创建顶点缓冲,填充索引,并根据是否可用VAO设置顶点缓冲,设置纹理。
            allocMemory();
            setupIndices();
            setTexture(oldBatch->getTexture());
#if CC_TEXTURE_ATLAS_USE_VAO
            setupVBOandVAO();
#else
            setupVBO();
#endif
        }
        // 如果批次结点有效,且更换前没有使用粒子批次结点。
        else if( !oldBatch )
        {
            // 取得相应粒子批次结点的矩形数组,取出相应索引位置的矩形数据,将相应的顶点缓冲数据拷到矩形数组中。
            ccV3F_C4B_T2F_Quad *batchQuads = m_pBatchNode->getTextureAtlas()->getQuads();
            ccV3F_C4B_T2F_Quad *quad = &(batchQuads[m_uAtlasIndex] );
            memcpy( quad, m_pQuads, m_uTotalParticles * sizeof(m_pQuads[0]) );
			//释放当前用的顶点缓冲
            CC_SAFE_FREE(m_pQuads);
            CC_SAFE_FREE(m_pIndices);
			//释放所用的VBO顶点对象与AVO名称
            glDeleteBuffers(2, &m_pBuffersVBO[0]);
#if CC_TEXTURE_ATLAS_USE_VAO
            glDeleteVertexArrays(1, &m_uVAOname);
#endif
        }
    }
}
//创建一个CCParticleSystemQuad,内部调用create实现。
CCParticleSystemQuad * CCParticleSystemQuad::node()
{
    return CCParticleSystemQuad::create();
}
//上面函数的create实现。
CCParticleSystemQuad * CCParticleSystemQuad::create() {
	//创建一个CCParticleSystemQuad的实例对象。
    CCParticleSystemQuad *pParticleSystemQuad = new CCParticleSystemQuad();
	//如果创建成功,进行初始化。成功后交由内存管理器进行引用计数器的管理。
    if (pParticleSystemQuad && pParticleSystemQuad->init())
    {
        pParticleSystemQuad->autorelease();
        return pParticleSystemQuad;
    }
	//如果失败,释放置空并返回NULL。
    CC_SAFE_DELETE(pParticleSystemQuad);
    return NULL;
}

         总结: 类CCParticleSystemQuad的源码进一步完善了粒子系统的功能,使我们可以在不需要批次结点时也能够实现粒子系统的OPENGL顶点和索引缓冲的创建和渲染,这么看来CCParticleSystem是一个不完整的粒子系统的类,它只提供了使用粒子批次结点的粒子系统的渲染方案。

 

        到这里,我们基本算领悟了Cocos2d-x中粒子系统的完整功能基类。后面我们将由此进入到多彩的粒子系统的演示中去!下课~

 

                   

抱歉!评论已关闭.