版本: cocos3.10
简介
在cocos2d-x 渲染机制简介简要说明了下UI元素的渲染流程:
-
通过
Node::visit
对节点遍历,然后调用draw
生成绘制命令 -
在
draw
中,通过Renderer::addCommand
将命令添加到RenderQueue
绘制命令栈中 -
通过
Renderer::render
对调用绘制队列进行排序 -
排序完成后,通过
Renderer::processRenderCommand
执行不同的绘制命令进行渲染
本篇文章主要讲述:
-
RenderCommand
不同元素绘制命令的生成 -
RenderQueue
绘制队列,用于管理不同的绘制命令并进行排序 -
Render
负责绘制队列的管理以及执行排序,渲染等
下面开始!
RenderCommand
它是针对于不同节点而进行特定绘制方式的封装。
class CC_DLL RenderCommand {
public:
// 绘制命令类型
enum class Type {
UNKNOWN_COMMAND,
QUAD_COMMAND, // 用于绘制多边形
CUSTOM_COMMAND, // 用于自定义绘制,通过回调进行渲染
BATCH_COMMAND, // 用于在纹理图集中批量绘制
GROUP_COMMAND, // 用于组相关,并不执行绘制,只是包含多个命令相关
MESH_COMMAND, // 用于绘制3D网格
PRIMITIVE_COMMAND, // 用于绘制线、点、三角形等
TRIANGLES_COMMAND // 用于绘制精灵等
};
void init(float globalZOrder, const Mat4& modelViewTransform, uint32_t flags);
// 获取globalZOrder
float getGlobalOrder() const { return _globalOrder; }
// 获取命令类型
Type getType() const { return _type; }
protected:
RenderCommand();
virtual ~RenderCommand();
每个RenderCommand
的实例中都会包含的两个属性:
globalZOrder
: 用于对绘制命令的排序type
: 不同的绘制命令类型,会用于做不同的绘制处理。
cocos2d-x提供的继承于RenderCommand
的主要接口有:
这些接口对应着RenderCommand
中的枚举类型,需要注意:每个UI元素对应着至少一个RenderCommand
。
不同的RenderCommand
进行组合完成一个UI元素的绘制。
注意绘制命令GroupCommand, 它并不会执行OpenGL的绘制命令, 它主要用于包装多个RenderCommand
。
其他的绘制命令大概的作用如下:
- BatchCommand: 对于
SpriteBatchNode
、ParticleBatchNode
的绘制 - TrianglesCommand: 对于
Sprite
的绘制 - CustomCommand: 主要针对于自定义绘制
- QuadCommand: 对于
Skin
的绘制 - GroupCommand: 用来包装多个RenderCommand的集合
这些绘制命令,都会被添加到RenderQueue
的绘制命令栈中。
CustomCommand
用于自定义绘制,比如ClippingNode
、DrawNode
、 LayerColor
等
class CC_DLL CustomCommand : public RenderCommand {
public:
CustomCommand();
~CustomCommand();
void init(float globalZOrder, const Mat4& modelViewTransform, uint32_t flags);
void init(float globalZOrder);
void execute();
std::function<void()> func;
};
通过 func
的变量,当处理CustomCommand时,只调用func
的回调方法相关
void Renderer::processRenderCommand(RenderCommand* command) {
if(RenderCommand::Type::CUSTOM_COMMAND == commandType) {
flush();
auto cmd = static_cast<CustomCommand*>(command);
cmd->execute();
}
}
void CustomCommand::execute() {
if(func) {
func();
}
}
GroupCommand
就如前文所说,它并不会执行具体的绘制命令,而是对多个的RenderCommand
进行了包装。
在cocos2d-x中,它可以被用来实现子元素裁剪的ClippingNode
,实现元素绘制到纹理的RenderTexture
等。
class CC_DLL GroupCommand : public RenderCommand {
public:
GroupCommand();
~GroupCommand();
void init(float globalOrder);
int getRenderQueueID() const { return _renderQueueID; }
protected:
int _renderQueueID;
};
该命令并不是添加到主绘制栈中,而是会添加到新的RenderQueue中。
/*
在初始化的时候,会调用一个GroupCommandManager的管理类
该类主要用于缓存已有的队列,减少重复创建
*/
void GroupCommand::init(float globalOrder) {
_globalOrder = globalOrder;
auto manager = Director::getInstance()->getRenderer()->getGroupCommandManager();
manager->releaseGroupID(_renderQueueID);
_renderQueueID = manager->getGroupID();
}
int GroupCommandManager::getGroupID() {
// 重用原有的ID
if (!_unusedIDs.empty()) {
int groupID = *_unusedIDs.rbegin();
_unusedIDs.pop_back();
_groupMapping[groupID] = true;
return groupID;
}
// 创建新的绘制栈
int newID = Director::getInstance()->getRenderer()->createRenderQueue();
_groupMapping[newID] = true;
return newID;
}
int Renderer::createRenderQueue() {
RenderQueue newRenderQueue;
_renderGroups.push_back(newRenderQueue);
return (int)_renderGroups.size() - 1;
}
添加新的绘制栈,这样有利于:
- GroupCommand内的RenderCommand不受主绘制栈的影响
- 可以针对于某些特殊的绘制单独的设置绘制状态
- 在绘制命令遍历的时候,主绘制栈也不会再遍历GroupCommand内的其他绘制命令
在被addCommand
后,程序会调用pushGroup
将该命令的绘制栈ID添加的Renderer的绘制栈ID数组中
Renderer *renderer = Director::getInstance()->getRenderer();
renderer->addCommand(&_groupCommand);
// 将queueId添加到绘制栈ID集合中
renderer->pushGroup(_groupCommand.getRenderQueueID());
void Renderer::pushGroup(int renderQueueID) {
_commandGroupStack.push(renderQueueID);
}
这样做的目的主要在于执行GroupCommand的时候,可以通过对应的queueId获取到RenderQueue的绘制栈信息。
void Renderer::processRenderCommand(RenderCommand* command) {
if(RenderCommand::Type::GROUP_COMMAND == commandType) {
flush();
// 获取queueId
int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
// 根据queueId获取指定的绘制栈信息,然后访问绘制
visitRenderQueue(_renderGroups[renderQueueID]);
}
}
而GroupCommand在不被使用后,需要调用popGroup
进行释放
Renderer *renderer = director->getRenderer();
renderer->popGroup();
void Renderer::popGroup()
{
_commandGroupStack.pop();
}
TrianglesCommand
它主要用于绘制Sprite,简要的看下代码:
class CC_DLL TrianglesCommand : public RenderCommand {
public:
/**The structure of Triangles. */
struct Triangles
{
/**Vertex data pointer.*/
V3F_C4B_T2F* verts;
/**Index data pointer.*/
unsigned short* indices;
/**The number of vertices.*/
int vertCount;
/**The number of indices.*/
int indexCount;
};
// ...
}
Sprite的绘制本质是使用的三角形,原因在于: cocos2d-x 使用的是OpenGL ES版本,它是OpenGL的一个分集,它不支持四边形的绘制,所以需要两个三角形组合绘制一个四边形,知道下这个就行了。
RenderQueue
它主要用于管理绘制命令RenderCommand, 并对这些绘制命令按照globalZOrder
进行排序。
class RenderQueue {
public:
// 类型,共有5种
enum QUEUE_GROUP
{
GLOBALZ_NEG = 0, // globalZOrder小于0的对象
OPAQUE_3D = 1, // globalZOrder为0的不透明3D对象
TRANSPARENT_3D = 2, // globalZOrder为0的3D对象
GLOBALZ_ZERO = 3, // globalZOrder为0的2D对象
GLOBALZ_POS = 4, // globalZOrder大于0的对象
QUEUE_COUNT = 5,
};
public:
RenderQueue();
void push_back(RenderCommand* command);
void sort();
void clear();
protected:
std::vector<RenderCommand*> _commands[QUEUE_COUNT];
};
在UI节点遍历时,不同的元素会通过draw
生成绘制命令RenderCommand, 然后通过addCommand
添加到这里。
在绘制队列中,会对绘制命令RenderCommand进行再一次的排序。
- 排序globalZOrder小于0的
- 排序globalZOrder大于0的
void RenderQueue::sort()
{
// globalZOrder小于0的对象
std::stable_sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_NEG]), std::end(_commands[QUEUE_GROUP::GLOBALZ_NEG]), compareRenderCommand);
// globalZOrder大于0的对象
std::stable_sort(std::begin(_commands[QUEUE_GROUP::GLOBALZ_POS]), std::end(_commands[QUEUE_GROUP::GLOBALZ_POS]), compareRenderCommand);
}
static bool compareRenderCommand(RenderCommand* a, RenderCommand* b) {
return a->getGlobalOrder() < b->getGlobalOrder();
}
没有对globalZOrder等于0的排序的原因是: 程序默认globalZOrder为0,而节点visit
遍历排序localZOrder
的时候,本质上就是对globalZOrder == 0
进行的排序。
Renderer
它主要负责绘制命令相关的遍历和渲染。大概的作用有:
- 在
draw
中调用addCommand 将生成的绘制命令放到RenderQueue
绘制队列中 - 通过
Renderer::render
对会展队列中的命令排序且执行渲染流程
class CC_DLL Renderer {
public:
// 添加命令
void addCommand(RenderCommand* command);
void addCommand(RenderCommand* command, int renderQueueID);
// 用于组命令相关
void pushGroup(int renderQueueID);
void popGroup();
int createRenderQueue();
// 渲染
void render();
protected:
void visitRenderQueue(RenderQueue& queue);
void processRenderCommand(RenderCommand* command);
std::vector<RenderQueue> _renderGroups;
std::stack<int> _commandGroupStack;
}
绘制队列可有多个,但索引为0的被称为主绘制栈,也就是_renderGroups[0]
。
void Renderer::render() {
_isRendering = true;
if (_glViewAssigned) {
for (auto &renderqueue : _renderGroups) {
// 遍历
renderqueue.sort();
}
// 调用主绘制栈开始渲染
visitRenderQueue(_renderGroups[0]);
}
clean();
_isRendering = false;
}
Renderer会有多个RenderQueue, 通过_renderGroups
来保存绘制命令栈的信息。而_commandGroupStack
来保存对应的绘制栈的ID信息。
void Renderer::addCommand(RenderCommand* command) {
// 获取绘制栈ID
int renderQueueID =_commandGroupStack.top();
addCommand(command, renderQueueID);
}
// 将绘制命令添加到指定ID的绘制栈中
void Renderer::addCommand(RenderCommand* command, int renderQueueID) {
_renderGroups[renderQueueID].push_back(command);
}
void RenderQueue::push_back(RenderCommand* command) {
// 获取绘制命令的globalZOrder
float z = command->getGlobalOrder();
// 根据globalZOrder的不同添加到不同的绘制队列中
if(z < 0) {
_commands[QUEUE_GROUP::GLOBALZ_NEG].push_back(command);
} else if(z > 0) {
_commands[QUEUE_GROUP::GLOBALZ_POS].push_back(command);
}
}
理解可能有误,欢迎大佬指出,祝大家学习生活愉快!