文章目录

  • 12、骨骼动画、变形动画
    • 12.1 骨骼动画原理
      • 相关类
      • Bone
      • 骨架Skeleton
      • Geometry(.skinWeights 和 .skinIndices 属性)
      • 骨骼网格模型 SkinnedMesh
      • 程序创建一个骨骼动画
      • 程序实现骨骼动画
      • 解析外部骨骼动画模型
      • 皮肤顶点权重属性.skinWeights
      • 骨骼动画顶点数据
    • 12.2 加载外部模型骨骼动画
      • 查看骨骼动画数据
      • 解析渲染骨骼动画
    • 12.3 变形目标动画原理
      • 创建变形动画的顶点数据
      • 网格模型.morphTargetInfluences属性
      • 生成变形动画
    • 12.4 解析外部模型变形动画目标数据
      • 加载查看模型变形动画数据
      • 鸟飞行变形动画

12、骨骼动画、变形动画

12.1 骨骼动画原理

所谓骨骼动画,以人体为例简单地说,人体的骨骼运动,骨骼运动会带动肌肉和人体皮肤的空间移动和表面变化,下面将会提到的蒙皮概念你可以类比人体的皮肤。

Threejs骨骼动画需要通过骨骼网格模型类SkinnedMesh来实现,一般来说骨骼动画模型都是3D美术创建,然后程序员通过threejs引擎加载解析,为了让大家更深入理解骨骼动画,下面就通过threejs程序编写一个简易的骨骼动画。

相关类

直接使用Threejs编写一个骨骼动画还是比较复杂的,你首先应该了解骨头关节Bone、骨骼网格模型SkinnedMesh、骨架对象Skeleton这三个骨骼相关的类,除此之外还需要了解几何体Geometry和骨骼动画相关的顶点数据。

Bone

通过Bone类可以实例化一个骨关节对象,然后通过多个骨关节对象可以构成一个骨骼层级系统,Bone基类是Object3D,可以通过add方法给一个骨关节对象Bone添加一个子骨关节Bone。

var Bone1 = new THREE.Bone(); //关节1,用来作为根关节
var Bone2 = new THREE.Bone(); //关节2
var Bone3 = new THREE.Bone(); //关节3
// 设置关节父子关系   多个骨头关节构成一个树结构
Bone1.add(Bone2);
Bone2.add(Bone3);
// 设置关节之间的相对位置
//根关节Bone1默认位置是(0,0,0)
Bone2.position.y = 60; //Bone2相对父对象Bone1位置
Bone3.position.y = 40; //Bone3相对父对象Bone2位置

骨架Skeleton

Threejs通过Skeleton类可以把所有骨关节对象Bone包含进来。

// 所有Bone对象插入到Skeleton中,全部设置为.bones属性的元素
var skeleton = new THREE.Skeleton([Bone1, Bone2, Bone3]); //创建骨骼系统
// 查看.bones属性中所有骨关节Bone
console.log(skeleton.bones);
// 返回所有关节的世界坐标
skeleton.bones.forEach(elem => {console.log(elem.getWorldPosition(new THREE.Vector3()));
});

Geometry(.skinWeights 和 .skinIndices 属性)

几何体Geometry的属性.skinWeights和.skinIndices主要作用是用来设置几何体的顶点位置是如何受骨关节运动影响的。比如几何体Geometry的顶点位置数据是你皮肤上的一个个点位,如果你的骨关节运动了,你的皮肤外形会跟着变化,就相当于Geometry的顶点坐标需要跟着骨关节变化,这时候需要注意,关节外面包裹的一层皮肤,不同区域变形程度不同,那也就是说如果骨关节Bone变化了,几何体Geometry顶点要像皮肤一样不同区域的顶点变化程度不同。这也正是.skinWeights和.skinIndices属性出现的原因,.skinWeights的字面意思就是设置骨骼蒙皮的权重。

骨骼网格模型 SkinnedMesh

SkinnedMesh类的字面意思就是骨骼网格模型,骨骼网格模型SkinnedMesh的基类是普通网格模型Mesh,SkinnedMesh和Mesh一样都是网格模型,只是一个有骨骼动画功能,一个没有骨骼动画功能。

骨骼网格模型SkinnedMesh绑定骨骼系统。

//骨骼关联网格模型
SkinnedMesh.add(Bone1); //根骨头关节添加到网格模型
SkinnedMesh.bind(skeleton); //网格模型绑定到骨骼系统

