• 第二篇 渲染框架2.x


    简介

    整个渲染框架主要包含:用于控制场景中所有渲染节点的渲染状态的流程的RenderFlow。更新渲染数据、写入Buffer的Assembler。暂存数据的RenderData。数据缓冲区的MeshBuffer、quadBuffer、spineBuffer。包含着色器程序和渲染技术的Material。渲染指令数据的装载、合批的ModelBatcher。依次对每个model数据进行真正调用渲染的forwardRenderer。

    一、RenderFlow

    cocos把每个渲染数据状态都划分成不同的flag的,并创建了与之对应的函数。多个flag组成了一个_renderFlag。renderFlow 主要功能是根据节点的_renderFlag 调用对应的链表函数,会优选对渲染数据进行更新暂存至RenderData中,再写入数据缓冲区Buffer中,最后调用forwardRender的render 进行渲染。

    再写入数据缓冲区之前,会把符合合批条件(material的hash、cullingMask一致)的渲染进行动态合批。

    • _renderFlag   

    当渲染数据被修改,需要渲染时会使用flag进行标记。其记录方式是使用掩码,通过掩码的位与运算、位或运算、位取反运算。

    位与运算:&    例如:如果你有两个二进制数 A = 1101 和 B = 1010,那么 A & B 的结果将是 1000。
    位取反运算:~     例如:如果你有一个二进制数 A = 1101,那么 ~A 的结果将是 0010。
    位或运算:|    例如:如果你有两个二进制数 A = 1101 和 B = 1010,那么 A | B 的结果将是 1111。

    • 链表函数

    因为cocos 根据渲染状态的调用频繁度划分出了多个分支,在初始化时就把所有可能存在的分支全部创建共1024个分支(此时不是完整的链表函数)。Render时会根据node的renderFlag调用对应的分支,如果存在完整的链表函数,直接调用。不存在会优先创建链表函数,再调用。

    LOCAL_TRANSFORM、WORLD_TRANSFORM、TRANSFORM 用于更新节点的本地矩阵和世界矩阵。

    UPDATE_RENDER_DATA  用于更新节点的uv数据、顶点数据,并暂时存在的RenderData中。

    OPACITY、COLOR    用于更新节点的透明度、颜色。

    RENDER   把RenderData数据写入缓冲区中,在写入缓冲区之前会进行动态合批处理。

    CHILDREN   使用DFS 深度遍历 所有的子节点,并处理透明度。

    POST_RENDER  屏幕后效效果处理。例如  技能的击打效果。

    二、Assembler

    Assembler 其主要用于对渲染数据的更新、写入数据缓冲区中。对应的数据更新函数和数据的写入函数都是在RenderFlow调用。每个渲染组件都有与之对应的Assembler。

    • Assembler的注册

    每个渲染节点根据它们的渲染类型可能存在一个或多个Assembler。在初始化渲染时即会把渲染节点所有的Assembler注册到渲染组件中的__assembler__。

    1. initWebGL(canvas, opts) {
    2. //注册Assembler
    3. require("./webgl/assemblers");
    4. .....
    5. }
    1. Assembler.register = function (renderCompCtor, assembler) {
    2. renderCompCtor.__assembler__ = assembler;
    3. };
    • Assembler的确定

    因为渲染节点会存在多个Assembler,但是运行时,需要确定使用的是那个Assembler,即再预加载前会进行Assembler的确定,此事一般都在RenderComponent的_proload函数中调用Assembler的类方法init,确定_assembler值。(这里需要注意实例方法和类方法)

    1. Assembler.init = function (renderComp) {
    2. let renderCompCtor = renderComp.constructor;
    3. let assemblerCtor = renderCompCtor.__assembler__;
    4. while (!assemblerCtor) {
    5. renderCompCtor = renderCompCtor.$super;
    6. if (!renderCompCtor) {
    7. cc.warn(
    8. `Can not find assembler for render component : [${cc.js.getClassName(
    9. renderComp
    10. )}]`
    11. );
    12. return;
    13. }
    14. assemblerCtor = renderCompCtor.__assembler__;
    15. }
    16. if (assemblerCtor.getConstructor) {
    17. assemblerCtor = assemblerCtor.getConstructor(renderComp);
    18. }
    19. if (
    20. !renderComp._assembler ||
    21. renderComp._assembler.constructor !== assemblerCtor
    22. ) {
    23. let assembler = assemblerPool.get(assemblerCtor);
    24. assembler.init(renderComp);
    25. renderComp._assembler = assembler;
    26. }
    27. };
    • 数据的更新

    1. updateRenderData 方法用于更新顶点数据、uv数据,并暂存至RenderData。
    2. updateColor  函数用于更新节点的color值,并暂存至RenderData。
    • 数据的写入

    1. fillBuffers 主要是把RenderData数据写入数据缓冲区中。例如sprite数据写入MeshBuffer中。

    官方文档

    三、RenderData

    renderData 存储渲染节点的顶点数据、索引数据、颜色值。数据的存储是以ArrayBuffer进行存储的。如果需要操作读写时需要通过视图。webGL-类型化数组_雷鸣_IT的博客-CSDN博客

    顶点数据包含了位置数据、uv数据、color数据。因为位置数据、uv数据是float类型。而color数据是整数数据,所以在RenderData中使用了两种视图操作一个ArrayBuffer。

    • vDatas 是以浮点32位进行读写,主要用于位置数据、uv数据的使用。
    • uintVDatas 是以无符号32整数进行读写。主要用于color值的使用。
    • iDatas 是无符号整数数据,主要用于存储索引数据。

    例如:sprite组件:

    四个顶点。每个顶点包含5个Float数据(位置数据2个,uv数据2个,color数据1个)。

    索引数据6个:因为webGL只能渲染点、线、三角形。因此一个sprite又两个三角形组成。

    uv数据的偏移量 ,color的偏移量:因为数据都在一个连续的一维数组中,前两位0,1存储位置数据,接下来两位2,3存储uv数据,第4位存储color数据。依次循环。

    1. floatsPerVert: 5, //每个顶点数据 有五个floats
    2. verticesCount: 4, //有4个顶点
    3. indicesCount: 6, //6个索引 即两个三角形
    4. uvOffset: 2, //uv数据在顶点数据的偏移
    5. colorOffset: 4, //color 数据在顶点数据的偏移

    color不是包含rgba四位数吗,为什么color值的存储只需要一位?

    rgba 每个数的范围都是0~255,可用8位二进制表示。Assembler的updateColor函数读取color值是通过_val。_val的赋值如下:

    this._val = ((a << 24) >>> 0) + (b << 16) + (g << 8) + (r|0);

    (a << 24) >>> 0)   向左移动24位并保证位整数

    (b << 16)  向左移动16位、(g << 8) 向左移动8位。 (r|0)  非零整数。

    _val 最终得到的是一个rgba组合而成32位二进制的整数,且是无符号整数。

    四、ModelBatcher

    当所有的新数据都已更新放入RenderData后,数据将会写入Buffer中。在写入缓冲区之前,会进行合批处理,即ModelBatcher的检测。

    batch的重要点,带着以下问题进行学习:

    Q:为什么要合批?

    合批的目的就是减少CPU向GPU发送渲染指令的次数减少GPU切换渲染状态的次数,让CPU一次可以做更多的事情,来提高逻辑线和渲染线的效率。

    Q:合批条件是什么?

    简单归纳即material、culingMash一致就会使用同一个model。

    具体的合批条件是:

    1. 着色器程序相同:顶点着色器、片元着色器
    2. 纹理相同
    3. 渲染状态相同:深度测试(Depth Test)模板测试(Stencil Test)裁剪测试(Scissor Test)透明度测试(Alpha Test)混合(blending)等
    4. 缓冲数据相同:顶点缓冲数据、索引缓冲数据。(此处的一致指的是同一个buffer)

    Q:满足合批条件会干什么?

    使用同一个model

    不满足合批条件会干什么?

    合批条件:

    有两个重要数据_iaPool、modelPool。

    一个ia会记录vertexBuffer、indexBuffer、索引的开始坐标、索引数。(buffer对象实际由meshBuffer管理)

    一个model时一个drawCall的buffer数据集合、以及对应的effect。

    五、MeshBuffer

    meshBuffer主要用于管理游戏中的vertexBuffer、indexBuffer 的类。_vb:当前使用的vertexBuffer实例对象,_ib:当前使用的indexBuffer实例对象。

    1. buffer的使用是通过model的inputAssembler,简称_ia。每个ia会引用一个_vb、一个_ib同时记录读取buffer的开始索引和数量。同一个drawCall的渲染,会共用一个ia。

    2. buffer数据的写入,在RenderFlow的render函数中,会调用Assembler的fillBuffer函数。fillBuffer函数会优先获取
    3. 切换buffer,当即将存储的数据大于最大值6553时,会重新创建新的vb、ib。

    vertexBuffer:主要用于创建和管理vertex、uv、index数据。数据的传输即通过createBuffer,bindBuffer,bufferData,bufferSubData。

    vertexFormat:记录每个顶点数据的信息(vertex、uv、color)每种类型数据,字节大小、步长、名字、类型、数据个数。已经顶点数据的总字节数,hash值。

    第三篇:实践篇1 《使用Assembler 实现图片任意切割功能》

  • 相关阅读:
    【C++庖丁解牛】默认成员函数
    锐捷端口安全实验配置
    Springboot集成redis和mybatis-plus及websocket异常框架代码封装
    SpringCloud - 服务注册中心
    UT_hash实现增删改查
    内插散点数据
    javaWeb宿舍管理系统
    【8章】Spark编程基础(Python版)
    ubuntu 20.04下查找pycharm-vscode-qtcreator安装路径-查快捷方式路径-pycharm要以root权限运行脚本
    Revit如何使用幕墙功能绘制百叶窗
  • 原文地址:https://blog.csdn.net/z1067832450_/article/details/133753203