Threejs制作骨骼模型

简介: 这篇文章详细介绍了在Three.js中创建骨骼动画的过程,包括骨骼节点的创建、权重设置以及控制骨骼关节实现动态效果的步骤,并通过一个具体的圆柱体模型演示了如何添加和控制骨骼动画。

今天讲threejs里比较复杂的一个功能,骨骼动画,骨骼动画也是应用场景很多的一种,因为无论是机器还是人都会存在骨骼动画,尤其是对设备或者人细节化的展示时候,以前的动画都是一个独立的模型进行移动或者旋转,但是物理世界中很多都是一个物体的移动是受制于另一个物体的,比如一个人的胳膊运动,大臂带动小臂,小臂上下摆动却不影响大臂,但是小臂的一头却固定在大臂上。

threejs中引入了bone来制作骨骼,不过要注意的是这个Bone并非是一个模型,而是指一个关节,这个不理解的话就很难理解骨骼动画了,比如一根圆柱形分为四段,大臂,小臂,手掌,手指,那么就需要5个节点,分别是肩膀处,胳膊肘,手腕,手指根部,手指尖部,然后移动或者旋转其中一个关节,会影响到边上的两段。

这里用threejs的代码实例,首先创建一个基础的3D场景:

initScene(){
      scene = new THREE.Scene();
      const axesHelper = new THREE.AxesHelper( 100 );
      axesHelper.position.set(0,0,0)
      scene.add( axesHelper );
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(200,400,200);
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(300,300,300)
      scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(-300,300,300)
      scene.add(directionalLight2);
    },
initRenderer(){
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.container = document.getElementById("container")
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setClearColor('#AAAAAA', 1.0);
      this.container.appendChild(this.renderer.domElement);
    },
    initControl(){
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.maxPolarAngle = Math.PI / 2.2;      // // 最大角度
    },

    initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
    }

然后在模型中创建一个圆柱形,作为接下来作为骨骼动画掩饰的对象,不过这里的网格要设置为SkinnedMesh对象了,否则没办法制作骨骼动画:

      // 圆柱体
      const geometry = new THREE.CylinderGeometry(2, 2, 40, 8, 12)
      const material = new THREE.MeshLambertMaterial({color:'#FCE6C9',metalness: 0.5,transparent: true, opacity:0.7});
      // 蒙皮 - 皮肤
      this.mesh = new THREE.SkinnedMesh(geometry, material)
      this.mesh.position.set(0,20,0)
      scene.add(this.mesh);

再创建5个Bone作为关节,为了方便观察加入骨骼辅助对象skeletonHelper,这个对象就类似threejs场景对象

      /* 这段代码作用是现实骨骼的节点,方便查看骨骼运动过程中节点的位置*/
      const group1 = new THREE.Group(); // 骨骼关节可以和普通网格模型一样作为其他模型子对象,添加到场景中
      group1.add(b1);
      const skeletonHelper = new THREE.SkeletonHelper(group1); // SkeletonHelper会可视化参数模型对象所包含的所有骨骼关节
      group1.add(skeletonHelper);
      scene.add(skeletonHelper);

这里已经能够看到一些绿色和蓝色组成的线条了,这个线条可以当做是骨头,颜色交汇处就是关节

然后重要的一部是给骨骼动画添加权重,因为模型本质上是无数个三角形组成的,每个三角形都有顶点,在骨骼动画进行拉伸旋转时,实际上是改变每个三角形的顶点位置,也就改变了三角形的形状和大小,设置权重就是关联骨骼动画时,每个关节运动对每个三角形顶点的影响程度,也就是上面提到的小臂的运动不会影响大臂,但是大臂会影响小臂,

 const skeleton = new THREE.Skeleton([b1, b2, b3, b4, b5])
      this.mesh.add(b1)
      this.mesh.bind(skeleton)
      // 添加权重   设置的就是蒙皮的权重,  顶点的蒙皮索引
      const index = [] // 索引
      const weight = [] // 权重
      const arr = geometry.attributes.position.array;
      //设置模型每个节点受骨骼的影响程度,如果是已有的模型文件会有自带的权重,这里是个圆柱体需要手动设置
      for (let i = 0; i < arr.length; i += 3) {
        const y = arr[i + 1] + 20
        const weightValue = (y % 10) / 10
        index.push(Math.floor(y / 10), Math.floor(y / 10) + 1, 0, 0)
        weight.push(1 - weightValue, weightValue, 0, 0);
      }
      geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(index, 4));
      geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(weight, 4));