程序创建一个骨骼动画

下面的的代码通过SkinnedMesh构造函数创建一个骨骼动画,如果你想深入理解骨骼动画可以研究一下下面的代码,下面代码还是比较复杂的,涉及到的知识点比较多,如果不想深入研究,可以大致看下有个印象,直接学习下一节,实际开发的时候,只需要会加载解析骨骼动画就可以。

/*** 创建骨骼网格模型SkinnedMesh*/
// 创建一个圆柱几何体,高度120,顶点坐标y分量范围[-60,60]
var geometry = new THREE.CylinderGeometry(5, 10, 120, 50, 300);
geometry.translate(0, 60, 0); //平移后,y分量范围[0,120]
console.log("name", geometry.vertices); //控制台查看顶点坐标
//
/*** 设置几何体对象Geometry的蒙皮索引skinIndices、权重skinWeights属性* 实现一个模拟腿部骨骼运动的效果*/
//遍历几何体顶点,为每一个顶点设置蒙皮索引、权重属性
//根据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/60geometry.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)/40geometry.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)/20geometry.skinWeights.push(new THREE.Vector4(1 - (vertex.y - 100) / 20, 0, 0, 0));}
}
// 材质对象
var material = new THREE.MeshPhongMaterial({skinning: true, //允许蒙皮动画// wireframe:true,
});
// 创建骨骼网格模型
var SkinnedMesh = new THREE.SkinnedMesh(geometry, material);
SkinnedMesh.position.set(50, 120, 50); //设置网格模型位置
SkinnedMesh.rotateX(Math.PI); //旋转网格模型
scene.add(SkinnedMesh); //网格模型添加到场景中/*** 骨骼系统*/
var Bone1 = new THREE.Bone(); //关节1,用来作为根关节
var Bone2 = new THREE.Bone(); //关节2
var Bone3 = new THREE.Bone(); //关节3
// 设置关节父子关系   多个骨头关节构成一个树结构
Bone1.add(Bone2);
Bone2.add(Bone3);
// 设置关节之间的相对位置
//根关节Bone1默认位置是(0,0,0)
Bone2.position.y = 60; //Bone2相对父对象Bone1位置
Bone3.position.y = 40; //Bone3相对父对象Bone2位置// 所有Bone对象插入到Skeleton中,全部设置为.bones属性的元素
var skeleton = new THREE.Skeleton([Bone1, Bone2, Bone3]); //创建骨骼系统
// console.log(skeleton.bones);
// 返回所有关节的世界坐标
// skeleton.bones.forEach(elem => {//   console.log(elem.getWorldPosition(new THREE.Vector3()));
// });
//骨骼关联网格模型
SkinnedMesh.add(Bone1); //根骨头关节添加到网格模型
SkinnedMesh.bind(skeleton); //网格模型绑定到骨骼系统
console.log(SkinnedMesh);
/*** 骨骼辅助显示*/
var skeletonHelper = new THREE.SkeletonHelper(SkinnedMesh);
scene.add(skeletonHelper);// 转动关节带动骨骼网格模型出现弯曲效果  好像腿弯曲一样
skeleton.bones[1].rotation.x = 0.5;
skeleton.bones[2].rotation.x = 0.5;

程序实现骨骼动画

通过骨骼骨骼系统代码实现骨骼动画效果。

