拥抱新一代 Web 3D 引擎,Three.js 项目快速升级 Galacean 指南
<blockquote data-pm-slice="0 0 []"><p>作者: vivo 互联网前端团队- Su Ning</p>
<p>本文从多个维度对比 Galacean 和 Three.js 两款Web3D 引擎的差异,并介绍拟我形象项目从Three.js 切换到 Galacean 以后带来的提升以及项目迁移的心得,为其他 Three.js 项目升级到 Galacean 提供参考。</p>
</blockquote>
<p> </p>
<p>1分钟看图掌握核心观点👇</p>
<p><img src="https://img2024.cnblogs.com/blog/1622697/202509/1622697-20250918111554094-641358964.jpg"></p>
<p> </p>
<h1>一、背景</h1>
<p>Web 3D 技术的发展日新月异,为我们带来了前所未有的沉浸式体验。从虚拟展示到游戏开发,从建筑可视化到教育模拟,Web 3D 技术的应用场景愈发广泛。而在这一领域,Three.js 作为一款广受欢迎的 JavaScript 3D 库,凭借其简洁易用的 API 和丰富的功能,帮助众多开发者实现了精彩的 3D 项目。</p>
<p> </p>
<p>然而,随着项目复杂度的不断提升,以及用户对性能和体验要求的日益苛刻,Three.js 逐渐显露出一些局限性。比如在处理重负载时,很容易遇到性能瓶颈,出现卡顿、掉帧等问题。这就如同一位经验丰富的车手,驾驶着一辆曾经性能卓越的赛车,但在面对愈发复杂的赛道和激烈的竞争时,却发现车辆的动力和操控性渐渐力不从心。</p>
<p> </p>
<h1>二、Galacean:新一代 Web 3D 引擎</h1>
<h2>2.1 业务简介</h2>
<p>拟我形象是 vivo 账号中的一个3D数字人功能,提供一种代表自由、个性、创新和时尚的虚拟形象,为用户提供更加生动、直观、有趣的交流方式。采用 Native+H5混合的开发方式,其中 3D 渲染的部分基于 Three.js 进行开发。</p>
<p> </p>
<h2>2.2 技术挑战与痛点</h2>
<ul>
<li>
<p><strong>性能瓶颈:</strong>人物模型包含大量形态键以实现多样化面部特征,导致模型加载解析耗时过长。</p>
</li>
<li>
<p><strong>线程阻塞:</strong>受限于JS单线程特性,模型解析过程会造成页面短暂无响应。</p>
</li>
<li>
<p><strong>多模型渲染:</strong>套装切换等场景下,多个模型同时渲染时性能问题尤为突出。</p>
</li>
<li>
<p><strong>阴影优化:</strong>Three.js 的阴影渲染性能消耗大,不得不通过局部阴影和限制捕捉范围等折中方案来平衡画质与性能。</p>
</li>
</ul>
<p> </p>
<h2>2.3 Galacean 引擎核心优势</h2>
<p>Galacean 是一款开源的 Web 游戏引擎,致力于打造一个开放、易用、高效的游戏开发工具,可以通过在线编辑器或者纯代码的形式进行使用。</p>
<p> </p>
<p>针对现存的技术挑战与痛点,Galacean做了深度优化:</p>
<blockquote>
<ul>
<li>
<p><strong>多线程处理:</strong>采用Worker避免主线程阻塞。</p>
</li>
<li>
<p><strong>移动端适配:</strong>对大量常量进行近似取值优化,完美适配移动端。</p>
</li>
<li>
<p><strong>性能突破:</strong>优化数据传输链路,创新缓存设计,显著降低重负载场景下的卡顿现象。</p>
</li>
</ul>
</blockquote>
<p> </p>
<p><img src="https://img2024.cnblogs.com/blog/1622697/202509/1622697-20250918111608784-674277064.gif"></p>
<p data-align-center="">对比视频1:加载速度</p>
<p data-align-center=""> </p>
<p><img src="https://img2024.cnblogs.com/blog/1622697/202509/1622697-20250918111618216-307021807.gif"></p>
<p data-align-center="">对比视频2:套装切换</p>
<p> </p>
<p>此外,Galacean 基于 EC(Entity-Component)架构设计,而非 Three.js 的面向对象,大幅提升了开发的灵活性。</p>
<p> </p>
<p>近期我们将渲染引擎由 Three.js 切换为 Galacean。这一举措不仅解决了页面卡顿问题,还提升了浏览器兼容性(可支持到 chrome82),帧率表现更出色,画面质感也得到显著改善。整体切换过程较为平滑,但也遇到了一些问题。接下来,将与大家分享此次整体升级的相关经验。</p>
<p> </p>
<h1>三、调优过程</h1>
<p><strong>任务拆解:</strong></p>
<p>作为一个数字人项目,涉及到引擎升级的模块大致有</p>
<p><strong>①环境初始化</strong>(场景、相机、光线、引擎设置)</p>
<p> </p>
<p><strong>② 模型加载</strong></p>
<ul>
<li>
<p>骨架获取</p>
</li>
<li>
<p>材质获取</p>
</li>
<li>
<p>动画获取</p>
</li>
</ul>
<p> </p>
<p><strong>③妆容、穿搭还原</strong></p>
<ul>
<li>
<p>形态键修改</p>
</li>
<li>
<p>贴图、颜色修改</p>
</li>
<li>
<p>模型替换</p>
</li>
<li>
<p>头像(静态头像、动态头像)导出</p>
</li>
<li>
<p>壁纸(静态壁纸、动态壁纸、视差壁纸)导出</p>
</li>
</ul>
<p> </p>
<p>经过梳理,可以大致分为四类:</p>
<blockquote>
<ul>
<li>
<p>初始化</p>
</li>
<li>
<p>模型加载</p>
</li>
<li>
<p>素材替换</p>
</li>
<li>
<p>动画状态</p>
</li>
</ul>
</blockquote>
<p> </p>
<p>接下来我们对这几个部分进行分别的处理</p>
<p> </p>
<h2>3.1 初始化</h2>
<p>有别于 Three.js 的渲染器创建,Galacean 的 engine 初始化是异步方法,所以后续用到用到engine的地方需要考虑加载的时序,以及engine存在状态的判断。另外,Three.js 中 renderer 的渲染行为需要手动调用,一般是使用requestAnimationFrame循环调用,而Galacean则不需要,引擎开始渲染只需要调用一次 engine.run 即可。</p>
<pre class="highlighter-hljs"><code>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)
})</code></pre>
<p> </p>
<pre class="highlighter-hljs"><code>const engine = await WebGLEngine.create({
canvas,
physics: new LitePhysics()
})
engine.run()</code></pre>
<p> </p>
<p>在 Three.js 中,尺寸单位统一以米为基准,无需额外进行特殊处理。不过在角度单位的使用上存在差异:Three.js 里,仅相机的 fov(视场角)采用角度单位,其他涉及角度的参数均以弧度计量;而 Galacean 则采用更为统一的设定,所有角度相关单位均为角度。</p>
<pre class="highlighter-hljs"><code>/** Three.js */
camera.fov = 15
item.rotation.y = 15 * Math.PI/180
/** Galacean */
camera.fieldOfView = 15
item.rotation.y = 15</code></pre>
<p> </p>
<p>在Three.js中颜色的设置更加灵活,可以使用16进制或者RGB值来进行赋值,但是在Galacean中只能通过RGB来进行赋值,且有别于0-255的取值范围,Galacean中的颜色范围是0-1。从Galacean1.5版本开始,默认的色彩空间改为线性,在代码中需要手动转换一下。</p>
<pre class="highlighter-hljs"><code>/** 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</code></pre>
<p> </p>
<h2>3.2 模型加载</h2>
<p>对于包含大量形态键和动画的模型,将模型打成zip包可以有效的压缩模型的体积,不论是Three.js还是Galacean都不支持加载zip包,但是我们可以自行扩展模型加载的链路,将zip下载后解压出的模型获取ObjectUrl再放到各自的加载器中加载,这样加载进度的获取也可以进行自定义,不需要进行额外的改造。</p>
<pre class="highlighter-hljs"><code>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
})
}
}</code></pre>
<p> </p>
<p>Three.js 解析 glTF 模型输出的数据结构较为简单,主要使用模型的场景和动画片段。由于后续需针对特定材质进行替换,所以要根据节点名获取特定节点,再取出节点中的材质信息,模型的骨架也通过这种方式获取。而 Galacean 输出的数据更为全面,除动画片段和实体信息外,模型中使用的材质、贴图、蒙皮和网格信息也会分门别类展示,需要对应内容时直接获取即可,相比 Three.js 更加方便。</p>
<p> </p>
<h2>3.3 素材替换</h2>
<p>素材替换如上文总结分为四种,分别是颜色、贴图、形态键和模型的替换,颜色设置我们在初始化中已经讲解,而模型加载和展示也没有特别的内容,无非是节点/实体的添加和移除,这里我们讲下贴图和形态键修改的一些tips。</p>
<p> </p>
<p>在Three.js中修改材质贴图map可以直接直接使用canvas或者image,修改后需要将材质needsUpdate属性设置为true。而在Galacean需要先将图片加载为texture,再进行赋值。</p>
<pre class="highlighter-hljs"><code>/** Three.js */
material.map=canvas
material.needsUpdate = true
/** Galacean */
const texture: Texture2D = await engine.resourceManager.load({
url,
type: AssetType.Texture2D
})
material.baseTexture = texture</code></pre>
<p> </p>
<p>在Three.js中修改形态键,可以先通过网格中的morphTargetDictionary属性获取到需要修改的形态键的索引,然后修改morphTargetInfluences中对应索引的值即可。</p>
<p> </p>
<p>在Galacean中网格渲染器中没有存储形态键的索引信息,而是存储在MeshRenderer下的mesh属性下的blendShapes属性中,通过获取对应名称的形态键在数组中的索引,修改网格渲染器中blendShapeWeights属性对应下标的值。</p>
<pre class="highlighter-hljs"><code>/** Three.js */
const index = morphTargetDictionary
if (index !== undefined) {
mesh.morphTargetInfluences = value
}
/** Galacean */
const blendShapes = skinMeshRenderer.mesh.blendShapes
const index = blendShapes.findIndex(i=>i.name===keyName)
if (index > -1){
skinMeshRenderer.blendShapeWeights = value
}</code></pre>
<p> </p>
<h2>3.4 动画</h2>
<p>相较于Three.js的AnimationMixer和AnimationClip,Galacean拥有更加完善的面向组件的动画系统,支持 状态机、混合动画、时长压缩等,不同动画之间的切换与播放更加简单易维护。</p>
<pre class="highlighter-hljs"><code>/** 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')</code></pre>
<p> </p>
<h1>四、结语</h1>
<p>Galacean 的出现,无疑为 Web 3D 开发领域带来了新的活力。它不仅解决了 Three.js 等传统技术在性能和功能上的诸多痛点,还以其卓越的性能、丰富的功能和易用性,为开发者打开了一扇通往更广阔创意空间的大门。</p>
<p> </p>
<p>需要注意的是,Galacean不同版本之间的API差异较大,需要进行甄别,同时开发文档及相关的案例也需要进一步完善。</p>
<p> </p>
<p>对于全新的项目,Galacean提供编码或在线编辑器两种方式保障创意的高效落地,详细的文档和案例也便于接触 Web3D 开发的新人快速上手。</p>
<p> </p>
<p>对于存量的项目,Galacean的迁移成本不高,且整个过程平滑可控,能够有效提升现有项目的画面表现和性能。为未来复杂度更高的需求提供性能保障。</p>
</div>
<div id="MySignature" role="contentinfo">
分享 vivo 互联网技术干货与沙龙活动,推荐最新行业动态与热门会议。<br><br>
来源:https://www.cnblogs.com/vivotech/p/19098383
頁:
[1]