likes
comments
collection
share

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

作者站长头像
站长
· 阅读数 68

前言

上一篇文章一文带你悟道 Threejs 3D 模型开发通过三只飞鸟的案例系统的带大家串联了模型的基本使用,轻松几笔勾勒,就实现了一个不错的效果。

但如果你更深入的思考一下,就会想到很多盲点。

  • 模型文件太大,如何优化模型来加快加载速度?
  • 模型有大小吗?当然是有的,但大多模型并不是标准几何体,又该怎么计算模型的尺寸?
  • 模型有大有小,如何保证模型显示的正合我意?
  • 我们能不能对模型进行一些轻微的修改?
  • ...

前段时间小包在sketchfab看到了一组特别炫酷的钢铁侠动作,念念不忘,本文就记录了小包与钢铁侠死死纠缠的故事,毕竟谁能拒绝会跳舞的钢铁侠那?

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

压缩模型

钢铁侠模型地址: 传送门

上一篇文章详细的讲解过 3D 模型的格式,推荐使用 GLTF 格式,并且最好是 glb 扩展格式,glb 把所有的文件都放置在一个二进制文件中,比较方便进行加载。

选择 glb 格式进行下载,讲实话,有点大,平均每个舞蹈为 9 MB 左右,最初的设想会加载多个舞蹈动作,累加起来加载速度不用测试肯定非常慢,所以我们首先要尝试降低模型的大小。

使用 gltf-pipeline 工具库来进行模型压缩。

npm install -g gltf-pipeline

该工具库基于谷歌推出的 Draco 算法,Draco 算法有两大优势: 通过 Edge breaker 3D 压缩算法改变了模型的网格数据的索引方法 通过减少顶点坐标、顶点纹理坐标等信息的位数,以减少数据的存储量

该工具库的具体使用参数大家可以百度,这里就直接上命令了,压缩并不会影响模型本身,会生成一个新的被压缩的模型。

gltf-pipeline -i ironman-dropkick.glb -o ironman-dropkick-zip.glb -d

以 ironman-dropkick 为例,压缩前 10.2MB,压缩后 3.3MB,压缩效果还是挺可观的。但这肯定不是该模型压缩的极限,还有一些更厉害的方法就等着各位巨佬们去探索了。

模型经过压缩后,模型的加载也需要进行一些改变,需要额外加载 DRACOLoader 及 dacro 算法。

// 部分代码逻辑请参考上一篇文章,不再重复赘述
async function loadIronman() {
  const loader = new GLTFLoader();
  const dracoLoader = new DRACOLoader(); // 加载 DRACOLoader
  dracoLoader.setDecoderPath("/draco/"); // draco 算法
  dracoLoader.preload(); // 预加载
  loader.setDRACOLoader(dracoLoader);
  const ironmanData = await loader.loadAsync(ironman_action_url);
  const ironman = setupModel(ironmanData);
  console.log(ironmanData);
  return ironman;
}

然后就成功的导入模型了,我们来看看效果。

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

???钢铁侠发来疑问,我咋这么小???

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

这种现象很好解释,相机太远,模型太小,就相当于你在眺望远处的人一样,看上去都像一群小蚂蚁。

方案同样有两种,要么拉近相机,要么放大模型。相机拉近小包感觉还是比较难以完美实现的,设想一下现实中的案例,拍照时很难选取到一个合适的角度,需要反复调整。且各个模型的大小也不尽相似,掌握以相机为基准的模式还是有必要的。

因此小包选择固定相机,以相机为基准,根据数学计算来调整模型,使模型自适应。

模型自适应

包围盒

Three 提供了 Box3 方法,该方法可以在 3D 空间内创建一个包围盒,用来表示物体在世界坐标系的边界框。可以理解为模型周围包裹着一个长方体,既然能转换成长方体,那模型的长宽高就可以类比为包围盒的长宽高。(ps: 获取模型的大小或许是包围盒最简单的用法了)

const bbox = new THREE.Box3().setFromObject(object);
let mdlen = bbox.max.x - bbox.min.x; //边界的最小坐标值 边界的最大坐标值
let mdhei = bbox.max.y - bbox.min.y;
let mdwid = bbox.max.z - bbox.min.z;

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

bbox 中返回了 3D 空间 xyz 轴的两端坐标,可以发现,钢铁侠属实是有点小,当然也可能设计师是以 m 为尺度进行设计。

获取到模型大小后,剩下的问题就转换为数学问题。我们需要计算模型大小与相机之间距离的比例,然后来合理设定模型的放缩比例。

// props 是父组件传入的参数
let dist = Math.abs(camera.position.z - object.position.z - mdwid / 2);
let vFov = (camera.fov * Math.PI) / 180;
let vheight = 2 * Math.tan(vFov * 0.5) * dist;
let fraction = mdhei / vheight;
let finalHeight = props.height * fraction;
let finalWidth = (finalHeight * mdlen) / mdhei;

