coco2d-x 3.x Render类

撰写于 2018-01-22 修改于 2018-01-29 分类 游戏开发 标签 cocos2d-x

上一篇解读了一下 cocos2d-x 3.x 的渲染流程,最后的渲染工作都交给了 Render类 ,那在 Render类 中具体是怎么渲染的呢?下面就以画一个 sprite 为例,一一解读一下,

sprite 中的 draw 函数

sprite 中的draw函数,并没有实际的去使用opengl代码画图,而是生成了一个渲染四边形的命令,加入Render类中的渲染队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags)
{
/*此处用于判断 node 通过 transform 变化后,是否已经出屏幕了,也就是检测是否被裁剪*/
_insideBounds = (flags & FLAGS_TRANSFORM_DIRTY) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds;
/*如果变化之后 还在 屏幕内 则进行渲染*/
if(_insideBounds)
{
/*初始化渲染命令*/
_quadCommand.init(_globalZOrder, _texture->getName(), getGLProgramState(), _blendFunc, &_quad, 1, transform);
/*加入渲染队列*/
renderer->addCommand(&_quadCommand);
}
}

Render::addCommand函数

渲染命令加入到渲染队列(RenderQueue),渲染队列(RenderQueue)又被加入到了不同的 渲染组(RenderGroups) 里面,一般默认的,比如,sprite,lable都是加入到了 默认组(0) 里面,其他特殊node(ClippingNode,RenderTexture,BatchNode)被放到了其他组里面,需要特殊处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void Renderer::addCommand(RenderCommand* command)
{
/*取_commandGroupStack 栈顶*/
int renderQueue =_commandGroupStack.top();
/*renderQueue必选要大于等于零*/
addCommand(command, renderQueue);
}

void Renderer::addCommand(RenderCommand* command, int renderQueue)
{
CCASSERT(!_isRendering, "Cannot add command while rendering");
CCASSERT(renderQueue >=0, "Invalid render queue");
CCASSERT(command->getType() != RenderCommand::Type::UNKNOWN_COMMAND, "Invalid Command Type");
/*如果是透明的,则加入到透明渲染组*/
if (command->isTransparent())
_transparentRenderGroups.push_back(command);
else
/*根据globalOrder >0, <0, =0 分别放进了renderQueue 的 _queuePosZ, _queueNegZ, _queue0*/
_renderGroups[renderQueue].push_back(command);
}

Render::render() 函数

其实render函数只处理了 渲染组 0 的渲染队列,其他组的渲染队列需要特殊处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void Renderer::render()
{
_isRendering = true;

if (_glViewAssigned)
{
/*Process render commands
1. Sort render commands based on ID*/
for (auto &renderqueue : _renderGroups)
{
/*先把 renderqueue 里面的 _queuePosZ, _queueNegZ, 根据globalOrder 从小到大排序*/
renderqueue.sort();
}
/*renderGroups[0] 中存放的就是 即将渲染的 renderqueue*/
visitRenderQueue(_renderGroups[0]);
flush();

/*Process render commands
draw transparent objects here, do not batch for transparent objects*/
if (0 < _transparentRenderGroups.size())
{
_transparentRenderGroups.sort();
glEnable(GL_DEPTH_TEST);
visitTransparentRenderQueue(_transparentRenderGroups);
glDisable(GL_DEPTH_TEST);
}
}
clean();
_isRendering = false;
}

