编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

网易公开课Three.js实践 - 勋章系统

wxchong 2024-08-01 03:27:32 开源技术 12 ℃ 0 评论

前言

WebGL是一种3D绘图协议,这种绘图协议允许javascript和openGL结合起来,WebGL可以为canvas提供3d硬件加速(使用GPU在浏览器渲染3d模型和场景)。WebGL是openGL的一种实现,是可以在支持WebGL的浏览器中进行3d渲染。由于浏览器的跨平台性,可以使用WebGL在多种设备多个平台展示3d效果。那么我们可以用WebGL做什么呢?

数据可视化

AR VR

3d游戏动画等

在现代前端技术发展中,使用WebGL创建3d效果是一种不可或缺的能力。由于webGL本身API只能绘制最普通的点、线、三角形,所以为了绘制大型场景和模型,我们需要选用一些框架帮助开发,比如three.js。

Three.js是对WebGL的API抽象化和封装化的js库,不必关心WebGL怎么渲染3d图形,并且在渲染中加入了各种优化,提高了性能。Three.js包含数学库,支持交互,扩展性强,还可以进行SVG,CSS3D等渲染,在WebGL不兼容的版本可以有回退解决方案。下图是three.js对WebGL做的封装:

勋章系统设计

勋章系统开发选型

勋章系统是对用户粘性提升的一种积极刺激行为,分为样式展示(简单图片)--行为触发(达到某种条件)--颁发勋章(提供3d勋章)--勋章升级(不同等级勋章提供不同展示)--趣味性(移动端控制旋转特效)。在开发勋章系统中有几个重点:

  • 技术选型 因为是在app中展示,要考虑安卓和ios的差异性,同时还需要在web端展示,所以最好的选择就是用前端的WebGL方式来做3d勋章系统,在app中通过webview引入该勋章网页。在框架选择中,three.js的兼容性功能性都是接受过业界业务考验的,我们选择了three.js当我们的勋章系统框架。
  • 模型选择 既然是3d勋章,3d模型的选择会影响开发周期和渲染效果,调研了three.js支持的3d模型格式:

Three.JS支持格式

特点

优缺点

JSON(*.js/ *.json)

专门为Three.js自己设计的JSON格式,你可以使用它以声明的方式定义模型,及模型材质和动画。

在app中的webview解析json比较慢

OBJ和MTL(*.obj/ *.mtl)

OBJ是一种简单的三维文件格式,只用来定义对象的几何体。

对象不支持动画,还需要搭配mtl来载入材质

Collada (*.dae)

用来定义XML类文件中数字内容的格式。差不多所有的三维软件和渲染引擎都支持这个格式。

使用最广,兼容性好

STL (*.stl)

立体成型术 。 广泛用于快速成型。

生成速度快,但是一般是3d打印机使用

FBX (*.fbx)

在max、maya、softimage等软件间进行模型、材质、动作和摄影机信息的互导,复用性比较好。

多平台支持,但是生成在部分平台会被转为mesh

CTM (*.ctm)

由openCTM创建的格式。可以用来压缩存储表示三维网格的三角形面片。

文件压缩效果好,压缩算法理解难度较高

VTK(*.vtk)

Visualization Tookit定义的文件格式,用来指定顶点和面。

可支持节点比较多,但threejs仅支持旧格式

PLY (*.ply)

多边形文件格式。

3d打印机使用

加上ui组使用c4d软件能轻松导出dae文件,最终选择了以xml为基础的dae的3d模型。

运行Three.js

Three.js是3d渲染,在3d渲染编程中包括:场景,相机,渲染器(物体,光源,纹理)。下图是three.js的运行过程:

要想使用three.js很简单,通过简单的js引用或者npm包下载即可。然后通过加载相应的解析模块,来载入个人模型或者控制器。

import 'three';
import 'three/examples/js/loaders/ColladaLoader2';
import 'three/examples/js/loaders/MTLLoader';

在threejs中初始化场景,相机,灯光:

function init() {
   initRender()
   initScene()
   initCamera()
   initLight()
}

导入模型,并加载到场景:

