creator 自定义顶点

从WebGL的角度倒推engine渲染实现

其实看完以上,我还是没有明白,不如倒推,我们已经知道如何渲染一个图片了,那么我们只需要关系提交的顶点数据即可,一层一层往上追顶点数据的来源。

最终的渲染都是汇总到这个地方

js 复制代码
// drawPrimitives
if (next.indexBuffer) { // 大部分情况下都会使用到顶点索引
    gl.drawElements(
      this._next.primitiveType, // 4 代表 gl.TRIANGLES
      count, // 6 绘制一个矩形需要4个顶点,但是需要2个三角形,一个三角形是3个顶点,这6个顶点是放在索引缓冲区
      next.indexBuffer._format, // 5123 为 gl.UNSIGNED_SHORT
      base * next.indexBuffer._bytesPerIndex// 0*2 ,gl.UNSIGNED_SHORT占2字节,base是上层传递过来的,是inputAssembler._start
    );
} else {
    gl.drawArrays(
      this._next.primitiveType,
      base,
      count
    );
}
js 复制代码
void gl.drawElements(mode 图元类型, count 图元数量, type 缓冲区值的类型, offset 缓冲区偏移);

真正的数据是放在buffer里面,我们就要网上找操作buffer的地方,很容易就能找到这部分的逻辑

js 复制代码
 // commit index-buffer
if (cur.indexBuffer !== next.indexBuffer) {
    // 使用next.indexBuffer,那么我们就得去寻找下indexBuffer里面的数据
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, next.indexBuffer && next.indexBuffer._glID !== -1 ? next.indexBuffer._glID : null);
}
// 发现有这个接口,那么这个indexBuffer就是通过外部设置的
setIndexBuffer(buffer) {
    this._next.indexBuffer = buffer;
}

顺着setIndexBuffer的接口,我们找到base-render,发现Buffer数据都是来自InputAssembler

js 复制代码
_draw (item) {
    const device = this._device;
    const programLib = this._programLib;
    const { node, ia, passes, effect } = item; // 很明显,ia来自item参数,我们还需要往上层追
    /// ...
    // set vertex buffer
    if (ia._vertexBuffer) {
        device.setVertexBuffer(0, ia._vertexBuffer);
    }

    // set index buffer
    if (ia._indexBuffer) {
        device.setIndexBuffer(ia._indexBuffer);
    }

    // set primitive type
    device.setPrimitiveType(ia._primitiveType);
}    

其实也很容易理解,buffer对象就像指针一样,这样才能方便外部修改填充数据,底层核心只需要持有这个指针就行了

js 复制代码
_drawItems (view, items) {
    let shadowLights = this._shadowLights;
    if (shadowLights.length === 0 && this._numLights === 0) {
      for (let i = 0; i < items.length; ++i) {// items.length被重载了
        let item = items.data[i];// items是个RecyclePool回收池
        this._draw(item); // 这个item就包含了ia
      }
    }
}

从以上的逻辑可以看到,数据是放在了RecyclePool里面,可能是出于性能考虑做的优化。接着往上层追数据

js 复制代码
_opaqueStage (view, items) {
   // 这里面仅仅做了参数透传
  this._drawItems(view, items);
}

下边的逻辑有点重,也是数据根源的地方,需要耐心看

js 复制代码
// get all draw items
this._drawItemsPools.reset();// 重置drawItemsPools
// drawItemsPools数据是长这样子的
this._drawItemsPools = new RecyclePool(() => {
  return {
    model: null,
    node: null,
    ia: null,
    effect: null,
    defines: null,
    uniforms: null
  };
}, 100);

for (let i = 0; i < scene._models.length; ++i) { // 数据的源头又被这里所控制
  let model = scene._models.data[i];

  // filter model by view
  if ((model._cullingMask & view._cullingMask) === 0) {
    continue;
  }

  let drawItem = this._drawItemsPools.add();
  model.extractDrawItem(drawItem);
  // extractDrawItem(out) {
  //  out.model = this;
  //  out.node = this._node;
  //  out.ia = this._inputAssembler; // inputAssember真正是在model身上,所以寻找源头又变成了scene._models
  //  out.effect = this._effect;
  // }
  }
}