let sacle1 = props.width / finalWidth;
let sacle2 = props.height / finalHeight;
// 将模型适当缩小,为动画空出空间
sacle2 *= props.scale;
sacle1 *= props.scale;
if (sacle1 >= sacle2) {
  object.scale.set(sacle2, sacle2, sacle2);
} else {
  object.scale.set(sacle1, sacle1, sacle1);
}

结果模型还是很小,小包有点懵了,难道上面算法错了。小包尝试着翻转模型,果然模型的下面多一个底板,底板距离钢铁侠的距离很远造成包围盒高度太高。

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

这样有可能不够形象,Three 提供了 BoxHelper 方法,可以来绘制模型的包围盒。

let boxhelper = new THREE.BoxHelper(object, 0xbe1915); //外面红色框
scene.add(boxhelper);

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

通过 Boxhelper,可以比较明显地看到模型的外边界,这一大长条。😂

问题找到了,该如何解决这个问题那?想办法斩掉多余的部分,就在小包尝试修改模型时,发现了更奇怪的现象。

奇怪的模型

小包下载了大约 5-6 个模型,正常情况下包围盒应该能够包围模型,结果这个会打拳的钢铁侠非得跟别人不一样?

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

讲实话,他好酷啊 😂,这就是钢铁侠的孤傲吗?包围盒为什么包不住他啊,懵圈,有路过的大佬希望可以指点一下。

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

给模型动刀

除了孤傲的帝皇侠外,别的模型还算容易处理,只需要将下面多余的底座部分删除即可,因此我们直接借助 Three 官方提供的 editor 进行实现。

导入需要修改的模型,在 editor 中可以比较明显的包围盒,找到右侧 scene 部分,找到下面多余的物体,将其进行删除。

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

然后再导出为 glb 格式即可。

将导出的模型重新加载到程序中,四位钢铁侠就变得合理起来了,但新问题来了,你咋这么偏那?

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

本案例中共使用了四个钢铁侠动作,分别在四个 canvas 中实现,居中针对于各个画布

模型居中

设计师在设计模型时通常默认将模型默认放置在 (0,0,0) 位置,当我们放大模型后,模型的默认位置并没有发生变化,只占据了 3D 的上半部分,因此需要给模型设置一个居中效果。

计算出模型的中心,将中心的位置移到原点处,便可以实现居中效果。模型的中心可以通过包围盒计算得出。

function setMiddle(object) {
  let box = new THREE.Box3().setFromObject(object); // 获取模型的包围盒
  let mdlen = box.max.x - box.min.x; // 模型长度
  let mdwid = box.max.z - box.min.z; // 模型宽度
  let mdhei = box.max.y - box.min.y; // 模型高度
  let x1 = box.min.x + mdlen / 2; // 模型中心点坐标X
  let y1 = box.min.y + mdhei / 2; // 模型中心点坐标Y
  let z1 = box.min.z + mdwid / 2; // 模型中心点坐标Z
  object.position.set(-x1, -y1, -z1); // 将模型进行偏移
}

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

钢铁侠劲舞团

四位钢铁侠模型都已经导入了,下面咱们把他们的舞蹈启动起来,如何播放 GLTF 动画上一篇文章已经讲过了,就不重复描述了。

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

这里来重点提一下 clipAction 函数的作用,Three 的动画控制系统是特别完善的。

function setupModel(data) {
  const model = data.scene.children[0];
  const clip = data.animations[0];
  const mixer = new AnimationMixer(model);
  const action = mixer.clipAction(clip);
  action.play();

  model.tick = (delta) => mixer.update(delta);
  return model;
}

clipAction 到底是做了什么那?

模型上的 animations 存储模型附带的动画效果,标准被称作动画剪辑——AnimationClip,每个动画剪辑都由多个关键帧 track 组成

【钢铁侠劲舞团】通过钢铁侠的案例来深入 3D 模型使用

AnimationAction 可以创建动画剪辑内的关键帧创建一个调度存储的控制功能。但为什么代码中并没有使用呐?

clipAction 本质就是运行了 AnimationAction,但该方法提供了缓存功能,能提供更好的性能。但需要注意,AnimationAction 虽然提供了动画的调度功能,但它并没有真正的开启权,它只提供开启信号,真正的开启由混合器启动。

下面展示了部分控制的参数及方法

  • clampWhenFinished: 动画执行完是否停止
  • loop: 动画循环次数
  • timeScale: 动画播放速度,负值代表倒放
  • play()/stop(): 动画开启与关闭信号
  • reset(): 动画重置

设置完动画后,我们就可以借助一些公共的组件库,实现一下钢铁侠的轮番登场,最终结果就呈现出来了。

源码仓库

实现的过程有几分急迫,所以效果不算炫酷,小包会继续努力的

后语

我是 战场小包 ,一个快速成长中的小前端,希望可以和大家一起进步。

一路加油,冲向未来!!!

转载自:https://juejin.cn/post/7171803353310035999
评论
请登录