var n = 0;
var T = 50;
var step = 0.01;
// 渲染函数
function render() {renderer.render(scene, camera);requestAnimationFrame(render);n += 1;if (n < T) {// 改变骨关节角度skeleton.bones[0].rotation.x = skeleton.bones[0].rotation.x - step;skeleton.bones[1].rotation.x = skeleton.bones[1].rotation.x + step;skeleton.bones[2].rotation.x = skeleton.bones[2].rotation.x + 2 * step;}if (n < 2 * T && n > T) {skeleton.bones[0].rotation.x = skeleton.bones[0].rotation.x + step;skeleton.bones[1].rotation.x = skeleton.bones[1].rotation.x - step;skeleton.bones[2].rotation.x = skeleton.bones[2].rotation.x - 2 * step;}if (n === 2 * T) {n = 0;}
}
render();

解析外部骨骼动画模型

var mixer = null; //声明一个混合器变量
loader.load("./marine_anims_core.json", function(obj) {console.log(obj)scene.add(obj); //添加到场景中//从返回对象获得骨骼网格模型var SkinnedMesh = obj.children[0];//骨骼网格模型作为参数创建一个混合器mixer = new THREE.AnimationMixer(SkinnedMesh);// 查看骨骼网格模型的帧动画数据// console.log(SkinnedMesh.geometry.animations)// 解析跑步状态对应剪辑对象clip中的关键帧数据var AnimationAction = mixer.clipAction(SkinnedMesh.geometry.animations[1]);// 解析步行状态对应剪辑对象clip中的关键帧数据// var AnimationAction = mixer.clipAction(SkinnedMesh.geometry.animations[3]);AnimationAction.play();// 骨骼辅助显示// var skeletonHelper = new THREE.SkeletonHelper(SkinnedMesh);// scene.add(skeletonHelper);
})
// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {renderer.render(scene, camera);requestAnimationFrame(render);if (mixer !== null) {//clock.getDelta()方法获得两帧的时间间隔// 更新混合器相关的时间mixer.update(clock.getDelta());}
}
render();

皮肤顶点权重属性.skinWeights

.skinWeights表示的是几何体顶点权重数据,当使用骨骼动画网格模型SkinnedMesh的时候, 每个顶点最多可以有4个骨关节Bone影响它. skinWeights属性是一个权重值数组,对应于几何体中顶点的顺序。 例如,第一个skinWeight将对应于几何体中的第一个顶点. 由于每个顶点可以被4个骨关节Bone修改,因此使用四维向量对象Vector4表示一个顶点的权重.四维向量Vector4每个分量的值通常应在0和1之间。当设置为0时,骨关节Bone变换将不起作用;设置为0.5时,将产生50%的影响;设置为100%时,会产生100%的影响。 如果只有一个骨关节Bone与顶点关联,那么你只需要考虑设置四维向量Vector4的第一个分量,其余分量的可以忽略并设置为0.顶点索引属性.skinIndices
顶点索引属性.skinIndices就像skinWeights属性一样,skinIndices的值对应几何体的顶点. 每个顶点最多可以有4个与之关联的骨关节Bone。

骨骼动画顶点数据

骨骼动画的相关的一些顶点数据,主要是描述几何体表面的顶点数据是如何受骨骼系统关节运动影响的。加载外部模型你可以访问骨骼动画网格模型的几何体对象查看骨骼动画相关顶点数据。网格模型的几何体类型可能是Geometry,也可能是BufferGeometry,一般是缓冲类型的几何体BufferGeometry比较常见。

Geometry的骨骼动画顶点数据,直接查看.skinWeights和.skinIndices属性

console.log('骨骼动画皮肤顶点权重数据',SkinnedMesh.geometry.skinWeights);
console.log('骨骼动画皮肤顶点索引数据',SkinnedMesh.geometry.skinIndices);

BufferGeometry的骨骼动画顶点数据,如果你熟悉BufferGeometry的结构,应该都知道该几何体通过.attributes属性存储各种顶点数据。

console.log('骨骼动画皮肤顶点权重数据',SkinnedMesh.geometry.attributes.skinWeights);
console.log('骨骼动画皮肤顶点索引数据',SkinnedMesh.geometry.attributes.skinIndices);

12.2 加载外部模型骨骼动画

查看骨骼动画数据

// 通过加载器ObjectLoader加载./marine_anims_core.json模型文件
var loader = new THREE.ObjectLoader();
loader.load("./marine_anims_core.json", function(obj) {console.log(obj);//浏览器控制台查看加载返回的结果scene.add(obj); //添加到场景中
})

你可以在上面回调函数中分别插入下面代码进行测试。

//从返回对象获得骨骼网格模型
SkinnedMesh = obj.children[0];
// 查看骨头关节Bone
console.log(SkinnedMesh.skeleton.bones);// 骨骼辅助显示
var skeletonHelper = new THREE.SkeletonHelper(SkinnedMesh);
scene.add(skeletonHelper);// 遍历骨骼模型中的骨关节Bone,并获得世界坐标
SkinnedMesh.traverse(function(elem) {if (elem.type === 'Bone') {console.log(elem.getWorldPosition(new THREE.Vector3()));}
});

解析渲染骨骼动画

