WebGL动画之如何变成狗

一.前言

一如标题,如何变成狗?当然这里不是指的物种的变换或者是变成单身狗。看如下动画:

attachmentId-133

二.分析与思考

从上面的动画分析看,开头像液体球一样融合的动画很明显是前面文章中解析过的Metaball元球融合原理,不清楚的可以跳转去先看下解析。Metaball元球在视觉上的融合会给人一种多变,柔性转变的效果,所以这里用作变形恰到好处,但问题是如何变成狗呢?需要将元球摆放成狗的形状或是用像素拼成狗吗?如果是这样那就还需要狗的像素矩阵才行,未免太过于繁琐。
再仔细观察动画,metaball液体分散消失时,像素并没有汇聚到狗身上而且分散在不同的点随机消失了,而狗是扭曲着出来的。或许是视觉的原因,开头动画觉得Metaball在扭曲,后面狗又扭曲着出现,我们就联想到这是一个变身!这其实是动画的魔术!动画实现中我们只需要将两部分动画衔接起来即可,动画解析可能不难,但是能将两部分知识结合起来运用于动画,是非常需要创造力的。

三.实现

1.创建Meteball

如果懂得Metaball动画原理,这里的实现就不会再觉得难了。这边我还是结合代码复述一下,就当复习一遍了

创建带有自定义材质的Mesh用于着色器编程

_createShape() {this._createMetaballs();const geo = new THREE.PlaneBufferGeometry(this.width, this.height, 1, 1);     //创建平面几何图形const mat = new THREE.ShaderMaterial({                                        //自定义材质vertexShader: vs,fragmentShader: fs,uniforms: this.uniforms,transparent: true,side: THREE.DoubleSide,});this.shape = new THREE.Mesh(geo, mat);this.add(this.shape);
}

构建Metaball数据

_createMetaballs() {for (let i = 0; i < 40; i++) {this.balls.push(new THREE.Vector4(0, 0, 0.001, 0));       //初始化坐标为(0,0) 半径为0.001}
}

这里balls的数据会由JS传到片元着色器中,数据由JS管理便于实现动画。比如修改balls的数据就可以对应改变Metaball的位置,大小,融合阈值。

2.片元着色器Metaball处理

  precision mediump float;varying vec2 v_uv;                    //线性uv  uniform float u_time;                 //u_time每帧累加的值uniform float u_rangeMax;             //最高阈值uniform vec4 u_metaballs[40];         //metaball数据void main() {vec2 st = 2.0 * v_uv - 1.0;float v = 0.0;for ( int i = 0; i < 40; i++ ) {                      //metaballs势能叠加vec4 mb = u_metaballs[i];float dx = st.x + cos(u_time * mb.w) * mb.x;        //u_time 和 w 属性变化来使小球运动float dy = st.y + sin(u_time * mb.w) * mb.y;float r = mb.z;                                     //半径v += r * r / (dx * dx + dy * dy);                   //叠加20个Metaball的势}vec4 color = vec4(1.0);float rangeMin = u_rangeMax - 0.5;          //最低阈值if (v > u_rangeMax) {                           color = vec4(0.0, 0.0, 0.0, 1.0);         //势能大于rangMax 则黑色} else if (v > rangeMin) {color = vec4(0.0, 0.0, 0.0, smoothstep(1.0, 0.0, (u_rangeMax - v) / (u_rangeMax - rangeMin)));  //势能处于大小阈值之间,则设置平滑的透明度} else {color = vec4(1.0, 1.0, 1.0, 0.0);         //势能小于rangeMin 则透明}gl_FragColor = color;}

用Metaball原理叠加势能,计算每个点的颜色,持续修改u_time和w的值,用来做随机变化即蠕动效果

3.用Tween来做淡入淡出效果

//淡入fadeIn(time = 1) {this.balls.forEach(ball => {TweenLite.to(ball, Utils.random(time - 1, time), {delay: Utils.random(0, 1),x: Utils.random(0, 1) < 0.5                   //一半左移,一半右移? Utils.random(-0.6, -0.1): Utils.random(0.1, 0.6),y: Utils.random(0, 1) < 0.5                   //一半下移,一半上移? Utils.random(-0.6, -0.1): Utils.random(0.1, 0.6),z: Utils.random(0.12, 0.25),                  //半径放大到0.12~0.25w: Utils.random(0.1, 1),ease: Power3.easeOut,});});TweenLite.delayedCall(2, () => {this.isAnimate = true;});}//淡出消失fadeOut(time = 1, cb) {                                                   this.balls.forEach(ball => {TweenLite.to(ball, Utils.random(time - 1, time), {        //依次缩放到最小delay: Utils.random(0, 1),z: 0.001,ease: Power2.easeInOut,});});TweenLite.to(this.uniforms.u_rangeMax, time, { value: 16 });  //势能最大阀值调高,即颜色慢慢变浅TweenLite.delayedCall(time + 1.5, () => {                     //1.5S之后消失this.visible = false;cb();});}

用Tween来线性改变balls数组的数据即可,再通过update更新即可实现淡入淡出

//u_time每帧叠加
update() {this.uniforms.u_time.value += 0.02;
}

4.狗的创建

也是创建带有自定义材质的Mesh,并带入texture(纹理就是静态的狗图片)

5.狗的变形

通过前面水波和噪声的文章的知识积累,我们基本可以断定这种变形是噪声相关的算法来实现的。
扭曲变形无非就是顶点的偏移,再加上一个偏移之后能还原的变量和能改变整体透明度的变量即可,看代码

  float rand(vec2 n) {return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);}float noise(vec2 p){                    //常规value噪声函数vec2 ip = floor(p);vec2 u = fract(p);                    //小数部分u来做缓和曲线u = u * u * (3.0 - 2.0 * u);float res = mix(                      //随机四个方格点的向量 做插值mix( rand(ip), rand(ip + vec2(1.0,0.0)), u.x ),mix( rand(ip+vec2(0.0,1.0)), rand(ip+vec2(1.0,1.0)), u.x ),u.y);return res*res;                       //结果再平方,相对于原结果更随机}void main() {vec2 uv = v_uv;float speed = 0.3;                                            //固定速度为0.3uv.x += (noise(uv * 10. + u_time * speed) - 0.5) * u_amp;     // uv*10 即扩大网格, noise取值逐渐增大uv.y += (noise(uv * 10. + u_time * speed) - 0.5) * u_amp;     // (10,100) * u_amp  (1.5,15)gl_FragColor = texture2D(u_texture, uv);                      //得到处理的uv后,取该点的像素值gl_FragColor.a *= u_opacity;                                  //乘以透明度,便于淡入淡出操作}

6.狗的淡入与淡出

    //显示动画show() {TweenLite.to(this.uniforms.u_amp, 3, {    //u_amp 3秒变为0,即从波动变为平静value: 0,ease: Power2.easeInOut,});}//1秒渐变消失 透明度变为0,u_amp变为0.15fadeOut() {TweenLite.to(this.uniforms.u_amp, 1, { value: 0.15 });TweenLite.to(this.uniforms.u_opacity, 1, { value: 0.0 });}

四.总结

这篇文章可以看作是前面几篇理论的结合和运用,图形学和数字特效的丰富多彩就在于各种理论的混合运用和变幻。我们需要逐一掌握这其中的各种理论,才能更敏锐更恰当的去运用它。

五.彩蛋

狗的另一种变形

attachmentId-134

只需对GLSL稍加改动即可,自己去试试吧

完整代码


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部