支付寶2020新春活動WebGL技術實踐

新春紅包項目,做爲每一年用戶基數最大的支付寶活動之一,對整個項目組的技術都是一個很大的考驗。而做爲前端,咱們的技術考驗就是如何在保證穩定性的同時,爲用戶不斷帶來更好的創新體驗。

而今年的新春紅包項目相比之前,多了很多互動圖形方面技術的運用,尤爲是第一次對 3D(WebGL)技術的引進。對於新春這個億萬量級的活動而言,這無疑是個巨大的挑戰。但做爲合格的工程師,效果和穩定性的平衡是咱們的一向的追求,通過了前期的積累,咱們使用自研的 Web3D 遊戲引擎以及特效編輯器,學習了許多在整個橫向前端領域、作的相對最好的遊戲領域的經驗,最終達到了比較複雜 3D 場景下極低的異常率。前端

咱們的成果

咱們在這次新春活動的兩個場景中都達到了極好的效果和穩定性的平衡:webpack

  • 首頁 3D 展現:5 個複雜模型的內存總開銷爲峯值 30M,穩定 20M,對總體穩定性無影響。
  • 福滿全球:3D+UI 總內存開銷峯值 70M,穩定 40M,加 Webview 總開銷 100M。

爲了最好的效果

技術的使命只有一個——爲用戶帶去最好的體驗。因此在項目最初確定是先按照最高視覺效果來,針對新春中的兩個使用到了 3D 的場景,咱們首先進行了嘗試,而後發現了問題。web

並不是最佳體驗

在這種狀況下,我發現雖然視覺效果達到了最優,但又出現了不少其餘方面的問題,這使得最終的用戶體驗反而並非很好:算法

  1. 加載時間過長
  2. 用戶體感卡頓
  3. 相對高發的崩潰(主要是 OOM)

而這某種程度上也符合咱們一開始的預測,由於在移動端 Web 這種技術方案中,有些限制是不可避免的,而這些不安定要素會在億萬用戶的量級被無限放大。在通過數據的詳細的分析,咱們找到了針對這兩個場景的瓶頸共性。瀏覽器

瓶頸在何處

爲了找到瓶頸,讓咱們先來看一些數據。性能優化

首先是首頁 3D 動畫,首頁總共五個場景模型,使用的資源包括:網絡

可見統計下來,3D 部分最終能夠預計的傳輸大小爲 15M,峯值內存爲 65M,而穩定下來最好狀況也有 30M 內存開銷(這種策略下通常達不到最好,預計 40M 左右)。同時因爲單場景 5W 三角形,對於中低端機的幀率也有較大挑戰。併發

其次是福滿全球頁面,福滿全球的模型資源開銷基本能夠忽略,但卻有其餘方面的問題:編輯器

可見,福滿全球 3D 部分的主要開銷是在峯值 70M 的紋理,以及高清屏大量透明物體的渲染開銷。函數

死磕解決方案

經過瓶頸分析可知,問題主要集中在內存、傳輸體積和運行性能三個方面,而這三者又互相關聯。那麼天然的,我想到了從相對容易解決同時收益又大的方面入手。

削減模型大小

首先就是削減模型大小了,注意這個大小指的是三角形 / 頂點數量。爲何這個如此重要呢?很簡單——模型的大小能夠直接影響到以上的三個要素:

1. 內存:模型的大小和其佔據的內存是線性正相關的。

2. 傳輸體積:和內存一致。

3. 運行性能:單幀三角形數量越多,頂點着色器的壓力越大,尤爲是在首頁 3D 模型這種具備骨骼動畫的狀況下。

因此削減模型大小顯然是必需要作的,那麼咱們如何去作呢?通常來說,這件事應該交由設計同窗,讓他們去下降模型精度來達到一個能夠接受的程度,可是結果不容樂觀,通過研究,咱們最終採用瞭如下兩個策略:

1. 使用工具減模

