擁抱新一代 Web 3D 引擎,Three.js 項目快速升級 Galacean 指南
作者: vivo 互聯網前端團隊- Su Ning
本文從多個維度對比 Galacean 和 Three.js 兩款Web3D 引擎的差異,并介紹擬我形象項目從Three.js 切換到 Galacean 以后帶來的提升以及項目遷移的心得,為其他 Three.js 項目升級到 Galacean 提供參考。
1分鐘看圖掌握核心觀點??

一、背景
Web 3D 技術的發展日新月異,為我們帶來了前所未有的沉浸式體驗。從虛擬展示到游戲開發,從建筑可視化到教育模擬,Web 3D 技術的應用場景愈發廣泛。而在這一領域,Three.js 作為一款廣受歡迎的 JavaScript 3D 庫,憑借其簡潔易用的 API 和豐富的功能,幫助眾多開發者實現了精彩的 3D 項目。
然而,隨著項目復雜度的不斷提升,以及用戶對性能和體驗要求的日益苛刻,Three.js 逐漸顯露出一些局限性。比如在處理重負載時,很容易遇到性能瓶頸,出現卡頓、掉幀等問題。這就如同一位經驗豐富的車手,駕駛著一輛曾經性能卓越的賽車,但在面對愈發復雜的賽道和激烈的競爭時,卻發現車輛的動力和操控性漸漸力不從心。
二、Galacean:新一代 Web 3D 引擎
2.1 業務簡介
擬我形象是 vivo 賬號中的一個3D數字人功能,提供一種代表自由、個性、創新和時尚的虛擬形象,為用戶提供更加生動、直觀、有趣的交流方式。采用 Native+H5混合的開發方式,其中 3D 渲染的部分基于 Three.js 進行開發。
2.2 技術挑戰與痛點
-
性能瓶頸:人物模型包含大量形態鍵以實現多樣化面部特征,導致模型加載解析耗時過長。
-
線程阻塞:受限于JS單線程特性,模型解析過程會造成頁面短暫無響應。
-
多模型渲染:套裝切換等場景下,多個模型同時渲染時性能問題尤為突出。
-
陰影優化:Three.js 的陰影渲染性能消耗大,不得不通過局部陰影和限制捕捉范圍等折中方案來平衡畫質與性能。
2.3 Galacean 引擎核心優勢
Galacean 是一款開源的 Web 游戲引擎,致力于打造一個開放、易用、高效的游戲開發工具,可以通過在線編輯器或者純代碼的形式進行使用。
針對現存的技術挑戰與痛點,Galacean做了深度優化:
多線程處理:采用Worker避免主線程阻塞。
移動端適配:對大量常量進行近似取值優化,完美適配移動端。
性能突破:優化數據傳輸鏈路,創新緩存設計,顯著降低重負載場景下的卡頓現象。

對比視頻1:加載速度

