First of All
这个系列是看了版本OpenGL超级宝典之后,重新看cocos2dx渲染部分的笔记。
2016
综述:
/**
* Sprite is a 2d image ( http://en.wikipedia.org/wiki/Sprite_(computer_graphics) )
*
* Sprite can be created with an image, or with a sub-rectangle of an image.
*
* To optimize the Sprite rendering, please follow the following best practices:
*
* - Put all your sprites in the same spritesheet (http://www.codeandweb.com/what-is-a-sprite-sheet)
* - Use the same blending function for all your sprites
* - ...and the Renderer will automatically "batch" your sprites (will draw all of them in one OpenGL call).
*
* To gain an additional 5% ~ 10% more in the rendering, you can parent your sprites into a `SpriteBatchNode`.
* But doing so carries the following limitations:
*
* - The Alias/Antialias property belongs to `SpriteBatchNode`, so you can't individually set the aliased property.
* - The Blending function property belongs to `SpriteBatchNode`, so you can't individually set the blending function property.
* - `ParallaxNode` is not supported, but can be simulated with a "proxy" sprite.
* - Sprites can only have other Sprites (or subclasses of Sprite) as children.
*
* The default anchorPoint in Sprite is (0.5, 0.5).
*/
/**
* Creates a sprite with an image filename.
*
* After creation, the rect of sprite will be the size of the image,
* and the offset will be (0,0).
*
* @param filename A path to image file, e.g., "scene1/monster.png"
* @return An autoreleased sprite object.
*/
static Sprite* create(const std::string& filename);
用图片创建的Sprite的尺寸会和图片一样大。
/**
* Creates a sprite with an sprite frame.
*
* @param spriteFrame A sprite frame which involves a texture and a rect
* @return An autoreleased sprite object
*/
static Sprite* createWithSpriteFrame(SpriteFrame *spriteFrame);
说明frame确实拥有一个rect信息。
/**
* Sets whether the sprite should be flipped horizontally or not.
*
* @param flippedX true if the sprite should be flipped horizontally, false otherwise.
*/
void setFlippedX(bool flippedX);
妈妈再也不用担心我用setScaleX(-1)来写代码啦~~
bool Sprite::initWithFile(const std::string& filename)
{
CCASSERT(filename.size()>0, "Invalid filename for sprite");
Texture2D *texture = Director::getInstance()->getTextureCache()->addImage(filename);
if (texture)
{
Rect rect = Rect::ZERO;
rect.size = texture->getContentSize();
return initWithTexture(texture, rect);
}
// don't release here.
// when load texture failed, it's better to get a "transparent" sprite then a crashed program
// this->release();
return false;
}
所以可以看到这里关键的一句是Director:getInstance():getTextureCache():addImage(fileName);也就是把文件转成纹理然后缓存。
另外,当载入纹理失败的时候会返回一个透明的Sprite。
bool Sprite::initWithSpriteFrameName(const std::string& spriteFrameName)
{
CCASSERT(spriteFrameName.size() > 0, "Invalid spriteFrameName");
SpriteFrame *frame = SpriteFrameCache::getInstance()->getSpriteFrameByName(spriteFrameName);
return initWithSpriteFrame(frame);
}
这里的知识是如何根据Frame的名字拿到Frame,SpriteFrameCache:getInstance():getSpriteFrameByName(frameName);这样子的~
/*
* This array is the data of a white image with 2 by 2 dimension.
* It's used for creating a default texture when sprite's texture is set to nullptr.
* Supposing codes as follows:
*
* auto sp = new (std::nothrow) Sprite();
* sp->init(); // Texture was set to nullptr, in order to make opacity and color to work correctly, we need to create a 2x2 white texture.
*
* The test is in "TestCpp/SpriteTest/Sprite without texture".
*/
static unsigned char cc_2x2_white_image[] = {
// RGBA8888
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF
};
#define CC_2x2_WHITE_IMAGE_KEY "/cc_2x2_white_image"
这是一个分辨率2x2的白色图片,当载入Texture失败的时候,会把这个当做默认图片传进去。
以上是2016年看的代码。
现在是2018年,现在从渲染的角度去考虑。
2018
cast
首先回顾一下c++的两种转换。
static_cast<T>
类似于直接加括号强转。dynamic_cast<T>
会检查能不能转换,若转换失败就返回一个nullptr。
就性能上来说静态转换少了一步检查,所以会好一些。
Texture2D
概况
其实就是封装了OpenGL的Texture。几个功能:
- 从内存里面拿到数据(数据来源可能是Image。Image封装着从实际的图片文件变成内存数据的过程),然后在OpenGL那里申请来一个TextureID存起来,然后把数据从client内存推送给OpenGL。
- 更新数据。使用mapbuffer方法从OpenGL拿到数据的内存,然后更改。
- 像素类型转换。(所谓像素类型就是RBGA8888,RGB888,RGB565之类的,主要影响一个图对内存的占用大小,当然因为信息量不一样,所以也影响最终texture的图像质量。)
初始化
构造方法啥事儿没做,初始化就一个initWithData,会转换数据然后调到initWithMipMap。
Sprite
从这个头文件的include就让人浮想联翩。
// ...
#include "2d/CCNode.h"
#include "2d/CCDrawNode.h"
#include "base/CCProtocols.h"
#include "renderer/CCTextureAtlas.h"
#include "renderer/CCTrianglesCommand.h"
#include "renderer/CCCustomCommand.h"
#include "2d/CCAutoPolygon.h"
- DrawNode作用
- Protocal主要是一些图像接口,设置颜色啊,设置透明度啊啥的
- 合图
- 三角形命令
- 自定义命令,作用多种多样,可以自己把callback塞进去。之前看见的作用是拿来操作OpenGL状态机,开启关闭stencil或者depth测试啥的。
- 多边形??
接下来看头文件里面的概况解说。
概况
Sprite可以用一个Image,或者一个Image的一部分来创建。
渲染优化方法:
- 使用一个图集的图片
- 使用一样的混合函数
为了把性能再提高个5%~10%,可以考虑使用SpriteBatchNode,但是有诸如以下的限制:
- 抗锯齿属性会被SpriteBatchNode控制住,无法对每个子控件做单独设置
- 混合函数同上
- 不能使用ParallaxNode(啥玩意儿??)
- 所有的子节点都应该是Sprite,不应该存在别的类型的子节点
Sprite是多继承,Node自然必不可少,还继承了TextureProtocol,就提供了一个texture的set\get接口。
创建方法多种多样:
- 默认创建
- 根据图片路径创建(可以猜到会先生成texture了)
- 根据多边形来创建
- 根据纹理创建
- 根据精灵帧创建
精灵帧是啥
其他的方法:
- updateTransform 就modelViewTransform嘛,旋转缩放啥的
- getBatchNode 就是拿到SpriteBatchNode,没啥,还有一个set的
- 对精灵帧的设置和取出
- 对dirty位的取出和设置
- 拿到合图里面的索引
- 翻转
和setScaleX(-1)的不同是setScale会把整个逻辑都真的反转,包括锚点啥的。而setFilppedX只是反转了texture。可以看一下怎么翻转的texture。翻转纹理
用contentWidth减去三角形顶点的x坐标,得到它新的x坐标,就是翻转。coord就不用变了。 - 设置blendFunc
- 拿到描述
其他的都是从node继承而来的,待会儿要看看draw方法。
受保护的方法:
- setTextureCoords(Rect rect) 这就很有意思了,应该可以根据这个来改变效果
持有的域(只列举感兴趣的):
- TrianglesCommand _trianglesCommand
- DrawNode _debugDrawNode
debug用的,应该会画出边界
方法详解
initWithTexture
有多种重写。最简单的是接收一个纹理,然后读出它的size作为大小矩阵。
- 设置blendFunc为ALPHA_PREMULTIPIED。
- 清空**_quad**。其中quad的类型为
也就是vector3, color4b, coord2f,分别代表矩形的四个角。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; };
把四个角的颜色都设置成白色 - 设置默认shader:GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP
- 设置纹理和纹理矩阵
调用到了setTexture()- 如果使用了batchNode,那么会判断传进来的纹理是否和上一个batchNode的纹理相同,否则异常。
- 使用dynamic_cast判断纹理类型是否合法,否则异常。
- 如果传进来的纹理就是空纹理,那么在纹理缓存里面找到名字为CC_2x2_WHITE_IMAGE_KEY的白色方块作为纹理(加入不存在就创建一个,白色方块很暴力,数据直接是定义一个2x2的数组在代码里)
- retain一下新纹理表示持有,释放旧有纹理
- 持有新纹理
- 设置dirty