开发背景
得益于“元宇宙”概念在前段时间的爆火,各家公司都推出了应用 3D 场景的流动或频道。
[]()
[]()
3D 场景相比传统的 2D 页面长处是多一个维度,同屏展现的内容能够更多,能残缺的展现物体、商品的信息。
相应带来的毛病是用户应用形式扭转,用户须要额定的学习老本。另外初期须要的开发量、美术资源和生成 3D 模型的设施也是减少的老本。
在这样的背景下,咱们团队接到了食品频道的一个互动我的项目的开发需要,心愿通过 3D 场景的展现和互动形式,作为对将来购物的一种尝试与摸索,满足用户对将来美妙离奇的一个需要。将购物场景化、娱乐化,给用户带来美妙的购物感触。
[]()
前端框架抉择
3D 我的项目相比之前的 2D 我的项目扭转的次要是客户端的体现。在心愿不依赖 app 客户端反对和在尽量多的环境下能运行,咱们首先采纳的计划是在 Web 端实现 3D 我的项目实现。
开发套件
首先咱们思考的是成熟的开发套件,如 unity/egret 等,但这些开发套件都有一些咱们不能绕过的问题,例如:
- 商业化应用须要免费
- 须要应用其余语言开发(如 C#),对团队学习老本较大
- 打包输入的文件大小过大
- 官网文档不够具体,学习曲线较抖
引擎名称 / 比照维度 | 应用价格 (权重 50% | 脚本上手 (权重 30% | 场景搭建 (权重 20% | 反对模型格局 (权重 10% | 社区材料丰盛水平 (权重 30% | 反对 web 端公布 (一票否决 |
---|---|---|---|---|---|---|
Unity 3d | 3 | 7 | 10 | 8 | 10 | Y |
Laya | 4 | 9 | 7 | 7 | 7 | Y |
Egret | 10 | 8 | 7 | 7 | 6 | Y |
Cocos2d-js | | | | | | N |
Godot | 10 | 7 | 7 | 8 | 7 | Y |
因为以上的起因,开发套件里没有令团队很称心的抉择,咱们从其余方向寻找开发工具。
开源渲染库
另外也比拟了 Web 前端使用量较多的两个 3D 渲染库:
◦three.js 提供的组件粒度较小,较根底,能做很高水平的定制化二次开发,但如果须要开发一个互动我的项目,须要开发的组件比拟多
◦babylon.js 既提供了粒度小的根底组件,也封装了靠近开箱即用的组件。并自带了性能测量工具,提供了不便的 debug 办法和优化策略
通过团队内对各个开发套件 / 渲染库的试用,最初抉择了 babylon.js 作为我的项目的渲染层库,在其提供的组件上二次开发业务逻辑。
我的项目场景搭建
渲染分层构造
我的项目渲染层级总体分为两层:3D 场景层和 HUD 层
1.3D 场景层顾名思义渲染 3D 场景,由 人物模型、修建模型和宝箱这些互动模型组成
2.HUD 层渲染互动按钮、弹窗、业务须要的商品列表等 2D UI 内容
原本 babylonjs 是反对 3D 和 2D 内容混合渲染的,然而如果都应用 babylonjs 渲染,在设置两种内容须要应用对立的分辨率,而在当初的挪动端设施上,能反对像素分辨率(如 iPhone 14 的像素分辨率为 1170×2532)渲染不卡顿的只占一小部分。在大部分的设施上,最多只能反对在逻辑分辨率(如 iPhone 14 逻辑分辨率为 390×844)下晦涩运行,但设置这样的分辨率会使 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,然而在资源交接的过程中,咱们发现了几个问题:
1. 短少导出 gltf 文件性能。 在某些版本的 C4D 不能导出 gltf 格局的模型;某些版本能导出,然而导出有问题。而又因为设计师应用的一些渲染器反对问题,不能轻易更新 C4D 版本。
2. 导出模型大小不对立。 可能因为某些版本的 C4D 导出的问题,或是 C4D 里的一些设置没能导出到 gltf 文件,设计师几次导出的模型大小并不对立,例如人物模型比修建模型还要大上好几倍。
3. 导出材质信息失落。 设计师在建模时,因为模型可能会在多个渠道应用,例如渲染宣传图片,大部分状况会应用第三方的渲染器做渲染,这时候可能模型里会应用这些渲染器独有的材质。而这些材质导出到 gltf 文件时,会失落这些独有材质的信息。再导入到页面的场景中时,设计师会发现展现的成果跟他们在建模软件里看到的相差甚远。
在和设计师屡次沟通后,咱们之间定立了一个导出模型的工作流:
在 C4D 建模实现后,导出 FBX 格局的文件,再导入到对 gltf 反对较好的 blender 软件中,设计师能够预览他们的材质在直达过程中有没有失落成果,blender 导出的 gltf 文件中的模型也能保持一致的大小。
预设光影
在默认的渲染设置中,咱们把设计侧输入的模型放进场景中,加上光源,也只有明暗的变动,没有影子,短少了一些立体感。
在咱们尝试退出影子的过程中,发现性能受到重大影响。在查阅了渲染原理后,发现当每在一个立体上减少影子,相当于多渲染一次场景,渲染的压力成倍增加。
跟设计侧交换后,决定在地板的贴图纹理上事后加上修建的投影。这种办法对大部分是固定模型的场景能有较好的成果,而人物的暗影能够用动态图片追随模型挪动模仿。
[]()
渲染优化
压缩纹理
在开发期间发现在型号旧一点的 iPhone 设施上很容易呈现闪退的景象,应该是页面应用的内存超过了下限。
在我的项目中应用的资源体积最大的是模型 gltf 文件,查看文件的内容,占体积很大一部分的是纹理贴图,解析资源发现很多贴图的大小是 3K(3072×3072 的图片),依据 WebGL 渲染原理,无论贴图的资源原来是什么格局,最初在渲染前须要解压,相当于一张贴图须要在内存中占 3072 x 3072 x 3Byte = 27MB,解压后还须要传到 GPU,在多张贴图同时渲染时很可能占用大量的内存。
通过和设计侧的沟通,批准在一些展现间隔不可能很近的模型上替换较低分辨率的贴图。
另外通常 2D 我的项目中应用的 png/jpg 格局图片,并不适宜 3D 渲染,他们须要通过上述的解压过程,能力被 GPU 读取。
在 3D 渲染畛域,有其余适宜 GPU 读取的格局,如安卓反对的 ETC,iOS 反对的 PVRTC,新一代的规范压缩纹理格局 ASTC,他们都不须要解压就能够被 GPU 读取,能够大大减少两头解压占用的内存容量。
在我的项目中,咱们应用 gltf-transform 工具做放大贴图分辨率,和转换格局的工作。
模型减面
模型在 WebGL 中渲染的流程是先用模型的顶点信息确定三角面,再在每个三角面上计算须要展现的色彩。所以如果能缩小模型面的数量,能缩小每次渲染的计算量,缩小每帧须要的渲染工夫。
而如下面所说的,设计师建模的时候,可能面对的需要是输入渲染图,而不会对实时渲染做优化,所以在某些中央可能应用了过多的面。
参考了团队内其他同学的优化教训 1,应用 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 场景我的项目。
参考起源:
- 说一说 glTF 文件压缩 https://jelly.jd.com/article/61057292df18aa019e8a2967
作者:京东批发 胡俊文
起源:京东云开发者社区