概述
前面我们介绍过,在 engine core/renderer/index.js 的 initWebGL
方法中,针对不同的平台,提供的渲染的方式也是不一样的。
本文介绍在 Web 平台的渲染方式,相关代码主要在 engine 的 cocos2d/core/renderer/ 、 cocos2d/renderer/ 和 cocos2d/core/components/中。
相关类
- RenderComponent:所有的渲染组件都是继承自 cc.RenderComponent,例如cc.Sprite,cc.Label 等
- Assembler:组件的 Assembler 主要负责组件数据的更新处理及填充,生成 RenderData,由于不同的渲染组件在数据内容及填充上也都不相同,所以每一个渲染组件都会对应拥有自己的 Assembler 对象,而所有的 Assembler 对象都是继承自 cc.Assembler。
- Material:作为资源,主要记录渲染组件的渲染状态,使用的纹理及 Shader。控制渲染组件的视觉效果,有 Effect 属性。
- RenderData:Assembler 中持有的渲染数据,用来保存顶点数据、顶点索引数据。
- RenderFlow:渲染流,用来遍历场景下面的所有节点,根据每个节点的_renderFlag,处理节点的位置,颜色,透明度,更新并渲染。
- ModelBatcher:用来管理渲染数据的 model,渲染批次合并,从而减少drawcall,提升新能。
- ForwardRenderer:持有的 Device 使用 gl 的函数,将定点数据、纹理等绘制到屏幕上。在
initWebGL
根据平台的不同,创建不同的 ForwardRenderer。 - Effect:可定义GLSL脚本。
初始化
1 | ├── _initRenderer // engine CCGame.js |
渲染流程
我们在《游戏启动运行流程》介绍到,每一帧的刷新都会调用 director.mainLoop 方法。
Web 平台的渲染流程如下:
1 | ├── CCDirector.mainLoop // engine CCDirector.js |
RenderComponent
相关代码在 cocos2d/core/components/CCRenderComponent.js
- _resetAssembler:在组件创建的时候会去调用,会在组件生命周期方法之前执行,主要负责创建并初始化渲染组件的 Assembler 实例。
- _activeMaterial:负责创建并设置渲染组件所使用的材质实例,会在组件启用及材质修改时调用。
- markForRender 、disableRender:控制渲染组件是否进行渲染
- setVertsDirty:在渲染组件数据有更新时,设置标记
- setMaterial:则是设置材质实例给渲染组件
- _updateMaterial:更新材质
- _updateColor:更新颜色
- _checkBacth :检测合批
Assembler
组件的 Assembler 主要负责组件数据的更新处理及填充,生成 RenderData,由于不同的渲染组件在数据内容及填充上也都不相同,所以每一个渲染组件都会对应拥有自己的 Assembler 对象,而所有的 Assembler 对象都是继承自 cc.Assembler。
相关代码在 cocos2d/core/renderer/webgl/assemblers/ 和 cocos2d/core/renderer/ 下面。
Assembler 定义了下面的方法:
- init 负责初始化渲染数据及一些局部参数
- updateRenderData 负责在渲染组件的顶点数据有变化时进行更新修改
- fillBuffers 负责在渲染时进行顶点数据的 Buffer 填充
2D渲染中,Assembler2D 类是一个重要的基础类,最常用的cc.Sprite的各种模式(Simple,平铺,九宫格)在内部都对应了不同的Assembler派生类。同样是一个四边形的节点,不同的Assembler可以将其转化成不同数量的顶点实现不同的渲染效果:
- Simple模式下是常规的四边形,有4个顶点
- 平铺模式下Assembler根据纹理的重复次数对节点进行“拆碎”,相当于每重复一次就产生1个四边形
- 九宫格模式下Assembler将节点拆分为9个四边形,每个四边形对应纹理上的一个“格子”
源码在 cocos2d/core/renderer/assembler-2d.js
1 | fillBuffers (comp, renderer) { |
这里为什么要需要准备顶点数据,而不是在 fillBuffers()
方法内直接计算后填入buffer呢?
因为 fillBuffers()
每帧都会被调用,是热点代码,需要关注效率。但是顶点数据不是每一帧都会更新,可以预先计算。
1 | getBuffer () { |
1 | // cocos2d/core/renderer/index.js |
这里 getBuffer 获取的是 ModelBatcher 的 _meshBuffer,也就是说数据被填充到了 _meshBuffer。
_meshBuffer 保存着所有顶点数据,包括不同的纹理。
顶点数据
这里介绍一下 RenderData 的顶点数据。
最常用的顶点格式是 vfmtPosUvColor ,也是Assembler2D默认使用的格式。
代码在 cocos2d/core/renderer/webgl/vertex-format.js
1 | var vfmtPosUvColor = new gfx.VertexFormat([ |
看下Assembler2D里的属性和顶点格式的对应关系:
1 | cc.js.addon(Assembler2D.prototype, { |
了解了上面的顶点格式之后,顶点数据无非就是计算 pos、uv、color几个值。
在Assembler里分别有 updateVerts()、 updateUVs()、 updateColor() 方法来准备这几个值,并且临时存储在Assembler自己分配的数组里。
1 | export default class Assembler2D extends Assembler { |
顶点索引
除了pos、uv、color数据之外,为什么还需要计算顶点索引数据?
我们发送给GPU的数据,实际上表示的是三角形,而不是四边形。一个四边形需要剖分成2个三角形然后传给GPU。
在4个顶点数据的基础上,三角形的描述信息单独存在 IndiceBuffer (即renderData.iDatas)里,IndiceBuffer里的每个值表示其对应顶点数据的下标。通过索引可以合并掉多个三角形中相同的顶点数据,减少总数据大小。
常规四边形的索引数据准备,源码位置:cocos2d/core/renderer/webgl/render-data.js。
1 | initQuadIndices(indices) { |
RenderFlow
源码在cocos2d/core/renderer/render-flow.js
可以参考 cocos 文档关于渲染流的介绍:https://docs.cocos.com/creator/manual/zh/advanced-topics/render-flow.html。
RenderFlow 即为渲染流,用来遍历场景下面的所有节点,根据每个节点的_renderFlag,处理节点的位置,颜色,透明度,更新并渲染。
在 2.X 的版本中,RenderFlow 会把渲染过程划分为多个渲染状态,比如Transform,Render,Children 等,每个渲染状态都对应了一个函数。在 RenderFlow 初始化的时候,会预先根据渲染状态创建好对应的渲染分支,这些分支会把对应的渲染状态链接在一起。
我们可以把它理解为一个函数链表。一个节点要被渲染前,会更新该节点的_renderFlag,然后会根据它的 _renderFlag 进行响应的分支处理。
比如:当节点上的位置、颜色、透明度变化之后,就需要同步修改 _renderFlag,这样在渲染时就会根据 _renderFlag 处理对应的流程。
1 | RenderFlow.render = function (scene, dt) { |
在 RenderFlow.visitRootNode 方法内部,会开始执行 RenderFlow 的渲染流程,根据 _renderFlag 来执行相对应的渲染阶段。
_renderFlag 有下面几种:
1 | const DONOTHING = 1 << FlagOfset++; |
通过 createFlow 绑定 _renderFlag 和对应的执行方法:
1 | function createFlow (flag, next) { |
1 | // 空方法,通常是渲染流的最后一个方法 |
来简单看一下一个图片的 render flow,这只是渲染图片时的一种渲染组合而已,具体情况不同,渲染组合也是不同的:
1 | ├── RenderFlow.init |
ModelBatcher
代码在 cocos2d/core/renderer/webgl/model-batcher.js。
用来管理渲染数据的 model,渲染批次合并,从而减少drawcall,提升新能。
在 RendererFlow 的 _render 方法中,会调用 CCRenderComponent 的 _checkBacth 方法:
1 | _checkBacth (renderer, cullingMask) { |
如果发现两个材质不同,就调用 ModelBatcher 的 _flush 方法创建一个新的渲染批次数据Model。
1 | _flush () { |
_flush 方法生成新的 Model 数据,并把数据加入到 _renderScene 中去,一个 Model 数据就是一个渲染批次,所以 Model 越多,DC(DrawCall)就越多。
在base-renderer.js 的 _render 方法中会获取 scene 中的 _models,提取数据转化为 drawItem:
1 | _render (view, scene) { |
1 | extractDrawItem(out) { |
在 _checkBacth 合批完成后,就会调用 _assembler.fillBuffers 将 _renderData 数据到 ModelBatcher 的 _meshBuffer 。
1 | // cocos2d/core/renderer/assembler-2d.js |
1 | // cocos2d/core/renderer/index.js |
ForwardRenderer
源码在 cocos2d/renderer/renderers/forward-renderer.js。
ForwardRender 继承自 Base,是和底层 gl 渲染接近的类。
- _device:对应平台的绘制对象,调用 gl 接口进行渲染。
- _programLib:管理 shader 定义、获取和检查等相关的变量。
- _stage2fn:保存有不同渲染通道的名称与其对应的不同渲染方法。
- _viewPools:单个相机的描述数据类(View)的对象池,一个View对应一个相机。
- _drawItemsPools:渲染数据类的对象池,保存有每个渲染批次需要的model,effect等数据。
- _stageItemsPools:单个渲染通道需要渲染的数据的对象池,对 _drawItemsPools 的数据按照不同的渲染通道进行了分类。
渲染通道
ForwardRenderer 意为前向渲染,泛指传统上只有opaque和transparent另个通道的渲染技术,cocos有三个渲染通道,
绑定 Stage 操作的相关方法:
1 | export default class ForwardRenderer extends BaseRenderer { |
Device
Device 源码在 cocos2d/renderer/gfx/device.js 。
Device 是和底层 OpenGL 渲染最接近的类,运行平台对应的绘制图形对象的实例,用于绘制图形到屏幕,内部的方法直接调用 _gl 的相关方法执行渲染任务。
Device 的构造方法中构造 gl 实例,默认采用 webgl 进行绘制:
1 | constructor(canvasEL, opts) { |
来看一下Device 的 draw 方法:
1 | // engien cocos2d/renderer/gfx/device.js |
相关文章
https://www.jianshu.com/u/01450ce9ecbf
https://www.jianshu.com/p/8653006df65d