SkeletonUtils
是 Three.js 的一个实用工具集,用于处理与骨骼(Skeleton
)相关的操作。这个工具集提供了一些方便的方法来简化骨骼动画和骨骼结构的处理。
-
克隆骨骼(Clone Skeleton):
允许你克隆一个完整的骨骼结构,包括所有的骨骼和连接。 -
计算骨骼的边界框(Compute Skeleton Bounding Box):
可以计算一个包含整个骨骼结构的边界框(bounding box),这对于某些渲染或碰撞检测任务可能很有用。 -
序列化与反序列化骨骼(Serialize and Deserialize Skeleton):
可以将骨骼结构转换为一个可以存储或传输的格式,然后再将其转换回骨骼对象。这对于保存和加载骨骼状态,或者在网络通信中传输骨骼数据非常有用。 -
创建辅助对象以可视化骨骼(Create Visual Helpers for Skeletons):
SkeletonUtils
可能提供方法来创建辅助的几何体或线条,以便在渲染时可视化骨骼的结构和动画。 -
处理骨骼的层级结构(Handle Skeleton Hierarchy):
可能包括遍历骨骼的层级结构、查找特定的骨骼、或者修改骨骼之间的连接关系。 -
骨骼动画的辅助功能(Helpers for Skeleton Animations):
可能包括设置骨骼的初始姿势、合并多个动画剪辑、或者调整动画的播放速度和方向。
通过以下方式引入 SkeletonUtils
:
import * as THREE from 'three';
import { SkeletonUtils } from 'three/examples/jsm/utils/SkeletonUtils.js';
在 Three.js 中,SkeletonUtils
通常作为实用工具类提供,并没有直接的“API调用方式”,因为它包含一系列静态方法,你可以直接通过类名来调用这些方法。以下是一些可能的 SkeletonUtils
方法调用示例,但请注意,这些方法可能会随着 Three.js 的版本更新而有所变化。
为了使用 SkeletonUtils
,你需要确保你已经正确导入了它。通常,SkeletonUtils
位于 examples/jsm/utils/
目录下,所以你需要这样导入它:
import * as THREE from 'three';
import { SkeletonUtils } from 'three/examples/jsm/utils/SkeletonUtils.js';
// 假设你有一个已经存在的骨骼对象
const skeleton = ...; // 你的骨骼对象
// 克隆骨骼
const clonedSkeleton = SkeletonUtils.clone(skeleton);
// 计算骨骼的边界框
const box = new THREE.Box3().setFromObject(skeleton);
// 或者如果 SkeletonUtils 提供了直接的方法
const box = SkeletonUtils.computeBoundingBox(skeleton);
// 序列化骨骼
const serializedSkeleton = SkeletonUtils.serialize(skeleton);
// 反序列化骨骼
const deserializedSkeleton = SkeletonUtils.deserialize(serializedSkeleton);
// 创建骨骼的可视化辅助对象
const visualHelper = SkeletonUtils.createVisualHelper(skeleton);
scene.add(visualHelper);
demo 源码
<!DOCTYPE html> <!-- HTML文档类型声明 -->
<html lang="en"> <!-- HTML文档语言设置为英语 -->
<head> <!-- HTML文档头部开始 -->
<title>Multiple animated skinned meshes</title> <!-- 设置页面标题 -->
<meta charset="utf-8"> <!-- 设置字符集为UTF-8 -->
<meta content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" name="viewport"> <!-- 设置视口信息 -->
<link type="text/css" rel="stylesheet" href="main.css"> <!-- 导入样式表 -->
</head> <!-- HTML文档头部结束 -->
<body> <!-- HTML文档主体开始 -->
<div id="info"> <!-- 创建信息显示区域 -->
This demo shows the usage of <strong>SkeletonUtils.clone()</strong> and how to setup a shared skeleton.<br/> <!-- 显示提示信息 -->
Soldier model from <a href="https://www.mixamo.com" target="_blank" rel="noopener">https://www.mixamo.com</a>. <!-- 显示模型来源 -->
</div> <!-- 信息显示区域结束 -->
<script type="importmap"> <!-- 开始导入模块映射 -->
{
"imports": { <!-- 导入配置项 -->
"three": "../build/three.module.js", <!-- 导入three.js核心模块 -->
"three/addons/": "./jsm/" <!-- 导入three.js附加模块 -->
}
}
</script> <!-- 导入模块映射结束 -->
<script type="module"> <!-- 开始模块化脚本 -->
// 导入所需的模块
import * as THREE from 'three'; <!-- 导入three.js核心模块 -->
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; <!-- 导入GLTF加载器模块 -->
import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js'; <!-- 导入骨骼工具模块 -->
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; <!-- 导入GUI模块 -->
// 创建全局变量
let camera, scene, renderer, clock; <!-- 相机、场景、渲染器、时钟变量 -->
let model, animations; <!-- 模型和动画变量 -->
const mixers = [], objects = []; <!-- 动画混合器和物体数组 -->
const params = { <!-- 参数对象 -->
sharedSkeleton: false <!-- 是否共享骨骼的标志 -->
};
// 初始化函数
init();
animate();
function init() { <!-- 初始化函数开始 -->
// 创建透视相机
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.set(2, 3, -6); <!-- 设置相机位置 -->
camera.lookAt(0, 1, 0); <!-- 设置相机观察点 -->
// 创建时钟
clock = new THREE.Clock();
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0xa0a0a0);
scene.fog = new THREE.Fog(0xa0a0a0, 10, 50); <!-- 添加雾效 -->
// 创建环境光和平行光
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x8d8d8d, 3);
hemiLight.position.set(0, 20, 0);
scene.add(hemiLight);
const dirLight = new THREE.DirectionalLight(0xffffff, 3);
dirLight.position.set(-3, 10, -10);
dirLight.castShadow = true;
dirLight.shadow.camera.top = 4;
dirLight.shadow.camera.bottom = -4;
dirLight.shadow.camera.left = -4;
dirLight.shadow.camera.right = 4;
dirLight.shadow.camera.near = 0.1;
dirLight.shadow.camera.far = 40;
scene.add(dirLight);
// 创建地面
const mesh = new THREE.Mesh(new THREE.PlaneGeometry(200, 200), new THREE.MeshPhongMaterial({color: 0xcbcbcb, depthWrite: false}));
mesh.rotation.x = -Math.PI / 2;
mesh.receiveShadow = true;
scene.add(mesh);
// 加载模型和动画
const loader = new GLTFLoader();
loader.load('models/gltf/Soldier.glb', function (gltf) {
model = gltf.scene;
animations = gltf.animations;
model.traverse(function (object) {
if (object.isMesh) object.castShadow = true;
});
setupDefaultScene(); <!-- 设置默认场景 -->
});
// 创建渲染器
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);
// 监听窗口大小变化
window.addEventListener('resize', onWindowResize);
// 创建GUI
const gui = new GUI();
gui.add(params, 'sharedSkeleton').onChange(function () { <!-- 添加共享骨骼参数的监听 -->
clearScene(); <!-- 清除场景 -->
if (params.sharedSkeleton === true) { <!-- 如果选择共享骨骼 -->
setupSharedSkeletonScene(); <!-- 设置共享骨骼
场景 -->
} else { <!-- 否则 -->
setupDefaultScene(); <!-- 设置默认场景 -->
}
});
gui.open(); <!-- 打开GUI界面 -->
} <!-- 初始化函数结束 -->
function clearScene() { <!-- 清除场景函数开始 -->
for (const mixer of mixers) { <!-- 停止所有动画混合器的动作 -->
mixer.stopAllAction();
}
mixers.length = 0; <!-- 清空动画混合器数组 -->
for (const object of objects) { <!-- 遍历物体数组 -->
scene.remove(object); <!-- 从场景中移除物体 -->
scene.traverse(function (child) { <!-- 遍历场景中的子对象 -->
if (child.isSkinnedMesh) child.skeleton.dispose(); <!-- 释放骨骼资源 -->
});
}
} <!-- 清除场景函数结束 -->
function setupDefaultScene() { <!-- 设置默认场景函数开始 -->
const model1 = SkeletonUtils.clone(model); <!-- 克隆模型 -->
const model2 = SkeletonUtils.clone(model);
const model3 = SkeletonUtils.clone(model);
model1.position.x = -2; <!-- 设置位置 -->
model2.position.x = 0;
model3.position.x = 2;
const mixer1 = new THREE.AnimationMixer(model1); <!-- 创建动画混合器 -->
const mixer2 = new THREE.AnimationMixer(model2);
const mixer3 = new THREE.AnimationMixer(model3);
mixer1.clipAction(animations[0]).play(); <!-- 播放动画 -->
mixer2.clipAction(animations[1]).play();
mixer3.clipAction(animations[3]).play();
scene.add(model1, model2, model3); <!-- 将模型添加到场景中 -->
objects.push(model1, model2, model3); <!-- 将模型添加到物体数组中 -->
mixers.push(mixer1, mixer2, mixer3); <!-- 将动画混合器添加到数组中 -->
} <!-- 设置默认场景函数结束 -->
function setupSharedSkeletonScene() { <!-- 设置共享骨骼场景函数开始 -->
const sharedModel = SkeletonUtils.clone(model); <!-- 克隆模型 -->
const shareSkinnedMesh = sharedModel.getObjectByName('vanguard_Mesh'); <!-- 获取皮肤网格 -->
const sharedSkeleton = shareSkinnedMesh.skeleton; <!-- 获取骨骼 -->
const sharedParentBone = sharedModel.getObjectByName('mixamorigHips'); <!-- 获取父级骨骼节点 -->
scene.add(sharedParentBone); <!-- 将父级骨骼节点添加到场景中 -->
const model1 = shareSkinnedMesh.clone(); <!-- 克隆皮肤网格 -->
const model2 = shareSkinnedMesh.clone();
const model3 = shareSkinnedMesh.clone();
model1.bindMode = THREE.DetachedBindMode; <!-- 设置绑定模式 -->
model2.bindMode = THREE.DetachedBindMode;
model3.bindMode = THREE.DetachedBindMode;
const identity = new THREE.Matrix4(); <!-- 创建单位矩阵 -->
model1.bind(sharedSkeleton, identity); <!-- 绑定骨骼 -->
model2.bind(sharedSkeleton, identity);
model3.bind(sharedSkeleton, identity);
model1.position.x = -2; <!-- 设置位置 -->
model2.position.x = 0;
model3.position.x = 2;
model1.scale.setScalar(0.01); <!-- 设置缩放 -->
model1.rotation.x = -Math.PI * 0.5;
model2.scale.setScalar(0.01);
model2.rotation.x = -Math.PI * 0.5;
model3.scale.setScalar(0.01);
model3.rotation.x = -Math.PI * 0.5;
const mixer = new THREE.AnimationMixer(sharedParentBone); <!-- 创建动画混合器 -->
mixer.clipAction(animations[1]).play(); <!-- 播放动画 -->
scene.add(sharedParentBone, model1, model2, model3); <!-- 将模型和父级骨骼节点添加到场景中 -->
objects.push(sharedParentBone, model1, model2, model3); <!-- 将模型和父级骨骼节点添加到物体数组中 -->
mixers.push(mixer); <!-- 将动画混合器添加到数组中 -->
} <!-- 设置共享骨骼场景函数结束 -->
function onWindowResize() { <!-- 窗口大小变化处理函数开始 -->
camera.aspect = window.innerWidth / window.innerHeight; <!-- 更新相机的宽高比 -->
camera.updateProjectionMatrix(); <!-- 更新相机的投影矩阵 -->
renderer.setSize(window.innerWidth, window.innerHeight); <!-- 更新渲染器的尺寸 -->
} <!-- 窗口大小变化处理函数结束 -->
function animate() { <!-- 动画循环函数开始 -->
requestAnimationFrame(animate); <!-- 请求下一帧动画 -->
const delta = clock.getDelta(); <!-- 获取时间间隔 -->
for (const mixer of mixers) mixer.update(delta); <!-- 更新动画混合器 -->
renderer.render(scene, camera); <!-- 渲染场景 -->
} <!-- 动画循环函数结束 -->
</script> <!-- 模块化脚本结束 -->
</body> <!-- HTML文档主体结束 -->
</html> <!-- HTML文档结束 -->