精灵)就是渲染一个2D图像。在cocos中,则限制为渲染一个2D矩形图像,图像数据可以是一个纹理或者是纹理的sub-rectangle(子区域)。

一、数据结构

  1. class CC_DLL Sprite : public Node, public TextureProtocol
  2. {
  3. // *******************************************************
  4. // 以下是当通过SpriteSheet创建渲染Sprite时用的数据:
  5. // *******************************************************
  6. // SpriteBatchNode,相关学习见;https://www.yuque.com/tvvhealth/cs/ix2yzs
  7. TextureAtlas* _textureAtlas; // SpriteBatchNode texture atlas (weak reference,即并没有retain)
  8. ssize_t _atlasIndex; // Absolute (real) Index on the SpriteSheet
  9. SpriteBatchNode* _batchNode; // Used batch node (weak reference,即并没有retain)
  10. bool _dirty; // 是否需要重新计算矩阵
  11. bool _recursiveDirty; // 是否需要递归子节点
  12. bool _shouldBeHidden; // should not be drawn because one of the ancestors is not visible
  13. Mat4 _transformToBatch; // 一般仿射变换矩阵
  14. // *******************************************************
  15. // 以下是self-rendered的Sprite使用到的数据
  16. // 一般就是指不通过SpriteSheet创建的。
  17. // *******************************************************
  18. BlendFunc _blendFunc; // 混合算法
  19. Texture2D* _texture; // 渲染时用到的采样纹理,在片段着色中采样。
  20. SpriteFrame* _spriteFrame; // createWithSpriteFrame
  21. TrianglesCommand _trianglesCommand; // Sprite的绘制指令
  22. #if CC_SPRITE_DEBUG_DRAW
  23. DrawNode * _debugDrawNode; // 绘制出三角形的边,如果是一个矩形图像,则会绘制两个拼在一起的三角形
  24. #endif //CC_SPRITE_DEBUG_DRAW
  25. // *******************************************************
  26. // 以下则是共享的数据
  27. // *******************************************************
  28. // texture
  29. Rect _rect; // texture中要渲染的区域,是设计分辨率标准下
  30. bool _rectRotated; // Whether the texture is rotated
  31. Rect _centerRectNormalized; // Rectangle to implement "slice 9"
  32. // Quad: Renders the sprite using 2 triangles (1 rectangle):
  33. // uses small memory, but renders empty pixels (slow)
  34. //
  35. // Slice9: Renders the sprite using 18 triangles (9 rectangles).
  36. // Useful to to scale buttons an other rectangular sprites
  37. //
  38. // Polygon: Renders the sprite using many triangles (depending on the setting):
  39. // Uses more memory, but doesn't render so much empty pixels (faster)
  40. //
  41. // Quad_Batchnode: Renders the sprite using 2 triangles (1 rectangle) with a static batch,
  42. // which has some limitations (see below)
  43. RenderMode _renderMode; // Sprite的渲染模式,有以上几种
  44. Vec2 _stretchFactor; // stretch factor to match the contentSize.
  45. // for 1- and 9- slice sprites
  46. Size _originalContentSize; // 纹理的大小(设计分辨率)
  47. // Offset Position (used by Zwoptex)
  48. Vec2 _offsetPosition;
  49. Vec2 _unflippedOffsetPositionFromCenter;
  50. // 顶点数据
  51. V3F_C4B_T2F_Quad _quad; // 四边形(2个三角形)的四个顶点数据
  52. V3F_C4B_T2F* _trianglesVertex; // 一个中间变量,存储一系列的顶点。
  53. unsigned short* _trianglesIndex; // 存储上面对应位置顶点的索引。
  54. PolygonInfo _polyInfo; // 存储最终的顶点数据
  55. // opacity and RGB protocol
  56. bool _opacityModifyRGB; // 是否启用displayOpacityRGB
  57. // image is flipped
  58. bool _flippedX; // Whether the sprite is flipped horizontally or not
  59. bool _flippedY; // Whether the sprite is flipped vertically or not
  60. bool _insideBounds; // whether or not the sprite was inside bounds the previous frame
  61. std::string _fileName; // create(fileName)
  62. int _fileType; // 目前看,没鸟用
  63. bool _stretchEnabled; // 是否开启延展,用于九宫图
  64. }