function loadModel() {
   mesh = new THREE.Mesh()
   let mtl = new THREE.MTLLoader()
   let loader = new THREE.ColladaLoader()
   mtl.load("../model/yemaozi.mtl", function (result) {
       result.preload()
       let { materials } = result
       loader.load("../model/yemaozi.dae", function (dae) {
           for (let key in dae.library.materials) {
               let name = dae.library.materials[key].name
               if (materials[name]) {
                   Object.assign(dae.library.materials[key].build, materials[name])
               }
               if (name === 'font') {
                   dae.library.materials[key].build.blending = THREE.NoBlending
                   dae.library.materials[key].build.needsUpdate = true
               }
           }
           mesh = dae.scene.children[0].clone()
           scene.add(mesh)
       })
   })

}

最后是最关键的渲染(包含动效):

// 动效
function animate() {
   requestAnimationFrame(animate)
   mesh.rotation.y +=0.01
   render()
}

展示效果如下:

勋章系统优化

支持换肤功能

  • 多加入一个包含材质和纹理信息的mtl文件,通过不同的mtl材质提供换肤功能 ,接口传递的数据为:
{
   // 材质文件
   image3DMaterial: "http://xxx.mtl",
   // gzip压缩过的模型文件
   image3DMoudle: "http://xxx.gz",
   // 已获得图片
   imageGot: "http://xxx.png",
   // 未获得图片
   imageNotget: "http://xxx.png"
}

生成模型文件较大,下载时间过长

  • 出于体验优化,一般大文件传输需要gzip处理,目前使用的cdn服务暂不支持文件的gzip(后期会迁移到新cdn)。所以前端用nodejs写一个预压缩脚本,将dae和mtl文件压缩并上传到cdn,同时同步更新到服务端的数据库中;拿到数据后,解压则是通过webWorker多线程,使用pako.js库进行gzip解压。

下载解析开销很大,并且3d模型占用内存过多

  • 目前产品需求是只有等级5的勋章,分两步优化:
// 尝试从临时缓存中读取模型
if (models[currentMedalInfo.id]) {
scene.add(models[currentMedalInfo.id]);
modelsList.splice(modelsList.findIndex((id) => currentMedalInfo.id === id), 1);
modelsList.push(currentMedalInfo.id);
resolve();
return;
}

let medalDataMap, medalDataCache, modelData, materialData;
if (indexedDBSupport) {

medalDataMap = await localforage.getItem('medalDataMap');
medalDataMap = medalDataMap || {};
medalDataCache = medalDataMap[currentMedalInfo.id];
if (medalDataCache) {
// 如果材质地址没变则直接从indexedDB读取,否则清理掉
if (medalDataCache.image3DMaterial === materialUrl) {
materialData = await localforage.getItem(materialUrl);
} else {
localforage.removeItem(medalDataCache.image3DMaterial);
}
// 模型同上
if (medalDataCache.image3DMoudle === modelUrl) {
modelData = await localforage.getItem(modelUrl);
} else {
localforage.removeItem(medalDataCache.image3DMoudle);
}
}
// 储存最新的信息
medalDataMap[currentMedalInfo.id] = currentMedalInfo;
localforage.setItem('medalDataMap', medalDataMap);
}

// 如果不支持或indexedDB中没有则加载
if (!materialData) {
materialData = loadResource({ url: materialUrl });
}
if (!modelData) {
modelData = loadResource({
url: modelUrl, type: isDae ? 'text' : 'arraybuffer', onProgress: (progress) =>{
progressCB(progress.loaded / progress.total * (isDae ? 100 : 80));
}
});
}
    • 内存中只会存储最近5次的解析过的模型,当有新的模型要加入,通过LRU策略,对于最长时间没有使用的模型内存会释放。
    • 数据会存在IndexedDB,每次请求先从内存读取,内存中不存在从IndexedDB读取,都不存在走网络请求,并且会把新数据通过key-value存入IndexedDB。

有些过老机型不支持WebGL,模型加载失败

  • 在获取数据时候,会返回两张图片(没获得等级勋章图片和获得等级勋章图片),通过图片展示当做优雅降级处理方法。

用户体验

  • 增加重力感应和粒子特效。

最终效果

总结

5G时代的来临,让网络传输和资源加载速度更胜一筹,在3D效果的制作可以采用WebGL这种跨平台兼容性高的方法。选用Three.js来快速开发WebGL项目,能为用户体验和业务需求提供多样化的选择。

作者简介

孙森 2018年加入网易传媒,高级前端开发工程师,目前主要做pc,移动端,小程序等版本迭代和升级优化工作,热爱足球和coding。

来源-微信公众号:网易传媒技术团队

出处:https://mp.weixin.qq.com/s/onxyXD1XMSOVaMKBPo12yQ

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表