擁抱新一代 Web 3D 引擎,Three.js 項(xiàng)目快速升級(jí) Galacean 指南

01、背景
Web 3D 技術(shù)的發(fā)展日新月異,為我們帶來(lái)了前所未有的沉浸式體驗(yàn)。從虛擬展示到游戲開(kāi)發(fā),從建筑可視化到教育模擬,Web 3D 技術(shù)的應(yīng)用場(chǎng)景愈發(fā)廣泛。而在這一領(lǐng)域,Three.js 作為一款廣受歡迎的 JavaScript 3D 庫(kù),憑借其簡(jiǎn)潔易用的 API 和豐富的功能,幫助眾多開(kāi)發(fā)者實(shí)現(xiàn)了精彩的 3D 項(xiàng)目。
然而,隨著項(xiàng)目復(fù)雜度的不斷提升,以及用戶對(duì)性能和體驗(yàn)要求的日益苛刻,Three.js 逐漸顯露出一些局限性。比如在處理重負(fù)載時(shí),很容易遇到性能瓶頸,出現(xiàn)卡頓、掉幀等問(wèn)題。這就如同一位經(jīng)驗(yàn)豐富的車手,駕駛著一輛曾經(jīng)性能卓越的賽車,但在面對(duì)愈發(fā)復(fù)雜的賽道和激烈的競(jìng)爭(zhēng)時(shí),卻發(fā)現(xiàn)車輛的動(dòng)力和操控性漸漸力不從心。
02、Galacean:新一代 Web 3D 引擎
2.1業(yè)務(wù)簡(jiǎn)介
擬我形象是 vivo 賬號(hào)中的一個(gè)3D數(shù)字人功能,提供一種代表自由、個(gè)性、創(chuàng)新和時(shí)尚的虛擬形象,為用戶提供更加生動(dòng)、直觀、有趣的交流方式。采用 Native+H5混合的開(kāi)發(fā)方式,其中 3D 渲染的部分基于 Three.js 進(jìn)行開(kāi)發(fā)。
2.2技術(shù)挑戰(zhàn)與痛點(diǎn)
- 性能瓶頸:人物模型包含大量形態(tài)鍵以實(shí)現(xiàn)多樣化面部特征,導(dǎo)致模型加載解析耗時(shí)過(guò)長(zhǎng)。
- 線程阻塞:受限于JS單線程特性,模型解析過(guò)程會(huì)造成頁(yè)面短暫無(wú)響應(yīng)。
- 多模型渲染:套裝切換等場(chǎng)景下,多個(gè)模型同時(shí)渲染時(shí)性能問(wèn)題尤為突出。
- 陰影優(yōu)化:Three.js 的陰影渲染性能消耗大,不得不通過(guò)局部陰影和限制捕捉范圍等折中方案來(lái)平衡畫(huà)質(zhì)與性能。
2.3Galacean 引擎核心優(yōu)勢(shì)
Galacean 是一款開(kāi)源的 Web 游戲引擎,致力于打造一個(gè)開(kāi)放、易用、高效的游戲開(kāi)發(fā)工具,可以通過(guò)在線編輯器或者純代碼的形式進(jìn)行使用。
針對(duì)現(xiàn)存的技術(shù)挑戰(zhàn)與痛點(diǎn),Galacean做了深度優(yōu)化:
- 多線程處理:采用Worker避免主線程阻塞。
- 移動(dòng)端適配:對(duì)大量常量進(jìn)行近似取值優(yōu)化,完美適配移動(dòng)端。
- 性能突破:優(yōu)化數(shù)據(jù)傳輸鏈路,創(chuàng)新緩存設(shè)計(jì),顯著降低重負(fù)載場(chǎng)景下的卡頓現(xiàn)象。
此外,Galacean 基于 EC(Entity-Component)架構(gòu)設(shè)計(jì),而非 Three.js 的面向?qū)ο?,大幅提升了開(kāi)發(fā)的靈活性。
近期我們將渲染引擎由 Three.js 切換為 Galacean。這一舉措不僅解決了頁(yè)面卡頓問(wèn)題,還提升了瀏覽器兼容性(可支持到 chrome82),幀率表現(xiàn)更出色,畫(huà)面質(zhì)感也得到顯著改善。整體切換過(guò)程較為平滑,但也遇到了一些問(wèn)題。接下來(lái),將與大家分享此次整體升級(jí)的相關(guān)經(jīng)驗(yàn)。
03、調(diào)優(yōu)過(guò)程
任務(wù)拆解:
作為一個(gè)數(shù)字人項(xiàng)目,涉及到引擎升級(jí)的模塊大致有
①環(huán)境初始化(場(chǎng)景、相機(jī)、光線、引擎設(shè)置)
② 模型加載
- 骨架獲取
- 材質(zhì)獲取
- 動(dòng)畫(huà)獲取
③妝容、穿搭還原
- 形態(tài)鍵修改
- 貼圖、顏色修改
- 模型替換
- 頭像(靜態(tài)頭像、動(dòng)態(tài)頭像)導(dǎo)出
- 壁紙(靜態(tài)壁紙、動(dòng)態(tài)壁紙、視差壁紙)導(dǎo)出
經(jīng)過(guò)梳理,可以大致分為四類:
- 初始化
- 模型加載
- 素材替換
- 動(dòng)畫(huà)狀態(tài)
接下來(lái)我們對(duì)這幾個(gè)部分進(jìn)行分別的處理
3.1初始化
有別于 Three.js 的渲染器創(chuàng)建,Galacean 的 engine 初始化是異步方法,所以后續(xù)用到用到engine的地方需要考慮加載的時(shí)序,以及engine存在狀態(tài)的判斷。另外,Three.js 中 renderer 的渲染行為需要手動(dòng)調(diào)用,一般是使用requestAnimationFrame循環(huán)調(diào)用,而Galacean則不需要,引擎開(kāi)始渲染只需要調(diào)用一次 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 中,尺寸單位統(tǒng)一以米為基準(zhǔn),無(wú)需額外進(jìn)行特殊處理。不過(guò)在角度單位的使用上存在差異:Three.js 里,僅相機(jī)的 fov(視場(chǎng)角)采用角度單位,其他涉及角度的參數(shù)均以弧度計(jì)量;而 Galacean 則采用更為統(tǒng)一的設(shè)定,所有角度相關(guān)單位均為角度。
/** Three.js */
camera.fov = 15
item.rotation.y = 15 * Math.PI/180
/** Galacean */
camera.fieldOfView = 15
item.rotation.y = 15在Three.js中顏色的設(shè)置更加靈活,可以使用16進(jìn)制或者RGB值來(lái)進(jìn)行賦值,但是在Galacean中只能通過(guò)RGB來(lái)進(jìn)行賦值,且有別于0-255的取值范圍,Galacean中的顏色范圍是0-1。從Galacean1.5版本開(kāi)始,默認(rèn)的色彩空間改為線性,在代碼中需要手動(dòng)轉(zhuǎn)換一下。
/** 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 = color3.2模型加載
對(duì)于包含大量形態(tài)鍵和動(dòng)畫(huà)的模型,將模型打成zip包可以有效的壓縮模型的體積,不論是Three.js還是Galacean都不支持加載zip包,但是我們可以自行擴(kuò)展模型加載的鏈路,將zip下載后解壓出的模型獲取ObjectUrl再放到各自的加載器中加載,這樣加載進(jìn)度的獲取也可以進(jìn)行自定義,不需要進(jìn)行額外的改造。
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 模型輸出的數(shù)據(jù)結(jié)構(gòu)較為簡(jiǎn)單,主要使用模型的場(chǎng)景和動(dòng)畫(huà)片段。由于后續(xù)需針對(duì)特定材質(zhì)進(jìn)行替換,所以要根據(jù)節(jié)點(diǎn)名獲取特定節(jié)點(diǎn),再取出節(jié)點(diǎn)中的材質(zhì)信息,模型的骨架也通過(guò)這種方式獲取。而 Galacean 輸出的數(shù)據(jù)更為全面,除動(dòng)畫(huà)片段和實(shí)體信息外,模型中使用的材質(zhì)、貼圖、蒙皮和網(wǎng)格信息也會(huì)分門(mén)別類展示,需要對(duì)應(yīng)內(nèi)容時(shí)直接獲取即可,相比 Three.js 更加方便。
3.3素材替換
素材替換如上文總結(jié)分為四種,分別是顏色、貼圖、形態(tài)鍵和模型的替換,顏色設(shè)置我們?cè)诔跏蓟幸呀?jīng)講解,而模型加載和展示也沒(méi)有特別的內(nèi)容,無(wú)非是節(jié)點(diǎn)/實(shí)體的添加和移除,這里我們講下貼圖和形態(tài)鍵修改的一些tips。
在Three.js中修改材質(zhì)貼圖map可以直接直接使用canvas或者image,修改后需要將材質(zhì)needsUpdate屬性設(shè)置為true。而在Galacean需要先將圖片加載為texture,再進(jìn)行賦值。
/** 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中修改形態(tài)鍵,可以先通過(guò)網(wǎng)格中的morphTargetDictionary屬性獲取到需要修改的形態(tài)鍵的索引,然后修改morphTargetInfluences中對(duì)應(yīng)索引的值即可。
在Galacean中網(wǎng)格渲染器中沒(méi)有存儲(chǔ)形態(tài)鍵的索引信息,而是存儲(chǔ)在MeshRenderer下的mesh屬性下的blendShapes屬性中,通過(guò)獲取對(duì)應(yīng)名稱的形態(tài)鍵在數(shù)組中的索引,修改網(wǎng)格渲染器中blendShapeWeights屬性對(duì)應(yīng)下標(biāo)的值。
/** 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動(dòng)畫(huà)
相較于Three.js的AnimationMixer和AnimationClip,Galacean擁有更加完善的面向組件的動(dòng)畫(huà)系統(tǒng),支持 狀態(tài)機(jī)、混合動(dòng)畫(huà)、時(shí)長(zhǎng)壓縮等,不同動(dòng)畫(huà)之間的切換與播放更加簡(jiǎn)單易維護(hù)。
/** Three.js 播放動(dòng)畫(huà)片段 */
const mixer = new THREE.AnimationMixer(scene)
const actinotallow=mixer.clipAction(avatarClip)
action.play()
ticker.addEvent(delta => {
mixer.update(delta)
})
/** Galacean 添加狀態(tài)機(jī),播放完成回到待機(jī)狀態(tài) */
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')04、結(jié)語(yǔ)
Galacean 的出現(xiàn),無(wú)疑為 Web 3D 開(kāi)發(fā)領(lǐng)域帶來(lái)了新的活力。它不僅解決了 Three.js 等傳統(tǒng)技術(shù)在性能和功能上的諸多痛點(diǎn),還以其卓越的性能、豐富的功能和易用性,為開(kāi)發(fā)者打開(kāi)了一扇通往更廣闊創(chuàng)意空間的大門(mén)。
需要注意的是,Galacean不同版本之間的API差異較大,需要進(jìn)行甄別,同時(shí)開(kāi)發(fā)文檔及相關(guān)的案例也需要進(jìn)一步完善。
對(duì)于全新的項(xiàng)目,Galacean提供編碼或在線編輯器兩種方式保障創(chuàng)意的高效落地,詳細(xì)的文檔和案例也便于接觸 Web3D 開(kāi)發(fā)的新人快速上手。
對(duì)于存量的項(xiàng)目,Galacean的遷移成本不高,且整個(gè)過(guò)程平滑可控,能夠有效提升現(xiàn)有項(xiàng)目的畫(huà)面表現(xiàn)和性能。為未來(lái)復(fù)雜度更高的需求提供性能保障。




