for (let i = 0; i < view._stages.length; ++i) {
  let stage = view._stages[i];
  let stageItems = this._stageItemsPools.add();
  stageItems.reset();

  for (let j = 0; j < this._drawItemsPools.length; ++j) {
    // 很明显受到了drawItemsPool控制,drawItemsPool同样是一个RecyclePool
    let drawItem = this._drawItemsPools.data[j];
    let passes = drawItem.effect.stagePasses[stage];
    if (!passes || passes.length === 0) continue;

    let stageItem = stageItems.add();// 递增生产stageItem,后续是对生成的item进行数据填充
    stageItem.passes = passes;
    stageItem.model = drawItem.model;
    stageItem.node = drawItem.node;
    stageItem.ia = drawItem.ia; // 这里就是InputAssembler的来源啦!,很明显来自drawItem
    stageItem.effect = drawItem.effect;
    stageItem.defines = drawItem.defines;
    stageItem.sortKey = -1;
    stageItem.uniforms = drawItem.uniforms;
  }

  let stageInfo = _stageInfos.add();
  stageInfo.stage = stage;
  stageInfo.items = stageItems; // info.items的具体数据源
}

// render stages
for (let i = 0; i < _stageInfos.length; ++i) { // 同样的_stageInfos也是RecyclePool类型,所以这个class的源码还是有必要仔细读一下,也不是非常复杂,也就不到100行
  let info = _stageInfos.data[i];
  let fn = this._stage2fn[info.stage];
  fn(view, info.items); // 不同的stage对应不同的函数实现
}

// 这个是_stagetInfos的关联数据
let _stageInfos = new RecyclePool(() => {
  return {
    stage: null,
    items: null,
  };
}, 8);

上边的分析有点长,但是我们最终会发现渲染数据是放在_batcher._renderScene._models

js 复制代码
RenderFlow.render = function (rootNode, dt) {
    _batcher.reset();
    _batcher.walking = true;

    RenderFlow.visitRootNode(rootNode);

    _batcher.terminate();
    _batcher.walking = false;

    _forward.render(_batcher._renderScene, dt);// 将渲染的场景传递过去
};

而这个_batcher._renderScene的来源如下:

js 复制代码
this._handle = new ModelBatcher(this.device, this.scene); // batcher的来源,同时也看到了scene的来源
this._flow.init(this._handle, this._forward);

RenderFlow.init = function (batcher, forwardRenderer) {
    _batcher = batcher;// 设置_batcher
    _forward = forwardRenderer;

    flows[0] = EMPTY_FLOW;
    for (let i = 1; i < FINAL; i++) {
        flows[i] = new RenderFlow();
    }
};

又追到了scene里面,因为我们很关心scene._models的数据

js 复制代码
  _add(pool, item) {
    if (item._poolID !== -1) {
      return;
    }

    pool.push(item);
    item._poolID = pool.length - 1;
  }
 addModel(model) {
    this._add(this._models, model);
  }

那么什么时候在触发这个model呢?

model-batcher就是在处理这件事

js 复制代码
_flush () {
    let material = this.material,
        buffer = this._buffer, // inputAssembler的关联,它是一个meshBuffer
        indiceCount = buffer.indiceOffset - buffer.indiceStart;
    if (!this.walking || !material || indiceCount <= 0) {
        return;
    }

    let effect = material.effect;
    if (!effect) return;

    // Generate ia(终于追到了根源,心好累)
    let ia = this._iaPool.add();
    // this._iaPool = new RecyclePool(function () {
    //   return new InputAssembler();
    // }, 16);
    // 你会发现数据又来自buffer
    ia._vertexBuffer = buffer._vb;
     // this._vb = new gfx.VertexBuffer(
     //       batcher._device,
     //       vertexFormat,
     //       gfx.USAGE_DYNAMIC,
     //       new ArrayBuffer(),
     //       0
     //   );
    ia._indexBuffer = buffer._ib;
     //  this._ib = new gfx.IndexBuffer(
     //       batcher._device,
     //       gfx.INDEX_FMT_UINT16,
     //       gfx.USAGE_STATIC,
     //       new ArrayBuffer(),
     //       0
     //   );
    ia._start = buffer.indiceStart;
    ia._count = indiceCount;

    // Generate model
    let model = this._modelPool.add();
    // 数据结构
    // this._modelPool = new RecyclePool(function () { 
    //    return new Model();
    // }, 16);
    this._batchedModels.push(model);
    model.sortKey = this._sortKey++;
    model._cullingMask = this.cullingMask;
    model.setNode(this.node);
    model.setEffect(effect);
    model.setInputAssembler(ia); // 设置 inputAssembler

    this._renderScene.addModel(model);
    buffer.forwardIndiceStartToOffset();
},

new ModelBatcher的时候会初始化这件事

