CC3.SpriteEffect.DemoProject
Demo of CC3.SpriteEffect
Install / Use
/learn @BricL/CC3.SpriteEffect.DemoProjectREADME
CC3.SpriteEffect.DemoProject
語言(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)
<p align="center"><img src="./doc/img/cc3_sprite_effect_demo.gif" width="350"></p>UI(Sprite) 利用 Property Atlas 合批
依官方文件【2D 渲染组件合批规则说明】,Sprite 一但使用 customMaterial 合批 (batch) 就會被拆分。而同 Shader 不同參數想合批,正統是將參數帶入頂點中。詳細做法論壇上的 bakabird 大大提供保母級的教程 【分享】CocosCreator3.x 应用在UI(Sprite) 上的 shader(.effect) 的合批,通过自定义顶点参数。
這方法需對 Sprite 的 4 種頂點宣告模式 (SIMPLE、SLICE、TILED、FILLED) 作實現。那...還有其他方法可以不用動到修改頂點格式嗎?
目錄
Propert Atlas
Peoperty Atlas 的特點在於,不同 Sprite 相同 Shader 效果下,將自己所屬的參數儲存在同一張 PropsTexture (參數貼圖) 中,渲染時透過索引於取出所屬參數計算,如此就能利用引擎本身的合批規則減少 Drawcall。
實踐思路
-
對一 Shader 效果準備一張格式 RGBA32 的
PropsTexture (參數貼圖)。 -
每個 Sprite 在同一 Shader 效果下,自有所屬唯一的 index。
-
藉此 index 在渲染時對
PropsTexture (參數貼圖)取出所屬參數並計算。 -
屬性貼圖的儲存格式
<p align="center"><img src="./doc/img/explain_props_texture_formate.png" width="350"></p>PropsTexture (參數貼圖)的 width 決定一次能合批(batch)多少個 sprite,例如:64 代表最多可以一次合批(batch) 64 不同參數設定的 Sprite。
上代碼
SpDemoEffect.ts
-
繼承 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,設定為預設值不使用。
-
SpDemoEffect.effect
這個 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
Related Skills
node-connect
339.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
83.9kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
339.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
commit-push-pr
83.9kCommit, push, and open a PR
