欢迎光临散文网 会员登陆 & 注册

[AE表达式]使用createPath创建与控制曲线_Part.2

2020-07-22 02:13 作者:小小の我  | 我要投稿

如果createPath的points参数会用了,基本切线研究一下也就会了,这篇主要解释下如何在点与手柄控制器有父子级的情况下传递正确的空间位置。

首先要理解AE中不同空间的坐标系,无论在二维还是三维中,坐标都有全局坐标(世界坐标)和局部坐标(本地坐标)之分,我们先在空合成中创建一个空物体,看一下它的坐标

AE的合成中心坐标根据合成大小而定

AE的坐标设定是从合成最左上角为[0,0], 最右下角是合成大小的长*,我的合成长宽是1920*1080的,所以最中心坐标是长宽的一半960*540

要注意的是,由于Y轴0点在上,向下为正,所以AE坐标Y轴数值增加是物体向下移动(所以C4D物体导入AE要匹配坐标的话要把Y轴乘以-1)

正确使用AE的姿势

然后我们复制一个空物体,把它的父级设置成第一个创建的空物体,命名成Son,父级命名成Dad,再来看一下它的坐标

由于连接了父子级导致坐标显示不同

虽然他们两个的位置是重合的,但是子级的坐标是[0,0],此时我们可以试一下随意移动父级,子级的坐标是不会变的

这在任何CG软件里都是个很通用也很好理解的概念,其实现在Son是以Dad为原点,无论Dad走到哪,Son的本地坐标也不会变,想查看它的真实坐标,也就是它的全局坐标的话,有几种方法,最简单的方法就是直接把它解除父子级

连接父子级时的坐标
解除父子级之后的坐标

当然我们希望在不解除父级的情况下就能获得子级准确的坐标,这里可以使用一个方法,先在随意一个图层上建立一个Point Control,打开它的表达式窗口,写入

thisLayer.toComp([0,0])

使用toComp获得物体全局坐标

这时无论是否连接父子,这个Point Control都会显示出来Son的全局坐标位置了

当然这里有个误区,我们的空物体锚点是在左上角,所以才能这样获得坐标位置,在AE中并不只有全局坐标和本地坐标,由于AE有三维和二维两种空间,全局坐标分为三维的世界空间和二维的合成空间两种坐标系统,本地坐标又直接存在于图层空间中,相比三维软件中的坐标系统更加复杂,如果有机会的话再做一篇专栏详细说明一下


下面正文

BB上面那些有啥意义呢,因为贝塞尔曲线的切线就是它的手柄,手柄有长度有方向,我们通过输入手柄的坐标位置就可以决定手柄的长度和方向,而这个坐标位置就是相对于它所在的点的坐标,是一种局部坐标,再来回顾一下官方文档中对切线数组的描述

也就是说手柄坐标是以它所在的点为原点,[x,y]表示相对位置的点,我们实际来操作一下

建立一个形状图层,建立空Path,记得把位置属性改为[0,0],先创建好连接Dad和Son的线段

注意由于Son有父级,这里为了方便,我先把第二个点的变量连接刚才我们做好的Point Control的值来确保输入的是全局位置

有父级的点变量连接给我们生成好的全局位置属性

记得给个stroke方便能看到路径

所以我们定义三个变量tansA、tansB、tansC,值分别是[0,0]、[100,0]、[-100,0],代表的是0长度手柄、向右100单位长度的手柄、向左100单位长度的手柄(AE的一个单位就是一个像素,100单位长度就是100像素)

因为我们又两个点,所以每个切线数组里也要有两个点,我们把分别把tansA、C,tansB、A装到入切线和出切线的数组中,替换掉createPath中的inTangents和ouTangents的值,具体写法在下面

PointA = thisComp.layer("Dad").transform.position;

PointB = thisComp.layer("Son").effect("Point Control")("Point");


cvs = [PointA, PointB];


tansA = [0,0];

tansB = [100,0];

tansC = [-100,0];


intans = [tansA,tansC];

outtans = [tansB,tansA];


createPath(points = cvs, inTangents = intans, outTangents = outtans, is_closed = false);


然后我们的曲线就有弯曲了,显然现在切线是起了作用了,我们想要查看切线需要选中路径,然后选择在视图中框选一下路径的点

100长度手柄的曲线

这个长度也正好是我们定义的100的长度,想让手柄指向特定的方向只要我们定义相应的变量就可以,或者我们像上面一样给物体加一个Point Control的效果然后把点数值传给切线变量,就可以在外面通过调点来控制手柄了

现在我只想要水平的手柄,所以我只添加一个滑块控制来控制手柄长度,并且把滑块控制命名为Handles Length,我就可以通过这个滑块来控制切线长度了

PointA = thisComp.layer("Dad").transform.position;

PointB = thisComp.layer("Son").effect("Point Control")("Point");


cvs = [PointA, PointB];


handlesLength = effect("Handles Length")("Slider");