最终设置完成还需要让整个骨骼动起来,可以在渲染里添加动画效果:

这里设置让圆柱来回摇摆,并且到一定程度后再设置负值进行反向摇摆,

 if (this.mesh.skeleton.bones[0].rotation.x > 0.3 || this.mesh.skeleton.bones[0].rotation.x < -0.3 ) {
        this.step = -this.step
      }
      //修改骨骼关节的位置角度来实现动画效果
      for (let i = 0; i < this.mesh.skeleton.bones.length; i++) {
        this.mesh.skeleton.bones[i].position.x += this.step;
        this.mesh.skeleton.bones[i].rotation.x += this.step * Math.PI / 180;
      }

最终效果如下:
WechatIMG117.jpg
简单的骨骼动画

这里不支持上传视频,我就只能上传个图片了,如果想看动态效果可以私我,我发给你视频

全部代码如下:

<template>
  <div>
    <div id="container"></div>
  </div>
</template>

<script>
import * as THREE from 'three'
import {OrbitControls} from "three/addons/controls/OrbitControls";

let scene;
export default {
  name: "bone-single",
  data() {
    return{
      camera:null,
      cameraCurve:null,
      renderer:null,
      container:null,
      controls:null,
      mesh:null,
      step: 0.1,
    }
  },
  methods:{
    initScene(){
      scene = new THREE.Scene();
      const axesHelper = new THREE.AxesHelper( 100 );
      axesHelper.position.set(0,0,0)
      scene.add( axesHelper );
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(200,400,200);
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(300,300,300)
      scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(-300,300,300)
      scene.add(directionalLight2);
    },
    initBone(){
      // 圆柱体
      const geometry = new THREE.CylinderGeometry(2, 2, 40, 8, 12)
      const material = new THREE.MeshLambertMaterial({color:'#FCE6C9',metalness: 0.5,transparent: true, opacity:0.7});
      // 蒙皮 - 皮肤
      this.mesh = new THREE.SkinnedMesh(geometry, material)
      this.mesh.position.set(0,20,0)
      scene.add(this.mesh);
      // 首先,创建一个起点. 创建骨骼系统,
      let b1 = new THREE.Bone(); //b1作为根
      b1.position.set(0, -20, 0);
      let b2 = new THREE.Bone();//基于bone1的-20位置多10
      b1.add(b2)
      b2.position.set(0, 10, 0);//基于bone2的-10位置多10
      let b3 = new THREE.Bone();
      b2.add(b3)
      b3.position.set(0, 10, 0);//基于bone3的0位置多10
      let b4 = new THREE.Bone();
      b3.add(b4)
      b4.position.set(0, 10, 0);//基于bone4的10位置多10
      let b5 = new THREE.Bone();
      b4.add(b5)
      b5.position.set(0, 10, 0);//基于bone5的10位置多10,到达圆柱最顶部

      /* 这段代码作用是现实骨骼的节点,方便查看骨骼运动过程中节点的位置*/
      const group1 = new THREE.Group(); // 骨骼关节可以和普通网格模型一样作为其他模型子对象,添加到场景中
      group1.add(b1);
      const skeletonHelper = new THREE.SkeletonHelper(group1); // SkeletonHelper会可视化参数模型对象所包含的所有骨骼关节
      group1.add(skeletonHelper);
      scene.add(skeletonHelper);

      // 创建骨架
      const skeleton = new THREE.Skeleton([b1, b2, b3, b4, b5])
      this.mesh.add(b1)
      this.mesh.bind(skeleton)
      // 添加权重   设置的就是蒙皮的权重,  顶点的蒙皮索引
      const index = [] // 索引
      const weight = [] // 权重
      const arr = geometry.attributes.position.array;
      //设置模型每个节点受骨骼的影响程度,如果是已有的模型文件会有自带的权重,这里是个圆柱体需要手动设置
      for (let i = 0; i < arr.length; i += 3) {
        const y = arr[i + 1] + 20
        const weightValue = (y % 10) / 10
        index.push(Math.floor(y / 10), Math.floor(y / 10) + 1, 0, 0)
        weight.push(1 - weightValue, weightValue, 0, 0);
      }
      geometry.setAttribute('skinIndex', new THREE.Uint16BufferAttribute(index, 4));
      geometry.setAttribute('skinWeight', new THREE.Float32BufferAttribute(weight, 4));
    },
    initRenderer(){
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.container = document.getElementById("container")
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setClearColor('#AAAAAA', 1.0);
      this.container.appendChild(this.renderer.domElement);
    },
    initControl(){
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.maxPolarAngle = Math.PI / 2.2;      // // 最大角度
    },

    initAnimate() {
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
      // 添加边界,如果弯曲超过一定的幅度,就设置负值向另一侧弯曲
      if (this.mesh.skeleton.bones[0].rotation.x > 0.3 || this.mesh.skeleton.bones[0].rotation.x < -0.3 ) {
        this.step = -this.step
      }
      //修改骨骼关节的位置角度来实现动画效果
      for (let i = 0; i < this.mesh.skeleton.bones.length; i++) {
        this.mesh.skeleton.bones[i].position.x += this.step;
        this.mesh.skeleton.bones[i].rotation.x += this.step * Math.PI / 180;
      }
    },
    initPage(){
      this.initScene();
      this.initCamera();
      this.initLight();
      this.initRenderer();
      this.initControl();
      this.initBone();
      this.initAnimate();
    }
  },
  mounted() {
    this.initPage()
  }
}
</script>

