three.js使用(9)精灵模型和粒子系统 ,帧动画

1,280 阅读6分钟

这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

精灵模型和粒子系统

精灵模型就是一个模型的标签,标签的内容可以是模型的信息(创建精灵模型对象Sprite和创建网格模型对象一样需要创建一个材质对象,不同的地方在于创建精灵模型对象不需要创建几何体对象Geometry)

  • 下面是创建精灵模型的方法
var texture = new THREE.TextureLoader().load("sprite.png");
// 创建精灵材质对象SpriteMaterial
var spriteMaterial = new THREE.SpriteMaterial({
  color:0xff00ff,//设置精灵矩形区域颜色
  rotation:Math.PI/4,//旋转精灵对象45度,弧度值
  map: texture,//设置精灵纹理贴图
});
// 创建精灵模型对象,不需要几何体geometry参数
var sprite = new THREE.Sprite(spriteMaterial);
scene.add(sprite);
// 控制精灵大小,比如可视化中精灵大小表征数据大小
sprite.scale.set(10, 10, 1); //// 只需要设置x、y两个分量就可以

粒子系统就是用很多个精灵模型组合在一起形成的效果,下面是一个用精灵模型(粒子系统)打造的一个树林模型

<!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/versions/threejsR92/build/three.js"></script>
    <script src="http://www.yanhuangxueyuan.com/threejs/examples/js/controls/OrbitControls.js"></script>
</head>
<body>
    <script>
        var scene = new THREE.Scene();
var textureTree=new THREE.TextureLoader().load("./tree.png");
for(let i = 0; i < 100; i++){
    var spriteMaterial = new THREE.SpriteMaterial({
    map:textureTree,//设置精灵纹理贴图
  });
  // 创建精灵模型对象
  var sprite = new THREE.Sprite(spriteMaterial);
  scene.add(sprite);
  // 控制精灵大小,
  sprite.scale.set(100, 100, 1); //// 只需要设置x、y两个分量就可以
  var k1 = Math.random() - 0.5;
  var k2 = Math.random() - 0.5;
  // 设置精灵模型位置,在xoz平面上随机分布
  sprite.position.set(1000 * k1, 50, 1000 * k2)
}
var geometry = new THREE.PlaneGeometry(1000, 1000); //矩形平面
// 加载草地纹理贴图

var material = new THREE.MeshLambertMaterial({
  color: 0x777700,
  // map:texture,
});
var mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
scene.add(mesh); //网格模型添加到场景中
mesh.rotateX(-Math.PI/2);


        //点光源
	var point = new THREE.PointLight(0xffffff);
    point.position.set(400, 200, 300); //点光源位置
    scene.add(point); //点光源添加到场景中
    //环境光
    var ambient = new THREE.AmbientLight(0x444444);
    scene.add(ambient);
    var width = window.innerWidth; //窗口宽度
    var height = window.innerHeight; //窗口高度
    var k = width / height; //窗口宽高比
    var s = 200; //三维场景显示范围控制系数,系数越大,显示的范围越大
    //创建相机对象
    var camera = new THREE.OrthographicCamera(-s * k, s * k, s, -s, 1, 1000);
    camera.position.set(200, 300, 200); //设置相机位置
    camera.lookAt(scene.position); //设置相机方向(指向的场景对象)
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(width, height);//设置渲染区域尺寸
    renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
    document.body.appendChild(renderer.domElement); //body元素中插入canvas对象
    //执行渲染操作   指定场景、相机作为参数
	function render() {
    renderer.render(scene, camera);
}
var controls = new THREE.OrbitControls(camera,renderer.domElement);//创建控件对象
controls.addEventListener('change', render);//监听鼠标、键盘事件
    </script>
</body>
</html>

帧动画

  • 编辑帧动画并解析播放

1.创建两个用于动画的网格模型

/**
 * 创建两个网格模型并设置一个父对象group
 */
mesh1.name = "Box"; //网格模型1命名
mesh2.name = "Sphere"; //网格模型2命名
group.add(mesh1); //网格模型添加到组中
group.add(mesh2); //网格模型添加到组中

2.编辑关键帧(通过关键帧KeyframeTrack和剪辑AnimationClip两个API来完成,实际开发中复杂的动画是通过3Dmax生成的)

/**
 * 编辑group子对象网格模型mesh1和mesh2的帧动画数据
 */
// 创建名为Box对象的关键帧数据
var times = [0, 10]; //关键帧时间数组,离散的时间点序列
var values = [0, 0, 0, 150, 0, 0]; //与时间点对应的值组成的数组
// 创建位置关键帧对象:0时刻对应位置0, 0, 0   10时刻对应位置150, 0, 0
var posTrack = new THREE.KeyframeTrack('Box.position', times, values);
// 创建颜色关键帧对象:10时刻对应颜色1, 0, 0   20时刻对应颜色0, 0, 1
var colorKF = new THREE.KeyframeTrack('Box.material.color', [10, 20], [1, 0, 0, 0, 0, 1]);
// 创建名为Sphere对象的关键帧数据  从0~20时间段,尺寸scale缩放3倍
var scaleTrack = new THREE.KeyframeTrack('Sphere.scale', [0, 20], [1, 1, 1, 3, 3, 3]);

// duration决定了默认的播放时间,一般取所有帧动画的最大时间
// duration偏小,帧动画数据无法播放完,偏大,播放完帧动画会继续空播放
var duration = 20;
// 多个帧动画作为元素创建一个剪辑clip对象,命名"default",持续时间20
var clip = new THREE.AnimationClip("default", duration, [posTrack, colorKF, scaleTrack]);