首先是尋求可否使用工具本身進行減模,咱們自研的 Web3D 遊戲引擎使用 Unity 進行場景編輯,而 Unity 做爲身經百戰、擴展性極強的一個遊戲引擎,有沒有這麼一個工具來幫助咱們呢?答案是有的—— UnityMeshSimplify 就提供了讓咱們在 Unity 中自由調整模型精度並序列化的能力。而也正是使用了它,在視覺效果損失較低的狀況下,平均下降了全部場景 30% 的圖元數據大小:

2. 削減沒必要要的數據

在工具減模之後,圖元數據大小從 12M 降到了 7.5M,但這顯然仍是不夠,那麼還有什麼辦法呢?在思考後發現了一個關鍵點——處於性能考量,這次模型的光影是烘焙到紋理的,也就是說整個場景沒有光照。

這裏就須要咱們瞭解一些細節了,即頂點數據的構成。

圖元的最基本單元是頂點,一個頂點有包含着若干信息,在繪製時這些頂點數據將會被送入頂點着色器進行一系列處理,而後進入光柵化階段。而一個頂點的信息,最多見來看,包含:

而其中的法線和切線在首頁 3D 展現中並無做用,因此能夠將其刪除,我在 UnityToolkit 中添加了 Unlit(No Normals) 選項來讓導出時能夠自動剔除這兩項:

而最終效果也使人滿意,圖元數據大小進一步下降到了 5MB。

3.成果小結

模型裁剪主要是針對首頁 3D 展現的,通過優化,咱們獲得了成果:

(1)單場景最大三角形數量從 5.5W 降到 2.9W;

(2)全部場景圖元和動畫數據大小從 12MB 降到 5MB。

可見,咱們成功將成功將圖元數據 + 動畫大小縮減了一半,還保證了最複雜的場景的三角形數量也縮減了將近一半,使得內存開銷低了很多,同時傳輸體積小了很多,還大幅優化了渲染性能開銷。

但顯然傳輸體積仍是太大了,這裏咱們還進行了進一步的優化。

使用壓縮紋理

解決了模型圖元大小,接下來就是紋理的開銷了。經過上面的瓶頸分析可知,福滿全球項目的開銷主要就是在紋理方面。

1. 何爲紋理

紋理讀者也能夠理解爲貼圖、圖片。通常來說,咱們存儲的圖片都是以 JPG、PNG 等格式存儲的,而格式決定的是什麼呢?實際上是壓縮和編碼算法。實際上,不管咱們把一張 JPG 或者 PNG 圖片壓縮得再小,它最終被解碼後在內存中仍是以 Bitmap 的形式存在的,並且在瀏覽器中,基本都是以 RGBA 的像素格式存在的。

不管使用那種方式編碼存儲,最終都會被解碼爲 RGBA32 的 Bitmap,一個像素 4 字節。

這就意味着不管咱們將圖片的存儲體積壓縮到多麼小,其內存開銷老是固定的,好比 512x512 的圖片內存開銷就是 1M,而 1024x1024 的就是 4M。那麼有沒有辦法解決這個問題呢?固然有——遊戲業界爲了解決這個問題,提出了壓縮紋理技術。

2. 壓縮紋理

壓縮紋理是一種遊戲領域經常使用的紋理壓縮技術,其依賴於特定硬件實現,本質上能夠以固定速率交由 GPU 即時解壓,其有以下優點:

(1)內存:大幅節省內存開銷。

(2)解碼:免去圖片解碼開銷,直接丟給 GPU,提高啓動性能。

(3)採樣:提高紋理隨機採樣性能。

(4)可控:因爲其自己就是在 JSHeap 上申請的 buffer,因此在 Web 容器下,提供了一個能夠精確控制內存的方式。

PVRTC 的 Block 說明

