cocos版本:2.4.4
參考:
Cocos3D文檔: 材質 、 常用 shader 內置 Uniform
基礎知識: The Book of Shader 中文版
水友文章: 學習shader的入門筆記
Cocos Creator Shader Effect 系列
從被攻擊閃白shader到相關原創整理,以及相關學習資料整理
目錄
一 Shader
二 Cocos中的Shader
三 學習TheBookOfShader,並在cocos中實現書中效果
一 Shader
着色器(Shader)是用來實現圖像渲染的,用來替代固定渲染管線的可編輯程序。其中Vertex Shader(頂點着色器)主要負責頂點的幾何關系等的運算,Pixel Shader(像素着色器)主要負責片源顏色等的計算。
着色器替代了傳統的固定渲染管線,可以實現3D圖形學計算中的相關計算,由於其可編輯性,可以實現各種各樣的圖像效果而不用受顯卡的固定渲染管線限制。
理解上Shader是一段代碼,通過編程告訴GPU如何繪制頂點和顏色,從而實現各種各樣的圖像效果,例如彩色字體、按鈕置灰、圖片馬賽克、描邊、動態讓圖片扭動起來等等。
二 Cocos中的Shader
每張圖片cc.Sprite或文本cc.Label都有一個默認Materials材質。
材質資源可以用來控制渲染組件在場景中的視覺效果。簡單來說材質就是用來指定物體表面的特性,如顏色、光亮程度、自發光度以及不透明度等。
材質對應的Effect文件,里面就是shader代碼
Shader語言有3種:
1.基於OpenGL的OpenGL Shading Language,簡稱GLSL。
2.基於DirectX的High Level Shading Language,簡稱HLSL。
3. NVIDIA公司的C for Graphic,簡稱Cg語言。
Cocos采用的是YAML和GLSL,YAML聲明控制流程清單,GLSL聲明實際的shader片段。具體查看Effect語法。
builtin-2d-sprite.effect完整代碼如下:
CCEffect編寫聲明
其中properties可以自定義外部變量,例如自定義顏色變量,定義后可在屬性面板選擇顏色。
CCPrograms vs
獲取頂點數據,向下一個渲染管道傳遞數據。
這段代碼之后基本不用動,主要理解是兩個變量:
out vec4 v_color; //當前node節點顏色。
out vec2 v_uv0; //坐標,原點在左上角,xy軸坐標分別通過v_uv0.x 和v_uv0.y獲取。
CCProgram fs
大部分自定義shader邏輯代碼寫在這里面。下面的代碼對cocos的圖片進行采樣,然后和node節點顏色混合后輸出,實現普通builtin-2d-sprite效果。
CCTexture(texture,v_uv0,o); //對圖片進行采樣,顏色存儲在o里
o *= v_color; // o和node節點顏色v_color進行混合
gl_FragColor = i; //輸出顏色
那么問題來了,里面的其它變量都是什么意思...都有哪些內置函數和變量,cocosAPI搜索根本查不到這些....哪里看這些API的文檔...
Cocos Effect語法:Effect語法
Cocos常用 shader 內置 Uniform:內置Uniform
GLSL語法:OpenGL shader GLSL 中文手冊
三 學習TheBookOfShader,並在cocos中實現書中效果
打開 The Book of Shader 中文版 ,開始學習。
邊看教程邊寫例子。在cocos中新建測試用的Effect和Material,分別命名為TestMaterial和TestEffect。
選擇TestMaterial,設置Effect屬性為TestEffect
選擇任意一張圖片,賦值Materials屬性為TestMaterial
在資源管理器,選擇這個圖片,將packable的勾去掉。如果這個打包的勾被勾選,則shader在發布后出問題。
雙擊TestEffect,則可以在vs code打開並編輯TestEffect文件,但是文件都是白字,沒有語法高亮。需要安裝一個插件來支持代碼高亮。
vscode中安裝Cocos Effect插件
這個插件可以高亮effect文件,方便閱讀代碼。選擇查看-擴展
搜索Cocos Effect並安裝
安裝后代碼有了顏色
第一個例子,將圖片變成紅色
修改TestEffect的CCProgram fs的最后一行gl_FragColor=o改為gl_FragColor = vec4(1.0,0,1.0,1.0)
vec4的4個參數分別代表顏色通道(red, green,blue,alpha),顏色值是范圍0-1,注意不要寫整數1,要寫浮點數1.0。
CCProgram fs %{ precision highp float; #include <alpha-test> #include <texture> #include <cc-global> #include <cc-local> in vec4 v_color; #if USE_TEXTURE in vec2 v_uv0; uniform sampler2D texture; #endif uniform color{ vec4 imgColor; }; void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); gl_FragColor = vec4(1.0,0,1.0,1.0)*o; } }%
關於顏色值vec4的訪問,下圖中訪問方式是等效的。
第二個例子,圖片紅色閃爍
cc_time.x就是書里的u_time,表示游戲的運行時間。o.r就是紅色通道值, abs(sin(cc_time.x))就是利用余弦函數,隨着游戲時間增加,紅色通道值一直在0-1之間變化,從而形成了閃爍效果。
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); o.r = abs(sin(cc_time.x)); gl_FragColor = o; }
第三個例子,漸變色
v_uv0就是書中的gl_FragCoord.xy/u_resolution,表示坐標。
v_uv0.x和v_uv0.y值是從0-1變化的,gl_FragColor = vec4(v_uv0.x,v_uv0.y,1.0,1.0)表示紅色和綠色通道值從左上角到右下角由0-1變化。
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); gl_FragColor = vec4(v_uv0.x,v_uv0.y,1.0,1.0); }
因為沒有計算cocos圖片顏色o,所以這里是單純的顏色值。
gl_FragColor = vec4(v_uv0.x,v_uv0.y,1.0,1.0)理解起來很抽象,我們代入幾個值到公式里看看就知道規律了。
x坐標 y坐標 顏色值 結果
v_uv0.x=0 v_uv0.y=0 vec4(0,0,1.0,1.0) (0,0)表示左上角,顏色值藍色
v_uv0.x = 1.0 v_uv0.y=1.0 vec4(1.0,1.0,1.0,1.0) (1,1)表示右下角,顏色值白色
v_uv0.x = 0.5 v_uv0.y=0.5 vec4(0.5,0.5,1.0,1.0) (0.5,0.5)表示中間,顏色值淡紫色
漸變色帶圖,這里計算了圖片本身的顏色o
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); gl_FragColor = vec4(v_uv0.x*o.r,v_uv0.y*o.g,o.b,o.a); }
第四個例子,畫一條綠線
smoothstep(起始值A,結束值B,插值t) ,參考cocos里的cc.Vec3.lerp函數,大致smoothstep(A,B,t)返回值應該是A + (B-A)*t
float plot(vec2 st) { return smoothstep(0.02, 0.0, abs(st.y - st.x)); } void main () { float y = v_uv0.x; vec3 color = vec3(y); // Plot a line float pct = plot(v_uv0); color = (1.0-pct)*color+pct*vec3(0.0,1.0,0.0); gl_FragColor = vec4(color,1.0); }
第五個例子,圓
distance計算距離, distance(v_uv0, vec2(0.5))得到坐標離圖片中心點的距離,距離越遠值越大,越接近白色;距離越近值越小,越接近黑色。
void main () { float pct = 0.0; pct = distance(v_uv0,vec2(0.5)); vec3 color = vec3(pct); gl_FragColor = vec4( color, 1.0 ); }
畫圓形,dot(x,y)返回x,y的點積。
float circle(in vec2 _st, in float _radius){ vec2 dist = _st-vec2(0.5); return 1.-smoothstep(_radius-(_radius*0.01), _radius+(_radius*0.01), dot(dist,dist)*4.0); } void main () { vec3 color = vec3(circle(v_uv0,0.25)); gl_FragColor = vec4(color,1.0); }
第六個例子 畫長方形
step(闕值A,檢測值B) B<A返回0.0,B>=A返回1.0。
下面只畫了左和上,left在x<0.1的地方是0,其它地方1;bottom在y<0.1的地方是0,其它地方1。left*bottom有&&的作用,只有x和y都=1結果才是1。
所以只有滿足x>=0.1&&y>=0.1的地方才會是1白色值。
void main () { vec3 color = vec3(0.0); float left = step(0.1,v_uv0.x); float top = step(0.1,v_uv0.y); color = vec3( left * top ); gl_FragColor = vec4(color,1.0); }
下面畫右和下,只有x<=0.9 && y<=0.9的地方是1白色。
void main () { vec3 color2 = vec3(0.0); float right = step(0.1,1.0-v_uv0.x); float bottom = step(0.1,1.0-v_uv0.y); color2 = vec3( right * bottom ); gl_FragColor = vec4(color2,1.0); }
代碼合起來
void main () { vec3 color = vec3(0.0); float left = step(0.1,v_uv0.x); float top = step(0.1,v_uv0.y); color = vec3( left * top ); vec3 color2 = vec3(0.0); float right = step(0.1,1.0-v_uv0.x); float bottom = step(0.1,1.0-v_uv0.y); color2 = vec3( right * bottom ); gl_FragColor = vec4(color*color2,1.0); }
第7個例子 從上到下四色漸變
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); vec4 color1 = vec4(1,0,0,1); vec4 color2 = vec4(0,1,0,1); vec4 color3 = vec4(0,0,1,1); vec4 color4 = vec4(1,1,1,1); vec4 resultColor = vec4(1,1,1,1); if(v_uv0.y < 0.33){ resultColor = vec4(color1.x + (color2.x - color1.x)*v_uv0.y*3.0, color1.y + (color2.y - color1.y)*v_uv0.y*3.0, color1.z + (color2.z - color1.z)*v_uv0.y*3.0, 1.0); }else if(v_uv0.y < 0.66){ resultColor = vec4(color2.x + (color3.x - color2.x)*(v_uv0.y-0.33)*3.0, color2.y + (color3.y - color2.y)*(v_uv0.y-0.33)*3.0, color2.z + (color3.z - color2.z)*(v_uv0.y-0.33)*3.0, 1.0); }else if(v_uv0.y <= 1.0){ resultColor = vec4(color3.x + (color4.x - color3.x)*(v_uv0.y-0.66)*3.0, color3.y + (color4.y - color3.y)*(v_uv0.y-0.66)*3.0, color3.z + (color4.z - color3.z)*(v_uv0.y-0.66)*3.0, 1.0); } gl_FragColor = resultColor*o; }
第8個例子 設置effect屬性
定義個imgColor屬性
定義變量
在TestMaterial屬性檢查器中看一看到屬性,並可以選擇顏色
將imgColor賦予圖片
void main () { vec4 o = vec4(1, 1, 1, 1); #if USE_TEXTURE CCTexture(texture, v_uv0, o); #endif o *= v_color; ALPHA_TEST(o); gl_FragColor = o*imgColor; }
第9個例子 攻擊閃白效果
復制cocos的sprite和spine的普通effect
修改復制的sprite effect,高亮圖片的顏色
修改復制的spine effect,高亮圖片顏色
將修改后的高亮effect賦予圖片,效果是這樣的
點擊一個圖片或spine、db時,切換高亮effect持續0.1秒,然后恢復正常的effect,就可以做出攻擊閃白的效果
const { ccclass, property } = cc._decorator; @ccclass export default class AttackFlash extends cc.Component { @property(dragonBones.ArmatureDisplay) //龍骨怪物 monster_db: dragonBones.ArmatureDisplay = null; @property(sp.Skeleton) //spine怪物 monster_spine: sp.Skeleton = null; @property(cc.Sprite) //普通圖片怪物 monster_img: cc.Sprite = null; @property(cc.Material) //sprite被攻擊閃白材質 mat_attacked_sprite: cc.Material = null; @property(cc.Material) //spine被攻擊閃白材質 mat_attacked_spine: cc.Material = null; @property(cc.Material) //普通材質 mat_normal: cc.Material = null; @property(cc.Material) //普通spine材質 mat_normal_spine: cc.Material = null; onLoad() { this.monster_db.node.on(cc.Node.EventType.TOUCH_END, this.onMonsterDbTap, this); this.monster_spine.node.on(cc.Node.EventType.TOUCH_END, this.onMonsterSpineTap, this); this.monster_img.node.on(cc.Node.EventType.TOUCH_END, this.onMonsterImgTap, this); } //點擊dragonBones,切換高亮material onMonsterDbTap() { this.monster_db.setMaterial(0, this.mat_attacked_sprite); this.unschedule(this.flashDb); this.schedule(this.flashDb, 0.1); } //計時結束,切換普通material flashDb() { this.monster_db.setMaterial(0, this.mat_normal); } //點擊spine,切換高亮material onMonsterSpineTap() { this.monster_spine.setMaterial(0, this.mat_attacked_spine); this.unschedule(this.flashSpine); this.schedule(this.flashSpine, 0.1); } //計時結束,切換普通material flashSpine() { this.monster_spine.setMaterial(0, this.mat_normal_spine); } //點擊圖片,切換高亮material onMonsterImgTap() { this.monster_img.setMaterial(0, this.mat_attacked_sprite); this.unschedule(this.flashImg); this.schedule(this.flashImg, 0.1); } //計時結束,切換普通material flashImg() { this.monster_img.setMaterial(0, this.mat_normal); } }