var loader = new THREE.ObjectLoader(); //创建一个加载器
var mixer = null; //声明一个混合器变量
loader.load("./marine_anims_core.json", function(obj) {scene.add(obj); //添加到场景中//从返回对象获得骨骼网格模型var SkinnedMesh = obj.children[0];//骨骼网格模型作为参数创建一个混合器mixer = new THREE.AnimationMixer(SkinnedMesh);// 查看骨骼网格模型的帧动画数据// console.log(SkinnedMesh.geometry.animations)// 解析跑步状态对应剪辑对象clip中的关键帧数据var AnimationAction = mixer.clipAction(SkinnedMesh.geometry.animations[1]);// 解析步行状态对应剪辑对象clip中的关键帧数据// var AnimationAction = mixer.clipAction(SkinnedMesh.geometry.animations[3]);AnimationAction.play();// 骨骼辅助显示// var skeletonHelper = new THREE.SkeletonHelper(SkinnedMesh);// scene.add(skeletonHelper);
})
// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {renderer.render(scene, camera); //执行渲染操作requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧if (mixer !== null) {//clock.getDelta()方法获得两帧的时间间隔// 更新混合器相关的时间mixer.update(clock.getDelta());}
}
render();

12.3 变形目标动画原理

关于变形动画,你可以理解为多组顶点数据,从一个状态变化到另一个状态,比如人的面部表情,哭的表情用一系列的顶点表示,笑的表情用一系列的顶点表示,从哭的表情过渡到笑的表情,就是表情对应的两组顶点之间的过渡,几何体的顶点的位置坐标发生变化,从一个状态过渡到另一个状态自然就产生了变形动画。

一般项目开发,变形动画和骨骼动画一样,由3D美术编辑好变形动画的模型数据,然后程序员通过Threejs相关的API解析渲染变形动画。

创建变形动画的顶点数据

先获取两个变形目标box1和box2的顶点数据,然后通过几何体Geometry的变形目标属性.morphTargets设置好变形动画。

/*** 创建网格模型,并给模型的几何体设置多个变形目标*/
// 创建一个几何体具有8个顶点
var geometry = new THREE.BoxGeometry(50, 50, 50); //立方体几何对象
console.log(geometry.vertices);
// 为geometry提供变形目标的数据
var box1 = new THREE.BoxGeometry(100, 5, 100); //为变形目标1提供数据
var box2 = new THREE.BoxGeometry(5, 200, 5); //为变形目标2提供数据
// 设置变形目标的数据
geometry.morphTargets[0] = {name: 'target1',vertices: box1.vertices};
geometry.morphTargets[1] = {name: 'target2',vertices: box2.vertices};
var material = new THREE.MeshLambertMaterial({morphTargets: true, //允许变形color: 0x0000ff
}); //材质对象
var mesh = new THREE.Mesh(geometry, material); //网格模型对象
scene.add(mesh); //网格模型添加到场景中

网格模型.morphTargetInfluences属性

通过上面代码把变形动画顶点数据设置好以后,如果想生成变形动画,首先要了解网格模型Mesh的.morphTargetInfluences属性。

你可以在上面代码中分别或同时插入下面代码进行测试,查看长方体网格模型mesh的外形变化。

//启用变形目标并设置变形目标影响权重,范围一般0~1
// 设置第一组顶点对几何体形状影响的变形系数
mesh.morphTargetInfluences[0] = 0.5;
// 设置第二组顶点对几何体形状影响的变形系数
mesh.morphTargetInfluences[1] = 1;

生成变形动画

通过上面的测试,你可以发现改变morphTargetInfluences属性的值可以改变网格模型的形状,那么回忆一下第11章讲解的帧动画,你只需要控制.morphTargetInfluences的属性值生成关键帧数据,就可以实现关键帧动画,然后播放关键帧动画即可实现变形动画。

第一步先利用关键帧KeyframeTrack、剪辑AnimationClip两个类创建好帧动画。

/*** 设置关键帧数据*/
// 设置变形目标1对应权重随着时间的变化
var Track1 = new THREE.KeyframeTrack('.morphTargetInfluences[0]', [0,10,20], [0,1, 0]);
// 设置变形目标2对应权重随着时间的变化
var Track2 = new THREE.KeyframeTrack('.morphTargetInfluences[1]', [20,30, 40], [0, 1,0]);
// 创建一个剪辑clip对象,命名"default",持续时间40
var clip = new THREE.AnimationClip("default", 40, [Track1,Track2]);

第二步是使用混合器AnimationMixer播放设置好的关键帧动画。