js 复制代码
this._handle = new ModelBatcher(this.device, this.scene);
var ModelBatcher = function (device, renderScene) {
    // buffers
    this._quadBuffer = this.getBuffer('quad', vfmtPosUvColor);
    this._meshBuffer = this.getBuffer('mesh', vfmtPosUvColor); // 统一的
    this._quadBuffer3D = this.getBuffer('quad', vfmt3D);
    this._meshBuffer3D = this.getBuffer('mesh', vfmt3D);
    this._buffer = this._meshBuffer;
};
getBuffer (type, vertextFormat) {
    let key = type + vertextFormat.getHash();
    let buffer = _buffers[key];
    if (!buffer) {
        if (type === 'mesh') {
            buffer = new MeshBuffer(this, vertextFormat);
        }
        _buffers[key] = buffer;
    }

    return buffer;
}

让我们再回过头看看提交数据时的样子

diff 复制代码
indexBuffer._glID

indexBuffer我们已经追查到其实对应的就是IndexBuffer类

js 复制代码
class IndexBuffer {
  /**
   * @constructor
   * @param {Device} device
   * @param {INDEX_FMT_*} format
   * @param {USAGE_*} usage
   * @param {ArrayBuffer | Uint8Array} data
   */
  constructor(device, format, usage, data) {
    this._device = device;
    this._format = format;
    this._usage = usage;
    this._bytesPerIndex = BYTES_PER_INDEX[format];
    this._bytes = data.byteLength;
    this._numIndices = this._bytes / this._bytesPerIndex;

    this._needExpandDataStore = true;

    // update
    this._glID = device._gl.createBuffer(); // webgl使用到的参数来源
    this.update(0, data);

    // stats
    device._stats.ib += this._bytes;
  }
}

追查了这么久,我们只是知道了webgl渲染所需要的数据来源,但是游戏的sprite是如何影响这条链路上的数据呢?

js 复制代码
RenderFlow.render = function (rootNode, dt) {
    _batcher.reset();
    _batcher.walking = true;

    RenderFlow.visitRootNode(rootNode); // 就发生在这个逻辑里面

    _batcher.terminate();
    _batcher.walking = false;

    _forward.render(_batcher._renderScene, dt);
};

在renderFlow里面,有几个比较重要的flow,

js 复制代码
_proto._updateRenderData = function (node) {
    let comp = node._renderComponent; // Sprite,Label都是继承子cc.RenderComponent
    comp._assembler.updateRenderData(comp); // RenderComponent需要绑定Assembler
    node._renderFlag &= ~UPDATE_RENDER_DATA;
    this._next._func(node);
};
_proto._render = function (node) {
    let comp = node._renderComponent;
    comp._checkBacth(_batcher, node._cullingMask);
    comp._assembler.fillBuffers(comp, _batcher);// RenderComponent需要绑定Assembler
    this._next._func(node);
};

fillBuffers的实现,以assembler-2d为例

js 复制代码
 fillBuffers (comp, renderer) {
    if (renderer.worldMatDirty) {
        this.updateWorldVerts(comp);
    }

    let renderData = this._renderData;
    let vData = renderData.vDatas[0];
    let iData = renderData.iDatas[0];

    // 注意:这里获取到的buffer,就是this._meshBuffer,后续的所有操作都是在操作这个buffer
    let buffer = this.getBuffer(renderer); 
    // meshBuffer
    let offsetInfo = buffer.request(this.verticesCount, this.indicesCount);

    // buffer data may be realloc, need get reference after request.

    // fill vertices
    let vertexOffset = offsetInfo.byteOffset >> 2,
        vbuf = buffer._vData;

    if (vData.length + vertexOffset > vbuf.length) {
        vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);
    } else {
        vbuf.set(vData, vertexOffset);
    }

    // fill indices
    let ibuf = buffer._iData,
        indiceOffset = offsetInfo.indiceOffset,
        vertexId = offsetInfo.vertexOffset;
    for (let i = 0, l = iData.length; i < l; i++) {
        ibuf[indiceOffset++] = vertexId + iData[i];
    }
}

getBuffer () {
    // 这里使用的buffer是meshBuffer
    return cc.renderer._handle._meshBuffer;
}

里面会反复使用到一个结构RenderData

而文档中也反复提到Assembler里面有2个非常重要的函数updateRenderData, fillBuffers在渲染流程中的确是必须的函数

提交顶点

所有的数据都在buffer的_iData, _vData

相关推荐
软件小伟4 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾25 分钟前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧34 分钟前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm43 分钟前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep7011 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm1 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王2 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
疯狂的沙粒2 小时前
对 TypeScript 中高级类型的理解?应该在哪些方面可以更好的使用!
前端·javascript·typescript
gqkmiss2 小时前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃2 小时前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter