80 - tree.js 笔记 -通过 AnimationMixer混合器解析骨骼动画
2018-11-16 16:02
736 查看
1、骨骼动画原理
骨骼包括骨架和骨头,在
three.js中,骨骼模型是
SkinnedMesh就是具有骨架
Skeleton和骨头
bones的网格
Mesh,骨骼网格可以控制几何体
Geometry的顶点生成骨骼动画。
因此,要实现骨骼动画就需要先创建骨架和骨头,这一般可以通过3D建模软件自动生成,因此
three.js只需要负责解析即可,但是也需要了解骨架和骨头所组成的数据结构。
1.1、创建一个手臂
1.1.1、Bone骨头对象
THREE.Bone是骨头关节,是骨骼
Skeleton的一部分,
THREE.Bone继承自
Object3D对象,因此可以完全等同于
THREE.Object3D对象,仅仅是语义上的区别,通过
THREE.Bone对象可以构建一个树结构,用来描述骨骼的各个关节的联系。一个父关节可以有多个子关节,父关节
vertex的变化,可以带动子关节的变化。
如下面的代码创建各个骨关节,然后创建一个骨骼系统:
var bones = [] // 肩膀 var shoulder = new THREE.Bone(); // 胳膊 var elbow = new THREE.Bone(); // 手臂 var hand = new THREE.Bone() shoulder.add(elbow) elbow.add(hand) bones.push(shoulder, elbow, hand); // 设置相对位置 elbow.position.y = 60;//胳膊相对于肩膀的位置 hand.position.y = 40;//手臂相对于胳膊的位置 var armSkeleton = new THREE.Skeleton(bones);
1.1.2、设置皮肤索引skinIndices
皮肤权重skinWeight
首先,需要创建一个几何体
Geometry然后对几何体的顶点进行皮肤索引和皮肤权重设置。
1、创建一个圆柱几何体
var geometry = new THREE.CylinderBufferGeometry(3, 7, 120, 50, 10);
2、设置皮肤索引和权重
通过遍历几何体顶点为每一个顶点设置皮肤索引和权重,
Geometry具有两个方法
skinWeights和
skinIndices,
skinWeights是一个权重值数组,对应于几何体中顶点的顺序,因此第一个
skinWeight对应于几何体中的第一个顶点由于每个顶点可以被
4个骨骼
Bone修改,因此,使用
THREE.Vector4()来表示顶点皮肤的权重,也就是第四个参数
w。
skinIndices同样对应于几何体的顶点,每个顶点最多可以有四个与之关联的骨骼,
skinIndices同样是一个
THREE.Vector4,对应骨架对象
Skeleton的属性
bones。
//根据y来分段,0~60一段、60~100一段、100~120一段 for (var i = 0; i < geometry.vertices.length; i++) { var vertex = geometry.vertices[i]; //第i个顶点 if (vertex.y <= 60) { // 设置每个顶点蒙皮索引属性 受根关节Bone1影响 geometry.skinIndices.push(new THREE.Vector4(0, 0, 0, 0)); // 设置每个顶点蒙皮权重属性 // 影响该顶点关节Bone1对应权重是1-vertex.y/60 geometry.skinWeights.push(new THREE.Vector4(1 - vertex.y / 60, 0, 0, 0)); } else if (60 < vertex.y && vertex.y <= 60 + 40) { // Vector4(1, 0, 0, 0)表示对应顶点受关节Bone2影响 geometry.skinIndices.push(new THREE.Vector4(1, 0, 0, 0)); // 影响该顶点关节Bone2对应权重是1-(vertex.y-60)/40 geometry.skinWeights.push(new THREE.Vector4(1 - (vertex.y - 60) / 40, 0, 0, 0)); } else if (60 + 40 < vertex.y && vertex.y <= 60 + 40 + 20) { // Vector4(2, 0, 0, 0)表示对应顶点受关节Bone3影响 geometry.skinIndices.push(new THREE.Vector4(2, 0, 0, 0)); // 影响该顶点关节Bone3对应权重是1-(vertex.y-100)/20 geometry.skinWeights.push(new THREE.Vector4(1 - (vertex.y - 100) / 20, 0, 0, 0)); } }
1.1.3、创建SkinnedMesh
对象
首先,需要创建材质,
var material = new THREE.MeshPhongMaterial({ color: 0xffff00, transparent: true, opacity: 0.5, skinning: true, });
这里的
skinning属性需要设置为
true表示支持使用皮肤
创建骨骼网格模型
var mesh = new THREE.SkinnedMesh(geometry, material); mesh.translateY(-60);
并将骨架绑定到
SkinnedMesh
var rootBone = armSkeleton.bones[0]; mesh.add(rootBone); //将骨架绑定到网格 mesh.bind(armSkeleton); //移动骨骼并操纵模型 armSkeleton.bones[0].rotation.x = -0.1; armSkeleton.bones[1].rotation.x = 0.2;
为了使骨骼显示的比较清晰,可以添加辅助线条来体现骨骼
// 骨骼辅助线显示 var helper = new THREE.SkeletonHelper(mesh); scene.add(helper);
效果如图所示
1:点击查看示例
2、外部模型骨骼动画
如果直接通过
three.js创建骨骼动画,工作量将会非常巨大,因此可以借助一些三维建模软件制作骨骼动画,然后导出以供
three.js直接加载解析即可。
2.1、加载外部模型
var loader = new THREE.JSONLoader(); loader.load('bf109e/bf109e.json', function (geometry, materials) { // 设置材质皮肤属性为true materials.forEach(elem => { elem.skinning = true; }); // 创建骨骼模型 skinnedMesh = new THREE.SkinnedMesh(geometry, materials); // 加入场景 scene.add(skinnedMesh ); }
一个飞机的模型
2.2、通过帧动画让飞机转动
在控制台打印一下
skinnedMesh找到
geometry属性下的
animations数组
可以看到,
animations数组中有一个剪辑动画,因此可以创建帧动画。如果对帧动画不了解可以查看上一篇文章帧动画介绍
1、首先,需要创建一个
AnimationMixer混合器
// 将模型作为参数创建一个混合器 mixer = new THREE.AnimationMixer(skinnedMesh); // 解析飞行状态对应剪辑对象 var action = mixer.clipAction(skinnedMesh.geometry.animations[0]); action.timeScale = 10; action.play();
2、循环执行
if (mixer !== undefined ) { mixer.update(clock.getDelta()); }
效果
2.点击查看示例
示例代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="icon" href="../../../three.png"> <title>加载外部模型谷歌动画</title> <style> body { margin: 0; overflow: hidden; /* 溢出隐藏 */ } </style> <script src="../../libs/build/three-r93.js"></script> <script src="../../libs/examples/js/controls/OrbitControls.js"></script> </head> <body> <script> var scene, camera, renderer, controls, mixer; var clock = new THREE.Clock(); var group = new THREE.Group(); /* 场景 */ function initScene() { scene = new THREE.Scene(); scene.background = new THREE.CubeTextureLoader().setPath('skybox/').load([ 'posx.jpg', 'negx.jpg', 'posy.jpg', 'negy.jpg', 'posz.jpg', 'negz.jpg' ]); } /* 相机 */ function initCamera() { camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000); camera.position.set(1, 0, 1.5); camera.lookAt(new THREE.Vector3(0, 0, 0)); } /* 渲染器 */ function initRender() { renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); } /* 灯光 */ function initLight() { scene.add(new THREE.AmbientLight(0xffffff)); var spotLight1 = new THREE.SpotLight(0xffffff); spotLight1.position.set(-400, -400, -400); var spotLight2 = new THREE.SpotLight(0xffffff); spotLight2.position.set(400, 400, 400); scene.add(spotLight1); scene.add(spotLight2); } /* 控制器 */ function initControls() { controls = new THREE.OrbitControls(camera, renderer.domElement); } var skinnedMesh; function initContent() { var loader = new THREE.JSONLoader(); loader.load('bf109e/bf109e.json', function (geometry, materials) { materials.forEach(elem => { elem.skinning = true; }); skinnedMesh = new THREE.SkinnedMesh(geometry, materials); group.add(skinnedMesh); scene.add(group); console.log(skinnedMesh); // 将模型作为参数创建一个混合器 mixer = new THREE.AnimationMixer(skinnedMesh); // 解析飞行状态对应剪辑对象 var action = mixer.clipAction(skinnedMesh.geometry.animations[0]); action.timeScale = 10; action.play(); console.log(skinnedMesh); }); } /* 窗口变动触发 */ function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } /* 数据更新 */ function update() { controls.update(); if (mixer !== undefined ) { mixer.update(clock.getDelta()); } } /* 初始化 */ function init() { initScene(); initCamera(); initRender(); initLight(); initControls(); initContent(); /* 监听事件 */ window.addEventListener('resize', onWindowResize, false); } /* 循环渲染 */ function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); update(); } /* 初始加载 */ (function () { init(); animate(); })(); </script> </body> </html>阅读更多
相关文章推荐
- Cocos2d-X 学习笔记 13 cocos2dx骨骼动画
- 骨骼蒙皮动画(Skinned Mesh)的原理解析
- 史上最全的CSS hack方式一览 jQuery 图片轮播的代码分离 JQuery中的动画 C#中Trim()、TrimStart()、TrimEnd()的用法 marquee 标签的使用详情 js鼠标事件 js添加遮罩层 页面上通过地址栏传值时出现乱码的两种解决方法 ref和out的区别在c#中 总结
- 蒙皮动画的解析与渲染(笔记)
- 骨骼蒙皮动画(SkinnedMesh)的原理解析(一)
- 骨骼蒙皮动画(SkinnedMesh)的原理解析(一)
- IE8对JS通过属性和数组遍历解析不一样的地方探讨
- 骨骼蒙皮动画(SkinnedMesh)的原理解析(一)
- 转:骨骼动画教程及微软示例Skinned Mesh的解析(一)
- COCOS学习笔记--骨骼动画
- 骨骼蒙皮动画的原理解析
- 《项目经验》--通过js获取前台数据向一般处理程序传递Json数据,并解析Json数据,将前台传来的Json数据写入数据库表中
- knock.js的subscribe使用 通过$.ajax(); $get();解析JSON数据
- JSON序列化与解析原生JS方法且IE6和chrome测试通过
- 从FBX解析骨骼蒙皮动画并使用OpenGL ES 渲染绘制
- ReactJS学习笔记八:动画
- 骨骼蒙皮动画(SkinnedMesh)的原理解析(一)
- 骨骼蒙皮动画(Skinned Mesh)的原理解析(二)
- 骨骼蒙皮动画(Skinned Mesh)的原理解析
- 骨骼蒙皮动画(Skinned Mesh)的原理解析(二)