精灵)就是渲染一个2D图像。在cocos中,则限制为渲染一个2D矩形图像,图像数据可以是一个纹理或者是纹理的sub-rectangle(子区域)。
一、数据结构
class CC_DLL Sprite : public Node, public TextureProtocol
{
// *******************************************************
// 以下是当通过SpriteSheet创建渲染Sprite时用的数据:
// *******************************************************
// SpriteBatchNode,相关学习见;https://www.yuque.com/tvvhealth/cs/ix2yzs
TextureAtlas* _textureAtlas; // SpriteBatchNode texture atlas (weak reference,即并没有retain)
ssize_t _atlasIndex; // Absolute (real) Index on the SpriteSheet
SpriteBatchNode* _batchNode; // Used batch node (weak reference,即并没有retain)
bool _dirty; // 是否需要重新计算矩阵
bool _recursiveDirty; // 是否需要递归子节点
bool _shouldBeHidden; // should not be drawn because one of the ancestors is not visible
Mat4 _transformToBatch; // 一般仿射变换矩阵
// *******************************************************
// 以下是self-rendered的Sprite使用到的数据
// 一般就是指不通过SpriteSheet创建的。
// *******************************************************
BlendFunc _blendFunc; // 混合算法
Texture2D* _texture; // 渲染时用到的采样纹理,在片段着色中采样。
SpriteFrame* _spriteFrame; // createWithSpriteFrame
TrianglesCommand _trianglesCommand; // Sprite的绘制指令
#if CC_SPRITE_DEBUG_DRAW
DrawNode * _debugDrawNode; // 绘制出三角形的边,如果是一个矩形图像,则会绘制两个拼在一起的三角形
#endif //CC_SPRITE_DEBUG_DRAW
// *******************************************************
// 以下则是共享的数据
// *******************************************************
// texture
Rect _rect; // texture中要渲染的区域,是设计分辨率标准下
bool _rectRotated; // Whether the texture is rotated
Rect _centerRectNormalized; // Rectangle to implement "slice 9"
// Quad: Renders the sprite using 2 triangles (1 rectangle):
// uses small memory, but renders empty pixels (slow)
//
// Slice9: Renders the sprite using 18 triangles (9 rectangles).
// Useful to to scale buttons an other rectangular sprites
//
// Polygon: Renders the sprite using many triangles (depending on the setting):
// Uses more memory, but doesn't render so much empty pixels (faster)
//
// Quad_Batchnode: Renders the sprite using 2 triangles (1 rectangle) with a static batch,
// which has some limitations (see below)
RenderMode _renderMode; // Sprite的渲染模式,有以上几种
Vec2 _stretchFactor; // stretch factor to match the contentSize.
// for 1- and 9- slice sprites
Size _originalContentSize; // 纹理的大小(设计分辨率)
// Offset Position (used by Zwoptex)
Vec2 _offsetPosition;
Vec2 _unflippedOffsetPositionFromCenter;
// 顶点数据
V3F_C4B_T2F_Quad _quad; // 四边形(2个三角形)的四个顶点数据
V3F_C4B_T2F* _trianglesVertex; // 一个中间变量,存储一系列的顶点。
unsigned short* _trianglesIndex; // 存储上面对应位置顶点的索引。
PolygonInfo _polyInfo; // 存储最终的顶点数据
// opacity and RGB protocol
bool _opacityModifyRGB; // 是否启用displayOpacityRGB
// image is flipped
bool _flippedX; // Whether the sprite is flipped horizontally or not
bool _flippedY; // Whether the sprite is flipped vertically or not
bool _insideBounds; // whether or not the sprite was inside bounds the previous frame
std::string _fileName; // create(fileName)
int _fileType; // 目前看,没鸟用
bool _stretchEnabled; // 是否开启延展,用于九宫图
}
二、创建
1、create with file
class CC_DLL Sprite : public Node, public TextureProtocol
{
// 渲染整张纹理
static Sprite* create(const std::string& filename);
// 渲染纹理的一部分区域
// 纹理的左下角为(0.0, 0.0)
// 假设width、height为纹理宽高,则(width, height)表示纹理右上角。
static Sprite* create(const std::string& filename, const Rect& rect);
}
2、create with texture
class CC_DLL Sprite : public Node, public TextureProtocol
{
// auto sprite = Sprite::create(fileName)
// 与下面等价:
// auto texture = Director::getInstance()->getTextureCache()->addImage(fileName);
// auto sprite = Sprite::createWithTexture(texture)
static Sprite* createWithTexture(Texture2D *texture);
// 渲染texture的指定区域rect
// texture A pointer to an existing Texture2D object.
// rect Only the contents inside the rect of this texture will be applied for this sprite.
// rotated Whether or not the rect is rotated.
static Sprite* createWithTexture(Texture2D *texture, const Rect& rect, bool rotated=false);
}
3、create with sprite frame
class CC_DLL Sprite : public Node, public TextureProtocol
{
// A sprite frame involves a texture and a rect.
static Sprite* createWithSpriteFrame(SpriteFrame *spriteFrame);
/**
* Creates a sprite with an sprite frame name.
*
* A SpriteFrame will be fetched from the SpriteFrameCache by spriteFrameName param.
* If the SpriteFrame doesn't exist it will raise an exception.
*/
static Sprite* createWithSpriteFrameName(const std::string& spriteFrameName);
}
例子一:plist
SpriteFrameCache::getInstance()->addSpriteFramesWithFile( "animations/ghosts.plist" );
auto father = Sprite::createWithSpriteFrameName( "father.gif" );
auto father = Sprite::createWithSpriteFrameName( "sister1.gif" );
auto father = Sprite::createWithSpriteFrameName( "sister2.gif" );
auto father = Sprite::createWithSpriteFrameName( "child1.gif" );
ghosts.plist文件一定对应一个同名的png文件。
png文件是由多个纹理通过工具合成的一张大纹理。
plist则是对每个小纹理的描述信息,格式如下:
<!-- **************************************************************** -->
<!-- *************************plist文件格式*************************** -->
<!-- **************************************************************** -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict> <!-- 内容都在一个dict标签中,其实就可以看成是一个字典 -->
<key>texture</key> <!-- key标签后面接了一个dict标签,通过这个key可以找到这个dict -->
<dict>
<key>width</key> <!-- key标签后面接了一个integer标签,通过这个key可以找到这个integer-->
<integer>256</integer>
<key>height</key>
<integer>256</integer>
</dict>
</dict>
</plist>
<!-- **************************************************************** -->
<!-- *************************对应字典如下:*************************** -->
<!-- **************************************************************** -->
{
texture : {
width : 256,
height : 256
}
}
例子二
grossini_polygon.plist.txt
grossini_polygon.png
string plist = nullptr;
Data imageData = nullptr;
Image* image = nullptr;
Texture2D* texture = nullptr;
plist = FileUtils::getInstance()->getStringFromFile("grossini_polygon.plist" );
imageData = FileUtils::getInstance()->getDataFromFile( "grossini_polygon.png" );
image = new( std::nothrow ) Image();
texture = new( std::nothrow ) Texture2D();
image->initWithImageData( ( const uint8_t * )imageData.getBytes(), imageData.getSize() );
texture->initWithImage( image );
texture->autorelease();
CC_SAFE_RELEASE( image );
SpriteFrameCache::getInstance():->addSpriteFramesWithFileContent( plist, texture );
4、create with polygon info
class CC_DLL Sprite : public Node, public TextureProtocol
{
// 普通的Sprite是一个矩形,2个三角形,4个顶点。
// 可以将Sprite细化成更多个小三角形,这样就可以减少绘制空白区域了。
// 相比于普通Sprite,绘制更少的像素提高绘制效率,但会消耗更多的内存(更多的顶点数据)
// 如果一张图有大量的空白,且图本身又比较大的时候可以考虑使用。
static Sprite* create(const PolygonInfo& info);
}
Polygon Sprite和普通Sprite对比效果如下:
Pixels draw:表示光栅化(rasterization)时生成的像素数量,数量越多,则绘制越慢。
verts:表示绘制的定点数量,越多则内存消耗越大。
绿色框框是开启了CC_SPRITE_DEBUG_DRAW,将GL三角形绘制出来。
下面是使用例子。
auto scaleFactor = Director::getInstance()->getContentScaleFactor();
auto filename = "Images/grossini.png";
auto ap = AutoPolygon(filename);
auto head = Rect(30,25,25,25); // 头部区域
auto epsilon = 2.0f; // 越小,三角形越细化,顶点越多,绘制空白越少
auto threhold = 0.0f; // alpha>threhold的像素被认为是不透明的
_polygonSprite = Sprite::create(ap.generateTriangles()); // 多边形Sprite
_normalSprite = Sprite::create(filename); // 普通Sprite
// 只绘制头部区域
_polygonSprite = Sprite::create(ap.generateTriangles(head));
_normalSprite = Sprite::create(filename, head);
// 只绘制头部区域,设置参数动态改变顶点数量
_polygonSprite->setPolygonInfo(ap.generateTriangles(head, epsilon, threhold));
auto polygonInfo = ap.generateTriangles();
auto size = polygonInfo.getRect().size / scaleFactor;
// 通过polygonInfo绘制的Sprite的顶点数量
auto vertCount = polygonInfo.getVertCount;
// 通过polygonInfo绘制的Sprite的像素 与 Sprite::create(filename)的像素的比
// 看看像素减少了多少。
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,如果存在,则加载。
<a name="SskRW"></a>
# 三、绘制
```cpp
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
if (_texture == nullptr)
{
return;
}
// 程序级剪裁(Culling),再发送指令给GL之前,在应用程序中做一个剪裁测试
// 在窗口之外的顶点将不会被绘制,这样可以提升一些性能
// 更好的做法是我们设计的时候自己注意不要有超出屏幕的顶点,这样我们可以关闭CC_USE_CULLING
#if CC_USE_CULLING
......
#endif
{
// 精灵的绘制实际是绘制一个或者多个三角形。
_trianglesCommand.init(
_globalZOrder, // globalOrder用于绘制栈全局排序
_texture, // 采样的纹理
getGLProgramState(), // 着色器的状态数据
_blendFunc, // 采用的混合方程
_polyInfo.triangles, // 三角形的顶点数据和顶点索引,用于绘制
transform, // 本节点的MV,通过父节点MV*本节点坐标系的变换矩阵得到
flags); // 标记
renderer->addCommand(&_trianglesCommand);
// 按绘制顺序依次画出构成Sprite的三角形
#if CC_SPRITE_DEBUG_DRAW
_debugDrawNode->clear();
auto count = _polyInfo.triangles.indexCount/3;
auto indices = _polyInfo.triangles.indices;
auto verts = _polyInfo.triangles.verts;
for(ssize_t i = 0; i < count; i++)
{
//draw 3 lines
Vec3 from =verts[indices[i*3]].vertices;
Vec3 to = verts[indices[i*3+1]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);
from =verts[indices[i*3+1]].vertices;
to = verts[indices[i*3+2]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);
from =verts[indices[i*3+2]].vertices;
to = verts[indices[i*3]].vertices;
_debugDrawNode->drawLine(Vec2(from.x, from.y), Vec2(to.x,to.y), Color4F::WHITE);
}
#endif //CC_SPRITE_DEBUG_DRAW
}
}
从上面我们可以知道,绘制Sprite其实就是OpenGL中的绘制三角形。
四、混合:Blending
就是指的OpenGL的混合阶段。
语雀内容
相关源码如下:
// cocos中对混合的封装
struct CC_DLL BlendFunc
{
// 混合算法包含两部分:源片段的混合、目标片段的混合
// 源片段指光栅化新生成的片段,目标片段指颜色缓冲区中的对应片段。
GLenum src; // 源片段混合算法,可能枚举值参考上面部分。
GLenum dst; // 目标片段混合算法,
// 下面是cocos预定义的混合类型
static const BlendFunc DISABLE; // {GL_ONE, GL_ZERO},关闭透明度
// alpha预乘,{GL_ONE, GL_ONE_MINUS_SRC_ALPHA}
// 源片段的色值已经是乘以了alpha,因此在GL 混合阶段无需再计算源片段的部分
static const BlendFunc ALPHA_PREMULTIPLIED;
// 关闭alpha预乘,{GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}
// 混合计算工作全部在混合阶段完成
static const BlendFunc ALPHA_NON_PREMULTIPLIED;
// {GL_SRC_ALPHA, GL_ONE}
static const BlendFunc ADDITIVE;
};
class CC_DLL Sprite : public Node, public TextureProtocol
{
// 设置混合函数
void setBlendFunc(const BlendFunc &blendFunc) override;
const BlendFunc& getBlendFunc() const override { return _blendFunc; }
void Sprite::updateBlendFunc(void)
{
CCASSERT(_renderMode != RenderMode::QUAD_BATCHNODE, "CCSprite: updateBlendFunc doesn't work when the sprite is rendered using a SpriteBatchNode");
// it is possible to have an untextured sprite
if (! _texture || ! _texture->hasPremultipliedAlpha())
{
_blendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED;
setOpacityModifyRGB(false);
}
else
{
_blendFunc = BlendFunc::ALPHA_PREMULTIPLIED;
setOpacityModifyRGB(true);
}
}
}
五、颜色叠加
1、Node颜色叠加
相关数据结构:
class CC_DLL Node : public Ref
{
Color3B _displayedColor; // 顶点中的颜色数据(rgb)
Color3B _realColor; // 给节点设置的颜色属性,通过setColor设置。
// setColor时,_displayedColor = _realColor
GLubyte _displayedOpacity; // 顶点中的颜色数据(alpha)
GLubyte _realOpacity; // 同上,setOpacity
// displayColor = parent.displayColor * realColor
// true: parent.displayColor = parent.displayColor
// false: parent.displayColor = WHITE
bool _cascadeColorEnabled;
// displayOpacity = parent.displayOpacity * realOpacity
// true: parent.displayOpacity = parent.displayOpacity
// false: parent.displayOpacity = 255
bool _cascadeOpacityEnabled;
bool _opacityModifyRGB; // 主要是为了实现alpha预乘,见下面的伪代码
// 注意在node中并没有,供node子节点自行实现
}
// 这么多数据,无非就是为了计算最终颜色,计算过程如下:伪代码
// realColor = displayColor = WHITE;
// realOpacity = displayOpacity = WHITE;
// setcolor(...); // realColor = displayColor = ...
// setOpacity(...); // realOpacity = displayOpacity = ...
// parentDisplayColor = WHITE;
// if(cascadeColor)
// parentDisplayColor = parent.DisplayColor;
// displayColor = parentDisplayColor * realColor; // 计算出displayColor
// parentDisplayOpacity = 255;
// if(cascadeOpacity)
// parentDisplayOpacity = parent.DisplayOpacity;
// displayOpacity = parentDisplayOpacity * realOpacity; // 计算出displayOpacity
// if(opacityModifyRGB)
// displayColor *= displayOpacity;
//
// vertexColor = color(displayColor, displayOpacity); // 顶点数据中的颜色数据
// 进入片段着色器
// 输出到屏幕的最终颜色 = vertexColor * textureColor; // textureColor是纹理颜色
// 当然也可能没有纹理颜色,比如LayerColor
// Sprite就有纹理
相关函数方法:
class CC_DLL Node : public Ref
{
// 下面是color相关的函数,opacity的函数类似,color该Opacity即可
virtual void setColor(const Color3B& color); // 初始化realColor = displayColor = color,然后计算最终displayColor
virtual void updateDisplayedColor( const Color3B& parentColor); // 根据parentColor,计算出displayColor
virtual void setCascadeColorEnabled(bool cascadeColorEnabled); // 设置cascade
virtual void disableCascadeColor(); // 非常蛋疼的函数,糟糕的设计
virtual void updateCascadeColor(); // 根据cascade,决定parent.displayColor的值,计算displayColor
virtual void setOpacityModifyRGB(bool value); // 需要子类自行实现,意义见上面伪代码
virtual void updateColor() {} // 子类字形实现,将displayColor、displayOpacity更新到顶点数据中
virtual const Color3B& getColor() const;
virtual const Color3B& getDisplayedColor() const;
virtual bool isCascadeColorEnabled() const;
virtual bool isOpacityModifyRGB() const;
}
2、Sprite颜色叠加
Sprite覆盖实现了上面的几个函数:
- updateColor
- setOpacityModifyRGB
- isOpacityModifyRGB ```cpp
void Sprite::updateColor(void) { Color4B color4( _displayedColor.r, _displayedColor.g, _displayedColor.b, _displayedOpacity );
// alpha预乘,提前计算到displayColor中,在混合阶段则无需再计算源片段的混合
if (_opacityModifyRGB) {
color4.r *= _displayedOpacity/255.0f;
color4.g *= _displayedOpacity/255.0f;
color4.b *= _displayedOpacity/255.0f;
}
// 将颜色数据更新到顶点数据中。
for (ssize_t i = 0; i < _polyInfo.triangles.vertCount; i++) {
_polyInfo.triangles.verts[i].colors = color4;
}
......
}
void Sprite::setOpacityModifyRGB(bool modify) { if (_opacityModifyRGB != modify) { _opacityModifyRGB = modify; updateColor(); } }
bool Sprite::isOpacityModifyRGB(void) const { return _opacityModifyRGB; }
```