對比視頻2:套裝切換
此外,Galacean 基于 EC(Entity-Component)架構設計,而非 Three.js 的面向對象,大幅提升了開發的靈活性。
近期我們將渲染引擎由 Three.js 切換為 Galacean。這一舉措不僅解決了頁面卡頓問題,還提升了瀏覽器兼容性(可支持到 chrome82),幀率表現更出色,畫面質感也得到顯著改善。整體切換過程較為平滑,但也遇到了一些問題。接下來,將與大家分享此次整體升級的相關經驗。
三、調優過程
任務拆解:
作為一個數字人項目,涉及到引擎升級的模塊大致有
①環境初始化(場景、相機、光線、引擎設置)
② 模型加載
-
骨架獲取
-
材質獲取
-
動畫獲取
③妝容、穿搭還原
-
形態鍵修改
-
貼圖、顏色修改
-
模型替換
-
頭像(靜態頭像、動態頭像)導出
-
壁紙(靜態壁紙、動態壁紙、視差壁紙)導出
經過梳理,可以大致分為四類:
初始化
模型加載
素材替換
動畫狀態
接下來我們對這幾個部分進行分別的處理
3.1 初始化
有別于 Three.js 的渲染器創建,Galacean 的 engine 初始化是異步方法,所以后續用到用到engine的地方需要考慮加載的時序,以及engine存在狀態的判斷。另外,Three.js 中 renderer 的渲染行為需要手動調用,一般是使用requestAnimationFrame循環調用,而Galacean則不需要,引擎開始渲染只需要調用一次 engine.run 即可。
const renderer=new THREE.WebGLRenderer({
alpha: true,
antialias: true,
})
document.body.appendChild(renderer.domElement)
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(15, window.innerWidth/window.innerHeight, 0.1, 100)
requestAnimationFrame(function render() {
renderer.render(scene, camera)
requestAnimationFrame(render)
})
const engine = await WebGLEngine.create({
canvas,
physics: new LitePhysics()
})
engine.run()
在 Three.js 中,尺寸單位統一以米為基準,無需額外進行特殊處理。不過在角度單位的使用上存在差異:Three.js 里,僅相機的 fov(視場角)采用角度單位,其他涉及角度的參數均以弧度計量;而 Galacean 則采用更為統一的設定,所有角度相關單位均為角度。
/** Three.js */
camera.fov = 15
item.rotation.y = 15 * Math.PI/180
/** Galacean */
camera.fieldOfView = 15
item.rotation.y = 15
在Three.js中顏色的設置更加靈活,可以使用16進制或者RGB值來進行賦值,但是在Galacean中只能通過RGB來進行賦值,且有別于0-255的取值范圍,Galacean中的顏色范圍是0-1。從Galacean1.5版本開始,默認的色彩空間改為線性,在代碼中需要手動轉換一下。
/** Three.js */
directLight.color=0xffffff
directLight.intensity=0.9
/** Galacean */
const color = new Color(0.9, 0.9, 0.9, 1)
color.toLinear(color)
directLight.color = color
3.2 模型加載
對于包含大量形態鍵和動畫的模型,將模型打成zip包可以有效的壓縮模型的體積,不論是Three.js還是Galacean都不支持加載zip包,但是我們可以自行擴展模型加載的鏈路,將zip下載后解壓出的模型獲取ObjectUrl再放到各自的加載器中加載,這樣加載進度的獲取也可以進行自定義,不需要進行額外的改造。
exportclassModelLoader {
engine: WebGLEngine
constructor(engine: WebGLEngine){
this.engine = engine
}
async load(src: string) {
const url = await fileLoader(src)
returnthis.engine.resourceManager.load<GLTFResource>({
url,
type: AssetType.GLTF
})
}
}
Three.js 解析 glTF 模型輸出的數據結構較為簡單,主要使用模型的場景和動畫片段。由于后續需針對特定材質進行替換,所以要根據節點名獲取特定節點,再取出節點中的材質信息,模型的骨架也通過這種方式獲取。而 Galacean 輸出的數據更為全面,除動畫片段和實體信息外,模型中使用的材質、貼圖、蒙皮和網格信息也會分門別類展示,需要對應內容時直接獲取即可,相比 Three.js 更加方便。
3.3 素材替換
素材替換如上文總結分為四種,分別是顏色、貼圖、形態鍵和模型的替換,顏色設置我們在初始化中已經講解,而模型加載和展示也沒有特別的內容,無非是節點/實體的添加和移除,這里我們講下貼圖和形態鍵修改的一些tips。
在Three.js中修改材質貼圖map可以直接直接使用canvas或者image,修改后需要將材質needsUpdate屬性設置為true。而在Galacean需要先將圖片加載為texture,再進行賦值。
/** Three.js */
material.map=canvas
material.needsUpdate = true
/** Galacean */
const texture: Texture2D = await engine.resourceManager.load({
url,
type: AssetType.Texture2D
})
material.baseTexture = texture
在Three.js中修改形態鍵,可以先通過網格中的morphTargetDictionary屬性獲取到需要修改的形態鍵的索引,然后修改morphTargetInfluences中對應索引的值即可。
在Galacean中網格渲染器中沒有存儲形態鍵的索引信息,而是存儲在MeshRenderer下的mesh屬性下的blendShapes屬性中,通過獲取對應名稱的形態鍵在數組中的索引,修改網格渲染器中blendShapeWeights屬性對應下標的值。
/** Three.js */
const index = morphTargetDictionary[keyName]
if (index !== undefined) {
mesh.morphTargetInfluences[index] = value
}
/** Galacean */
const blendShapes = skinMeshRenderer.mesh.blendShapes
const index = blendShapes.findIndex(i=>i.name===keyName)
if (index > -1){
skinMeshRenderer.blendShapeWeights[index] = value
}
3.4 動畫
相較于Three.js的AnimationMixer和AnimationClip,Galacean擁有更加完善的面向組件的動畫系統,支持 狀態機、混合動畫、時長壓縮等,不同動畫之間的切換與播放更加簡單易維護。
/** Three.js 播放動畫片段 */
const mixer = new THREE.AnimationMixer(scene)
const action=mixer.clipAction(avatarClip)
action.play()
ticker.addEvent(delta => {
mixer.update(delta)
})
/** Galacean 添加狀態機,播放完成回到待機狀態 */
const animationState = animator.findAnimatorState('action')
const idleStatle = animator.findAnimatorState('idle')
const transition = new AnimatorStateTransition()
transition.duration = 1
transition.offset = 0
transition.exitTime = 1
transition.destinationState = idleStatle
animationState.addTransition(transition)
animator.play('action')
四、結語
Galacean 的出現,無疑為 Web 3D 開發領域帶來了新的活力。它不僅解決了 Three.js 等傳統技術在性能和功能上的諸多痛點,還以其卓越的性能、豐富的功能和易用性,為開發者打開了一扇通往更廣闊創意空間的大門。
需要注意的是,Galacean不同版本之間的API差異較大,需要進行甄別,同時開發文檔及相關的案例也需要進一步完善。
對于全新的項目,Galacean提供編碼或在線編輯器兩種方式保障創意的高效落地,詳細的文檔和案例也便于接觸 Web3D 開發的新人快速上手。
對于存量的項目,Galacean的遷移成本不高,且整個過程平滑可控,能夠有效提升現有項目的畫面表現和性能。為未來復雜度更高的需求提供性能保障。

浙公網安備 33010602011771號