万能弹性表达式的改进

  • 经验类型经验/观点
  • 经验属性原创文章
  • 经验版权署名
9800 7 63 2015-12-06

根据万能弹性表达式的两个问题在原表达式上做了修改并详细介绍了修改思路,解决了原表达式的问题。希望对大家工作和学习有帮助。

    国外表达式大神Harry J Frank写的AE万能弹性表达式给广大UI动效制作人员及动效爱好者带来了非常大的方便,简单的Copy、Paste就能让你的动效显得高大上,让人兴奋不已。但是大家也提到该表达式有两个明显的问题:

    1.这个表达式在关键帧做平滑以后是不起作用的,因为平滑后的关键帧速度是0;

    2.如果有两个以上关键帧的时候,除第一个外都会有弹性效果,表达式未做判断。

    本人在动效制作的过程中对表达式进行了一些不太深入的研究,对该表达式做了一些修改,目前来看上述两个问题都已解决。下面就跟大家分享一下修改后的表达式及修改思路,如果有错误和不准确的地方欢迎大家拍砖(表拍太猛就行,呵呵)。

    先付上J大神的原版弹性表达式:

amp = .1;

freq = 2.0;

decay = 2.0;

n = 0;

if (numKeys > 0){

n = nearestKey(time).index;

if (key(n).time > time){n--;}

}

if (n == 0){ t = 0;}

else{t = time - key(n).time;}

if (n > 0){

v = velocityAtTime(key(n).time - thisComp.frameDuration/10);

value + v*amp*Math.sin(freq*t*2*Math.PI)/Math.exp(decay*t);

}

else{value}

n=0;

if (numKeys > 0)

{n = nearestKey(time).index;}

if (key(n).time > time){n--;}

if (n == 0){ t = 0;}

else{t = time - key(n).time;}

if (n > 0){

v = velocityAtTime(key(n).time - thisComp.frameDuration/10);

value + v*amp*Math.sin(freq*t*2*Math.PI)/Math.exp(decay*t)

}

else{value}

    J大神的表达式我不再做单独的解释,下面详细解释一下修改后的表达式。(很多内容跟J大神的表达式是相同或相近的,如果不明白原版弹性表达式的意思看接下来的解释也会很有帮助。)

    修改后的表达式分为单轴和多轴运动(单轴运动不仅包含沿某一轴运动的意思,物体运动的x、y、z轴使用各自不同的关键帧也应使用单轴运动表达式),先来看多轴运动表达式及解释:

amp = 1;

    amp是振幅,是可以自己调节的参数,当amp为1时在弹性强度上能够得到最真实的效果。

freq = 2.5;

    freq是震动频率,是可以自己调节的参数,该数值越大弹性效果的震动速度越快,建议设置值为2到3。

decay = 4;

    decay是阻力强度参数,是可以自己调节的参数,该数值越大弹性效果衰减越快,一般不超过10。

FPS = 25

    FPS是帧率,该值须设为与合成的帧率相同,否则弹性效果会出现非真实的效果,大部分情况下该值为25。

n = 0;

    n是该表达式中获取关键帧个数的变量,初始值为0,不可修改。

if (numKeys > 0){n = numKeys; }

    如果关键帧个数大于0,n等于关键帧的个数。这里与原版表达式不同,原版表达式采用的是查找最近关键帧(n = nearestKey(time).index),大家提到的原版表达式的第二个问题就是因为它造成的。修改以后物体只会在最后一个关键帧处出现弹性效果。如果希望中间某帧出现弹性效果,也可将n的值赋为该关键帧的序号,比如希望第三个关键帧的时候出现弹性效果,大括号内即改为n=3。

if (n == 0){ t = 0;}

    t为我们自定义的跟时间有关的变量,这里的意思是如果n=0,即没有关键帧,那么时间t=0。

else{t = time - key(n).time;}

    如果n不等于0,即该层有关键帧,t等于当前时间(time)减去最后一个关键帧的时间,可以看出t为表示时间差的一个变量。