二、创建

1、create with file

  1. class CC_DLL Sprite : public Node, public TextureProtocol
  2. {
  3. // 渲染整张纹理
  4. static Sprite* create(const std::string& filename);
  5. // 渲染纹理的一部分区域
  6. // 纹理的左下角为(0.0, 0.0)
  7. // 假设width、height为纹理宽高,则(width, height)表示纹理右上角。
  8. static Sprite* create(const std::string& filename, const Rect& rect);
  9. }

2、create with texture

  1. class CC_DLL Sprite : public Node, public TextureProtocol
  2. {
  3. // auto sprite = Sprite::create(fileName)
  4. // 与下面等价:
  5. // auto texture = Director::getInstance()->getTextureCache()->addImage(fileName);
  6. // auto sprite = Sprite::createWithTexture(texture)
  7. static Sprite* createWithTexture(Texture2D *texture);
  8. // 渲染texture的指定区域rect
  9. // texture A pointer to an existing Texture2D object.
  10. // rect Only the contents inside the rect of this texture will be applied for this sprite.
  11. // rotated Whether or not the rect is rotated.
  12. static Sprite* createWithTexture(Texture2D *texture, const Rect& rect, bool rotated=false);
  13. }

3、create with sprite frame

  1. class CC_DLL Sprite : public Node, public TextureProtocol
  2. {
  3. // A sprite frame involves a texture and a rect.
  4. static Sprite* createWithSpriteFrame(SpriteFrame *spriteFrame);
  5. /**
  6. * Creates a sprite with an sprite frame name.
  7. *
  8. * A SpriteFrame will be fetched from the SpriteFrameCache by spriteFrameName param.
  9. * If the SpriteFrame doesn't exist it will raise an exception.
  10. */
  11. static Sprite* createWithSpriteFrameName(const std::string& spriteFrameName);
  12. }

例子一:plist

ghosts.png
ghosts.plist.txt

  1. SpriteFrameCache::getInstance()->addSpriteFramesWithFile( "animations/ghosts.plist" );
  2. auto father = Sprite::createWithSpriteFrameName( "father.gif" );
  3. auto father = Sprite::createWithSpriteFrameName( "sister1.gif" );
  4. auto father = Sprite::createWithSpriteFrameName( "sister2.gif" );
  5. auto father = Sprite::createWithSpriteFrameName( "child1.gif" );

ghosts.plist文件一定对应一个同名的png文件。
png文件是由多个纹理通过工具合成的一张大纹理。
plist则是对每个小纹理的描述信息,格式如下:

  1. <!-- **************************************************************** -->
  2. <!-- *************************plist文件格式*************************** -->
  3. <!-- **************************************************************** -->
  4. <?xml version="1.0" encoding="UTF-8"?>
  5. <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  6. <plist version="1.0">
  7. <dict> <!-- 内容都在一个dict标签中,其实就可以看成是一个字典 -->
  8. <key>texture</key> <!-- key标签后面接了一个dict标签,通过这个key可以找到这个dict -->
  9. <dict>
  10. <key>width</key> <!-- key标签后面接了一个integer标签,通过这个key可以找到这个integer-->
  11. <integer>256</integer>
  12. <key>height</key>
  13. <integer>256</integer>
  14. </dict>
  15. </dict>
  16. </plist>
  17. <!-- **************************************************************** -->
  18. <!-- *************************对应字典如下:*************************** -->
  19. <!-- **************************************************************** -->
  20. {
  21. texture : {
  22. width : 256,
  23. height : 256
  24. }
  25. }

例子二

grossini_polygon.plist.txt
grossini_polygon.png

  1. string plist = nullptr;
  2. Data imageData = nullptr;
  3. Image* image = nullptr;
  4. Texture2D* texture = nullptr;
  5. plist = FileUtils::getInstance()->getStringFromFile("grossini_polygon.plist" );
  6. imageData = FileUtils::getInstance()->getDataFromFile( "grossini_polygon.png" );
  7. image = new( std::nothrow ) Image();
  8. texture = new( std::nothrow ) Texture2D();
  9. image->initWithImageData( ( const uint8_t * )imageData.getBytes(), imageData.getSize() );
  10. texture->initWithImage( image );
  11. texture->autorelease();
  12. CC_SAFE_RELEASE( image );
  13. SpriteFrameCache::getInstance():->addSpriteFramesWithFileContent( plist, texture );