3.播放关键帧(通过操作AnimationAction和混合器AnimationMixer两个API播放已有的帧动画数据,混合器THREE.AnimationMixer()的参数是案例代码中编写的两个网格模型的父对象group,实际开发中参数Group也可以是你加载外部模型返回的模型对象。)

**
 * 播放编辑好的关键帧数据
 */
// group作为混合器的参数,可以播放group中所有子对象的帧动画
var mixer = new THREE.AnimationMixer(group);
// 剪辑clip作为参数,通过混合器clipAction方法返回一个操作对象AnimationAction
var AnimationAction = mixer.clipAction(clip);
//通过操作Action设置播放方式
AnimationAction.timeScale = 20;//默认1,可以调节播放速度
// AnimationAction.loop = THREE.LoopOnce; //不循环播放
AnimationAction.play();//开始播放

4.设置两次渲染时间间隔(获得时间间隔可以通过Threejs提供的一个时钟类Clock实现)

// 创建一个时钟对象Clock
var clock = new THREE.Clock();
// 渲染函数
function render() {
  renderer.render(scene, camera); //执行渲染操作
  requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧

  //clock.getDelta()方法获得两帧的时间间隔
  // 更新混合器相关的时间
  mixer.update(clock.getDelta());
}
render();
  • 解析外部模型的帧动画

如果你引用的外部模型文件里保存着动画信息(例如JSON文件),需要通过ObjectLoader解析JSON文件,首先在外部定义一个空的mixer混合器变量,然后在解析文件的回调函数里通过THREE.AnimationMixer把模型对象作为参数传入生成mixer,之后通过mixer.clipAction把模型对象的动画作为参数传入生成操作对象AnimationAction ,最后设置两次渲染时间间隔

// 通过ObjectLoader加载模型文件model.json
var loader = new THREE.ObjectLoader();
var mixer = null; //声明一个混合器变量
// 加载文件返回一个对象obj
loader.load("model.json", function(obj) {
  obj.scale.set(15, 15, 15);//缩放加载的模型
  scene.add(obj);
  // obj作为混合器的参数,可以播放obj包含的帧动画数据
  mixer = new THREE.AnimationMixer(obj);
  // obj.animations[0]:获得剪辑clip对象
  // // 剪辑clip作为参数,通过混合器clipAction方法返回一个操作对象AnimationAction
  var AnimationAction = mixer.clipAction(obj.animations[0]);
  AnimationAction.play();
});
/ 创建一个时钟对象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();
  • 播放设置(暂停,快进,定位到某一时间,播放特定时间段,滚动条播放)

播放/暂停(.paused属性)

<button onclick="pause()" type="button" style="position: absolute;padding: 10px;">暂停/继续</button>
<script>
  // 暂停继续播放函数
  function pause() {
    if (AnimationAction.paused) {
      // 如果是播放状态,设置为暂停状态
      AnimationAction.paused = false;
    } else {
      // 如果是暂停状态,设置为播放状态
      AnimationAction.paused = true;
    }
  }
</script>

播放clip特定时间段

/**
 * 播放编辑好的关键帧数据
 */
var mixer = new THREE.AnimationMixer(mesh); //创建混合器
var AnimationAction = mixer.clipAction(clip); //返回动画操作对象
// AnimationAction.timeScale = 5; //默认1,可以调节播放速度
AnimationAction.loop = THREE.LoopOnce; //不循环播放
AnimationAction.clampWhenFinished = true; //暂停在最后一帧播放的状态
// 设置播放区间10~18   关键帧数据总时间是20
AnimationAction.time = 10; //操作对象设置开始播放时间
clip.duration = 18;//剪辑对象设置播放结束时间
AnimationAction.play(); //开始播放

定位在某个时间点(开始结束时间设置为一样,相当于播放时间为0,直接跳转到时间点对应的状态)

**
 * 播放编辑好的关键帧数据
 */
var mixer = new THREE.AnimationMixer(mesh); //创建混合器
var AnimationAction = mixer.clipAction(clip); //返回动画操作对象
// AnimationAction.timeScale = 5; //默认1,可以调节播放速度
AnimationAction.loop = THREE.LoopOnce; //不循环播放
AnimationAction.clampWhenFinished = true; //暂停在最后一帧播放的状态
// 开始结束时间设置为一样,相当于播放时间为0,直接跳转到时间点对应的状态
AnimationAction.time = 10; //操作对象设置开始播放时间
clip.duration = AnimationAction.time;//剪辑对象设置播放结束时间
AnimationAction.play(); //开始播放

快进(按钮递增时间点)

	AnimationAction.time += 2; //操作对象设置开始播放时间
    clip.duration = AnimationAction.time;//剪辑对象设置播放结束时间
    AnimationAction.play(); //开始播放

滚动条拖动播放

<div id="app">
  <div class="block" style="display:inline;width:500px">
    <el-slider v-model="time" show-input :max=20 :step=0.01></el-slider>
  </div>
</div>
<script>
...
...
  var mixer = new THREE.AnimationMixer(mesh);
  var AnimationAction = mixer.clipAction(clip);
  AnimationAction.loop = THREE.LoopOnce;
  AnimationAction.clampWhenFinished = true;
  //实例化vue
  vm = new Vue({
    el: "#app",
    data: {
      time: 0,
    },
    watch: {
      time:function (value) {
        // 开始结束时间设置为一样,相当于播放时间为0,直接跳转到时间点对应的状态
        AnimationAction.time = value; //操作对象设置开始播放时间
        clip.duration = AnimationAction.time;//剪辑对象设置播放结束时间
        AnimationAction.play(); //开始播放
      }
    },
  })
</script>