通過調研和一些測試,咱們最終選擇了安卓下使用 ASTC 和 iOS 下使用 PVRTC 的策略來進行紋理壓縮,其中更爲細節的配置暫且不表(都是中等精度壓縮),最終在項目中得出的成果以下:

(1)首頁 3D 展現:

(2)福滿全球:

可見壓縮紋理對於內存的開銷有着極大的優化,基本徹底解決了內存問題。

3. 條件和代價

固然,這世界上並無免費的午飯,咱們接受了壓縮紋理的優勢,就要相對得付出代價以及接受它的約束:

(1)壓縮紋理是有損壓縮,會對圖片的質量有必定減損,這個須要視項目而定。

(2)壓縮紋理的傳輸體積可能比 JPG/PNG 方案要高 1~4 倍。

(3)壓縮紋理要求 POT,即長寬都是二的冪次。

(4)對於 iOS 的 PVRTC,還要求長寬相等。

(5)因爲壓縮紋理格式在不一樣平臺不能通用,加上降級須要三份資源,對於離線加速技術不友好。

對於某些代價,好比視覺質量損失、傳輸體積咱們是能夠自行調整的,不屬於原則性難題,但這個 POT 對於很對前端應用可真是個原則性問題了,好比福滿全球中的地標和紅包貼圖,就不是 POT 的,那麼怎麼辦呢?有辦法——使用圖集。

4. 紋理標準化 - 圖集

圖集是一種紋理標準化的方式,在遊戲領域經常用於處理 UI、2D 精靈等,簡單來說,圖集就是將許多圖片拼到一張上,不錯就是咱們常說的雪碧圖(精靈圖):

如圖,咱們將四個 500x500 的地標圖片拼到了一張 1024x1024 的圖集中,來知足壓縮紋理的需求。那麼咱們又如何去使用這個圖集呢?很簡單,咱們的引擎內置了 AtlasManager,可讓你很是簡單得使用它,而且在引擎標準的開發流程中,依賴於 Unity+Webpack 工做流,這個能力可以十分方便得引入——在 Unity 中直接編輯圖集,後面會說到。

圖集還有別的優點,就是減小內存碎片,減小數據提交次數,某些狀況下還能夠減小資源請求。

精確掌控內存

目前咱們擁有了削減模型和壓縮紋理兩種策略,大幅下降了內存開銷,並下降了一部分傳輸體積,但經過上面的論述不難發現其實咱們還能夠更進一步——咱們很容易發現,在整個過程當中,同一份數據可能在 CPU 和 GPU 端同時存在,尤爲是移動設備 CPU 和 GPU 是共享內存的。因此咱們必定有辦法再更進一步去解決這個問題。

這也就是我選用壓縮紋理的另外一個理由——壓縮紋理本質上是 JSHEAP 上的 ArrayBuffer,咱們能夠很好得經過控制引用來幫助 GC,這也就是爲什麼上面的數據分析中能保證穩定開銷是峯值的一半。

在咱們引擎的設計中,這個功能是可選的,經過紋理的 isImageCanRelease 來開啓,而若是遵照標準工做流,這一切都是自動的,無需開發者操心。

固然這也有代價,就是在 GL 上下文丟失後沒法恢復,請酌情使用。

進一步減小傳輸體積

到目前爲止,內存已經被控制得很好了,可是在傳輸體積上仍是有更大的優化空間,在這個方面我首先考慮的就是內部的 Hilo3d 團隊提供的模型壓縮方案。

1. 模型壓縮

咱們採用的模型壓縮方案原理很簡單,針對移動端使用的模型,並不須要每一個頂點數據都是 32bits 的 float 型,通常來說 13bits 或者 14bits 就夠用了,因此這裏有很大的可壓縮空間。而事實上通過測試,發現確實如此,但固然這也是有代價的,經過模型壓縮:

也就是說,模型壓縮後,首頁的全部資源大小達到了安卓 5.8M、iOS5.2M。但代價是增長了解壓時間和 1.5M 的峯值內存。相對於收益,開銷是能夠接受的。