4、create with polygon info

  1. class CC_DLL Sprite : public Node, public TextureProtocol
  2. {
  3. // 普通的Sprite是一个矩形,2个三角形,4个顶点。
  4. // 可以将Sprite细化成更多个小三角形,这样就可以减少绘制空白区域了。
  5. // 相比于普通Sprite,绘制更少的像素提高绘制效率,但会消耗更多的内存(更多的顶点数据)
  6. // 如果一张图有大量的空白,且图本身又比较大的时候可以考虑使用。
  7. static Sprite* create(const PolygonInfo& info);
  8. }

Polygon Sprite和普通Sprite对比效果如下:
image.png
Pixels draw:表示光栅化(rasterization)时生成的像素数量,数量越多,则绘制越慢。
verts:表示绘制的定点数量,越多则内存消耗越大。

绿色框框是开启了CC_SPRITE_DEBUG_DRAW,将GL三角形绘制出来。

下面是使用例子。

  1. auto scaleFactor = Director::getInstance()->getContentScaleFactor();
  2. auto filename = "Images/grossini.png";
  3. auto ap = AutoPolygon(filename);
  4. auto head = Rect(30,25,25,25); // 头部区域
  5. auto epsilon = 2.0f; // 越小,三角形越细化,顶点越多,绘制空白越少
  6. auto threhold = 0.0f; // alpha>threhold的像素被认为是不透明的
  7. _polygonSprite = Sprite::create(ap.generateTriangles()); // 多边形Sprite
  8. _normalSprite = Sprite::create(filename); // 普通Sprite
  9. // 只绘制头部区域
  10. _polygonSprite = Sprite::create(ap.generateTriangles(head));
  11. _normalSprite = Sprite::create(filename, head);
  12. // 只绘制头部区域,设置参数动态改变顶点数量
  13. _polygonSprite->setPolygonInfo(ap.generateTriangles(head, epsilon, threhold));
  14. auto polygonInfo = ap.generateTriangles();
  15. auto size = polygonInfo.getRect().size / scaleFactor;
  16. // 通过polygonInfo绘制的Sprite的顶点数量
  17. auto vertCount = polygonInfo.getVertCount;
  18. // 通过polygonInfo绘制的Sprite的像素 与 Sprite::create(filename)的像素的比
  19. // 看看像素减少了多少。
  20. auto percent = polygonInfo.getArea() / (size.width * size.height) * 100;

5、create with etc1

原理参见:

// etc1格式的扩展名:pkm、ktx,gles2.0均支持。

auto etc1 = “Images/etc1-alpha.pkm”;

// 方法一 auto etc1_sprite = Sprite::create(etc1); // 触发了TextureCache::addImage

// 方法二 auto etcTexture = director->getTextureCache()->addImage(etc1); auto etc1_sprite = Sprite::create(); etc1_sprite->initWithTexture(etcTexture);

