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 应用服务中间件 图形学
基于Threejs实现glb三维模型的预览
文章将重点介绍如何基于Threejs进行三维模型glb进行预览,可以旋转控制。
1371 0
基于Threejs实现glb三维模型的预览
|
1月前
Threejs制作窗户透亮效果
这篇文章讲解了如何在Three.js中制作窗户的透亮效果,包括设置透明材质和光照以实现逼真的窗户渲染效果的技术细节。
41 1
|
1月前
ThreeJs制作全息投影视频
这篇文章介绍了使用Three.js来创建全息投影效果的视频教程,涵盖了实现全息效果的技术要点和具体实施步骤。
39 2
ThreeJs制作全息投影视频
|
1月前
|
编解码 前端开发 JavaScript
ThreeJs制作模型图片
这篇文章介绍了如何使用Three.js将一张图片转化为3D场景中的像素化模型,通过提取图片的像素颜色并将它们应用到3D立方体上,形成一种特殊的图像展示效果。
25 0
ThreeJs制作模型图片
|
1月前
Threejs制作海面效果
这篇文章详细介绍了利用Three.js制作逼真海面效果的过程,包括设置Water材质、调整光照及实现波动动画的步骤。
32 0
Threejs制作海面效果
|
3月前
|
API
|
3月前
|
API
【threejs教程】threejs中的边边角角:几何体详解
【8月更文挑战第6天】threejs中的几何体详解
91 4
【threejs教程】threejs中的边边角角:几何体详解
|
3月前
|
API
【threejs教程】让你的场景五颜绿色:纹理贴图与环境贴图
【8月更文挑战第6天】【threejs教程】让你的场景五颜绿色:纹理贴图与环境贴图
135 11
GLTF纹理贴图工具让模型更逼真
纹理贴图在三维渲染中的作用是增加模型的视觉细节、丰富其外观、增强光照效果,并使模型看起来更加真实、立体和有层次感。它们对于提升渲染质量、增加观众的沉浸感和提供更好的视觉体验起着重要的作用
181 0
|
机器学习/深度学习 人工智能 计算机视觉
华南理工TANGO项目原作解读: 文本驱动的三维物体风格化模型
华南理工TANGO项目原作解读: 文本驱动的三维物体风格化模型
170 0