核心函数 visitRenderQueue 的作用就是遍历渲染队列,延迟渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
void Renderer::visitRenderQueue(const RenderQueue& queue)
{
ssize_t size = queue.size();
/*此处进行分类处理 三角形渲染命令 四边形渲染命令 其他的命令暂时不管*/
for (ssize_t index = 0; index < size; ++index)
{
auto command = queue[index];
auto commandType = command->getType();
if( RenderCommand::Type::TRIANGLES_COMMAND == commandType)
{
flush3D();
/*此处的目的是,如果上个命令是 四边形渲染命令 且没有去渲染(vbo未填满),则此处强制进行渲染上个命令*/
if(_numberQuads > 0)
{
drawBatchedQuads();
_lastMaterialID = 0;
}

auto cmd = static_cast<TrianglesCommand*>(command);
/*如果vbo 已经被填满,则进行批渲染*/
if( _filledVertex + cmd->getVertexCount() > VBO_SIZE || _filledIndex + cmd->getIndexCount() > INDEX_VBO_SIZE)
{
CCASSERT(cmd->getVertexCount()>= 0 && cmd->getVertexCount() < VBO_SIZE, "VBO for vertex is not big enough, please break the data down or use customized render command");
CCASSERT(cmd->getIndexCount()>= 0 && cmd->getIndexCount() < INDEX_VBO_SIZE, "VBO for index is not big enough, please break the data down or use customized render command");
drawBatchedTriangles();
}
/*如果没有被填满 则加入 批处理 数组*/
_batchedCommands.push_back(cmd);
/*记录该命令中定点和索引的个数,累计进_filledVertex中*/
fillVerticesAndIndices(cmd);

}
else if ( RenderCommand::Type::QUAD_COMMAND == commandType )
{
flush3D();
/*此处同 三角形的渲染命令类似,如果上个命令是 三角形渲染命令 且没有去渲染(vbo未填满),则此处强制进行渲染上个命令*/

if(_filledIndex > 0)
{
drawBatchedTriangles();
_lastMaterialID = 0;
}

auto cmd = static_cast<QuadCommand*>(command);
/*如果vbo 已经被填满,则进行批渲染*/
if( (_numberQuads + cmd->getQuadCount()) * 4 > VBO_SIZE )
{
CCASSERT(cmd->getQuadCount()>= 0 && cmd->getQuadCount() * 4 < VBO_SIZE, "VBO for vertex is not big enough, please break the data down or use customized render command");
drawBatchedQuads();
}
/*如果没有被填满 则加入 批处理 数组*/
_batchQuadCommands.push_back(cmd);
/*记录该命令中定点和索引的个数,累计进_numberQuads中*/
fillQuads(cmd);

}
else if(RenderCommand::Type::GROUP_COMMAND == commandType)
{
flush();
int renderQueueID = ((GroupCommand*) command)->getRenderQueueID();
visitRenderQueue(_renderGroups[renderQueueID]);
}
else if(RenderCommand::Type::CUSTOM_COMMAND == commandType)
{
flush();
auto cmd = static_cast<CustomCommand*>(command);
cmd->execute();
}
else if(RenderCommand::Type::BATCH_COMMAND == commandType)
{
flush();
auto cmd = static_cast<BatchCommand*>(command);
cmd->execute();
}
else if(RenderCommand::Type::PRIMITIVE_COMMAND == commandType)
{
flush();
auto cmd = static_cast<PrimitiveCommand*>(command);
cmd->execute();
}
else if (RenderCommand::Type::MESH_COMMAND == commandType)
{
flush2D();
auto cmd = static_cast<MeshCommand*>(command);
if (_lastBatchedMeshCommand == nullptr || _lastBatchedMeshCommand->getMaterialID() != cmd->getMaterialID())
{
flush3D();
cmd->preBatchDraw();
cmd->batchDraw();
_lastBatchedMeshCommand = cmd;
}
else
{
cmd->batchDraw();
}
}
else
{
CCLOGERROR("Unknown commands in renderQueue");
}
}
}