if (n > 0&time>=key(n).time){

    这里是一个关键的地方。原版表达式的条件中(小括号内)仅有n>0,即该层有关键帧,我又加上了一个条件,即当前时间(time)大于等于最后一个关键帧所在时间,这样做的目的就是为了这个if语句在时间到达最后一个关键帧时才起作用。避免第二个问题中的除了第一个关键帧外,其他所有关键帧都有弹性效果,这是我们不想要的效果。

TB = key(n).time;

    TA = TB - 1/FPS;

    PA = thisLayer.position.valueAtTime(TA);

    PB = thisLayer.position.valueAtTime(TB);

    这里是另一个关键的地方。原版表达式使用的是velocityAtTime这个表达式来求得弹性效果开始时的初始速度。这本来没有什么问题,但是正如前面所述,这个表达式在关键帧做平滑以后是不起作用的,换言之就是如果要平滑关键帧,弹性表达式就没用了。但是平滑关键帧在动效制作中是很常见的事情,这样就极大限制了弹性表达式的使用范围。如果强行使用弹性表达式,即不平滑关键帧,又会影响动效整体的节奏和流畅性,会降低动效的品质。不管怎样结果都不好。

    为了求得弹性效果的初始速度,我换了一种思路,不再使用velocityAtTime这个表达式,而是根据物理学中对速度的定义,手动写表达式来得到。物理学中是这样定义速度的(大概意思,不一定准确):单位时间内物体移动的距离。在AE的时间轴上,最小时间就是两帧(不是两个关键帧)之间的时间,如果是PAL制式(帧率25),即最小时间为1/25秒。我们可以把这个时间作为一个单位的时间,如果求得这段时间内物体移动的距离,那么就求得了物体的速度。按照这种思路,只要物体在每帧之间都有位移,就能够求得它的速度,而跟关键帧是否平滑已经没有关系。为了求得这个速度,我定义了4个变量TA、TB、PA、PB,从表达式就很容易理解这4个变量的意义。

    先看TB,这个是最后一个关键帧的时间,TA是TB前一帧的时间。PA是时间为TA时物体的位置坐标,PB是时间为TB时物体的位置坐标。可以假设有两点A和B,物体在倒数第二帧的时候运动到A点,TA是倒数第二帧的时间,PA是倒数第二帧时物体所在的位置,物体在最后一帧(也是最后一个关键帧)运动到B点,TB是最后一帧的时间,PB是最后一帧时物体所在位置。根据前文所述,TA、TB之间的时间是一个单位时间,那么PA、PB之间的距离就是单位时间的位移,即物体的速度。

    V = sub(PB, PA);

    变量V即物体的速度。需要注意的是在物理学中对速度的定义是一个矢量,即既有大小又有方向的物理量。在数学中我们知道向量是既可表示大小又可表示方向的(高中数学内容),这里PB点坐标减去PA点的坐标得到的差就是向量V的值。向量V的方向代表了物体运动的方向,即速度方向;向量V的长度(即大小)就是速度的大小。

    MO= V*amp*Math.sin(freq*t*2*Math.PI)/Math.exp(decay*t);

    变量MO(单词move前两个字母)是弹性效果的位移量,与原版弹性表达式相同,不做详细解释。

    value + MO

    value是物体原始位移(没有表达式的情况下的位移),再加上弹性效果的位移(MO),弹性效果就实现了。

}

else{value}

    其他情况下,即没有关键帧的情况和有关键帧但是时间没有到达最后一个关键帧之前的情况,位移都为原始位移,表达式不对物体运动产生任何作用。

再来看单轴位移的表达式:

amp = 1;

freq = 2.5;

decay = 6;

FPS = 25

n = 0;

if (numKeys > 0){n = numKeys; }

if (n == 0){ t = 0;}

else{t = time - key(n).time;}

if (n > 0&time>=key(n).time){

TB = key(n).time;

TA = TB - 1/FPS;

PA = thisLayer.position.valueAtTime(TA);

PB = thisLayer.position.valueAtTime(TB);

VX = length(PA, PB);

if(PB[0] > PA[0] || PB[1] > PA[1]){

    这里没有再使用向量差来求速度的大小和方向了(使用向量差会出现表达式维数报错),因为物体的位移已经被固定在单个轴向上,只需求得位移大小即可。length为求PA、PB两点间距离的表达式,VX即物体的速度的值。if语句为判断位移是沿轴正向还是沿轴负向移动,如果是3D图层,小括号内还要加上:||PB[2] > PA[2]。

MO= VX*amp*Math.sin(freq*t*2*Math.PI)/Math.exp(decay*t);}

else{MO= -VX*amp*Math.sin(freq*t*2*Math.PI)/Math.exp(decay*t);}

value + MO

}

else{value}

    修改后的弹性表达式的适用范围更广。只要物体或图层在position属性上有两个或两个以上关键帧(仅一个关键帧是没法运动的),并且所有关键帧序列上最后两帧之间有位移, 即可使用。多轴的版本用于position的x,y,z轴没有分开做关键帧动画。单轴的版本用于x,y,z轴分开做了关键帧动画。


    最后,欢迎大家来TinyHouse群,我是管理员Duke,4群群号:461829100,欢迎大家讨论交流!

    为了方便大家使用表达式,再附上修改后的表达式代码:

多轴弹性表达式:

amp = 1;

freq = 2.5;

decay = 4;

FPS = 25

n = 0;

if (numKeys > 0){n = numKeys; }

if (n == 0){ t = 0;}

else{t = time - key(n).time;}

if (n > 0&time>=key(n).time){

    TB = key(n).time;

    TA = TB - 1/FPS;

    PA = thisLayer.position.valueAtTime(TA);

    PB = thisLayer.position.valueAtTime(TB);

    V = sub(PB, PA);

    MO= V*amp*Math.sin(freq*t*2*Math.PI)/Math.exp(decay*t);

    value + MO

}

else{value}


单轴弹性表达式:

amp = 1;

freq = 2.5;

decay = 6;

FPS = 25

n = 0;

if (numKeys > 0){n = numKeys; }

if (n == 0){ t = 0;}

else{t = time - key(n).time;}

if (n > 0&time>=key(n).time){

    TB = key(n).time;

    TA = TB - 1/FPS;

    PA = thisLayer.position.valueAtTime(TA);

    PB = thisLayer.position.valueAtTime(TB);

    VX = length(PA, PB);

    if(PB[0] > PA[0] || PB[1] > PA[1]){

    MO= VX*amp*Math.sin(freq*t*2*Math.PI)/Math.exp(decay*t);}

    else{MO= -VX*amp*Math.sin(freq*t*2*Math.PI)/Math.exp(decay*t);}

    value + MO

}

else{value}

全部评论:7

发表评论

取消

点击右上角
分享给朋友吧

分享到

取消

每人每天仅限5票,快给你心仪的作品鼓励的一票。

投票