然而即使如此,5M 的資源大小對於億萬 UV 的量級仍是有些大,咱們還有更多的辦法嗎?有,這時候就要請出咱們的老朋友 GZIP 了。

2. GZIP

你們都熟知的 GZIP 其實在不少時候都能發揮意想不到的做用,而在咱們的工做鏈路下,模型壓縮會提高 GZIP 的效果,而壓縮紋理也能得到收益,在 GZIP 後:

可見,咱們讓資源體積再減半,和一開始相比縮減了六倍。

3. 通常圖片資源

固然除了 3D 相關的資源,咱們也提供了方法來對普通圖片進行了壓縮,主要是將 PNG 圖片編碼壓縮成了索引色,這是一種有損壓縮,也就是你們經常使用的 TinyPNG 的策略,固然這個並無什麼神奇的,咱們已經將這種算法做爲了一個插件融入了工具鏈中,能夠經過 Webpack 工做流直接無縫整合,最終廣泛帶來了 2~4 倍的體積壓縮。

減小資源請求

到了這裏咱們解決了大部分主要問題,但還有一些邊角問題會對體驗的極致構成影響。這一點就是資源請求數量,咱們不難發現,對於兩個場景而言,3D 場景的資源請求數量都接近 20 個,而這個問題並不是不可解。

對 3D 領域有必定了解的讀者想必是知道 glTF 這個格式的,而咱們自研引擎的場景序列化也是使用了這個格式。爲了應對某些場合,glTF 有它的二進制形式 GLB,其能夠將索引、紋理、圖元數據等等都打包到一個二進制文件中,大幅下降請求數量,在兩個新春場景中,請求數量均被降到了 1 次。

而打包 GLB 的功能也被咱們整合進了 Webpack 鏈路中,開發者能夠零成本將其引入。

剩餘的性能問題

以上問題解決完成後,基本就能夠保證項目穩定了。對於福滿全球大量透明物體和高清屏的問題,通過業務層面的調優,最終發如今可控範圍內。這個是因爲業務性質決定的,不然咱們固然能夠採用強制最大畫布尺寸來下降開銷。

除此以外,還有一點須要注意的是咱們極可能忽略的一點——運行時的 GPU 資源提交。因爲引擎的設計是用到了在提交的原則(固然這很符合規範),但對於這兩個項目,保證用戶操做時不卡頓的優先級是很高的,而同時通過了上面的內存優化咱們也已經保證了即便全部資源都被提交也可控,因此就須要一個策略將全部資源先提交到 GPU,並預編譯全部 Shader。

爲了作到這一點,咱們採起了一個簡單的策略:在第一幀將全部物體渲染一遍,再結束 Loading,這增長了些許的加載時間,但保證了整個過程當中不會卡頓。

而對於首頁 3D 展現,爲了作到極致的效果,咱們設計了漸進式展現的策略。

漸進式展現

作這個策略是考慮到項目用戶量級極大,網絡狀況不一,因此不可能等待 Loaing 結束才展現頁面,那樣首次性能會不好,因此咱們敲定方案——老是先展現靜態圖片,3D 資源加載、解析、提交 GPU 成功後,才無縫切換爲 3D 動畫。

首頁 3D 動畫的這種策略是值得不少展現型項目參考的,這裏還須要注意的的一點是:若模型比較複雜,首幀渲染會卡住用戶操做。因此針對本項目的場景,咱們採用了時間分片的策略,將五個模型拆分爲五次渲染,每次間隔 200ms,留給用戶操做的時間:

而且咱們還保證靜態圖片和 3D 場景的姿態徹底一致,從而達到視覺上無縫切換的目的。

酷炫易用 - 粒子特效

咱們在大促的時候都須要炫酷的頁面來吸引用戶,可是動畫一般都是開發的噩夢,一般咱們在作動畫會遇到如下三個問題:

  • 動畫粗糙,不能打動用戶;
  • 還原度不高,和設計差距較大;
  • 性能優化不足,兼容性很差。

