<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【GT】Assembler 源碼解讀及使用 !Cocos Creator!

          共 2326字,需瀏覽 5分鐘

           ·

          2020-07-10 15:27


          原文鏈接: ?https://forum.cocos.org/t/demo/95087
          作者: GT
          排版整理: 白玉無冰

          背景

          自定義渲染可以實現(xiàn)很多酷炫的 shader 特效,目前常用的有兩種方法:

          • 創(chuàng)建自定義材質(zhì),給材質(zhì)增加參數(shù)。這個參數(shù)會作為 uniform 變量傳入 shader 由于渲染合批要求材質(zhì)參數(shù)保持一致,所以如果大量對象使用自定義材質(zhì)時,并且材質(zhì)參數(shù)各不相同,是無法進(jìn)行合批渲染的,一個對象占一個 draw call
          • 創(chuàng)建自定義 assembler,在頂點(diǎn)數(shù)據(jù)輸入渲染管道前修改它的值 這種方式比較靈活,如果需要輸入更多自定義參數(shù),標(biāo)準(zhǔn)的頂點(diǎn)格式就不夠用了

          本文介紹另一種方法,即能讓shader獲得自定義參數(shù),又能讓自定義材質(zhì)合批渲染。這種方法就是自定義頂點(diǎn)格式 。

          Assembler詳解

          Assembler是實現(xiàn)本文相關(guān)功能的核心類,先簡單回顧一下官方文檔里介紹的內(nèi)容?https://docs.cocos.com/creator/manual/zh/advanced-topics/custom-render.html

          03d4df2340364ed1c5540db03607c45c.webp

          Assembler 中必須要定義 updateRenderDatafillBuffers 方法 前者需要更新準(zhǔn)備頂點(diǎn)數(shù)據(jù),后者則是將準(zhǔn)備好的頂點(diǎn)數(shù)據(jù)填充進(jìn) VetexBufferIndiceBuffer

          2D渲染中,Assember2D類是一個重要的基礎(chǔ)類,最常用的cc.Sprite的各種模式(Simple,平鋪,九宮格)在內(nèi)部都對應(yīng)了不同的Assembler派生類。同樣是一個四邊形的節(jié)點(diǎn),不同的Assembler可以將其轉(zhuǎn)化成不同數(shù)量的頂點(diǎn)實現(xiàn)不同的渲染效果

          • Simple模式下是常規(guī)的四邊形,有4個頂點(diǎn)。(利用這個可以實現(xiàn)漸變色效果)
          • 平鋪模式下Assembler根據(jù)紋理的重復(fù)次數(shù)對節(jié)點(diǎn)進(jìn)行“拆碎”,相當(dāng)于每重復(fù)一次就產(chǎn)生1個四邊形。(利用這個可以實現(xiàn)頂點(diǎn)動畫之水紋旗子)
          • 九宮格模式下Assembler將節(jié)點(diǎn)拆分為9個四邊形,每個四邊形對應(yīng)紋理上的一個“格子”

          fillBuffers源碼解讀

          先看看Assembler2D是如何實現(xiàn)?fillBuffers?的 源碼位置:https://github.com/cocos-creator/engine/blob/master/cocos2d/core/renderer/assembler-2d.js

          ????fillBuffers?(comp,?renderer)?{
          ???????//?如果節(jié)點(diǎn)的世界坐標(biāo)發(fā)生變化,重新從當(dāng)前節(jié)點(diǎn)的世界坐標(biāo)計算一次頂點(diǎn)數(shù)據(jù)
          ????????if?(renderer.worldMatDirty)?{
          ????????????this.updateWorldVerts(comp);
          ????????}

          ???????//?獲取準(zhǔn)備好的頂點(diǎn)數(shù)據(jù)
          ???????//?vData包含pos、uv、color數(shù)據(jù)
          ???????//?iData包含三角剖分后的頂點(diǎn)索引數(shù)據(jù)
          ????????let?renderData?=?this._renderData;
          ????????let?vData?=?renderData.vDatas[0];
          ????????let?iData?=?renderData.iDatas[0];

          ???????//?獲取頂點(diǎn)緩存
          ???????//?getBuffer()方法后面會被我們重載,以便獲得支持自定義頂點(diǎn)格式的緩存
          ????????let?buffer?=?this.getBuffer(renderer);

          ???????//?獲取當(dāng)前節(jié)點(diǎn)的頂點(diǎn)數(shù)據(jù)對應(yīng)最終buffer的偏移量
          ???????//?可以簡單理解為當(dāng)前節(jié)點(diǎn)和其他同格式節(jié)點(diǎn)的數(shù)據(jù),都將按順序追加到這個大buffer里
          ????????let?offsetInfo?=?buffer.request(this.verticesCount,?this.indicesCount);

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

          ???????//?將準(zhǔn)備好的vData拷貝到VetexBuffer里。這里會判斷如果buffer裝不下了,vData會被截斷一部分
          ???????//?通常是因為節(jié)點(diǎn)數(shù)量太多導(dǎo)致的,從下個節(jié)點(diǎn)開始會使用新的buffer,也就是重新開一個合批
          ???????//?當(dāng)前節(jié)點(diǎn)的數(shù)據(jù)被截斷后,則只能被渲染一部分(推測)
          ????????if?(vData.length?+?vertexOffset?>?vbuf.length)?{
          ????????????vbuf.set(vData.subarray(0,?vbuf.length?-?vertexOffset),?vertexOffset);
          ????????}?else?{
          ????????????vbuf.set(vData,?vertexOffset);
          ????????}

          ????????//?將準(zhǔn)備好的iData拷貝到IndiceBuffer里
          ????????let?ibuf?=?buffer._iData,
          ????????????indiceOffset?=?offsetInfo.indiceOffset,
          ????????????vertexId?=?offsetInfo.vertexOffset;
          ????????for?(let?i?=?0,?l?=?iData.length;?i?????????????ibuf[indiceOffset++]?=?vertexId?+?iData[i];
          ????????}
          ????}

          思考

          Q: 為什么要需要準(zhǔn)備頂點(diǎn)數(shù)據(jù),而不是在fillBuffer()方法內(nèi)直接計算后填入buffer?
          A: 因為fillBuffer()每幀都會被調(diào)用,是熱點(diǎn)代碼,需要關(guān)注效率。但是頂點(diǎn)數(shù)據(jù)不是每一幀都會更新,可以預(yù)先計算

          Q: 實現(xiàn)自定義頂點(diǎn)格式需要修改fillBuffer()方法嗎?
          A: 不需要,fillBuffer()是簡單的字節(jié)流拷貝,只關(guān)心數(shù)據(jù)長度,不關(guān)心數(shù)據(jù)內(nèi)容

          Q: 頂點(diǎn)數(shù)據(jù)包含哪些內(nèi)容?如何計算?
          A: 見下文

          頂點(diǎn)數(shù)據(jù)格式描述

          最常用的頂點(diǎn)格式是?vfmtPosUvColor?,也是Assembler2D默認(rèn)使用的格式。https://github.com/cocos-creator/engine/blob/master/cocos2d/core/renderer/webgl/vertex-format.js

          var?vfmtPosUvColor?=?new?gfx.VertexFormat([
          ????//?節(jié)點(diǎn)的世界坐標(biāo),占2個float32
          ????{?name:?gfx.ATTR_POSITION,?type:?gfx.ATTR_TYPE_FLOAT32,?num:?2?},

          ????//?節(jié)點(diǎn)的紋理uv坐標(biāo),占2個float32
          ????//?如果節(jié)點(diǎn)使用了獨(dú)立的紋理(未合圖),這里的uv值通常是0或1
          ????//?合圖后的紋理,這里的uv對應(yīng)其在圖集里的相對位置,取值范圍在[0,1)內(nèi)
          ????{?name:?gfx.ATTR_UV0,?type:?gfx.ATTR_TYPE_FLOAT32,?num:?2?},

          ????//?節(jié)點(diǎn)顏色值,cc.Sprite組件上可以設(shè)置。占4個uint8 = 1個float32
          ????{?name:?gfx.ATTR_COLOR,?type:?gfx.ATTR_TYPE_UINT8,?num:?4,?normalize:?true?},
          ]);

          頂點(diǎn)格式和shader頂點(diǎn)著色器的attribute變量對應(yīng)關(guān)系如下

          CCProgram?vs?%{
          ??precision?highp?float;

          ??#include?
          ??#include?

          ??//?對應(yīng)vfmtPosUvColor結(jié)構(gòu)里的3個字段
          ??//?注意這里a_position是vec3類型,但是vfmtPosUvColor對其自定義了2個float長度。所以a_position.z =?0
          ??in?vec3?a_position;??????????//?gfx.ATTR_POSITION
          ??in?vec2?a_uv0;???//?gfx.ATTR_UV0
          ??in?vec4?a_color;???//?gfx.ATTR_COLOR

          ??//?...

          ??void?main?()?{
          ??????//?...
          ??}
          }%

          看下Assembler2D里的屬性和頂點(diǎn)格式的對應(yīng)關(guān)系 源碼位置:https://github.com/cocos-creator/engine/blob/master/cocos2d/core/renderer/assembler-2d.js

          cc.js.addon(Assembler2D.prototype,?{
          ????//?vfmtPosUvColor?結(jié)構(gòu)占5個float32
          ????floatsPerVert:?5,

          ????//?一個四邊形4個頂點(diǎn)
          ????verticesCount:?4,

          ????//?一個四邊形按照對角拆分成2個三角形,2*3?=?6個頂點(diǎn)索引
          ????indicesCount:?6,

          ????//?uv的值在vfmtPosUvColor結(jié)構(gòu)里下標(biāo)從2開始算
          ????uvOffset:?2,

          ????//?color的值在vfmtPosUvColor結(jié)構(gòu)里下標(biāo)從4開始算
          ????colorOffset:?4,
          });

          除了默認(rèn)屬性之外,這里還定義了一批可以使用的attribute變量。https://github.com/cocos-creator/engine/blob/master/cocos2d/renderer/gfx/enums.js

          22a8be84ca4329e23643507ff160edf8.webp

          頂點(diǎn)數(shù)據(jù)計算

          ffc1327728efa4a6a5188e83bea420e4.webp

          了解了上面的頂點(diǎn)格式之后,頂點(diǎn)數(shù)據(jù)無非就是計算 pos、uv、color幾個值。在Assembler里分別有?updateVerts()?updateUVs()``updateColor()?方法來準(zhǔn)備這幾個值,并且臨時存儲在Assembler自己分配的數(shù)組里。頂點(diǎn)數(shù)據(jù)存在RenderData中,源碼位置:https://github.com/cocos-creator/engine/blob/master/cocos2d/core/renderer/assembler-2d.js

          export?default?class?Assembler2D?extends?Assembler?{
          ????constructor?()?{
          ????????super();

          ???????//?renderData.vDatas用來存儲pos、uv、color數(shù)據(jù)
          ????????//?renderData.iDatas用來存儲頂點(diǎn)索引數(shù)據(jù)
          ????????this._renderData?=?new?RenderData();
          ????????this._renderData.init(this);

          ????????this.initData();
          ????????this.initLocal();
          ????}

          ????get?verticesFloats?()?{
          ???????//?當(dāng)前節(jié)點(diǎn)的所有頂點(diǎn)數(shù)據(jù)總大小
          ????????return?this.verticesCount?*?this.floatsPerVert;
          ????}

          ????initData?()?{
          ????????let?data?=?this._renderData;
          ???????//?創(chuàng)建一個足夠長的空間用來存儲頂點(diǎn)數(shù)據(jù)?&?頂點(diǎn)索引數(shù)據(jù)
          ???????//?這個方法內(nèi)部會初始化頂點(diǎn)索引數(shù)據(jù)
          ????????data.createQuadData(0,?this.verticesFloats,?this.indicesCount);
          ????}

          ???//?...
          }

          updateUVs()?方法解讀

          源碼位置:https://github.com/cocos-creator/engine/blob/master/cocos2d/core/renderer/webgl/assemblers/sprite/2d/simple.js

          ????updateUVs?(sprite)?{
          ???????//?獲取當(dāng)前cc.Sprite組件設(shè)置的spriteFrame對應(yīng)的uv
          ???????//?uv數(shù)組長度=8,分別表示4個頂點(diǎn)的uv.x,?uv.y
          ???????//?按照左下、右下、左上、右上的順序存儲,注意這里的順序和頂點(diǎn)索引的數(shù)據(jù)需要對應(yīng)上
          ????????let?uv?=?sprite._spriteFrame.uv;
          ????????let?uvOffset?=?this.uvOffset;??//?之前提到過vfmtPosUvColor結(jié)構(gòu)里uvOffset?=?2
          ????????let?floatsPerVert?=?this.floatsPerVert;?//?floatsPerVert?=?vfmtPosUvColor結(jié)構(gòu)大小?=?5
          ????????let?verts?=?this._renderData.vDatas[0];
          ????????for?(let?i?=?0;?i?????????????// 2個1組取uv數(shù)據(jù),寫入renderData.vDatas對應(yīng)位置
          ????????????let?srcOffset?=?i?*?2;
          ????????????let?dstOffset?=?floatsPerVert?*?i?+?uvOffset;
          ????????????verts[dstOffset]?=?uv[srcOffset];
          ????????????verts[dstOffset?+?1]?=?uv[srcOffset?+?1];
          ????????}
          ????}

          updateColor()?和?updateVerts()?的具體實現(xiàn)這里不再分析。

          由于上面多次提到了頂點(diǎn)索引,對于不了解它的同學(xué)需要再單獨(dú)解釋一下。

          理解頂點(diǎn)索引

          除了pos、uvcolor數(shù)據(jù)之外,為什么還需要計算頂點(diǎn)索引數(shù)據(jù)?我們發(fā)送給GPU的數(shù)據(jù),實際上表示的是三角形,而不是四邊形。一個四邊形需要剖分成2個三角形然后傳給GPU。在4個頂點(diǎn)數(shù)據(jù)的基礎(chǔ)上,三角形的描述信息單獨(dú)存在IndiceBuffer (即renderData.iDatas)里,IndiceBuffer里的每個值表示其對應(yīng)頂點(diǎn)數(shù)據(jù)的下標(biāo)。通過索引可以合并掉多個三角形中相同的頂點(diǎn)數(shù)據(jù),減少總數(shù)據(jù)大小。

          fb11e05c39a2b0bdfc99f329f52c3d7b.webp


          常規(guī)四邊形的索引數(shù)據(jù)準(zhǔn)備,源碼位置:https://github.com/cocos-creator/engine/blob/master/cocos2d/core/renderer/webgl/render-data.js

          ????initQuadIndices(indices)?{
          ???????//?按照上述剖分方式得到的下標(biāo):?[0,1,2]?[1,3,2]
          ????????// 6個一組(對應(yīng)1個四邊形)生成索引數(shù)據(jù)
          ????????let?count?=?indices.length?/?6;
          ????????for?(let?i?=?0,?idx?=?0;?i?????????????let?vertextID?=?i?*?4;
          ????????????indices[idx++]?=?vertextID;
          ????????????indices[idx++]?=?vertextID+1;
          ????????????indices[idx++]?=?vertextID+2;
          ????????????indices[idx++]?=?vertextID+1;
          ????????????indices[idx++]?=?vertextID+3;
          ????????????indices[idx++]?=?vertextID+2;
          ????????}
          ????}

          頂點(diǎn)格式自定義

          現(xiàn)在進(jìn)入正題,基于上面對Assembler以及相關(guān)類的解讀,頂點(diǎn)格式自定義需要做這么幾件事

          1. 定義新的格式
          2. 用新的格式準(zhǔn)備足夠長的renderData
          3. renderData對應(yīng)位置寫入自定義數(shù)據(jù)
          4. fillBuffers()方法內(nèi)將renderData數(shù)據(jù)正確刷入buffer
          //?自定義頂點(diǎn)格式,在vfmtPosUvColor基礎(chǔ)上,加入gfx.ATTR_UV1,去掉gfx.ATTR_COLOR
          let?gfx?=?cc.gfx;
          var?vfmtCustom?=?new?gfx.VertexFormat([
          ????{?name:?gfx.ATTR_POSITION,?type:?gfx.ATTR_TYPE_FLOAT32,?num:?2?},
          ????{?name:?gfx.ATTR_UV0,?type:?gfx.ATTR_TYPE_FLOAT32,?num:?2?},????????//?texture紋理uv
          ????{?name:?gfx.ATTR_UV1,?type:?gfx.ATTR_TYPE_FLOAT32,?num:?2?}?????????//?自定義數(shù)據(jù)
          ]);

          const?VEC2_ZERO?=?cc.Vec2.ZERO;

          export?default?class?PieceMaskAssembler?extends?GTSimpleSpriteAssembler2D?{
          ????//?根據(jù)自定義頂點(diǎn)格式,調(diào)整下述常量
          ????verticesCount?=?4;
          ????indicesCount?=?6;
          ????uvOffset?=?2;
          ????uv1Offset?=?4;
          ????floatsPerVert?=?6;

          ????//?自定義數(shù)據(jù),將被寫入uv1的位置
          ????public?moveSpeed:?cc.Vec2?=?VEC2_ZERO;
          ????initData()?{
          ????????let?data?=?this._renderData;
          ????????//?createFlexData支持創(chuàng)建指定格式的renderData
          ????????data.createFlexData(0,?this.verticesCount,?this.indicesCount,?this.getVfmt());

          ????????//?createFlexData不會填充頂點(diǎn)索引信息,手動補(bǔ)充一下
          ????????let?indices?=?data.iDatas[0];
          ????????let?count?=?indices.length?/?6;
          ????????for?(let?i?=?0,?idx?=?0;?i?????????????let?vertextID?=?i?*?4;
          ????????????indices[idx++]?=?vertextID;
          ????????????indices[idx++]?=?vertextID+1;
          ????????????indices[idx++]?=?vertextID+2;
          ????????????indices[idx++]?=?vertextID+1;
          ????????????indices[idx++]?=?vertextID+3;
          ????????????indices[idx++]?=?vertextID+2;
          ????????}
          ????}

          ????//?自定義格式以getVfmt()方式提供出去,除了當(dāng)前assembler,render-flow的其他地方也會用到
          ????getVfmt()?{
          ????????return?vfmtCustom;
          ????}

          ????//?重載getBuffer(),?返回一個能容納自定義頂點(diǎn)數(shù)據(jù)的buffer
          ????//?默認(rèn)fillBuffers()方法中會調(diào)用到
          ????getBuffer()?{
          ????????return?cc.renderer._handle.getBuffer("mesh",?this.getVfmt());
          ????}

          ????//?pos數(shù)據(jù)沒有變化,不用重載
          ????//?updateVerts(sprite)?{
          ????//?}

          ????updateUVs(sprite)?{
          ????????//?uv0調(diào)用基類方法寫入
          ????????super.updateUVs(sprite);
          ????????//?填入自己的uv1數(shù)據(jù)
          ????????//?...
          ????????//?方法類似uv0寫入,詳見Demo
          ???????//?https://github.com/caogtaa/CCBatchingTricks
          ????}

          ????updateColor(sprite)?{
          ????????//?由于已經(jīng)去掉了color字段,這里重載原方法,并且不做任何事
          ????}
          }

          上面用到的?GTSimpleSpriteAssembler2D基類代碼大部分參考官方cc.Sprite的實現(xiàn)。

          雙uv坐標(biāo)shader案例

          這里將通過額外的一組uv數(shù)據(jù),實現(xiàn)紋理滾動的方向 & 速度控制。

          d07205c7325eb73849e11f89edb76b99.webp


          用材質(zhì)參數(shù)的方法同樣能夠?qū)崿F(xiàn)這個效果,但是無法做到合批渲染?;谏厦娼o出的Assembler類,繼續(xù)完善一下其他輔助類

          材質(zhì)

          材質(zhì)只用于關(guān)聯(lián)effect,沒有額外邏輯,也不需要新建uniform變量

          RenderComponent

          RenderComponent這里的角色:

          • 創(chuàng)建對應(yīng)的Assembler
          • Assembler傳參。讓業(yè)務(wù)邏輯可以控制Assembler

          直接繼承cc.Sprite后可以少些很多代碼

          @ccclass
          export?default?class?PieceMaskSprite?extends?cc.Sprite?{
          ????@property(cc.Vec2)
          ????bgOffset:?cc.Vec2?=?cc.Vec2.ZERO;

          ????//?參數(shù)傳遞給assembler,在設(shè)置完所有參數(shù)后調(diào)用
          ????//?也可以在bgOffset?setter方法內(nèi)主動傳值,需要調(diào)用setVertsDirty()使頂點(diǎn)數(shù)據(jù)重算
          ????public?FlushProperties()?{
          ????????let?assembler:?PieceMaskAssembler?=?this._assembler;
          ????????if?(!assembler)
          ????????????return;

          ????????assembler.bgOffset?=?this.bgOffset;
          ????????this.setVertsDirty();
          ????}

          ????_resetAssembler?()?{
          ????????this.setVertsDirty();
          ????????let?assembler?=?this._assembler?=?new?PieceMaskAssembler();
          ????????this.FlushProperties();

          ????????assembler.init(this);
          ????}
          }

          Effect (shader)

          滾動效果非常簡單,這里只貼出片元著色器代碼 紋理滾動通過v_uv1.xy控制方向和速度

          CCProgram?fs?%{
          ??precision?highp?float;

          ??#include?
          ??#include?

          ??in?vec2?v_uv0;
          ??in?vec2?v_uv1;

          ??uniform?sampler2D?texture;

          ??void?main()
          ??{
          ????vec2?uv?=?v_uv0.xy;
          ????float?tx?=?cc_time.x?*?v_uv1.x;
          ????float?ty?=?cc_time.x?*?v_uv1.y;

          ????uv.x?=?fract(uv.x?-?tx);
          ????uv.y?=?fract(uv.y?+?ty);
          ????vec4?col?=?texture(texture,?uv);

          ????gl_FragColor?=?col;
          ??}
          }%

          RenderComponent組建掛在到對應(yīng)節(jié)點(diǎn)上即可使用。至此,一個簡單的自定義頂點(diǎn)格式達(dá)到合批目的的功能就實現(xiàn)了!

          小結(jié)

          Demo地址

          demo基于Cocos Creator 2.4.0 (2.4會是Cocos Creator 2D的最后一個版本,也是LTS版本,大家趕緊用起來吧?。?/p>

          如果小伙伴覺得這個Demo對自己有幫助,記得star哦~^_^~

          https://github.com/caogtaa/CCBatchingTricks

          寫在后面

          實際項目中可以靈活利用自定義頂點(diǎn)格式,達(dá)到給shader傳參的目的,同時不會打斷合批。當(dāng)然想要實現(xiàn)合批渲染,還有其他前置條件要滿足,包括節(jié)點(diǎn)層級關(guān)系、合圖、紋理狀態(tài)等,這些在論壇其他帖子有詳細(xì)討論。

          有錯誤的地方歡迎指正

          cc3f6dc3ca018f7b9aa316464ccbaaec.webpGT贊賞碼

          原文鏈接: ?https://forum.cocos.org/t/demo/95087 作者: GT

          更多精彩

          ec210fa18d750902ea8de47b4a7b41cb.webpb9d7416ac751aa8b9875c4f88c5acac6.webp
          c587997a878948ed1f6dd77063ddb3f6.webp
          f49124321b3db695cacb4843e67fb42c.webp
          09749e1b9930a4ff0fd65f260cc2e1c4.webp


          瀏覽 110
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  综合久久狼人 | 免费成人视频久久 | 国产乱码一区二区三区的区别 | 五月天黄网站 | 波多野结衣一区久久在线观看 |