語言(Language) CN | EN
此項目為 CC3.SpriteEffect 中每個 Effect 的 Demo。
- CC3.SpriteEffect 為此專案的 Submodule
- 請記得一起下載 CC3.SpriteEffect 並安裝至
${PROJECT_FOLDER}/extensions/sprite_effect
,或啟動 submodule 並 clone CC3.SpriteEffect
itch.io Demo (https://bricl.itch.io/cc3spriteeffectdemo)
依官方文件【2D 渲染组件合批规则说明】,Sprite 一但使用 customMaterial 合批 (batch) 就會被拆分。而同 Shader 不同參數想合批,正統是將參數帶入頂點中。詳細做法論壇上的 bakabird 大大提供保母級的教程 【分享】CocosCreator3.x 应用在UI(Sprite) 上的 shader(.effect) 的合批,通过自定义顶点参数。
這方法需對 Sprite 的 4 種頂點宣告模式 (SIMPLE、SLICE、TILED、FILLED) 作實現。那...還有其他方法可以不用動到修改頂點格式嗎?
Peoperty Atlas
的特點在於,不同 Sprite 相同 Shader 效果下,將自己所屬的參數儲存在同一張 PropsTexture (參數貼圖)
中,渲染時透過索引於取出所屬參數計算,如此就能利用引擎本身的合批規則減少 Drawcall。
-
對一 Shader 效果準備一張格式 RGBA32 的
PropsTexture (參數貼圖)
。 -
每個 Sprite 在同一 Shader 效果下,自有所屬唯一的 index。
-
藉此 index 在渲染時對
PropsTexture (參數貼圖)
取出所屬參數並計算。 -
屬性貼圖的儲存格式
PropsTexture (參數貼圖)
的 width 決定一次能合批(batch)多少個 sprite,例如:64 代表最多可以一次合批(batch) 64 不同參數設定的 Sprite。
-
繼承 Sprite Component
@ccclass('SpDemoEffect') export class SpDemoEffect extends Sprite { //...略 }
-
static 參數
@ccclass('SpDemoEffect') export class SpDemoEffect extends Sprite { private static propsTexture: Texture2D | null = null; private static propBuffer: Float32Array | null = null; private static effectUUID: string[] = []; private static mat: Material | null = null; private static isDirty: boolean = false; private instanceID: number = -1; //...略 }
在同一 Shader 效果下,所有 Sprite 共用的參數:
-
propsTexture (參數貼圖)
,,用來儲存同一個 Shader 效果不同 Sprite 各自的設定參數。 -
propBuffer
,TypeScript 端參數 buffer,暫存參數並於laterUpdate()
檢查異動同步PropsTexture (參數貼圖)
。 -
effectUUID
與instanceID
,利用 CC 每個 Node 的 uuid 唯一性,給予當下 Sprite 一個唯一的instanceID
。this.instanceID = SpDemoEffect.effectUUID.findIndex((uuid) => uuid === this.node.uuid); if (this.instanceID === -1) { this.instanceID = SpDemoEffect.effectUUID.push(this.node.uuid) - 1; }
-
mat
,同 Sahder 效果共用一個材質。 -
isDirty
,參數異動的旗標。
-
-
建立參數貼圖
const PROP_TEXTURE_SIZE = 128; // 定義屬性貼圖 width @ccclass('SpDemoEffect') export class SpDemoEffect extends Sprite { //...略 start() { if (!this.effectAsset) { warn("需指定 effect asset"); return; } // 利用 CC 每個 Node 的 uuid 唯一性,給予當下 Sprite 一個唯一的 `instanceID` this.instanceID = SpDemoEffect.effectUUID.findIndex((uuid) => uuid === this.node.uuid); if (this.instanceID === -1) { this.instanceID = SpDemoEffect.effectUUID.push(this.node.uuid) - 1; } // 利用 Sprite Component 中的 color 將 instanceID 傳入 Shader 效果中 this.color = new Color(this.instanceID % PROP_TEXTURE_SIZE, this.pixelsUsage, PROP_TEXTURE_SIZE, 255); if (SpDemoEffect.mat === null) { // 建立材質與屬性貼圖 PropsTexture (參數貼圖) const w = PROP_TEXTURE_SIZE; const h = this.pixelsUsage; SpDemoEffect.propBuffer = new Float32Array(w * h * 4); for (let y = 0; y < h; y++) { for (let x = 0; x < w; x++) { const index = (x + (y * w)) * 4; SpDemoEffect.propBuffer[index] = 1; SpDemoEffect.propBuffer[index + 1] = 0; SpDemoEffect.propBuffer[index + 2] = 1; SpDemoEffect.propBuffer[index + 3] = 1; } } SpDemoEffect.propsTexture = new Texture2D(); SpDemoEffect.propsTexture.setFilters(Texture2D.Filter.NEAREST, Texture2D.Filter.NEAREST); SpDemoEffect.propsTexture.reset({ width: w, height: h, format: Texture2D.PixelFormat.RGBA32F, mipmapLevel: 0 }); SpDemoEffect.propsTexture.uploadData(SpDemoEffect.propBuffer); //...略 } //...略 } }
-
建立材質並綁定
PropsTexture (參數貼圖)
,指定給customMaterial
參數const PROP_TEXTURE_SIZE = 128; @ccclass('SpDemoEffect') export class SpDemoEffect extends Sprite { //...略 start() { //...略 // 建立客制材質,綁定 `PropsTexture (參數貼圖)` 指定至 customMaterial 參數 if (SpDemoEffect.mat === null) { //...略 SpDemoEffect.mat = new Material(); SpDemoEffect.mat.initialize( { effectAsset: this.effectAsset, defines: {}, technique: 0 } ); SpDemoEffect.mat.setProperty('propsTexture', SpDemoEffect.propsTexture); } this.customMaterial = SpDemoEffect.mat; this.reflashParams(); } //...略 }
-
laterUpdate
,若有參數有異動時進行更新@ccclass('SpDemoEffect') export class SpDemoEffect extends Sprite { //...略 start() { //...略 } lateUpdate(deltaTime: number) { if (SpDemoEffect.isDirty) { SpDemoEffect.propsTexture!.uploadData(SpDemoEffect.propBuffer!); SpDemoEffect.isDirty = false; } } }
-
每個 Sprite 的
instanceID
利用 Sprite.color 傳入 Shader 效果中,在 TypeScript 中編碼this.color = new Color(this.instanceID, this.pixelsUsage, PROP_TEXTURE_SIZE, 255);
-
R通道
this._instanceID
-
G通道
pixelsUsage
,一個 pixel 有 4 個 float 可以保存參數,代表這個 Shader 效果參數用了幾個 4 各 float。 -
B通道
PROP_TEXTURE_SIZE
,參數貼圖 Width。 -
A通道
255
,設定為預設值不使用。
-
這個 Shader 效果為簡單定義一個 effectColor
對原 Sprite 進行顏色相加。
-
代碼如下
CCEffect %{ techniques: - name: default passes: - vert: sprite-vs:vert frag: sprite-fs:frag depthStencilState: depthTest: false depthWrite: false blendState: targets: - blend: true blendSrc: src_alpha blendDst: one_minus_src_alpha blendDstAlpha: one_minus_src_alpha rasterizerState: cullMode: none properties: &props alphaThreshold: { value: 0.5 } propsTexture: { value: white, editor: { type: sampler2D }} }% CCProgram sprite-vs %{ precision highp float; #include <cc-global> #if USE_LOCAL #include <cc-local> #endif #if SAMPLE_FROM_RT #include <common/common-define> #endif in vec3 a_position; in vec2 a_texCoord; in vec4 a_color; out vec4 color; out vec2 uv0; vec4 vert () { //...略 } }% CCProgram sprite-fs %{ precision highp float; #include <embedded-alpha> #include <alpha-test> #include <sprite-texture> #include "./chunks/util.chunk" in vec4 color; // 原 Sprite 屬性 color,用來當做 index 參數。 in vec2 uv0; uniform sampler2D propsTexture; vec4 frag () { // [記住] Sprite 原始的 color 屬性已經被拿去當作 index。 vec4 effectColor = getPropFromPropTexture(propsTexture, color, 0); // [小心思] index 編碼避免使用 a,因此會保留為 CC 中上一階 Canvas // 透明 a,讓自定義的效果依然能正常受影響。 effectColor = vec4(effectColor.rgb, effectColor.a * color.a); vec4 o = vec4(1, 1, 1, 1); o = CCSampleWithAlphaSeparated(cc_spriteTexture, uv0); // 顏色與 effectColor 相加 o = vec4(o.rgb + effectColor.rgb, o.a * effectColor.a); ALPHA_TEST(o); return o; } }%
-
在 Shader 中解碼 Sprite.color
// propTexture: 參數贴图 // encodeIdx: 參數索引編碼 // idxOfProps: 效果中的參數索引 vec4 getPropFromPropTexture(sampler2D propsTexture, vec4 encodeIdx, int idxOfProps) { vec2 prop_uv = vec2((1.0/(encodeIdx.b * 255.0)) * (encodeIdx.r * 255.0), (1.0/(encodeIdx.g * 255.0)) * float(idxOfProps)); return texture(propsTexture, prop_uv); }
-
PropsTexture (參數貼圖)
-
encodeColor
,傳入的 Sprite.color,解碼後即為instanceID
,貼圖座標的u
用來存取PropsTexture (參數貼圖)
。 -
idxOfProps
,解法後為 Shader 效果中的第幾個參數,貼圖座標的v
用來存取PropsTexture (參數貼圖)
。
-
-
範例完整代碼在 GitHub CC3.SpriteEffect.DemoProject 。
-
上述概念在 GitHub CC3.SpriteEffect 實現了一個樣板庫,可依此延伸出各種 Sprite 效果合批方。