太原0元網(wǎng)站建設(shè)谷歌代運(yùn)營(yíng)
前言
?
Sprite是游戲內(nèi)容的一個(gè)基本組成元素,包括ui、道具、立繪等各種地方都會(huì)用到。大部分情況下美術(shù)會(huì)幫我們調(diào)好圖片顏色,我們只要把圖片直接放到游戲里就行了。Sprite默認(rèn)的渲染頂點(diǎn)數(shù)據(jù)中包含了顏色數(shù)據(jù),由于我們并不需要去修改顏色,某些情況下這似乎是一個(gè)不必要的東西。
去年底的時(shí)候,由于希望在性能優(yōu)化方面做一些研究,在論壇找到了江南百景圖研發(fā)負(fù)責(zé)人的技術(shù)分享文章,其中提到:
優(yōu)化 Shader 的輸入數(shù)據(jù)
由于《江南百景圖》的圖片資源中不會(huì)用到 Color 這個(gè)屬性,因此在材質(zhì)中,我們將原有的 Color 數(shù)據(jù)去除掉。
將原有的 Color 數(shù)據(jù)去除掉。用來(lái)存放項(xiàng)目中所需要的其它信息,這樣做可以減少 CPU 與 GPU 互相傳輸?shù)臄?shù)據(jù)量。
本文參照文章中的思路實(shí)現(xiàn)了這個(gè)優(yōu)化。
開(kāi)發(fā)環(huán)境
瀏覽器:Chrome
開(kāi)發(fā)語(yǔ)言:JavaScript
引擎版本:CocosCreator 2.4.3
詞語(yǔ)縮寫(xiě)對(duì)照
頂點(diǎn)格式:頂點(diǎn)數(shù)據(jù)格式。
研究過(guò)程
按照思路,需要改動(dòng)Sprite渲染相關(guān)代碼,以及修改對(duì)應(yīng)的材質(zhì)。
翻源碼找出,Sprite對(duì)應(yīng)的assembler是SimpleSpriteAssembler(渲染模式為simple時(shí))。
源碼位于:cocos2d\core\renderer\webgl\assemblers\sprite\2d\simple.js
其繼承關(guān)系為:cc.Assembler->cc.Assembler2D->SimpleSpriteAssembler。
縷清關(guān)系后,我們要找出頂點(diǎn)數(shù)據(jù)格式是在哪定義的。assembler用于填充頂點(diǎn)數(shù)據(jù),所以我們到Assembler.js中找找。
import { vfmtPosUvColor } from './webgl/vertex-format'; export default class Assembler {getVfmt () {return vfmtPosUvColor;} }
找到了!默認(rèn)頂點(diǎn)格式就是這個(gè)vfmtPosUvColor。
var vfmtPosUvColor = 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 },{ name: gfx.ATTR_COLOR, type: gfx.ATTR_TYPE_UINT8, num: 4, normalize: true }, ]);
從vfmtPosUvColor的定義可以看出,頂點(diǎn)數(shù)據(jù)中含有三個(gè)數(shù)據(jù)(括號(hào)里的英文對(duì)應(yīng)材質(zhì)中的輸入變量名):
- ATTR_POSITION(a_position 位置)
- ATTR_UV0(a_uv0 uv)
- ATTR_COLOR(a_color 顏色)
很明顯ATTR_COLOR就是我們今天的目標(biāo)!
弄清楚頂點(diǎn)格式后,下一步是找到填充這些數(shù)據(jù)的地方。
頂點(diǎn)數(shù)據(jù)保存于RenderData,這三個(gè)數(shù)據(jù)是分開(kāi)填充的,避免有些時(shí)候只需要更新其中一個(gè)(如只是移動(dòng)了位置),卻要全部跑一遍。頂點(diǎn)數(shù)據(jù)填充時(shí),是按定義好的順序填充的,此處引用論壇文章的圖:
由于我們只是去除最后的color,所以位置和uv的填充函數(shù)是不需要修改的(在一個(gè)頂點(diǎn)數(shù)據(jù)中的相對(duì)位置沒(méi)有發(fā)生改變)。
找出顏色填充函數(shù),updateColor函數(shù)聲明于assembler-2d.js。
updateColor (comp, color) {let uintVerts = this._renderData.uintVDatas[0];if (!uintVerts) return;color = color != null ? color : comp.node.color._val;let floatsPerVert = this.floatsPerVert;let colorOffset = this.colorOffset;for (let i = colorOffset, l = uintVerts.length; i < l; i += floatsPerVert) {uintVerts[i] = color;} }
函數(shù)中將顏色值填充在每個(gè)頂點(diǎn)數(shù)據(jù)的末尾(position和uv之后)。我們需要修改updateColor函數(shù),因?yàn)椴辉傩枰畛漕伾盗?/strong>。
實(shí)現(xiàn)思路
看完又是要改源碼了。不過(guò)還是可以通過(guò)繼承相關(guān)類(lèi)實(shí)現(xiàn)。繼承方案相對(duì)來(lái)說(shuō)會(huì)比較麻煩,但在實(shí)驗(yàn)階段需要頻繁修改時(shí)會(huì)更方便快速。
我們需要自定義Sprite、Assembler、Material、Effect。分別命名為NoColorSprite、NoColorSpriteAssembler、noColorMaterial、noColorEffect。
需求可拆分為如下實(shí)現(xiàn)步驟:
- 新建noColorEffect及noColorMaterial,在內(nèi)置的代碼基礎(chǔ)上,去除顏色相關(guān)內(nèi)容。
- 新建NoColorSpriteAssembler,新建頂點(diǎn)格式,并重寫(xiě)/實(shí)現(xiàn)渲染數(shù)據(jù)填充的相關(guān)函數(shù)。
- 新建NoColorSprite,將默認(rèn)的assembler改為我們自己的NoColorSpriteAssembler。
代碼
第一步是effect和material,主要工作是刪代碼(顏色相關(guān)的)... 新建的material只要將effect引用改為noColorEffect即可。
// 刪除顏色相關(guān)輸入輸出處理 CCProgram vs %{precision highp float;#include <cc-global>#include <cc-local>in vec3 a_position;#if USE_TEXTUREin vec2 a_uv0;out vec2 v_uv0;#endifvoid main () {vec4 pos = vec4(a_position, 1);#if CC_USE_MODELpos = cc_matViewProj * cc_matWorld * pos;#elsepos = cc_matViewProj * pos;#endif#if USE_TEXTUREv_uv0 = a_uv0;#endifgl_Position = pos;} }%// 刪除顏色相關(guān)輸入處理 輸出顏色直接取像素顏色 CCProgram fs %{precision highp float;#include <alpha-test>#include <texture>#if USE_TEXTUREin vec2 v_uv0;uniform sampler2D texture;#endifvoid main () {vec4 o = vec4(1, 1, 1, 1);#if USE_TEXTURECCTexture(texture, v_uv0, o);#endifALPHA_TEST(o);gl_FragColor = o;} }%
接著,創(chuàng)建NoColorSpriteAssembler.js,自定義頂點(diǎn)格式,去掉默認(rèn)的顏色字段。
let gfx = cc.gfx; let vfmtNoColor = 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 ]);
我們只是想去除顏色,可以通過(guò)繼承cc.Assembler實(shí)現(xiàn)noColorMaterial,其他渲染相關(guān)代碼則可以從Assembler2D及SimpleSpriteAssembler中復(fù)制。這里貼出主要的代碼。
因?yàn)槲覀?strong>修改了頂點(diǎn)格式,需要同步修改相關(guān)值。這里重寫(xiě)構(gòu)造函數(shù)進(jìn)行修改。
floatsPerVert是頂點(diǎn)格式數(shù)據(jù)長(zhǎng)度(用浮點(diǎn)數(shù)計(jì)算),原本是5個(gè)浮點(diǎn)數(shù),這里去掉了顏色,所以改為4。其他數(shù)據(jù)照抄Assembler2D中的值即可。
export default class NoColorAssembler extends cc.Assembler {constructor () {super();// uv在頂點(diǎn)數(shù)據(jù)中的偏移位置(前面有兩個(gè)float的值表示position)this.uvOffset = 2;// 每個(gè)頂點(diǎn)的浮點(diǎn)數(shù)數(shù)量(position 2浮點(diǎn)數(shù),uv 2浮點(diǎn)數(shù))this.floatsPerVert = 4;// 頂點(diǎn)數(shù)量 (可以用4個(gè)點(diǎn)來(lái)表示兩個(gè)三角形)this.verticesCount = 4;// 頂點(diǎn)索引數(shù)量 (兩個(gè)三角形共6個(gè)頂點(diǎn)索引) 這個(gè)部分可以看鏈接中的文章有說(shuō)明。this.indicesCount = 6;this.initData();this.initLocal();} }
修改頂點(diǎn)數(shù)據(jù)格式后,我們需要一個(gè)不一樣的RenderData來(lái)存儲(chǔ)這些數(shù)據(jù),模仿Assembler2D實(shí)現(xiàn)initData函數(shù),在里面按我們定義的格式創(chuàng)建RenderData。
/** * 初始化this._renderData 仿照Assembler2D.initData 創(chuàng)建自定義格式的renderData */ initData () {let data = this._renderData = new cc.RenderData();this._renderData.init(this);// 按我們自己的格式創(chuàng)建RenderDatadata.createFlexData(0, this.verticesCount, this.indicesCount, this.getVfmt());// createFlexData不會(huì)填充頂點(diǎn)索引信息,手動(dòng)補(bǔ)充一下 仿照cc.RenderData.initQuadIndiceslet indices = data.iDatas[0];let count = indices.length / 6;for (let i = 0, idx = 0; i < count; 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),把顏色的填充功能去掉!
/** * 更新顏色 啥也不干😆 */ updateColor () { }
最后,改動(dòng)頂點(diǎn)數(shù)據(jù)格式后還有一些需要同步修改的地方。
/** * 獲得存放自定義頂點(diǎn)數(shù)據(jù)的buffer * @returns {cc.MeshBuffer} */ getBuffer() {return cc.renderer._handle.getBuffer("mesh", this.getVfmt()); } /** * 獲得頂點(diǎn)數(shù)據(jù)格式 * 重寫(xiě) 返回自定義的頂點(diǎn)數(shù)據(jù)格式 * @returns {cc.gfx.VertexFormat} */ getVfmt () {return vfmtNoColor; }
代碼有點(diǎn)長(zhǎng),沒(méi)有全部貼出來(lái)。可以在后面的源碼附件中查看,其他函數(shù)基本是從Assembler2D及SimpleSpriteAssembler復(fù)制出來(lái)的。
最最最后,如果產(chǎn)生一些如繼承、函數(shù)為空之類(lèi)的報(bào)錯(cuò),可以在creator.d.ts文件中增加以下聲明。
declare namespace cc {export class Assembler {public _renderComp: cc.RenderComponent;public init(comp: cc.RenderComponent);public getVfmt();static public register(renderCompCtor, assembler);}export class RenderData {init(assembler: cc.Assembler);createQuadData(index, verticesFloats, indicesCount);createFlexData(index, verticesFloats, indicesCount, vfmt): cc.FlexBuffer;initQuadIndices(idata);vDatas;uintVDatas;iDatas;meshCount: number;_infos;_flexBuffer;} }
效果對(duì)比
測(cè)試案例
一個(gè)sprite,復(fù)制200次。分別使用默認(rèn)的cc.Sprite和我們實(shí)現(xiàn)的NoColorSprite。
使用console.time函數(shù)結(jié)合cc.Director中的EVENT_BEFORE_UPDATE、EVENT_AFTER_UPDATE、EVENT_AFTER_DRAW事件統(tǒng)計(jì)前兩百幀的游戲邏輯耗時(shí)及渲染耗時(shí)。
耗時(shí)對(duì)比如下:
綠色線為優(yōu)化前,藍(lán)色線為優(yōu)化后??梢钥闯鼍幸欢ǔ潭鹊臏p少。
render耗時(shí)由于前幾幀較高,圖表看起來(lái)比較奇怪,再貼一張去掉前三幀的對(duì)比圖。
總結(jié)
簡(jiǎn)單來(lái)說(shuō),少了1/5的數(shù)據(jù)傳輸量,material中也不需要計(jì)算顏色,優(yōu)化效果是可想而知的。
本優(yōu)化并不適用于所有項(xiàng)目,由于顏色數(shù)據(jù)被去除了,透明度作為顏色值的其中一項(xiàng),也不再生效了。圖片本身的透明度會(huì)被保留,但無(wú)法再通過(guò)修改節(jié)點(diǎn)的透明度進(jìn)行動(dòng)態(tài)修改。