Cocos Creator 3D 材質系統:曲面效果如何實現?

遊資網發表於2019-12-31
引言

前不久釋出的 Cocos Creator 1.0.2 版本中正式加入了對 OPPO 小遊戲、vivo 小遊戲以及華為快遊戲平臺的支援,在諸多 Creator 3D 製作的小遊戲案例中,《豬豬俠:極速狂飆》已上線 OPPO 小遊戲平臺。

這款休閒跑酷小遊戲,採用了曲面材質效果來使跑酷賽道更加多變有趣,今日,Cocos 引擎開發工程師 ChiaNing 將為各位開發者來解析這種曲面效果的實現思路和方案,在閱讀完本文之後,大家便可以將這種效果應用在自己的遊戲中。

1、使用背景

在固定背後視角的跑酷遊戲中,玩家面對的始終是前方佈滿障礙的賽道,除去有趣的障礙設計可以一直吸引玩家的注意外,許多遊戲還會新增一些視覺效果來使遊戲畫面看起來更豐富有趣,比如今天我們要分享的這款《豬豬俠:極速狂飆》就採用了曲面的效果使得賽道看起來更多變,也更有立體感和縱深感。

Cocos Creator 3D 材質系統:曲面效果如何實現?

那麼,這樣的曲面效果在 Cocos Creator 3D 中是如何實現的呢?

2、實現方案分析


要實現曲面的效果,我們有幾種方案可選擇:

1. 直接使用曲面模型

這是最直觀最容易想到的實現方案,從模型層面直接將效果做好,省去了其他處理,但這種方案也存在著很多嚴重的問題:

(1)模型複用不便,模型生成時的狀態幾乎決定了它的使用場合,這對於遊戲開發中需要大量複用資源以減小包體來說有嚴重的問題。

(2)對於跑酷遊戲這種物理需求並不複雜的遊戲來說,大部分的遊戲邏輯都可以直接通過計算直接完成而並不需要依賴物理引擎實現,對於正常的模型來說,規則的形狀對於邏輯實現是很友好的,但是啟用曲面模型就會對這種計算帶來很多困難,幾乎只能通過使用物理引擎來實現,過多的物理計算對效能是會有較大的影響的。

包體不友好,效能不友好,異型模型還會對製作帶來麻煩,對於只是為了實現顯示效果來說,這些損耗得不償失。

2. 使用材質系統實現

我們需要明白一點,要實現的曲面的效果,實際上影響的只有顯示效果,與其他的任何系統是不相關的,它不應當影響到其他無關的模組,既然只想改變顯示,那採用材質系統相較於採用曲面模型的方案有著諸多好處:

(1)不必使用物理引擎,簡單的物理效果可以通過計算來實現,效率更優。

(2)模型可複用,想要實現不同的彎曲效果也很方便,只要使用帶有曲面效果的不同引數的材質即可實現同一模型的不同效果。相較於方案一的多重模型來說,只需要幾個材質即可解決問題。

(3)引數可配置,可以通過引數調節來得到不同的效果。

分析看來,相較於直接使用曲面模型的方案來說,使用材質系統實現的方案優勢很明顯,沒有額外的開銷,也沒有太大的包體負擔。

綜上所述,使用材質系統實現更能滿足我們的需求,因此採用材質系統來實現這個效果。

3、方案思路分析

從需求來看,我們的目的是實現一個與我們的觀察點相關的模型變形,既然只是變形,並不涉及到顏色的變化和處理,那麼需要處理的就只有頂點著色器部分,並不涉及片段著色器。對於不太清楚渲染管線各個階段的讀者,可以參考 [LearnOpenGL]的渲染管線介紹。

在 Shader 中,通過頂點著色器即可完成對模型頂點位置的操作。明確了是對頂點位置進行操作後,我們將攝像機所在的點定為原點。

