用 Shader 實現(xiàn)旗幟飄揚動畫效果
共 8864字,需瀏覽 18分鐘
·
2024-04-26 12:07
我覺得對于剛?cè)腴T 3D 編程的朋友來說,如果能夠完成代碼創(chuàng)建模型數(shù)據(jù)->創(chuàng)建材質(zhì)->編寫Shader動畫這一系列,想必會有滿滿的成就感。
今天就用 Cocos Creator 的 utils.MeshUtils.createMesh 接口,帶大家感受一下這個流程。
這個流程不僅可以用于新手學(xué)習(xí),也是一些高端特效的必備技巧。
準(zhǔn)備:基礎(chǔ)
-
你需要一個Cocos Creator,我這里用的是3.8.2版本
-
了解下創(chuàng)建網(wǎng)格的接口:查看官方文檔
utils.MeshUtils.createMesh
這個接口是用來創(chuàng)建網(wǎng)格的,你可以通過這個接口創(chuàng)建一個網(wǎng)格,綁定到節(jié)點的 MeshRender的 mesh 屬性上,就可以看到你的模型了。
起點:顯示一個三角形
三角形是 3D 世界的基本單位,我們在屏幕上看到的三維模型,也是通過三角形構(gòu)成的。三角形數(shù)量越多,能夠表示的模型細節(jié)就越多。
我們先從一個三角形開始,然后再慢慢的擴展到更多的三角形,最后就是一個完整的模型了。
private createTriangle() {
this._meshCreator.reset();
let p0 = vec3Pool.alloc().set(0, 0, 0);
let p1 = vec3Pool.alloc().set(1, 0, 0);
let p3 = vec3Pool.alloc().set(0, -1, 0);
this._meshCreator.verticles.push(p0 , p1, p3);
let uv0 = vec2Pool.alloc().set(0, 0);
let uv1 = vec2Pool.alloc().set(1, 0);
let uv2 = vec2Pool.alloc().set(0, 1);
this._meshCreator.uvs.push(uv0, uv1, uv2);
this._meshCreator.indices.push(0, 2, 1);
this._meshRenderer.mesh = this._meshCreator.buildMesh();
}
代碼看起來是不是很簡單?
我們只需要創(chuàng)建三個頂點,然后創(chuàng)建三個uv,最后創(chuàng)建一個三角形,然后就可以看到一個三角形了。如果你不用顯示貼圖的話,甚至于可以不用計算uv,亂寫一個就行了。其實創(chuàng)建網(wǎng)格還有很多參數(shù),這里我們都可以先忽略它們!
進階:顯示一個正方形
正方形是兩個三角形組成的,我們只需要創(chuàng)建兩個三角形,然后組合在一起,就可以顯示一個正方形了。
private createQuad() {
this._meshCreator.reset();
let p0 = vec3Pool.alloc().set(0, 0, 0);
let p1 = vec3Pool.alloc().set(1, 0, 0);
let p2 = vec3Pool.alloc().set(1, -1, 0);
let p3 = vec3Pool.alloc().set(0, -1, 0);
this._meshCreator.verticles.push(p0, p1, p2, p3);
let uv0 = vec2Pool.alloc().set(0, 0);
let uv1 = vec2Pool.alloc().set(1, 0);
let uv2 = vec2Pool.alloc().set(1, 1);
let uv3 = vec2Pool.alloc().set(0, 1);
this._meshCreator.uvs.push(uv0, uv1, uv2, uv3);
this._meshCreator.indices.push(0, 2, 1, 0, 3, 2);
this._meshRenderer.mesh = this._meshCreator.buildMesh();
}
與三角形類似,我們只需要創(chuàng)建四個點,然后創(chuàng)建四個uv,最后創(chuàng)建兩個三角形,然后就可以看到一個正方形了。
聰明的你可能已經(jīng)發(fā)現(xiàn)了,正方形雖然是兩個三角形組成的,但是我們只需要創(chuàng)建一個正方形的四個點,然后通過索引來組合兩個三角形,這樣就可以減少重復(fù)的點了。
看代碼還是太累,這里用圖片來表示一下頂點:
終極:顯示一個飄動的旗幟
旗子其實就是一個長方形,然后通過Shader來讓它動起來,唯一不同的是,我們需要給它多個頂點,然后通過Shader控制各個頂點,讓它動起來,從而看起來像是飄動的旗幟。
private buildMesh() {
this._dirty = DirtyType.None;
this._meshCreator.reset();
const width = this._size.x;
const height = this._size.y;
const verticePreUnit = this._verticePreUnit;
const widthSegments = Math.floor(width * verticePreUnit);
const heightSegments = Math.floor(height * verticePreUnit);
for(let j = 0; j <= heightSegments; j++) {
const y = - j / heightSegments * height;
for(let i = 0; i <= widthSegments; i++) {
const x = i / widthSegments * width;
const v = vec3Pool.alloc();
v.set(x, y, 0);
this._meshCreator.verticles.push(v);
const uv = vec2Pool.alloc();
uv.set(i / widthSegments, j / heightSegments);
this._meshCreator.uvs.push(uv);
}
}
for(let j = 0; j < heightSegments; j++) {
for(let i = 0; i < widthSegments; i++) {
const a = i + (widthSegments + 1) * j;
const b = i + (widthSegments + 1) * (j + 1);
const c = (i + 1) + (widthSegments + 1) * (j + 1);
const d = (i + 1) + (widthSegments + 1) * j;
this._meshCreator.indices.push(a, b, c);
this._meshCreator.indices.push(a, c, d);
}
}
this._meshRenderer.mesh = this._meshCreator.buildMesh();
}
網(wǎng)格的創(chuàng)建沒什么再說的了,但是這里要注意需要精確計算uv坐標(biāo),保證紋理顯示正確;
而且在shader中需用通過uv坐標(biāo)來控制頂點的位置,從而讓旗幟動起來,簡單的來說就是通過uv的橫坐標(biāo)控制頂點偏移的強度,畢竟靠近旗桿的地方是不會動的,而靠近旗尾的地方是會動的。
我們通過shader顯示一下旗幟UV水平方向上的變化,可以看到離旗桿越遠,旗幟就會動的越強烈:
vec4 SurfacesFragmentModifyBaseColorAndTransparency()
{
return vec4(ALBEDO_UV.x, 0.0, 0.0, 1.0);
}
至此,給網(wǎng)格添加材質(zhì)普通的材質(zhì)球后,我們就有了一個靜態(tài)的旗幟,接下來就是通過Shader讓它動起來了。
進化:讓旗幟動起來
讓旗子動起來很簡單,只需要在Cocos Surface Shader的函數(shù)SurfacesVertexModifyWorldPos中修改頂點的位置即可,最簡單的方式就是給一個sin,讓他隨時間的變化平移頂點的位置,這樣就可以讓旗幟動起來了。
vec3 SurfacesVertexModifyWorldPos(in SurfacesStandardVertexIntermediate In)
{
In.worldPos += a_normal * sin(cc_time.w + In.worldPos.x);
return In.worldPos;
}
效果如下:
好吧,一言難盡,確實是動起來了,但是也飛起來了,旗桿完全沒達到約束他的效果,我們用上文提到的uv來控制頂點的偏移,再試試效果:
vec3 SurfacesVertexModifyWorldPos(in SurfacesStandardVertexIntermediate In)
{
In.worldPos += a_normal * sin(cc_time.w + In.worldPos.x) * a_texCoord.x;
return In.worldPos;
}
效果如下:
這次的效果明顯好多了,但是還是有點問題,旗幟的波動不夠自然,我們可以通過增加一張噪聲貼圖來讓它更自然一些,這里我用了一個簡單的噪聲貼圖,你可以用更復(fù)雜的噪聲貼圖來讓它更自然一些,當(dāng)然這里需要再頂點著色器中做紋理采樣,可能有些低端手機或者平臺不支持,所以需要注意一下。
當(dāng)然,你也可以通過更復(fù)雜的算法來模擬噪聲:
vec3 SurfacesVertexModifyWorldPos(in SurfacesStandardVertexIntermediate In)
{
float noise = texture2D(a_noiseMap, a_texCoord).r;
In.worldPos += a_normal * sin(cc_time.w + In.worldPos.x) * a_texCoord.x * noise;
return In.worldPos;
}
效果如下:
有了噪聲貼圖,旗幟的波動就更自然了, 但是有些小問題,我再次修改下代碼,形成最終的效果:
vec3 SurfacesVertexModifyWorldPos(in SurfacesStandardVertexIntermediate In)
{
float t = sin(cc_time.w * speed + In.worldPos.x);
vec3 oldPos = In.worldPos;
In.worldPos += a_normal * t * height * a_texCoord.x;
vec2 uv = a_texCoord + vec2(t * 0.3, 0.0);
vec4 noise = texture(noiseMap, uv);
In.worldPos -= noise.r * height * a_texCoord.x;
return In.worldPos;
}
最后,把圖片換成 Cocos 的標(biāo)準(zhǔn)模板 LOGO。
得到的效果如下:
總結(jié)
看到?jīng)]有,從頭到尾,我們只關(guān)注兩件事:
1. 創(chuàng)建網(wǎng)格
網(wǎng)格我已經(jīng)通過工具類封裝好了,你只需要調(diào)用接口,傳入?yún)?shù),就可以創(chuàng)建一個網(wǎng)格了。
2. 編寫Shader
而Shader我們從頭到尾值關(guān)注了一個函數(shù)SurfacesVertexModifyWorldPos,這個函數(shù)是用來修改頂點的位置的,我們只需要在這個函數(shù)中修改頂點的位置,就可以讓模型動起來了。
所以,3D 繪制其實是很簡單的,只要你掌握了這兩個點,你就可以做出很多很多的東西了,這里只是一個簡單的例子,你可以通過這個例子,去嘗試更多的東西,比如更復(fù)雜的模型,更復(fù)雜的Shader,更復(fù)雜的動畫,這些都是你可以嘗試的,希朥這個教程能夠幫助到你,也希望你能夠享受這個過程。
不知道你有沒有發(fā)現(xiàn),從頭繪制一個3D模型,并且給予它生命一般的動畫,這個過程是多么的有趣,這個過程就像是一個創(chuàng)造者,你可以創(chuàng)造出你想要的一切,這種感覺是多么的美妙,希望你也能夠感受到這種美妙。
項目文件
點擊【閱讀原文】,可在文章末尾獲得項目文件,直接解壓放到你的工程資源文件夾下即可。
