[√]creator 自定义顶点

简介: [√]creator 自定义顶点

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

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

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

// 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
    );
}
void gl.drawElements(mode 图元类型, count 图元数量, type 缓冲区值的类型, offset 缓冲区偏移);

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

 // 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;
}

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

_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里面,可能是出于性能考虑做的优化。接着往上层追数据

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

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

// 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

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的来源如下:

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的数据

  _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就是在处理这件事

_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的时候会初始化这件事

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;
}

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

indexBuffer._glID

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

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是如何影响这条链路上的数据呢?

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,

_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为例

 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

image.png

目录
相关文章
|
6月前
|
Java Android开发
Android12 双击power键启动相机源码解析
Android12 双击power键启动相机源码解析
193 0
|
5月前
Qt安装之后添加或移除组件(Qt Creator 10.0.1)
Qt安装之后添加或移除组件(Qt Creator 10.0.1)
225 2
|
6月前
|
iOS开发
XCode 4.3.2 如何新建 Window-based Application应用的简单例子
XCode 4.3.2 如何新建 Window-based Application应用的简单例子
44 2
|
5月前
|
JavaScript 前端开发 C#
初识Unity——创建代码、场景以及五个常用面板(创建C#代码、打开代码文件、场景的创建、Project、Hierarchy、Inspector、Scene、Game )
初识Unity——创建代码、场景以及五个常用面板(创建C#代码、打开代码文件、场景的创建、Project、Hierarchy、Inspector、Scene、Game )
334 0
|
存储 异构计算
[√]creator对color处理的细节逻辑
[√]creator对color处理的细节逻辑
94 0
|
图形学
[√]qdockerwidget自定义titlebar遇到的各种问题
[√]qdockerwidget自定义titlebar遇到的各种问题
94 0
|
编解码
UE中使用Editor Utility Widget创建编辑器工具
UE中使用Editor Utility Widget创建编辑器工具
449 0
UE中使用Editor Utility Widget创建编辑器工具
cocos creator锚点分析
锚点(Anchor) 是节点的另一个重要属性,它决定了节点以自身约束框中的哪一个点作为整个节点的位置。我们选中节点后看到变换工具出现的位置就是节点的锚点位置。
647 0
cocos creator锚点分析