由於我們的攝像機是固定在人物背後,且賽道始終保持向 Z 軸負方向延伸,所以可以將模型與攝像機的 Z 軸方向的距離看作函式的輸入值,想要得到曲面的效果,模型的點的變化規律如下:

  • 距離原點越遠的點產生的偏置值越大(函式在區間內為增函式)
  • 距離原點越遠的點偏置的變化速度越快(函式的導數為一次函式)
  • 由上述兩條規律不難得出,二次函式的變化規律與我們想要實現的曲面效果的規律契合,所以我們的頂點著色器的運算為一個關於頂點位置 Z 值的二次函式運算。


我們剛剛得出的規律是建立在一個特定空間下的,即以攝像機為原點的空間,這個空間正是空間變換中的觀察空間階段,所以我們之後對頂點的操作正是在這個空間中進行才能夠得到正確的結果。

4、材質系統簡介

Cocos Creator 3D 提供了完備的材質系統,基於這套材質系統,我們能夠很方便地在引擎中建立使用編輯材質,並且在場景預覽視窗能夠隨時觀察到材質更改所帶來的變化。

在 Cocos Creator 3D 編輯器中與材質系統相關的資源有兩種,分別為:

Effect 資源

此型別資源為符合 Cocos Effect 語法標準的渲染流程描述檔案,由 YAML 格式的流程控制清單和基於 GLSL 300 ES 語法的 Shader 片段共同組成。

Material 資源

此資源可看做是 Effect 資源在場景中的資源例項,其本身除了 Effect 資源的引用外,還包括很多可配置引數以決定 Material 的狀態。在實際使用中,我們的模型是需要使用 Material 資源的,這樣就可以實現使用同一個 Effect 但引數不同以實現不同效果的需求了。

材質的使用也非常的方便,在 Cocos Creator 3D 編輯器的資源管理器中右鍵即可新建出 Effect 資源和 Material 資源。

Cocos Creator 3D 材質系統:曲面效果如何實現?

在 Material 資源可選擇需要使用的 Effect 還可對其他引數進行配置,完成配置並儲存後,選中需要使用材質的模型,選中需要的 material 或者直接將 material 拖入框中即可完成材質的設定。

Cocos Creator 3D 材質系統:曲面效果如何實現?

具體詳盡的材質介紹和參數列請參看官方文件 [材質系統]。

5、具體實現

思路已經很清晰了,那麼現在開始著手實現 Shader。下面是具體的實現步驟:

1、啟動 Cocos Creator 3D 編輯器(以下簡稱編輯器),為實驗方便,使用最簡單的場景即可,新建場景後,在場景編輯器中新建一個 Plane 模型,之後以此物件作為檢視 Shader 效果的物件。

2、在編輯器的資源管理器中右鍵新建 Effect,將其命名為 curved 或者符合要求的名字。

3、這時新建的 Effect 檔案為編輯器內建的 Effect 模板,其包含了最基礎的 shader 結構,我們需要在這個基礎上新增我們需要的功能,關於 Effect 的具體介紹請參看我們的 [材質 Effect 文件],在裡我只對我們需要的更改做出介紹。