此次的新春紅包項目大量使用了 3D 場景,在 3D 中加入了不少粒子特效,那麼這些特效是如何產出而且解決以上三個問題的?

讓動畫設計更精美

咱們在首頁切換的時候增長旋轉的粒子特效,效果以下:

這個是設計同窗的原稿,因爲 Lottie 技術的普及,設計同窗作動畫大多使用 After Effect 在 AE 中製做好的 transform 動畫(僅使用 translate、scale、rotate 變化)導出可以使用 Lottie 播放,大大下降開發成本。而 AE 自己是一個視頻後期軟件,裏面除了能夠製做簡單的 transform 動畫,還能夠開啓 3D 渲染,進行圖像跟蹤,加濾鏡等等。這個粒子特效就是用 AE 裏 Particular 插件製做的,因此 AE 的上限就是設計師設計的上限。

設計師的設計工具將直接決定設計產物的質量。若是沒有 particular 插件,那麼咱們的設計產物永遠都只會是 transform 動畫,不少影視級別的特效就不會出如今產品頁面中,因此提升設計工具能力將直接決定動畫產出的質量。固然還有一個值得焦慮的問題,咱們的產品開發並不知知道 particular 的插件是怎麼實現的,那麼很大機率是沒法還原的,因此既要提升設計工具的質量,也要限制設計隨意使用設計工具致使沒法實現。

新春紅包項目的粒子特效設計所有在工具裏實現:

若是是手寫代碼還原設計稿的話,恐怕最要命的就是函數曲線的還原,動畫爲了更加順滑會加入不少曲線來控制,好比說剛纔的旋轉上升的星星,會有一個先加速再減速的過程:

一個複雜的粒子系統有 60 多個屬性,若是開發經過肉眼還原數據,哪怕是複製粘貼屬性值,均可能會出問題。

最好的還原方法是不寫代碼。編輯器直接導出動畫數據,在手機上進行播放,開發徹底不用關心各類參數。而產物很容易使用,直接保存項目工程,經過 webpack 進行加載,像使用圖片同樣簡單。

新春紅包項目中 3D 場景由 引擎 搭建渲染,使用的時候也是相似的方式,將編輯器工程做爲資源引入,直接播放就能夠了。動畫播放起來以後就是開發最關心的問題了。

保證動畫性能

其實任務首頁的粒子效果還不多,談不上性能瓶頸,而福滿全球大量使用粒子,特別是煙花做爲常駐特效,須要特別進行優化。這裏咱們參考了遊戲領域粒子系統的許多優化策略,將其運用到了本次的優化中。

優化一:粒子運動徹底 GPU 運算

對於粒子系統來講,由於粒子數量大,使用曲線控制後運動計算複雜,若是經過 CPU 計算粒子的運動,那麼網頁將不堪重負,因此粒子的運動旋轉和顏色變化計算所有放在 GPU 中,經過定製 shader 完成,在 shader 中計算曲線是比較複雜的事情(此處省略 3 千字)。

優化二:優化粒子發射器

能夠看到進度條的粒子持續產生,由於粒子有生命週期,因此會有老的粒子死亡,新的粒子出生,繁衍不息。首先咱們在內存中開闢一塊固定的地址,有一個按照粒子的生命週期排序的雙向列表,每一幀須要產生新粒子的時候,檢查列表最早死亡的粒子,若是此粒子已經死亡,那麼會把這個粒子的地址寫入新粒子的數據,同時將此列表元素從後插入。大概相似以下過程:

這樣的列表能夠保證粒子插入的速度,假設一個粒子系統有 200 個粒子,每幀其實只有 3-4 個新粒子的插入,在 CPU 中的計算量很小。

優化三:合併發射器

