WebGL与动画实现| 青训营笔记

268 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的第25天

webgl的3D可视化开发中,播放动画,是一种非常常见的技术。动画技术的使用,可以让3D场景更加生动,交互效果更优,极大的提高用户体验。接下来,我们就聊聊在3D可视化项目中常见的动画技术。

基础动画

动画的本质就是在一段时间内,物体某些属性(位置、旋转角度、大小、颜色等)的连续变化。由于我们的主循环本身就是一个递归时间片段,所以基础动画是直接在render主循环中加入动画逻辑,比如每次渲染的时候修改对象的一些属性,这种方法比较简单粗暴,适合临时性的加入一些简单的动态效果。

let step = 0;
const animate = function () {
    requestAnimationFrame(animate);

    step += 0.01;
    cube.position.x = Math.cos(step)
    cube.position.y = 2*Math.abs(Math.sin(step)); 
    cube.rotation.y += 0.03;

    renderer.render(scene, camera);
};

animate();

代码非常的简单,就是在主循环中加入一些控制3D物体的TRS(position、rotation、scale)等逻辑,达到物体位置或者外观动态变化的效果。

使用tween.js制作动画

只有最基础的动画,表现力总感觉差强人意,动画也比较生硬。所以接下来,我们来介绍下如何使用tween.js这个动画库来实现更加细粒度控制的动画。Tween 的Git地址是 github.com/tweenjs/twe… ,可以看到它有7k+的小星星,是一个使用非常广泛的动画库。值得注意的是,它并不是一个专门给3d设计的库,在任何地方都可以使用,它也可以作为普通的html dom操作的动画库。tweenjs使用起来也非常方便。

第一步npm install

npm i @tweenjs/tween.js@^18

在js中引入tween.js

import TWEEN from '@tweenjs/tween.js';

在如何使用tween创建一个动画呢。我们只要在需要执行动画的地方创建一个tween对象。然后在执行的时候调用start()方法就可以执行动画了。

var tweenPosition =new TWEEN.Tween(cube.position)
.to({ x: cube.position.x+3, y: 0, z: cube.position.z }, 1500)
.easing(TWEEN.Easing.Back.Out)
.start();

然后我们在主循环中每帧更新tween,注意这里不是更新我们实例化的对象,而是调用TWEEN类上的方法

const animate = function () {
    requestAnimationFrame(animate);

    TWEEN.update();

    renderer.render(scene, camera);
};

比如我们还可以写两个动画,一个用来修改位置,一个修改大小。

var tweenScale = new TWEEN.Tween(cubeMesh.scale)
            .to({ x: 1, y: height * full, z: 1 }, 1500)
            .easing(TWEEN.Easing.Back.Out)
            .start();
        var tweenPosition = new TWEEN.Tween(cubeMesh.position)
           .to({ x: cubeMesh.position.x, y: height * 0.5 * full, z: cubeMesh.position.z }, 1500)
           .easing(TWEEN.Easing.Back.Out)
           .start();

Tween支持多种Easing函数(Linear, Quadratic, Cubic, Quartic, Quintic, Sinusoidal, Exponential, Circular, Elastic, Back and Bounce),让你的动画更加生动,比如弹跳效果等。每个缓动函数都有In, Out,InOut三种类型可以选用。

相机动画

相机动画是3D场景动画中最常见的一种,例如我们需要快速定位到某个模型,或者查看模型的不同地方细节,如果直接跳转过去的话,会显得十分突兀,这个时候如果有个相机动画,让视角慢慢飞向物体,那么视觉感觉会好很多。

相机动画本质和其他的动画没有什么区别,只是动画的对象是我们场景的相机,通过移动相机位置,达到一种场景自动漫游的赶脚。我们可以直接使用上面的tweenjs控制相机的方式来实现,这里就不再重复赘述。

UV动画

UV动画,顾名思义,就是针对材质UV做的动画。这里如果对材质uv等基础知识,可以看看之前的科普知识。在游戏中,一些动态水面,飞流直下的瀑布,流动的岩浆,跳动的火焰等等,很多都是通过操作UV做的动画。在threejs中,我们可以通过控制Textureoffset属性来达到控制uv偏移量的效果。

模型动画

之前介绍的都是用程序代码实现的动画,虽然可以做的比较复杂,但是程序控制起来略显费劲。这件事交给专业的动画师,做好动画我们直接导入播放不好么,省下来的时间喝个咖啡它不香么?那我们就来看看如何导入模型动画。带动画的模型格式有dae、fbx、gltf等。在threejs中加载模型动画,要使用三个最重要的类AnimationMixer,AnimationClip,AnimationAction。他们对应着动画系统中的三个概念。

AnimationMixer : 是一个动画混合器,类似一个动画总控制器,控制这个动画的一些全局属性。类似一个mp3播放器,可以控制进度条的时间,播放速率等全局属性。

AnimationClip : 动画时间段,一个模型动画,支持多个时间片段。很多时候,我们把人物的动作进行分段处理,例如人物跑动是一个动画段,攻击是一个动画段,死亡倒地是一个动画段。一般一个时间段我们会叫做一个"take",而一段动画中,我们控制的具体模型属性变化叫做一个"track"。举个例子,一段动画是人物跑动(take 01),里面包含左脚运动的track、右脚运动的track、左手运动的track、右手运动的track...

AnimationAction : 是一个具体的动画段播放器,可以控制当前动画段的播放,时间,循环等属性。

在threejs中导入模型,并播放动画我们需要做的是

首先使用loader导入模型

var loader = new GLTFLoader().setPath('assets/models/');
        loader.load('scene.gltf', function (gltf) {
            var object = gltf.scene;
            //获取gltf中的所有分段动画
            var clips = gltf.animations;
            if (clips && clips.length) {
                //创建mixer
                mixer = new THREE.AnimationMixer(object);
                for (var i = 0; i < clips.length; i++) {
                    //当前动画段
                    var take = clips[i];
                    //创建播放器 AnimationAction 并播放
                    mixer.clipAction(take).play();
                }
            }
            scene.add(gltf.scene);
            animate();
        });

然后在主渲染循环中更新mixer

var clock = new THREE.Clock();
const animate = function () {
    requestAnimationFrame(animate);
    controls.update()
    mixer.update(clock.getDelta())
    renderer.render(scene, camera);
};

模型动画一般来说常用的有两大类,一类叫做骨骼动画(Bones Animation),一类叫做变形动画(Morph Animation)。

骨骼动画

首先,我们介绍一下3d骨骼蒙皮动画。大家在三维动画中经常会用到的一种动画形式就是骨骼动画。那么什么是骨骼动画呢。一般来说3d骨骼动画的对象都是一些虚拟的人物,动物,怪物等。当模型师把模型建好后会交给动画师制作动画。动画师拿到模型一般来说会先把骨骼绑定到人物模型上,这部分工作我们称为绑骨。当骨骼已经绑定完成后需要对将骨骼与mesh进行关联控制,每个顶点被哪些骨骼牵动,他们的牵动影响值是多少。这个过程一般叫做刷权重。当骨骼已经绑定好了,权重没啥问题,一般才开始真正的动画帧制作。这部分和普通的帧动画区别不大,就是给人物每个pose打个关键帧记录下当前的TRS属性。所以,其实我们控制的只是骨骼,并不是控制真正的模型。

变形动画

除了骨骼动画以外,模型常见的动画还有一种叫做变形动画(morph animation)。这种动画一般用在一些人物的微表情变化上。制作的时候,一般是把整个源对象clone一份出来,然后移动一些顶点(morph target)