開發背景
得益于“元宇宙”概念在前段時間的爆火,各家公司都推出了使用 3D 場景的活動或頻道。
3D 場景相比傳統的 2D 頁面優點是多一個次元,同屏展示的内容可以更多,能完整的展示物體、商品的資訊。
相應帶來的缺點是使用者使用方式改變,使用者需要額外的學習成本。另外初期需要的開發量、美術資源和生成3D模型的裝置也是增加的成本。
在這樣的背景下,我們團隊接到了食品頻道的一個互動項目的開發需求,希望通過 3D 場景的展示和互動方式,作為對未來購物的一種嘗試與探索,滿足使用者對未來美好新奇的一個需求。将購物場景化、娛樂化,給使用者帶來美好的購物感受。
前端架構選擇
3D項目相比之前的2D項目改變的主要是用戶端的表現。在希望不依賴app用戶端支援和在盡量多的環境下能運作,我們首先采用的方案是在 Web 端實作 3D 項目。
開發套件—
首先我們考慮的是成熟的開發套件,如unity/egret等,但這些開發套件都有一些我們不能繞過的問題,例如:
- 商業化使用需要收費
- 需要使用其他語言開發(如 C# ),對團隊學習成本較大
- 打包輸出的檔案大小過大
- 官方文檔不夠詳細,學習曲線較抖
引擎名稱/對比次元使用價格(權重50%)腳本上手(權重30%)場景搭建(權重20%)支援模型格式(權重10%)社群資料豐富程度(權重30%)支援web端釋出(一票否決)Unity 3d3710810YLaya49777YEgret108776YCocos2d-js
NGodot107787Y
由于以上的原因,開發套件裡沒有令團隊很滿意的選擇,我們從其他方向尋找開發工具。
開源渲染庫—
另外也比較了 Web 前端使用量較多的兩個 3D 渲染庫:
- three.js 提供的元件粒度較小,較基礎,能做很高程度的定制化二次開發,但如果需要開發一個互動項目,需要開發的元件比較多
- babylon.js 既提供了粒度小的基礎元件,也封裝了接近開箱即用的元件。并自帶了性能測量工具,提供了友善的debug方法和優化政策
經過團隊内對各個開發套件/渲染庫的試用,最後選擇了 babylon.js 作為項目的渲染層庫,在其提供的元件上二次開發業務邏輯。
項目場景搭建
渲染分層結構—
項目渲染層級總體分為兩層:3D 場景層和 HUD 層
3D 場景層顧名思義渲染 3D 場景,由人物模型、建築模型和寶箱這些互動模型組成
HUD 層渲染互動按鈕、彈窗、業務需要的商品清單等2D UI 内容
本來 babylonjs 是支援 3D 和 2D 内容混合渲染的,但是如果都使用 babylonjs 渲染,在設定兩種内容需要使用統一的分辨率,而在現在的移動端裝置上,能支援像素分辨率(如iPhone 14的像素分辨率為1170x2532)渲染不卡頓的隻占一小部分。在大部分的裝置上,最多隻能支援在邏輯分辨率(如iPhone 14邏輯分辨率為390x844)下流暢運作,但設定這樣的分辨率會使 2D 層渲染模糊,是以使用分層的方法渲染。
由 babylonjs 渲染 3D 場景層,而 HUD 層則通過 react 架構使用傳統 DOM 方式渲染。
第二個 3D 渲染層—
渲染層分為 3D 場景層和 HUD 層帶來了一個問題,需要在 HUD 層上再渲染 3D 内容時,例如展示 3D 模型,則不得不再增加一層 3D渲染層。而 3D 渲染層不停地在調用渲染方法,以響應使用者操作和播放動畫,這耗費了大量 CPU 和 GPU 的計算資源,還占用了存儲模型頂點資訊和貼圖紋理的記憶體空間,是以在多個 3D 渲染層共存的情況下,需進行一定的管理以優化性能。我們采用以下政策管理多個 3D 渲染層:
- 在展示另外的 3D 渲染層時再執行個體化,并暫停原來 3D 渲染層的渲染
- 在不需要展示的時候銷毀,恢複原 3D 渲染層的渲染方法調用
以盡量減少資源的占用,提高項目的渲染性能。
互動元件開發
碰撞檢測—
babylonjs 自帶檢測模型間是否碰撞的方法,但使用設計師提供的高精度模型直接去調用碰撞檢測方法的話,計算量會很大,雖然未在測試裝置上出現較嚴重的卡頓現象,但是已經使裝置發熱。
是以需要使用一個包圍模型的不可見的、精簡面的“空氣牆”模型來做碰撞檢測。在項目初期,這個“空氣牆”模型需要設計師提供,在模組化軟體裡根據原模型制作低精度包圍模型。在後續疊代開發中,我們團隊開發了“一鍵生成空氣牆”的工具,自動生成低精度模型,減少設計師傳遞的資源數量,也減少更新模型時出錯的機會。
鏡頭避障—
因為項目用的是第三人稱的鏡頭,鏡頭離開人物模型有一定的距離,在人物走動或使用者控制角度的時候,鏡頭有可能和建築模型或場景模型碰撞,造成“鏡頭穿模”的現象。
babylonjs 自帶的鏡頭沒有避開模型的功能,在産品也沒有處理經驗的時候,我們做了如下兩個方案:
- 鏡頭外圍用一個不可見模型包圍,跟人物一樣與建築、場景模型做碰撞檢測,使鏡頭不會進入到模型中去。
這種方法的優點是可以使用内置的碰撞檢測方法,不需要額外的開發量。但是缺點也很明顯,使用者對鏡頭和模型的碰撞導緻停止沒有預期,總會覺得鏡頭不自然的不受控制。
- 鏡頭和人物之間用棒狀的模型連接配接,同樣在棒狀模型上調用與建築、場景模型的碰撞檢測,當棒狀模型的某個位置發生碰撞時,鏡頭将移動到人物與碰撞點之間的位置,避免鏡頭進入模型的同時,也避免模型穿插在人物與鏡頭中間,造成導緻使用者找不到人物的問題。
這種方法實作的效果符合一些同樣是第三人稱視角的 3D 遊戲的鏡頭運動邏輯,使用者感受更自然,不會出現失控的現象。而引入的額外開發量也在可控的範圍内。
與設計團隊的資源交接
模型格式—
在衆多的 3D 模型格式中,我們選擇了 .gltf 格式。相對于其他模型格式,.gltf 可以減少 3D 格式中與渲染無關的的備援資料,進而確定檔案體積更小。
目前 3D 素材相對來說都比較大,這對于移動端加載體驗來說,無疑是緻命的。是以擁有更小體積的格式,也擁有了更高的優先選擇權重。
除此之外,.gltf 是對近二十年來各種 3D 格式的總結,使用最優的資料結構,進而保證最大的相容性以及可伸縮性,在擁有大容量的同時,支援更多的拓展,比如支援多貼圖、多動畫等。
是以 .gltf 成為了我們與視覺約定好的唯一素材格式。
模型輸出流程—
本來設計師工作流使用的模組化軟體是 C4D ,但是在資源交接的過程中,我們發現了幾個問題:
- 缺少導出 gltf 檔案功能。在某些版本的 C4D 不能導出 gltf 格式的模型;某些版本能導出,但是導出有問題。而又因為設計師使用的一些渲染器支援問題,不能輕易更新 C4D 版本。
- 導出模型大小不統一。可能因為某些版本的 C4D 導出的問題,或是 C4D 裡的一些設定沒能導出到 gltf 檔案,設計師幾次導出的模型大小并不統一,例如人物模型比建築模型還要大上好幾倍。
- 導出材質資訊丢失。設計師在模組化時,因為模型可能會在多個管道使用,例如渲染宣傳圖檔,大部分情況會使用第三方的渲染器做渲染,這時候可能模型裡會使用這些渲染器獨有的材質。而這些材質導出到 gltf 檔案時,會丢失這些獨有材質的資訊。再導入到頁面的場景中時,設計師會發現展示的效果跟他們在模組化軟體裡看到的相差甚遠。
在和設計師多次溝通後,我們之間定立了一個導出模型的工作流:
在 C4D 模組化完成後,導出 FBX 格式的檔案,再導入到對 gltf 支援較好的 blender 軟體中,設計師可以預覽他們的材質在中轉過程中有沒有丢失效果,blender 導出的 gltf 檔案中的模型也能保持一緻的大小。
預設光影—
在預設的渲染設定中,我們把設計側輸出的模型放進場景中,加上光源,也隻有明暗的變化,沒有影子,缺少了一些立體感。
在我們嘗試加入影子的過程中,發現性能受到嚴重影響。在查閱了渲染原理後,發現當每在一個平面上增加影子,相當于多渲染一次場景,渲染的壓力成倍增加。
在跟設計側交流後,決定在地闆的貼圖紋理上預先加上建築的投影。這種方法對大部分是固定模型的場景能有較好的效果,而人物的陰影可以用靜态圖檔跟随模型移動模拟。
渲染優化
壓縮紋理—
在開發期間發現在型号舊一點的iPhone裝置上很容易出現閃退的現象,應該是頁面使用的記憶體超過了上限。
在項目中使用的資源體積最大的是模型 gltf 檔案,檢查檔案的内容,占體積很大一部分的是紋理貼圖,解析資源發現很多貼圖的大小是3K(3072x3072的圖檔),根據 WebGL 渲染原理,無論貼圖的資源原來是什麼格式,最後在渲染前需要解壓,相當于一張貼圖需要在記憶體中占 3072 x 3072 x 3Byte = 27MB,解壓後還需要傳到 GPU,在多張貼圖同時渲染時很可能占用大量的記憶體。
經過和設計側的溝通,同意在一些展示距離不可能很近的模型上替換較低分辨率的貼圖。
另外通常 2D 項目中使用的 png/jpg 格式圖檔,并不适合 3D 渲染,他們需要經過上述的解壓過程,才能被 GPU 讀取。
在 3D 渲染領域,有其他适合 GPU 讀取的格式,如安卓支援的 ETC ,iOS 支援的 PVRTC,新一代的标準壓縮紋理格式 ASTC ,他們都不需要解壓就可以被 GPU 讀取,可以大大減少中間解壓占用的記憶體容量。
在項目中,我們使用 gltf-transform 工具做縮小貼圖分辨率,和轉換格式的工作。
模型減面—
模型在 WebGL 中渲染的流程是先用模型的頂點資訊确定三角面,再在每個三角面上計算需要展示的顔色。
是以如果能減少模型面的數量,能減少每次渲染的計算量,減少每幀需要的渲染時間。
而如上面所說的,設計師模組化的時候,可能面對的需求是輸出渲染圖,而不會對實時渲染做優化,是以在某些地方可能使用了過多的面。
參考團隊内其他同學的優化經驗(說一說 glTF 檔案壓縮),使用 gltf-transform 工具對模型進行自動化減面。在和設計測反複溝通後,我們确定了減面的參數 ratio = 0, error = 0.0001 。
合批渲染—
在 3D 渲染中有一個 draw call 的概念,一次 draw call 就是 CPU 向 GPU 下的一次畫圖指令。在一次指令中,CPU 會向 GPU 傳遞需要畫的三角形資訊,和三角形上顔色怎麼計算的方法,這個方法用人類明白的語言稱作材質。是以一次 draw call 隻能畫相同材質的面。
因為每次 draw call 有這些準備的動作,是以通常兩次 draw call 會比一次花的時間多。
在模型檔案中,相同材質的面,可能不是定義在同一個模型中,這樣 CPU 會把這些面拆分成不同的畫圖指令,令 draw call 數量增加。
有一種對這種情況的優化方法叫合批,可以對這些相同材質的面合并,使他們可以在一次 draw call 中完成繪制。
這工作沒有工具幫助我們處理模型檔案,但是在前端加載模型檔案時,可以周遊模型中的網格 mesh ,把使用相同材質的做合并。
需要注意的是帶動畫的網格不能這樣處理,因為合并後的物體中心會變化,例如兩個自轉的球合并之後會圍繞兩個球的中點公轉。
後續疊代
模型懶加載和分級加載—
雖然暫時的項目展示的場景還不是很大,同時加載和渲染對裝置的壓力不算很大,但在場景增長到一定程度的時候,需要引入模型的懶加載和分級加載。
- 懶加載政策:在鏡頭移動到足夠靠近時再加載并插入模型到場景,銷毀離鏡頭足夠遠的模型。
- 分級加載政策:在鏡頭較遠時,加載較低精度的模型,較近時再切換成精度高的模型。
以上兩個政策都是現在較大型的 3D 遊戲會使用的加載政策,能減少同一螢幕中繪制的面數量,減輕渲染壓力。
分級渲染—
現時通路 3D 項目的裝置性能差距非常大,有加上特效也能流暢運作的,也有隻能在裝置分辨率下基本運作的。
babylonjs 自帶一個分級渲染的功能,能實時檢測運作幀率決定是否降級,在之後的疊代中,可以增加從像素分辨率加上特效到裝置分辨率基本渲染的分級渲染政策。
實時光影—
在使用以上的分級渲染政策後,可以在性能較好的裝置上加上實時光影的特效,動态替換預烘焙貼圖。
場景搭建工具—
在之前的項目開發過程中,設計師和産品、營運都需要通過前端輸出demo才能大概體驗到 3D 場景的效果,決定下一步如何調整。為解決這個痛點,我們團隊開發了一個 3D 場景的搭建工具,使用者可通過上傳 gltf 檔案搭建 3D 場景,實時預覽渲染效果。
并加入了在項目中沉澱的互動元件,快速生成 3D 場景項目。
總結
以上内容基本覆寫了團隊内開發 3D web 項目的整個流程,在從 0 到 1 的過程中積累了對 3D 模型的控制方法和 3D 渲染原理的了解,并用工程化手段簡化中間的一些渲染優化流程。
在對獨立的模型檔案作優化後,對搭建完成的場景還可以作進一步優化,如模型間共用材質的合并,重複模型的執行個體化。并在與設計側的溝通中,除了用規範控制輸出模型的規格外,還需要能即時地告知渲染效果作出回報,由此引發出開發場景搭建工具的想法。
3D 互動項目的開發經驗還在不斷累積的階段,在往後的項目開發中将不斷疊代開發工作流及沉澱開發工具,希望能和有相關開發經驗和興趣的同學更多交流。
參考資料
[1] 說一說 glTF 檔案壓縮
作者:凹凸曼-June Wu
來源:微信公衆号:凹凸實驗室
出處:https://mp.weixin.qq.com/s/lVMALJsN5rImtCgSBWKyZw