/*** 播放编辑好的关键帧数据*/
var mixer = new THREE.AnimationMixer(mesh); //创建混合器
var AnimationAction = mixer.clipAction(clip); //返回动画操作对象
AnimationAction.timeScale = 5; //默认1,可以调节播放速度
// AnimationAction.loop = THREE.LoopOnce; //不循环播放
// AnimationAction.clampWhenFinished=true;//暂停在最后一帧播放的状态
AnimationAction.play(); //开始播放
...
...
// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {renderer.render(scene, camera); //执行渲染操作requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧//clock.getDelta()方法获得两帧的时间间隔// 更新混合器相关的时间mixer.update(clock.getDelta());
}
render();

12.4 解析外部模型变形动画目标数据

加载查看模型变形动画数据

加载完三维模型后,通过console.log(geometry.morphTargets)可以在浏览器控制台查看模型的变形数据。

// 通过加载器JSONLoader加载变形动画模型文件./人/umich_ucs.json
var loader = new THREE.JSONLoader();
loader.load("./人/umich_ucs.json", function(geometry, materials) {// console.log(geometry);// console.log(materials);// 通过平均面法线来计算顶点法线,效果更光滑geometry.computeVertexNormals();var mesh = new THREE.Mesh(geometry, materials[0]);// 材质对象开启渲染目标mesh.material.morphTargets = truemesh.rotateX(-Math.PI / 2);mesh.position.y = -50;scene.add(mesh); //插入到场景中// 查看变形目标数据console.log(geometry.morphTargets);
})

你可以在代码中给网格模型的.morphTargetInfluences属性赋予不同的值,测试人的胖瘦变化。

// 变胖
mesh.morphTargetInfluences[0] = 1;
// 变瘦
mesh.morphTargetInfluences[4] = 1;

下面代码通过vuejs把网格模型Mesh的.morphTargetInfluences属性和滚动条进行绑定,只要滚动条改变就可以同步改变mesh.morphTargetInfluences属性值,mesh.morphTargetInfluences属性值改变,人的胖瘦自然跟着改变。

var loader = new THREE.JSONLoader(); //创建加载器
loader.load("./人/umich_ucs.json", function(geometry, materials) {// 通过平均面法线来计算顶点法线,效果更光滑geometry.computeVertexNormals();var mesh = new THREE.Mesh(geometry, materials[0]);// 材质对象开启渲染目标mesh.material.morphTargets = truemesh.rotateX(-Math.PI / 2);mesh.position.y = -50;scene.add(mesh); //插入到场景中//实例化vuevm = new Vue({el: "#app",data: {time: 0,},watch: {time: function(value) {mesh.morphTargetInfluences[0] = value; // 变胖// mesh.morphTargetInfluences[4] = value; // 变瘦}},})
})

鸟飞行变形动画

通过混合器AnimationMixer获取模型中的变形动画关键帧数据然后进行播放。

var loader = new THREE.JSONLoader(); //创建加载器
var mixer = null; //声明一个混合器变量
loader.load("./鸟/flamingo.json", function(geometry) {// console.log(geometry);var material = new THREE.MeshPhongMaterial({morphTargets: true,vertexColors: THREE.FaceColors,});// 通过平均面法线来计算顶点法线,效果更光滑geometry.computeVertexNormals();var mesh = new THREE.Mesh(geometry, material);scene.add(mesh); //插入到场景中// 创建一个混合器,播放网格模型模型的变形动画mixer = new THREE.AnimationMixer(mesh);// geometry.animations[0]:获得剪辑对象clipvar AnimationAction=mixer.clipAction(geometry.animations[0]);// AnimationAction.timeScale = 0.5; //默认1,可以调节播放速度// AnimationAction.loop = THREE.LoopOnce; //不循环播放// AnimationAction.clampWhenFinished=true;//暂停在最后一帧播放的状态AnimationAction.play();//播放动画
})

在渲染函数中,通过时钟类Clock获得两次渲染的时间间隔,然后执行mixer.update();传递给混合器AnimationMixer。

// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {renderer.render(scene, camera); //执行渲染操作requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧//clock.getDelta()方法获得两帧的时间间隔// 更新混合器相关的时间mixer.update(clock.getDelta());
}
render();