4、先來看 CCEffect 部分:

  1. <p>CCEffect %{</p><p><span style="white-space:pre">        </span>techniques:</p><p><span style="white-space:pre">                </span>- name: opaque</p><p><span style="white-space:pre">        </span>passes:</p><p><span style="white-space:pre">                </span>- vert: general-vs:vert # builtin header</p><p><span style="white-space:pre">        </span>frag: unlit-fs:frag</p><p><span style="white-space:pre">        </span>properties: &props</p><p><span style="white-space:pre">        </span>mainTexture: { value: white }</p><p><span style="white-space:pre">        </span>mainColor: { value: [1, 1, 1, 1], editor: { type: color } }</p><p><span style="white-space:pre">                </span>- name: transparent</p><p><span style="white-space:pre">        </span>passes:</p><p><span style="white-space:pre">                </span>- vert: general-vs:vert # builtin header</p><p><span style="white-space:pre">        </span>frag: unlit-fs:frag</p><p><span style="white-space:pre">        </span>blendState:</p><p><span style="white-space:pre">        </span>targets:</p><p><span style="white-space:pre">                </span>- blend: true</p><p><span style="white-space:pre">        </span>blendSrc: src_alpha</p><p><span style="white-space:pre">        </span>blendDst: one_minus_src_alpha</p><p><span style="white-space:pre">        </span>blendSrcAlpha: src_alpha</p><p><span style="white-space:pre">        </span>blendDstAlpha: one_minus_src_alpha</p><p><span style="white-space:pre">        </span>properties: *props</p><p>}%</p>
複製程式碼

在預設的 Effect 模板中,我們需要更改的是 vert 欄位所使用的 Shader 片段,預設模板中提供的 general-vs:vert 是內建的頂點著色器,所以我們需要將其替換為我們即將實現的頂點著色器的名字(暫定為 unlit-vs).

接下來,需要對 properties 部分進行修改,property 列表將會將屬性暴露在編輯器皮膚上方便我們的編輯和更改。

此時需要決定哪些資料是需要作為 uniform 傳入 shader 中對效果做出影響的了,結合之前分析的需求:需要有一個決定模型點在各個分量軸上偏置值的偏置位置資訊,我們使用一個 vec4 來儲存這個偏置值(allOffset);需要有一個決定偏置變化的係數的值,使用一個 float 即可(dist);還可以新增模型的主貼圖等(mainTexture)

經過以上更改之後,Effect 的 CCEffect 部分看起來是這個樣子的:

  1. <p>CCEffect %{</p><p><span style="white-space:pre">        </span>techniques:</p><p><span style="white-space:pre">                </span>- name: opaque</p><p><span style="white-space:pre">        </span>passes:</p><p><span style="white-space:pre">                </span>- vert: unlit-vs:vert</p><p><span style="white-space:pre">        </span>frag: unlit-fs:frag</p><p><span style="white-space:pre">        </span>properties: &props</p><p><span style="white-space:pre">        </span>mainTexture: { value: grey }</p><p><span style="white-space:pre">        </span>allOffset: { value: [0, 0, 0, 0] }</p><p><span style="white-space:pre">        </span>dist: { value: 1 }</p><p><span style="white-space:pre">                </span>- name: transparent</p><p><span style="white-space:pre">        </span>passes:</p><p><span style="white-space:pre">        </span>- vert: unlit-vs:vert</p><p><span style="white-space:pre">        </span>frag: unlit-fs:frag</p><p><span style="white-space:pre">        </span>depthStencilState:</p><p><span style="white-space:pre">        </span>depthTest: true</p><p><span style="white-space:pre">        </span>depthWrite: false</p><p><span style="white-space:pre">        </span>blendState:</p><p><span style="white-space:pre">        </span>targets:</p><p><span style="white-space:pre">                </span>- blend: true</p><p><span style="white-space:pre">        </span>blendSrc: src_alpha</p><p><span style="white-space:pre">        </span>blendDst: one_minus_src_alpha</p><p><span style="white-space:pre">        </span>blendDstAlpha: one_minus_src_alpha</p><p><span style="white-space:pre">        </span>properties: *props</p><p>}%</p>
複製程式碼

5、由於預設的 Effect 模板中使用了內建的頂點著色器,所以這裡需要實現自己的頂點著色器,可以參考內建的 builtin-unlit 的實現來編寫此段 shader:

新增需要的 uniform :
  1. <p>
  2. </p><p>uniform Constants {</p><p>  vec4 allOffset;</p><p>  float dist;</p><p>};</p>
複製程式碼

編寫入口函式:vert

按照引擎要求對接骨骼動畫和資料解壓,直接在開頭呼叫 CCVertInput 工具函式。

模型資源在場景中可能出現很多重複的,這樣就需要對模型進行動態合批,對接引擎的動態合批流程,在包含 cc-local-batch 標頭檔案後,通過 CCGetWorldMatrix 函式獲取世界矩陣。

  1. <p>vec4 position;</p><p>CCVertInput(position);</p><p>
  2. </p><p>highp mat4 matWorld;</p><p>CCGetWorldMatrix(matWorld);</p><p></p>
複製程式碼

在分析時提到,需要在觀察空間下對頂點進行處理,所以需要將座標轉換到觀察空間下。曲面效果是和 Z 座標直接相關的,所以係數也是直接影響 Z 座標的。

dist 係數為影響變化的係數,所以在和 vpos.z 的運算時,可以使用乘法也可以使用除法,但這個改變會直接影響 dist 的取值,所以在決定是使用除法還是乘法後,需要對值進行對應修改,且注意使用除法時 dist 的值不可為 0。

對於各軸分量的修改,需要 allOffset 參與運算然後造成影響,此處的 zOff 的平方運算即為分析中的二次函式符合變化規律的實現。

在處理完成之後,按照正常的變換邏輯繼續將觀察空間通過投影矩陣變換為裁剪空間下的座標之後繼續傳遞給片段著色器即可。

  1. <p>highp vec4 vpos = cc_matView * matWorld * position;</p><p>highp float zOff = vpos.z / dist;</p><p>vpos += allOffset * zOff * zOff;</p><p>highp vec4 pos = cc_matProj * vpos;</p><p>
  2. </p><p>v_uv = a_texCoord;</p><p>return pos;</p>
複製程式碼

6、對於片段著色器,我們並未做特殊的操作,所以直接使用預設提供的就可以。

7、最終的 effect 如下:

  1. <p>CCEffect %{</p><p><span style="white-space:pre">        </span>techniques:</p><p><span style="white-space:pre">                </span>- name: opaque</p><p><span style="white-space:pre">        </span>passes:</p><p><span style="white-space:pre">                </span>- vert: unlit-vs:vert</p><p><span style="white-space:pre">        </span>frag: unlit-fs:frag</p><p><span style="white-space:pre">        </span>properties: &props</p><p><span style="white-space:pre">        </span>mainTexture: { value: grey }</p><p><span style="white-space:pre">        </span>allOffset: { value: [0, 0, 0, 0] }</p><p><span style="white-space:pre">        </span>dist: { value: 1 }</p><p><span style="white-space:pre">                </span>- name: transparent</p><p><span style="white-space:pre">        </span>passes:</p><p><span style="white-space:pre">                </span>- vert: unlit-vs:vert</p><p><span style="white-space:pre">        </span>frag: unlit-fs:frag</p><p><span style="white-space:pre">        </span>depthStencilState:</p><p><span style="white-space:pre">        </span>depthTest: true</p><p><span style="white-space:pre">        </span>depthWrite: false</p><p><span style="white-space:pre">        </span>blendState:</p><p><span style="white-space:pre">        </span>targets:</p><p><span style="white-space:pre">                </span>- blend: true</p><p><span style="white-space:pre">        </span>blendSrc: src_alpha</p><p><span style="white-space:pre">        </span>blendDst: one_minus_src_alpha</p><p><span style="white-space:pre">        </span>blendDstAlpha: one_minus_src_alpha</p><p><span style="white-space:pre">        </span>properties: *props</p><p>}%</p><p>
  2. </p><p>CCProgram unlit-vs %{</p><p><span style="white-space:pre">        </span>precision highp float;</p><p>
  3. </p><p><span style="white-space:pre">        </span>#include <cc-global></p><p><span style="white-space:pre">        </span>#include <cc-local-batch></p><p><span style="white-space:pre">        </span>#include <input></p><p>
  4. </p><p><span style="white-space:pre">        </span>in vec2 a_texCoord;</p><p><span style="white-space:pre">        </span>out vec2 v_uv;</p><p>
  5. </p><p><span style="white-space:pre">        </span>uniform Constants {</p><p><span style="white-space:pre">                </span>vec4 allOffset;</p><p><span style="white-space:pre">                </span>float dist;</p><p><span style="white-space:pre">        </span>};</p><p>
  6. </p><p><span style="white-space:pre">        </span>highp vec4 vert () {</p><p><span style="white-space:pre">                </span>vec4 position;</p><p><span style="white-space:pre">                </span>CCVertInput(position);</p><p>
  7. </p><p><span style="white-space:pre">                </span>highp mat4 matWorld;</p><p><span style="white-space:pre">                </span>CCGetWorldMatrix(matWorld);</p><p>
  8. </p><p><span style="white-space:pre">                </span>highp vec4 vpos = cc_matView * matWorld * position;</p><p><span style="white-space:pre">                </span>highp float zOff = vpos.z / dist;</p><p><span style="white-space:pre">                </span>vpos += allOffset * zOff * zOff;</p><p><span style="white-space:pre">                </span>highp vec4 pos = cc_matProj * vpos;</p><p>
  9. </p><p><span style="white-space:pre">                </span>v_uv = a_texCoord;</p><p><span style="white-space:pre">                </span>#if FLIP_UV</p><p><span style="white-space:pre">                </span>v_uv.y = 1.0 - v_uv.y;</p><p><span style="white-space:pre">                </span>#endif</p><p><span style="white-space:pre">                </span>return pos;</p><p><span style="white-space:pre">        </span>}</p><p>}%</p><p>
  10. </p><p>CCProgram unlit-fs %{</p><p><span style="white-space:pre">        </span>precision highp float;</p><p><span style="white-space:pre">        </span></p><p><span style="white-space:pre">        </span>#include <output></p><p>
  11. </p><p><span style="white-space:pre">        </span>in vec2 v_uv;</p><p><span style="white-space:pre">        </span>uniform sampler2D mainTexture;</p><p>
  12. </p><p><span style="white-space:pre">        </span>vec4 frag () {</p><p><span style="white-space:pre">                </span>vec4 o = vec4(1, 1, 1, 1);</p><p>
  13. </p><p><span style="white-space:pre">                </span>o *= texture(mainTexture, v_uv);</p><p>
  14. </p><p><span style="white-space:pre">                </span>return CCFragOutput(o);</p><p><span style="white-space:pre">        </span>}</p><p>}%</p>
複製程式碼

8、在完成了 effect 之後,我們可以在編輯器中新建一個材質,在材質的 Effect 中選擇剛剛完成的 curved 之後傳入想要的貼圖,填入 dist 和 AllOffset 引數,儲存之後將這個材質賦予剛剛提到的 plane 物件,調整引數可以看到我們的片面就能出現偏置效果了,移動攝像機可看到偏置效果是與頂點距離攝像機的距離相關的:

Cocos Creator 3D 材質系統:曲面效果如何實現?

9、請注意,Shader 的引數與模型的尺寸是相關的,上圖所示效果是在 dist 為 100,AllOffset 的 Y 值為 10 時的效果,各位開發者可嘗試不同的組合來達到想要的效果。

10、似乎上圖的效果還不是太直觀,所以我用一些建築模型和一些路面模型簡單搭建了一段賽道來模擬遊戲可能會出現的場景。建議最好是能夠顯示出縱深效果的連續模型段,更能顯示出效果,當然這個效果並不只限於 Y 軸方向,還可以同時滿足 X 軸方向的偏置需求,下圖所示即為 Dist 為 100,X 為 -20,Y 為 -10 時的效果圖:

Cocos Creator 3D 材質系統:曲面效果如何實現?

6、總結

相信各位對 [ShaderToy] 並不陌生,之後我們將提供從 ShaderToy 中遷移 Shader 的方法,海量 Shader 等你來學習!目前在我們的 [官方案例倉庫] 的 demo02 中,已經實現了一個遷移好的場景案例 (ShaderToy),感興趣的小夥伴們趕快去試試吧!

有很多小夥伴在社群分享了炫酷的材質效果,在這裡感謝 @yans 做出的分享,歡迎有想法的開發者多做分享,共同促進社群成長。

希望通過這個簡單的案例為各位提供一個瞭解材質系統的入口。材質系統功能十分豐富,能夠實現的效果也是多種多樣的,各位快快開啟腦洞,用 Cocos Creator 3D 來實現各種炫酷的效果吧!

歡迎小夥伴們繼續通過論壇、GitHub、Cocos 企業服務等渠道向我們提交 Cocos Creator 3D 使用反饋!

以上,謝謝!

作者:CocosEngine
來源:indienova
原地址:https://indienova.com/indie-game-development/cocos-creator-3d-curve-effect/

相關文章