tansA = [0, 0];

tansB = [handlesLength, 0];

tansC = [-1 * handlesLength, 0];


intans = [tansA, tansC];

outtans = [tansB, tansA];


createPath(points = cvs, inTangents = intans, outTangents = outtans, is_closed = false);


通过滑块控制切线长度

然后可以用这个东西来做类似节点连接的动画(添加一点细节)

自制节点连接,还挺像那么回事的

仔细看的话我这个还能自动吸附,能根据节点距离自动调节切线长度,具体制作方法大家自己研究就好了,很多软件里的节点就是这样的形式

MAYA的节点编辑器
C4D的Xpresso
Houdini的VOP

当然做成竖的斜的都行,下面我们用空物体控制手柄的方式继续研究这个表达式,也不知道你看会了吗,没看会就假装看会了吧

时间倒流一下,接着只有Dad和Son两个物体的那步继续做,就是下面这步的时候

我们再新建一个空物体,命名为Dad outHandle, 然后在路径表达式里把它的位置信息传给一个新变量outHandleA,同样建立一个切线数组outtans,把需要的切线都放进去

记得我们有两个点,切线数组也需要有两个值,我们暂时先把第二个值设定为[0,0],表示第二个点切线长度是0,然后将这个切线数组替换掉原来的outTangents的值

PointA = thisComp.layer("Dad").transform.position;

PointB = thisComp.layer("Son").effect("Point Control")("Point");


cvs = [PointA, PointB];


outHandleA = thisComp.layer("Dad outHandle").transform.position;


outtans = [outHandleA, [0,0]];


createPath(points=cvs, inTangents=[], outTangents=outtans, is_closed=false);

显然现在切线是起了作用了,为了查看切线我们需要选中path然后在视图中框选曲线的点

这手柄位置显然太长了,完全不在Dad outHandle的位置上,解决这个问题之前我们先解决另一个问题,想看到切线就得选择曲线点,选择了之后空物体就隐藏了,有个办法就是选择空物体然后在素材窗口按住alt拖拽空物体的素材替换掉合成中的空物体,然后把空物体的不透明度调高,就能看到了

因为AE的空物体不可见,但是它在素材中只是个100*100的固态层,替换掉之后它就只是个固态层了,透明度属性就可以起作用了


随便给点颜色,跟曲线有点区分

这时就能同时看到手柄和空物体的位置了,从这个位置不难看出来,它的手柄位置取了Dad outHandle的坐标位置,变成了960*540,所以这个切线的真实位置并不是我们合成窗口的[960,540]的位置,而是以第一个点为原点的[960,540]的位置

那么要把它正确匹配到Dad outHandle的位置也很容易,把Dad outHandle的父级设置成Dad即可

这回得到了正确的手柄位置,移动几个空物体的位置试试,我们得到了一段曲线,并且它的手柄完全受我们控制

但是不要觉得万事大吉,这只是我们在位置上匹配上了所有坐标,如果此时我们旋转Dad的话

在改变父级旋转之后子级没有改变本地坐标导致位置错误

在旋转父级的时候子级位置不会变化,而我们的手柄位置是直接取的子级本地坐标的值,它没有跟随Dad Handle一起移动

但不要担心,仔细看发现,虽然手柄没有跟上物体位置,但是另一个端点却好好的跟随了Son的位置,这是因为我们获取的是Son的全局坐标位置,与它的父物体无关,我们可以也借此修正手柄的坐标

切线是把物体到合成原点的线段移动到了基于曲线点的位置去

哦豁,我们忘记了这里的手柄坐标是基于曲线点为原点的,但是仔细观察会发现,切线手柄的位置其实就是把Dad Handle物体的位置与原点连成的线段移动到了手柄位置与它所在点组成的线段上去了


这里又要先从正文出来一下,介绍一下向量这个东西(线性代数的最基本概念)

本来写之前我觉得不用讲这个的,但是写着写着感觉不对劲了,好像必须得说了

如果你学过线代或者用过houdini啥的经常需要计算矢量的东西的话,估计你也用不到看这篇教程了,为了方便理解,我就从图形的角度简单说一下好了,只要你知道坐标系这个东西的话就应该能理解得了,这里不会涉及到太难的东西,真的想了解线性代数的话可以搜索专业课程(讽刺的是我大学线代其实挂科了)

如果我们的空间中有一个点A的坐标是[2,0],那么它在这个位置


[2,0]这个坐标其实就是一个向量,它代表的是从原点指向A点的方向,以及长度(和贝塞尔的切线坐标的概念一样吧),也就是从原点起步,往X轴方向移动两个单位,我们可以用一个箭头来表示它

如果我们有另一个点B,坐标是[0,1],代表它的向量自然是下图这样

B的向量就是在原点起步,向Y轴移动一个单位的长度

在正常情况下,向量的起点都是原点,但是我们在之前介绍父子级的时候,有个全局坐标与局部坐标的概念,也就是当一个子级以父级为原点进行变换,会怎么样呢,我们让B向量从A点开始起步,仍然向Y轴移动一个单位