Three.js(十二)——骨骼动画、变形动画相关推荐

  1. JavaFX官方教程(十二)之树动画示例

    翻译自  树动画示例 本章提供有关树动画示例的详细信息.您将了解场景中的所有元素是如何创建和动画的. 图4-1显示了带树的场景. 图4-1树动画 项目和要素 树动画项目由几个文件组成.每个元素,如树叶 ...

  2. 在浏览器中进行深度学习:TensorFlow.js (十二)异常检测算法

    2019独角兽企业重金招聘Python工程师标准>>> 异常检测是机器学习领域常见的应用场景,例如金融领域里的信用卡欺诈,企业安全领域里的非法入侵,IT运维里预测设备的维护时间点等. ...

  3. css3新增动画属性(过度动画 变形动画 关键帧动画)

    目录 过度动画transition 多属性值过渡 2d变形transform 平移 应用:实现居中(不要求知道盒子的宽高) 缩放 旋转 倾斜 修改变形中心的属性 变形属性的复合写法 3D变形动画 3d ...

  4. web前端基础 html5+css3(十二.2D转换,动画,3D转换)

    一.2D转换 1.2D转换之移动translate (1)transform:translate(x,y); transform:translate(100px,100px); (2)transfor ...

  5. 十二正经与奇经八脉动画

    from:   www.oldzy.com  --老中医 具体见附件.

  6. Windows PE 第十二章 PE变形技术

    PE变形技术 这章东西太多,太细了.这里我只记录了一些重点概念.为后面学习做铺垫. PE变形:改变PE结构之后,PE加载器依然可以成功加载运行我们的程序. 一 变形常用技术: 结构重叠技术.空间调整技 ...

  7. 前端HTML5CSS动画变形动画之过渡

    <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8" ...

  8. 动画骨骼【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一)...

    间时紧张,先记一笔,后续优化与完善. 本系列文章由zhmxy555(毛星云)编写,载转请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/detail ...

  9. 【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一)

    这是答应大家的讲解骨骼动画的文章的N部曲的第二篇.这篇文章里,我们对现行的三种模型动画技术进行了概述,然后对X文件构成进行了详细的剖析,最后放出了骨骼动画的第一个示例程序,载入了<诛仙>中 ...

  10. 【Visual C++】游戏开发五十二 浅墨DirectX教程二十 骨骼动画来袭(一)

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhmxy555/article/details/8832812 作者:毛星云(浅墨 ...

最新文章

  1. mysql的存储引擎详解_Mysql存储引擎详解
  2. 中国科学家发现恐惧情绪的新环路
  3. 一篇为你讲透Yii2的widget这货
  4. Python学习之字符串格式化
  5. 阿里云PyODPS 0.7.18发布,针对聚合函数进行优化同时新增对Python 3.7支持
  6. HDU 2079-课程时间(生成函数)
  7. ASP.NET中Server与Request对象的方法
  8. php mdecrypt generic,mdecrypt_generic
  9. B+树(加强版多路平衡查找树)
  10. Spring Boot 后台验证 Hibernate Validation
  11. element-ui clearable 不显示_从ElementUI的loading组件说起
  12. 市场上血糖仪的测试原理和优缺点评价
  13. windows系统bat批处理 电脑换IP清空dns地址 清空缓存重新获取ip
  14. 弱电总包施工组织设计与实施方案
  15. linux 卸载dnw命令,《转载》linux下利用dnw烧写文件,dnw安装与配置
  16. 有毒气体传感器代替金丝雀和老鼠在矿山
  17. 批量移动文件夹到对应文件夹
  18. 正则表达式 匹配中文,英文字母和数字及_长度详解
  19. Android开机启动流程
  20. 超分文章记录 SRCNN-FSRCNN-ESPCN-VDCN-DRCN-RDN-LapSRN-SRDenseNet-SRGAN

热门文章

  1. Python 一百多行实现抢票助手
  2. 智商情商哪个重要_智商和情商哪个更重要 一辩辩词
  3. 核磁共振谱仪定义、发展及基本原理
  4. 计算机网络代表第几次革命,互联网:人类生产力的第三次革命
  5. ewebeditor 3.8php漏洞,asp eWebEditor v3.8 列目录漏洞
  6. C++ Reflection
  7. egret 里面设置MovieClip的scale缩放值时,没有效果的情况
  8. axis webservice 实验
  9. 雷石服务器可以接入电视信号吗,雷石机顶盒使用方法
  10. Linux-bash笔记