一.前言
一如标题,如何变成狗?当然这里不是指的物种的变换或者是变成单身狗。看如下动画:
二.分析与思考
从上面的动画分析看,开头像液体球一样融合的动画很明显是前面文章中解析过的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));
}
}
这里balls的数据会由JS传到片元着色器中,数据由JS管理便于实现动画。比如修改balls的数据就可以对应改变Metaball的位置,大小,融合阈值。
2.片元着色器Metaball处理
precision mediump float;
varying vec2 v_uv;
uniform float u_time;
uniform float u_rangeMax;
uniform vec4 u_metaballs[40];
void main() {
vec2 st = 2.0 * v_uv - 1.0;
float v = 0.0;
for ( int i = 0; i < 40; i++ ) {
vec4 mb = u_metaballs[i];
float dx = st.x + cos(u_time * mb.w) * mb.x;
float dy = st.y + sin(u_time * mb.w) * mb.y;
float r = mb.z;
v += r * r / (dx * dx + dy * dy);
}
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);
} 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);
}
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),
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, () => {
this.visible = false;
cb();
});
}
用Tween来线性改变balls数组的数据即可,再通过update更新即可实现淡入淡出
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){
vec2 ip = floor(p);
vec2 u = fract(p);
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;
uv.x += (noise(uv * 10. + u_time * speed) - 0.5) * u_amp;
uv.y += (noise(uv * 10. + u_time * speed) - 0.5) * u_amp;
gl_FragColor = texture2D(u_texture, uv);
gl_FragColor.a *= u_opacity;
}
6.狗的淡入与淡出
show() {
TweenLite.to(this.uniforms.u_amp, 3, {
value: 0,
ease: Power2.easeInOut,
});
}
fadeOut() {
TweenLite.to(this.uniforms.u_amp, 1, { value: 0.15 });
TweenLite.to(this.uniforms.u_opacity, 1, { value: 0.0 });
}
四.总结
这篇文章可以看作是前面几篇理论的结合和运用,图形学和数字特效的丰富多彩就在于各种理论的混合运用和变幻。我们需要逐一掌握这其中的各种理论,才能更敏锐更恰当的去运用它。
五.彩蛋
狗的另一种变形
只需对GLSL稍加改动即可,自己去试试吧
|