// TextureCache::addImage逻辑中,增加了etc1逻辑 // 假如是etc1格式,则会检查是否存在alpha texture,路径为:et1+”@alpha”,即: // Images/etc1-alpha.pkm@alpha,如果存在,则加载。

  1. <a name="SskRW"></a>
  2. # 三、绘制
  3. ```cpp
  4. void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
  5. {
  6. if (_texture == nullptr)
  7. {
  8. return;
  9. }
  10. // 程序级剪裁(Culling),再发送指令给GL之前,在应用程序中做一个剪裁测试
  11. // 在窗口之外的顶点将不会被绘制,这样可以提升一些性能
  12. // 更好的做法是我们设计的时候自己注意不要有超出屏幕的顶点,这样我们可以关闭CC_USE_CULLING
  13. #if CC_USE_CULLING
  14. ......
  15. #endif
  16. {
  17. // 精灵的绘制实际是绘制一个或者多个三角形。
  18. _trianglesCommand.init(
  19. _globalZOrder, // globalOrder用于绘制栈全局排序
  20. _texture, // 采样的纹理
  21. getGLProgramState(), // 着色器的状态数据
  22. _blendFunc, // 采用的混合方程
  23. _polyInfo.triangles, // 三角形的顶点数据和顶点索引,用于绘制
  24. transform, // 本节点的MV,通过父节点MV*本节点坐标系的变换矩阵得到
  25. flags); // 标记
  26. renderer->addCommand(&_trianglesCommand);
  27. // 按绘制顺序依次画出构成Sprite的三角形
  28. #if CC_SPRITE_DEBUG_DRAW
  29. _debugDrawNode->clear();
  30. auto count = _polyInfo.triangles.indexCount/3;
  31. auto indices = _polyInfo.triangles.indices;
  32. auto verts = _polyInfo.triangles.verts;
  33. for(ssize_t i = 0; i < count; i++)
  34. {
  35. //draw 3 lines
  36. Vec3 from =verts[indices[i*3]].vertices;
  37. Vec3 to = verts[indices[i*3+1]].vertices;
  38. _debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);
  39. from =verts[indices[i*3+1]].vertices;
  40. to = verts[indices[i*3+2]].vertices;
  41. _debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);
  42. from =verts[indices[i*3+2]].vertices;
  43. to = verts[indices[i*3]].vertices;
  44. _debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);
  45. }
  46. #endif //CC_SPRITE_DEBUG_DRAW
  47. }
  48. }

从上面我们可以知道,绘制Sprite其实就是OpenGL中的绘制三角形。

四、混合:Blending

就是指的OpenGL的混合阶段。
语雀内容
相关源码如下:

  1. // cocos中对混合的封装
  2. struct CC_DLL BlendFunc
  3. {
  4. // 混合算法包含两部分:源片段的混合、目标片段的混合
  5. // 源片段指光栅化新生成的片段,目标片段指颜色缓冲区中的对应片段。
  6. GLenum src; // 源片段混合算法,可能枚举值参考上面部分。
  7. GLenum dst; // 目标片段混合算法,
  8. // 下面是cocos预定义的混合类型
  9. static const BlendFunc DISABLE; // {GL_ONE, GL_ZERO},关闭透明度
  10. // alpha预乘,{GL_ONE, GL_ONE_MINUS_SRC_ALPHA}
  11. // 源片段的色值已经是乘以了alpha,因此在GL 混合阶段无需再计算源片段的部分
  12. static const BlendFunc ALPHA_PREMULTIPLIED;
  13. // 关闭alpha预乘,{GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}
  14. // 混合计算工作全部在混合阶段完成
  15. static const BlendFunc ALPHA_NON_PREMULTIPLIED;
  16. // {GL_SRC_ALPHA, GL_ONE}
  17. static const BlendFunc ADDITIVE;
  18. };
  19. class CC_DLL Sprite : public Node, public TextureProtocol
  20. {
  21. // 设置混合函数
  22. void setBlendFunc(const BlendFunc &blendFunc) override;
  23. const BlendFunc& getBlendFunc() const override { return _blendFunc; }
  24. void Sprite::updateBlendFunc(void)
  25. {
  26. CCASSERT(_renderMode != RenderMode::QUAD_BATCHNODE, "CCSprite: updateBlendFunc doesn't work when the sprite is rendered using a SpriteBatchNode");
  27. // it is possible to have an untextured sprite
  28. if (! _texture || ! _texture->hasPremultipliedAlpha())
  29. {
  30. _blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED;
  31. setOpacityModifyRGB(false);
  32. }
  33. else
  34. {
  35. _blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
  36. setOpacityModifyRGB(true);
  37. }
  38. }
  39. }

五、颜色叠加

1、Node颜色叠加

相关数据结构:

  1. class CC_DLL Node : public Ref
  2. {
  3. Color3B _displayedColor; // 顶点中的颜色数据(rgb)
  4. Color3B _realColor; // 给节点设置的颜色属性,通过setColor设置。
  5. // setColor时,_displayedColor = _realColor
  6. GLubyte _displayedOpacity; // 顶点中的颜色数据(alpha)
  7. GLubyte _realOpacity; // 同上,setOpacity
  8. // displayColor = parent.displayColor * realColor
  9. // true: parent.displayColor = parent.displayColor
  10. // false: parent.displayColor = WHITE
  11. bool _cascadeColorEnabled;
  12. // displayOpacity = parent.displayOpacity * realOpacity
  13. // true: parent.displayOpacity = parent.displayOpacity
  14. // false: parent.displayOpacity = 255
  15. bool _cascadeOpacityEnabled;
  16. bool _opacityModifyRGB; // 主要是为了实现alpha预乘,见下面的伪代码
  17. // 注意在node中并没有,供node子节点自行实现
  18. }
  19. // 这么多数据,无非就是为了计算最终颜色,计算过程如下:伪代码
  20. // realColor = displayColor = WHITE;
  21. // realOpacity = displayOpacity = WHITE;
  22. // setcolor(...); // realColor = displayColor = ...
  23. // setOpacity(...); // realOpacity = displayOpacity = ...
  24. // parentDisplayColor = WHITE;
  25. // if(cascadeColor)
  26. // parentDisplayColor = parent.DisplayColor;
  27. // displayColor = parentDisplayColor * realColor; // 计算出displayColor
  28. // parentDisplayOpacity = 255;
  29. // if(cascadeOpacity)
  30. // parentDisplayOpacity = parent.DisplayOpacity;
  31. // displayOpacity = parentDisplayOpacity * realOpacity; // 计算出displayOpacity
  32. // if(opacityModifyRGB)
  33. // displayColor *= displayOpacity;
  34. //
  35. // vertexColor = color(displayColor, displayOpacity); // 顶点数据中的颜色数据
  36. // 进入片段着色器
  37. // 输出到屏幕的最终颜色 = vertexColor * textureColor; // textureColor是纹理颜色
  38. // 当然也可能没有纹理颜色,比如LayerColor
  39. // Sprite就有纹理

相关函数方法:

  1. class CC_DLL Node : public Ref
  2. {
  3. // 下面是color相关的函数,opacity的函数类似,color该Opacity即可
  4. virtual void setColor(const Color3B& color); // 初始化realColor = displayColor = color,然后计算最终displayColor
  5. virtual void updateDisplayedColor( const Color3B& parentColor); // 根据parentColor,计算出displayColor
  6. virtual void setCascadeColorEnabled(bool cascadeColorEnabled); // 设置cascade
  7. virtual void disableCascadeColor(); // 非常蛋疼的函数,糟糕的设计
  8. virtual void updateCascadeColor(); // 根据cascade,决定parent.displayColor的值,计算displayColor
  9. virtual void setOpacityModifyRGB(bool value); // 需要子类自行实现,意义见上面伪代码
  10. virtual void updateColor() {} // 子类字形实现,将displayColor、displayOpacity更新到顶点数据中
  11. virtual const Color3B& getColor() const;
  12. virtual const Color3B& getDisplayedColor() const;
  13. virtual bool isCascadeColorEnabled() const;
  14. virtual bool isOpacityModifyRGB() const;
  15. }

2、Sprite颜色叠加

Sprite覆盖实现了上面的几个函数:

  • updateColor
  • setOpacityModifyRGB
  • isOpacityModifyRGB ```cpp

void Sprite::updateColor(void) { Color4B color4( _displayedColor.r, _displayedColor.g, _displayedColor.b, _displayedOpacity );

  1. // alpha预乘,提前计算到displayColor中,在混合阶段则无需再计算源片段的混合
  2. if (_opacityModifyRGB) {
  3. color4.r *= _displayedOpacity/255.0f;
  4. color4.g *= _displayedOpacity/255.0f;
  5. color4.b *= _displayedOpacity/255.0f;
  6. }
  7. // 将颜色数据更新到顶点数据中。
  8. for (ssize_t i = 0; i < _polyInfo.triangles.vertCount; i++) {
  9. _polyInfo.triangles.verts[i].colors = color4;
  10. }
  11. ......

}

void Sprite::setOpacityModifyRGB(bool modify) { if (_opacityModifyRGB != modify) { _opacityModifyRGB = modify; updateColor(); } }

bool Sprite::isOpacityModifyRGB(void) const { return _opacityModifyRGB; }

```