这时B点新的位置的坐标很容易看出是[2,1],它与A、B两点的坐标有什么联系呢,将A、B两点的坐标[2,0]、[0,1]X轴相加,Y轴也相加

我们得到一个与新B点完全匹配的坐标[2,1],那么如果我们把AB交换一下,让A从B的位置起步,沿X轴向右移动2个单位呢

其实无论从平行四边形的特性,还是数学的数值计算,还是路径的首位相接各种概念来判断,我们最后都会得到相同的结果

既然是平行四边形,我们的向量并不需要横平竖直,我们换一个斜着的方向来计算

神奇的是无论我们如何变换基础向量的方向,将他们相加之后总能得到与图形匹配的相同结果,而且我们得到了一个新的向量C,它虽然也是从原点起步,但它是向量AB围城的平行四边形的对角线,而它的坐标对应了这个对角的点的坐标

顺便一提向量的乘法也很简单,[1,2]乘以2就是[2,4],想得到相反的向量只需要乘以-1就可以

好的,向量只需要知道这么一点点就够了,足够我们运用在AE表达式中操作坐标了

(如果你愿意深入了解数学和物理的话,会发现更多数字公式和图形乃至物理中的量子在冥冥之中的相互联系,堪比魔法一般令人难以置信,只怪我太垃圾,学不好数理化,最终成为一个臭做动画的)


那么联系一下我们之前讲过的父子级,一个子级的坐标是以父级为原点的,那我们把向量的计算方式带入AE坐标来看看,由于AE中一个单位是一个像素,虽然数值上没有问题,但是肉眼很难看清物体在哪,所以我们把坐标提到100或者1000这个量级上,建两个空物体,随便输入坐标,然后连接父子级

然后我们ctrl D复制一个B,命名为C,解除它的父子级,这样C点虽然和B点重合,但是C显示的是真实的在合成空间中的坐标

多试几个数值就会发现,它完全能对应向量的坐标计算方式(注意AE的坐标系是Y轴向下),这就是另一种获得AE子级全局坐标的方式,将它的本地坐标直接与父级相加,就可以得到它的全局坐标(如果父级还有父级的话还有再加上父级的父级(爷级)),反过来它的全局坐标减去本地坐标也能得到父级的坐标

注意这只是在父级没有旋转的情况下才能这样算,物体的变换信息是个矩阵,包含位置旋转缩放三个信息,只有另外两个信息都是默认数值的时候我们才可以直接加减坐标数值(这也是为什么我们不能直接用空物体的本地坐标传给手柄坐标的原因)


回到正文,现在回头来看看我们的曲线手柄

这个平行四边形已经告诉我们如何获得正确的手柄坐标了,没错,我们只要把手柄物体的全局坐标减去它所在点的全局坐标就可以了

outHandleA = thisComp.layer("Dad outHandle").toComp([0, 0])-PointA;

这样我们完美的控制曲线的点位置以及它的手柄位置,并且可以随意组合控制点的物体和控制手柄的物体的父子关系,或者旋转点,都不会影响表达式对位置的判定

虽然我们想要把曲线圆滑很简单,大多数时候貌似也不太需要单独控制切线,但是某些时候还是需要切线这个精确的控制方式的(大概)

比如这个类似正太分布的曲线,两边应该要趋近于水平的

而下面这个不知道是什么的曲线,是我随便生成的,我就叫他萝莉曲线吧,尽管我已经给它7个点了,中间的点都比两端的点高,但它还是“出轨”了,而且曲率形态也不太好看,所以rotoBezier还是在对精确性没有太高要求的时候适用

(还是带把的正太好哇)

还有一点是手柄的切线让曲线点有了旋转的意义,rotoBezier的点只有位置信息是有效的,旋转没有任何作用,那么我们如果要制作一个弯曲的箭头的话,箭头指向的方向可以用端点切线的方式精确匹配

带有手柄的点可以旋转

或者类似管道这种有直也有弯的东西也是使用切线来生成更准确点

我加了个Offset Paths效果器,可以把单线偏移出去变成双层的线

需要准确定义切线方向的管道

当然我们的目的不是生成这个曲线本身,直接画出来效率更高,我们需要的是不对Path本身K帧的情况下精确控制它的动画形式

其实做到这里我也很想吐槽,我写了一大坨就做了这么个傻B玩应?

其实我本来还有些别的东西想展示的,但是写到这里发现已经6000多字了,可能是我写得太墨迹了,所以后面的部分放到Part3吧(这么个鸟东西能写到Part3我是没想到的)

下一篇我们用这个表达式命令做一些程序化曲线,解决一些实际问题,比如不同大小齿轮的啮合问题,还有...还有啥就再说吧,也可能啥不出来


[AE表达式]使用createPath创建与控制曲线_Part.2的评论 (共 条)

分享到微博请遵守国家法律