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.25
                w: 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.3
    uv.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.15
    fadeOut() {
        TweenLite.to(this.uniforms.u_amp, 1, { value: 0.15 });
        TweenLite.to(this.uniforms.u_opacity, 1, { value: 0.0 });
    }

四.总结

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

五.彩蛋

狗的另一种变形

attachmentId-134

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

完整代码

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值