绘图的过程就是:如果支持vao,则先绑定已经设置好的vao,然后往vbo中填充顶点数据,然后进行绘制;如果不支持vao,则需要设置顶点属性,然后进行绘制。如果绘制命令中使用的材质id相同的话,则不去绘制,记录顶点跨度,如果材质id不同,则进行绘制,自动实现批渲染,减少drawcall次数,所以游戏中把图片打包还是有一定好处的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
void Renderer::drawBatchedQuads()
{
int indexToDraw = 0;
int startIndex = 0;

/*如果渲染点的数量小于零,或者批渲染命令为空*/
if(_numberQuads <= 0 || _batchQuadCommands.empty())
{
return;
}
/*是否支持vao,vao已经在setupVBOAndVAO中进行设置
如果支持vao,绘图时不用再进行顶点属性的设置,直接设置vbo数据即可,
*/
if (Configuration::getInstance()->supportsShareableVAO())
{
/*绑定绘制四边形的vao*/
GL::bindVAO(_quadVAO);
/*绑定vbo*/
glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);
/*生成vbo的空间,此处没有把数据放入当前绑定的vbo中,只是开辟了一个空间*/
glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * _numberQuads * 4, nullptr, GL_DYNAMIC_DRAW);
/*获取生成的vbo指针*/
void *buf = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
/*vbo中放入数据*/
memcpy(buf, _quadVerts, sizeof(_quadVerts[0])* _numberQuads * 4);
/*解绑*/
glUnmapBuffer(GL_ARRAY_BUFFER);

glBindBuffer(GL_ARRAY_BUFFER, 0);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
}
/*一下是不支持vao的顶点属性设置*/
else
{
#define kQuadSize sizeof(_verts[0])
glBindBuffer(GL_ARRAY_BUFFER, _quadbuffersVBO[0]);

glBufferData(GL_ARRAY_BUFFER, sizeof(_quadVerts[0]) * _numberQuads * 4 , _quadVerts, GL_DYNAMIC_DRAW);

GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);

/* vertices*/
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, vertices));

/* colors*/
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, colors));

/* tex coords*/
glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (GLvoid*) offsetof(V3F_C4B_T2F, texCoords));

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _quadbuffersVBO[1]);
}

/*开始绘制顶点*/
for(const auto& cmd : _batchQuadCommands)
{
/*获取材质id*/
auto newMaterialID = cmd->getMaterialID();
/*如果上一个绘制命令的材质同当前命令的材质相同(使用同一张图片),则不渲染,
如果材质不相同,则开始绘制,实现自动批处理的绘制,有效减少drawcall次数
*/
if(_lastMaterialID != newMaterialID || newMaterialID == MATERIAL_ID_DO_NOT_BATCH)
{
if(indexToDraw > 0)
{
glDrawElements(GL_TRIANGLES, (GLsizei) indexToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (startIndex*sizeof(_indices[0])) );
_drawnBatches++;
_drawnVertices += indexToDraw;

startIndex += indexToDraw;
indexToDraw = 0;
}

/*Use new material*/
cmd->useMaterial();
_lastMaterialID = newMaterialID;
}

indexToDraw += cmd->getQuadCount() * 6;
}

/*此处是防止游戏命令没有绘制完毕*/
if(indexToDraw > 0)
{
glDrawElements(GL_TRIANGLES, (GLsizei) indexToDraw, GL_UNSIGNED_SHORT, (GLvoid*) (startIndex*sizeof(_indices[0])) );
_drawnBatches++;
_drawnVertices += indexToDraw;
}

if (Configuration::getInstance()->supportsShareableVAO())
{
/*绘制完毕,解绑VAO*/
GL::bindVAO(0);
}
else
{
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}

_batchQuadCommands.clear();
_numberQuads = 0;
}

总结

总结:以上就是cocos2d-x 3.x的核心渲染部分,相比2.x,渲染部分提高了效率,主要体现以下两个方面:

  1. 采用延迟渲染的方式,非立即渲染的方式,即把渲染顶点的数量达到vbo的最大容量才进行渲染,减少drawcall次数
  2. 自动批渲染,不需要手动添加batchnode,减少drawcall次数。
Site by ZHJ using Hexo & Random

Hide