一個煙花是由兩個發射器組成的,構成了雙層煙花的效果,同時每一個煙花增長一個拖尾效果,煙花飛過的地方就有個小尾巴。編輯效果以下:

能夠看到煙花以相同的模式爆炸了 6 次,可是每次爆炸的位置不同,一般狀況下咱們在編輯器會作好一次的爆炸,而後複製 6 次,時間軸相似以下:

但這樣的話會致使頻繁建立銷燬繪製元素,性能消耗很大,因此編輯器提供了合併粒子爆炸的選項,而且每次能夠修改爆炸的位置。對於習慣了複製粘貼的設計師來講,很容易複製不少相同元素致使性能開銷過大,將六個繪製元素合併成一個元素能夠大大下降開銷,同時重複利用內存。

優化四:減小拖尾使用

拖尾就是飛線,在粒子運動過的地方生成一個頂點,繪製的時候連成一條線,這樣就有流星劃過的感受。可是由於粒子的計算都是在 GPU 中的,因此每幀若是要生成新的頂點,必須在 CPU 中也從新計算粒子的位置,這樣的計算量是很大的。若是煙花只是進場爆炸一次,那麼開銷能夠忽略,可是有一些煙花是常駐的,隔一段時間就會播放一次,那麼對於常駐的動畫,就要避免使用拖尾。此次咱們選擇了用貼圖縮放的方法來替代拖尾。

若是不用貼圖的話,看起來就是一圈延展的小菊花,這是經過經過增長長方形長度實現的,換上咱們尖角的貼圖就很是像一條尾巴,最後常駐煙花沒有使用拖尾,可是視覺效果仍然很像劃過的流星,這樣保證全部的計算都在 GPU 中進行,提升動畫性能。

作到極致 - 工程自動化

固然,做爲引擎開發者,除了將這些技術應用到新春項目中,使得更多開發者能夠簡便使用這些功能也是很重要的,因此我將這一切都封入了引擎的標準工做流中:

引擎工做流

引擎的工做流集成了以上所論述的全部優化策略,其主要包括 UnityToolkit 和 Webpack 鏈路兩部分。

1. UnityToolkit

UnityToolkit 是 Unity 的一個插件,用於將 Unity 中的各類特性導出供引擎使用,整個流程集成度很高,目前已經支持了大量特性,包括但不限於 GameObject、模型、材質、紋理、動畫、光源、攝像機、天空盒、圖集、精靈、物理、音頻、環境反射、環境照明、光照貼圖的導出和導入,支持自定義擴展組件,支持腳本邏輯綁定等等。

2. Webpack 鏈路

而後就是 Webpack 鏈路了,我對 Webpack 鏈路作了深度定製,用於知足引擎的工做流的需求。上面提到的壓縮紋理、模型壓縮、資源預處理、資源自動化發佈等都被集成到了其中,包括多平臺適配也是經過 Webpack 插件實現的。

這裏先大概介紹一下這次項目用到的最主要的鏈路:gltf-loader

這個 Loader 是整個鏈路中很是核心的一向,其提供了加載 gltf 文件並進行復雜預處理的能力。使用它,咱們能夠作到:

(1)模型壓縮。

(2)紋理壓縮。

(3)打包 GLB。

(4)資源預處理:對 gltf 文件引用的資源進行預處理,經過定製 Processor 接口你能夠實現任何你想要的任何預處理。

(5)資源發佈器:自定義發佈器,在 gltf 文件引用的資源(包括自身)被產出時,攔截並進行自動化處理。

而在這兩個項目中,這幾個功能都被用到了,也爲最終項目的穩定可靠提供了重要的保障。

關於團隊

本次分享來自於支付寶 Turandot Studio,Turandot Studio 致力於經過互動和圖形技術,讓前端變得更加具備創意,爲用戶帶來更美好的體驗,若是你有意加入咱們,請聯繫個人郵箱:shunguang.dty@antfin.com。

相關文章
相關標籤/搜索