帧动画
之前用的gsap动画库可以帮我们做补间动画。也就是说,我们只需定义开始和末尾的状态,这之间的过渡效果动画库会帮我们填充上。
但是,有时我们需要播放一系列自定义的动作,比如我们想做一个小人做体操的动画,我们需要绘制小人的不同动作,也就是说,我们需要自定义帧去存储物体的运动数据。
以一个普通物体为例,我们可以先绘制出这个物体的运动轨迹,然后定义出每一帧的动作,然后连贯起来就可以了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="http://www.yanhuangxueyuan.com/threejs/build/three.min.js"></script>
<script src="http://www.yanhuangxueyuan.com/threejs/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
<div id="webgl"></div>
</body>
</html>
<script>
//场景
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 100);
scene.add(camera);//添加相机
//添加坐标轴
var axes = new THREE.AxesHelper(500);//500表示xyz轴的长度,红:x,绿:y,蓝:z
scene.add(axes);
//===========================================================================//
const box = new THREE.Mesh(new THREE.BoxGeometry(5, 5, 5), new THREE.MeshBasicMaterial({ color: 0x0000ff }));
scene.add(box);
box.position.set(-10, -50, -50);
const curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-10, -50, -50),
new THREE.Vector3(10, 0, 0),
new THREE.Vector3(8, 50, 50),
new THREE.Vector3(-5, 0, 100)
]);
const points = curve.getPoints(100);//将曲线切分100份,总共是101个顶点
console.log(points);//101个点
const geometry = new THREE.BufferGeometry().setFromPoints(points);//创建一个空几何体,用来存储顶点
const line = new THREE.Line(geometry, new THREE.LineBasicMaterial({ color: 0xff00ff }));
scene.add(line);
// 声明一个数组用于存储时间序列
let arr = [];
for (let i = 0; i < 101; i++) {
arr.push(i);
}
// 生成一个时间序列
var times = new Float32Array(arr);
var posArr = [];
points.forEach(elem => {
posArr.push(elem.x, elem.y, elem.z);
});
// 创建一个和时间序列相对应的位置坐标系列
var values = new Float32Array(posArr);
// 创建一个帧动画的关键帧数据,曲线上的位置序列对应一个时间序列
var posTrack = new THREE.KeyframeTrack('.position', times, values);
let duration = 101;
let clip = new THREE.AnimationClip("default", duration, [posTrack]);
var mixer = new THREE.AnimationMixer(box);
let AnimationAction = mixer.clipAction(clip);
AnimationAction.timeScale = 20;
AnimationAction.play();
//===========================================================================//
var renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});//画布
renderer.setSize(window.innerWidth, window.innerHeight);//设置渲染区域尺寸
renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
document.getElementById('webgl').appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
});
var clock = new THREE.Clock();//声明一个时钟对象
function animate() {
renderer.render(scene, camera);//开始渲染
requestAnimationFrame(animate);
if (mixer) {
mixer.update(clock.getDelta());
}
}
animate();
</script>

模型的帧动画
在枪战小游戏中,敌人中弹倒地到消失,这是一个完整的动作,需要自定义完成。
不过这个工作一般在做模型时就会完成,我们只需播放这个动画即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="http://www.yanhuangxueyuan.com/threejs/build/three.min.js"></script>
<script src="http://www.yanhuangxueyuan.com/threejs/examples/js/controls/OrbitControls.js"></script>
<script src="http://www.yanhuangxueyuan.com/threejs/examples/js/loaders/GLTFLoader.js"></script>
<script src="http://www.yanhuangxueyuan.com/threejs/examples/js/loaders/DRACOLoader.js"></script>
</head>
<body>
<div id="webgl"></div>
</body>
</html>
<script>
//场景
var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 100);
scene.add(camera);//添加相机
//添加坐标轴
var axes = new THREE.AxesHelper(500);//500表示xyz轴的长度,红:x,绿:y,蓝:z
scene.add(axes);
addGlb();
var mixer;//更新帧要用到
async function addGlb() {
const loader = new THREE.GLTFLoader();
const dracoloader = getDracoLoader();
loader.setDRACOLoader(dracoloader);//注入loader
const city = await loadGlb('./model/city.glb', loader);
scene.add(city.scene);
city.scene.traverse(child => {
if (child.name === '热气球') {
// 混合动画
mixer = new THREE.AnimationMixer(child);//初始化
// 裁剪动画
const clip = city.animations[0];
const action = mixer.clipAction(clip);
action.play();
}
});
}
function getDracoLoader() {
//对模型解压
const dracoloader = new THREE.DRACOLoader();
dracoloader.setDecoderPath("./draco/");//把examples\jsm\libs\draco这个文件夹复制过来
dracoloader.setDecoderConfig({ type: "js" })
dracoloader.preload();
return dracoloader;
}
function loadGlb(filepath, loader) {
return new Promise((resolve, reject) => {
loader.setCrossOrigin('Anonymous');//跨域问题
loader.load(filepath, (gltf) => {
console.log('gltf>>>', gltf);
//处理材质丢失的情况
gltf.scene.traverse(child => {
if (child.isMesh) {
child.material.emissive = child.material.color;
child.material.emissiveMap = child.material.map;
}
});
resolve(gltf);
}, undefined, (error) => {
console.error(error);
reject(error);
});
});
}
var renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true
});//画布
renderer.setSize(window.innerWidth, window.innerHeight);//设置渲染区域尺寸
renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
document.getElementById('webgl').appendChild(renderer.domElement);
var controls = new THREE.OrbitControls(camera, renderer.domElement);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
});
var clock = new THREE.Clock();
function animate() {
renderer.render(scene, camera);//开始渲染
if(mixer) {
mixer.update(clock.getDelta());
}
requestAnimationFrame(animate);
}
animate();
</script>

网友评论