在游戏中我们一个非常重要的元素就是图片,给最初的文字Mud游戏带来了多姿多彩。在3D游戏中,一个图片是采用什么结构表示的呢?其实灰常简单,你也能想得到:
1张纹理图,2个三角面片。那接下来就让我们看看cocos2d-x.们带来什么惊喜不?
游戏世界中的数据结构在我看来可以分为两种:一个是资源,另外一个是物理对象。是不是有点不好理解?资源就是一些原材料,比如纹理啊,声音啊,不能独立的作为实体存在。物理对象则是我们对游戏世界中真实存在的东西的一种抽象,资源必须依附在物理对象上。比如说一个精灵肯定拥有一张纹理。废话少说,进入我们的正题。
先看看最重要的资源,纹理的表示。让我们仔细探究一下cocos2d-x中纹理是如何表示的
CCTexture2D的结构
class CC_DLL CCTexture2D : public CCObject { public: bool initWithData(const void* data, CCTexture2DPixelFormat pixelFormat, unsigned int pixelsWide, unsigned int pixelsHigh, const CCSize& contentSize); //初始化纹理 bool initWithImage(CCImage * uiImage); void drawAtPoint(const CCPoint& point); // 绘制 void drawInRect(const CCRect& rect); protected: CC_PROPERTY_READONLY(GLuint, m_uName, Name) // 唯一的标示 CC_PROPERTY_READONLY(unsigned int, m_uPixelsWide, PixelsWide) CC_PROPERTY_READONLY(unsigned int, m_uPixelsHigh, PixelsHigh) CC_PROPERTY(GLfloat, m_fMaxS, MaxS) CC_PROPERTY(GLfloat, m_fMaxT, MaxT) CC_PROPERTY(CCGLProgram*, m_pShaderProgram, ShaderProgram); // 渲染时使用的Shader }
从上面的数据结构我们可以看到CCTexture2D还是蛮简单的,包含有:
1. 纹理在OpenGL中的唯一标示符m_uName
2. 纹理的基本属性(大小)。
另外还有一些初始化的接口,如从CCImage和从内存raw data从进行加载。最好还需要提下CCTexture2D的两个非常简单的绘制方法,这两个绘制其实是异曲同工的.,.只不过drawInRect带有缩放,让我们仔细瞧一瞧:
CCTexture2D的简单绘制
void CCTexture2D::drawInRect(const CCRect& rect) {. GLfloat vertices[] = { rect.origin.x, rect.origin.y, /*0.0f,*/ //初始化4个顶点 rect.origin.x + rect.size.width, rect.origin.y, /*0.0f,*/ rect.origin.x, rect.origin.y + rect.size.height, /*0.0f,*/ rect.origin.x + rect.size.width, rect.origin.y + rect.size.height, /*0.0f*/ }; ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position | kCCVertexAttribFlag_TexCoords ); //设置shader m_pShaderProgram->use(); m_pShaderProgram->setUniformsForBuiltins(); ccGLBindTexture2D( m_uName ); // 绑定纹理 glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, 0, coordinates); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }
下面是这个绘制过程的一个示意图:
这段draw其实是绘制了两个三角面片,大致分为4个步骤:
1. m_pShaderProgram设置了渲染时使用的Shader,可以阅读源代码的时候会有疑问,这个shader是从哪里来的呢?仔细看CCTexture2D::initWithData来加载纹理的时候最后还有一句话哦:
setShaderProgram(CCShaderCache::sharedShaderCache()->programForKey(kCCShader_PositionTexture));
这句话是从CCShaderCache缓存中选择了key为kCCShader_PositionTexture的一段shader代码,这段代码位于ccShader_PositionTexture_vert.h以及ccShader_PositionTexture_frag.h中。
void main() \n\
{ \n\
gl_Position = CC_MVPMatrix * a_position; \n\
v_texCoord = a_texCoord; \n\
}
void main() \n\
{ \n\
gl_FragColor = texture2D(CC_Texture0, v_texCoord); \n\
}
这两段代码分别是顶点着色器和像素着色器,代码比较简单,喜欢探究的读者可以自行琢磨一下
2. ccGLBindTexture2D绑定了纹理
3. glVertexAttribPointer和glVertexAttribPointer设置了两个三角面片的顶点和索引
4. glDrawArrays批量绘制三角面片
如果说CCTexture2D可以说是整个纹理的表示,那CCSpriteFrame就是纹理的一个区域或者说是一个子集,它的表示如下:
class CC_DLL CCSpriteFrame : public CCObject { public: static CCSpriteFrame* createWithTexture(CCTexture2D* pobTexture, const CCRect& rect); protected: CCRect m_obRect; CCTexture2D *m_pobTexture; }
相比于CCTexture2D增加了一个m_obRect区域。看到这里,可能会有一个疑问,既然有了CCTexture2D为什么还要CCSpriteFrame 呢?其实不得不解释一下:有一种动画的表现形式就是多个精灵动画小纹理集中在一个大纹理上,然后每帧更新纹理的区域,有了CCSpriteFrame就方便了。或者说用多个独立的纹理聚CCSpriteFrame系列合成一个CCAnimation来表现动画。
绕了这么大一圈,现在可以说我们这个主题的主角CCSprite了。
CCSprite的秘密
先看看CCSprite的类结构
class CC_DLL CCSprite : public CCNodeRGBA, public CCTextureProtocol { public: static CCSprite* create(const char *pszFileName, const CCRect& rect); static CCSprite* createWithTexture(CCTexture2D *pTexture) static CCSprite* createWithSpriteFrame(CCSpriteFrame *pSpriteFrame) virtual void draw(void); protected: CCTexture2D* m_pobTexture; /// CCTexture2D object that is used to render the sprite ccV3F_C4B_T2F_Quad m_sQuad; // vertex coords, texture coords and color info }
这个结构中最重要的成员变量就是m_sQuad,这个变量表示了我之前提到的两个三角面片的所有信息:纹理,顶点和颜色信息。在渲染的时候会用到,这些信息是在CCSprite初始化的过程中赋值的,细心的你可以探究一下,也不复杂。
void CCSprite::draw(void) { CCAssert(!m_pobBatchNode, "If CCSprite is being rendered by CCSpriteBatchNode, CCSprite#draw SHOULD NOT be called"); ccGLBlendFunc( m_sBlendFunc.src, m_sBlendFunc.dst ); if (m_pobTexture != NULL) { ccGLBindTexture2D( m_pobTexture->getName() ); ccGLEnableVertexAttribs( kCCVertexAttribFlag_PosColorTex ); } else { ccGLBindTexture2D(0); ccGLEnableVertexAttribs( kCCVertexAttribFlag_Position | kCCVertexAttribFlag_Color ); } long offset = (long)&m_sQuad; // vertex int diff = offsetof( ccV3F_C4B_T2F, vertices); glVertexAttribPointer(kCCVertexAttrib_Position, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*) (offset + diff)); if (m_pobTexture != NULL) { // texCoods diff = offsetof( ccV3F_C4B_T2F, texCoords); glVertexAttribPointer(kCCVertexAttrib_TexCoords, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff)); } // color diff = offsetof( ccV3F_C4B_T2F, colors); glVertexAttribPointer(kCCVertexAttrib_Color, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff)); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); ccDrawPoly(vertices, 4, true); }
可以看到这个和CCTexture2D的draw是类似的,就不再详细描述咯。
最后还有一个优化CCSpriteBatchNode,这个是干嘛的呢?如果我们有多个CCSprite用的是同一张纹理,我们就可以把三角面片集中起来,在一个draw call把所有的三角面片提交,可以提高渲染效率。比如说我们有100个相同的精灵在舞蹈,每个精灵的纹理一样只是动作不一样,普通的渲染方法需要100个Draw Call,代价很大,使用CCSpriteBatchNode就只需要一个Draw Call,是不是很给力呢
再归纳一下我们遇到的精灵类之间的关系: