旧的绘制系统 每个元素的绘制逻辑均分布于每个元素内部的draw方法中,并且紧密依赖于UI树的遍历。不易拓展优化。eg:依赖UI树的遍历顺序导致无法在多个层级之间调整绘制顺序,各个绘制逻辑分布在每个元素内部不利于针对绘制进行优化(如自动批绘制)
新的绘制系统 新的绘制系统将绘制部分从UI树的遍历中分离出来
特点
每个UI元素的类型更多的是根据它在应用程序中的特征而不是绘制方式不同划分的(多个不同类型的UI元素可能拥有相同的绘制方式)之前的每个UI元素拥有自己的绘制逻辑
采用应用程序级别的视口裁剪。即一个UI元素在场景中的坐标位于视窗区域之外时,就不会发送绘制命令到绘制栈上,避免了OpenGL在图元装配阶段的工作
采用自动批绘制技术:如果在一个场景中有很多的元素都使用同一张纹理,同一个着色器程序,理论上可以只调用一次绘制命令。自动批绘制需要相关的绘制命令在执行顺序上相邻。可以减少OpenGL的Draw Calls
更简单的实现绘制的自定义
流程 生成绘制命令 通过UI树的遍历,给每个元素生成一个绘制命令。
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 void Sprite::draw(Renderer *renderer, const Mat4 &transform, uint32_t flags){ if (_texture == nullptr ) { return ; } #if CC_USE_CULLING auto visitingCamera = Camera::getVisitingCamera(); auto defaultCamera = Camera::getDefaultCamera(); if (visitingCamera == nullptr ) { _insideBounds = true ; } else if (visitingCamera == defaultCamera) { _insideBounds = ((flags & FLAGS_TRANSFORM_DIRTY) || visitingCamera->isViewProjectionUpdated()) ? renderer->checkVisibility(transform, _contentSize) : _insideBounds; } else { _insideBounds = renderer->checkVisibility(transform, _contentSize); } if (_insideBounds) #endif { _trianglesCommand.init(_globalZOrder, _texture, getGLProgramState(), _blendFunc, _polyInfo.triangles, transform, flags); renderer->addCommand(&_trianglesCommand); #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++) { 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 } }
RenderCommand表示一个绘制类型,定义了怎样绘制一个UI元素。一般情况下每个UI元素会关联0个过着一个RenderCommand并在draw方法中将会致命了发送给renderer。
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 class CC_DLL RenderCommand { public: /**Enum the type of render command. */ enum class Type { /** Reserved type.*/ UNKNOWN_COMMAND, /** Quad command, used for draw quad.*/ QUAD_COMMAND, /**Custom command, used for calling callback for rendering.*/ CUSTOM_COMMAND, /**Batch command, used for draw batches in texture atlas.*/ BATCH_COMMAND, /**Group command, which can group command in a tree hierarchy.*/ GROUP_COMMAND, /**Mesh command, used to draw 3D meshes.*/ MESH_COMMAND, /**Primitive command, used to draw primitives such as lines, points and triangles.*/ PRIMITIVE_COMMAND, /**Triangles command, used to draw triangles.*/ TRIANGLES_COMMAND };
GROUP_COMMAND用来包装多个RenderCommand的集合,GroupCommand中的每一个RenderCommand都不会参与全局排序,它可以用来实现子元素裁剪,绘制子元素到纹理
TrianglesCommand继承RenderCommand。class CC_DLL TrianglesCommand : public RenderCommand
http://www.cocos2d-x.org/reference/native-cpp/V3.5/d9/d0d/classcocos2d_1_1_triangles_command.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 TrianglesCommand _trianglesCommand; _trianglesCommand.init(_globalZOrder, _texture, getGLProgramState(), _blendFunc, _polyInfo.triangles, transform, flags); renderer->addCommand(&_trianglesCommand);
只发送绘制命令,将每个元素的绘制部分从UI树的遍历过程中抽取出来统一处理,这样可以灵活的调整不同UI层级之间的元素绘制顺序,和特点3
绘制命令排序 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 RenderQueue enum QUEUE_GROUP { GLOBALZ_NEG = 0 , OPAQUE_3D = 1 , TRANSPARENT_3D = 2 , GLOBALZ_ZERO = 3 , GLOBALZ_POS = 4 , QUEUE_COUNT = 5 , };
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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 void Renderer::render() { //Uncomment this once everything is rendered by new renderer //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //TODO: setup camera or MVP _isRendering = true; if (_glViewAssigned) { //Process render commands //1. Sort render commands based on ID for (auto &renderqueue : _renderGroups) { renderqueue.sort(); } visitRenderQueue(_renderGroups[0]); } clean(); _isRendering = false; } void Renderer::visitRenderQueue(RenderQueue& queue) { queue.saveRenderState(); // //Process Global-Z < 0 Objects // const auto& zNegQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_NEG); if (zNegQueue.size() > 0) { if(_isDepthTestFor2D) { glEnable(GL_DEPTH_TEST); glDepthMask(true); glEnable(GL_BLEND); RenderState::StateBlock::_defaultState->setDepthTest(true); RenderState::StateBlock::_defaultState->setDepthWrite(true); RenderState::StateBlock::_defaultState->setBlend(true); } else { glDisable(GL_DEPTH_TEST); glDepthMask(false); glEnable(GL_BLEND); RenderState::StateBlock::_defaultState->setDepthTest(false); RenderState::StateBlock::_defaultState->setDepthWrite(false); RenderState::StateBlock::_defaultState->setBlend(true); } glDisable(GL_CULL_FACE); RenderState::StateBlock::_defaultState->setCullFace(false); for (const auto& zNegNext : zNegQueue) { processRenderCommand(zNegNext); } flush(); } // //Process Opaque Object // const auto& opaqueQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::OPAQUE_3D); if (opaqueQueue.size() > 0) { //Clear depth to achieve layered rendering glEnable(GL_DEPTH_TEST); glDepthMask(true); glDisable(GL_BLEND); glEnable(GL_CULL_FACE); RenderState::StateBlock::_defaultState->setDepthTest(true); RenderState::StateBlock::_defaultState->setDepthWrite(true); RenderState::StateBlock::_defaultState->setBlend(false); RenderState::StateBlock::_defaultState->setCullFace(true); for (const auto& opaqueNext : opaqueQueue) { processRenderCommand(opaqueNext); } flush(); } // //Process 3D Transparent object // const auto& transQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::TRANSPARENT_3D); if (transQueue.size() > 0) { glEnable(GL_DEPTH_TEST); glDepthMask(false); glEnable(GL_BLEND); glEnable(GL_CULL_FACE); RenderState::StateBlock::_defaultState->setDepthTest(true); RenderState::StateBlock::_defaultState->setDepthWrite(false); RenderState::StateBlock::_defaultState->setBlend(true); RenderState::StateBlock::_defaultState->setCullFace(true); for (const auto& transNext : transQueue) { processRenderCommand(transNext); } flush(); } // //Process Global-Z = 0 Queue // const auto& zZeroQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_ZERO); if (zZeroQueue.size() > 0) { if(_isDepthTestFor2D) { glEnable(GL_DEPTH_TEST); glDepthMask(true); glEnable(GL_BLEND); RenderState::StateBlock::_defaultState->setDepthTest(true); RenderState::StateBlock::_defaultState->setDepthWrite(true); RenderState::StateBlock::_defaultState->setBlend(true); } else { glDisable(GL_DEPTH_TEST); glDepthMask(false); glEnable(GL_BLEND); RenderState::StateBlock::_defaultState->setDepthTest(false); RenderState::StateBlock::_defaultState->setDepthWrite(false); RenderState::StateBlock::_defaultState->setBlend(true); } glDisable(GL_CULL_FACE); RenderState::StateBlock::_defaultState->setCullFace(false); for (const auto& zZeroNext : zZeroQueue) { processRenderCommand(zZeroNext); } flush(); } // //Process Global-Z > 0 Queue // const auto& zPosQueue = queue.getSubQueue(RenderQueue::QUEUE_GROUP::GLOBALZ_POS); if (zPosQueue.size() > 0) { if(_isDepthTestFor2D) { glEnable(GL_DEPTH_TEST); glDepthMask(true); glEnable(GL_BLEND); RenderState::StateBlock::_defaultState->setDepthTest(true); RenderState::StateBlock::_defaultState->setDepthWrite(true); RenderState::StateBlock::_defaultState->setBlend(true); } else { glDisable(GL_DEPTH_TEST); glDepthMask(false); glEnable(GL_BLEND); RenderState::StateBlock::_defaultState->setDepthTest(false); RenderState::StateBlock::_defaultState->setDepthWrite(false); RenderState::StateBlock::_defaultState->setBlend(true); } glDisable(GL_CULL_FACE); RenderState::StateBlock::_defaultState->setCullFace(false); for (const auto& zPosNext : zPosQueue) { processRenderCommand(zPosNext); } flush(); } queue.restoreRenderState(); }
在UI全部遍历完成之后,执行命令之前,对栈上的绘制命令进行排序,然后按照新的顺序执行它们
绘制顺序首先由globalZOrder决定,然后才是按照元素的遍历顺序
Render实际上维护着一个RenderQueue的数组,每个RenderQueue对应一组RenderCommand或者一个GroupCommand,这些RenderQueue不是简单的线性关系,而是通过GroupCommand构成的树状的关系
####执行绘制
对于一般的RenderCommand按顺序执行,对于相邻使用相同纹理的QuadCommand,将会组合成一个。也就是自动批绘制
GroupCommand GroupCommand通常不包括具体的GL绘制命令,它只指向一个RenderQueue。当渲染系统绘制一个GroupCommand时,它将找到对应的RenderQueue,然后执行其中的RenderCommand,
把一组RenderCommand加入GroupCommand指向的RenderQueue中,则可以实现对这组RenderCommand的独立绘制,执行顺序不会受其它RenderCommand的影响