<style scoped>
#container{
  position: absolute;
  width:100%;
  height:100%;
  overflow: hidden;
}

</style>
相关文章
|
数据可视化 前端开发 JavaScript
vue3+threejs可视化项目——引入threejs加载钢铁侠模型(第二步)
vue3+threejs可视化项目——引入threejs加载钢铁侠模型(第二步)
1372 3
Threejs实现模拟管道液体流动
Threejs实现模拟管道液体流动
3319 0
Threejs实现模拟管道液体流动
|
JavaScript 前端开发 开发者
ThreeJs控制模型骨骼实现数字人
这篇文章讲解了如何使用Three.js通过控制模型的骨骼来实现数字人的动态表现,包括加载模型、获取骨骼信息以及通过编程控制骨骼动作的具体方法。
1374 1
|
10月前
|
人工智能 数据可视化 数据处理
2025低代码前瞻:平台赋能的无限可能
低代码平台正逐渐成为企业数字化转型的核心工具,通过高效、灵活、智能的特点改变传统开发模式。展望2025年,低代码技术将推动可视化开发普及,支持全员参与应用构建;核心引擎升级,提升开发效率与灵活性;模型驱动开发更加成熟,实现自动化代码生成和智能逻辑优化;数据处理能力增强,应对复杂业务需求;AI深度融合,优化开发体验;插件生态丰富,覆盖多行业场景;架构更开放,支持开源与高性能需求;企业功能强化,赋能运营与决策。低代码平台不仅将成为开发工具,更是企业数字化生态的重要组成部分,为企业带来更高的效率、更低的成本和更快的创新能力。
2025低代码前瞻:平台赋能的无限可能
|
传感器 缓存 监控
Stream 组件在 Flutter 中的应用场景有哪些?
Stream 组件在 Flutter 中的应用场景有哪些?
438 58
|
前端开发 定位技术
Pixi绘制地图和小车
这篇文章讲解了如何使用Pixi.js来绘制地图并在地图上显示小车,包括地图网格的创建和小车图像的定位与移动。
325 1
Pixi绘制地图和小车
ThreeJs通过射线获取自己的点击位置坐标
这篇文章详细说明了如何使用Three.js来绘制线条,包括创建线几何体、设置材质以及将线条添加到3D场景中的具体步骤。
521 1
ThreeJs通过射线获取自己的点击位置坐标
Threejs中导入GLTF模型克隆后合并
这篇文章详细说明了在Three.js中如何导入GLTF模型,对其进行克隆,并将多个克隆模型合并成一个整体模型的过程。
510 2
Threejs中导入GLTF模型克隆后合并
ThreeJs手动控制动画播放与暂停
这篇文章介绍了如何在Three.js中手动控制动画的播放与暂停,包括设置动画混合器、监听按键事件以调整动画状态和速度的方法。
430 0
ThreeJs手动控制动画播放与暂停
ThreeJs绘制线
这篇文章详细说明了如何使用Three.js来绘制线条,包括创建线几何体、设置材质以及将线条添加到3D场景中的具体步骤。
370 1