问题

数量级的问题:

昨天我朋友问:要做三百多个人名(文本)从宇宙深处飞出来的效果,怎么处理?

对于这样的需求,做 AE 包装的小伙伴们都不陌生,就是 3D文本层摆位置,打个相机穿梭一下。没错,对于几十个图层文本这么操作没什么太大问题,就是繁琐一点,但是量级在 300 以上,恐怕得累疯了。

这里有两个关键难点:

  1. 三百多个人名要每一个图层都编辑一遍内容,且不说样式可能需要修改,就是切换图层-打字的操作就能要了多半条命;
  2. 三百多个文本图层,每一层都要调整 Z轴 位置,估计也活不成。

粒子不靠谱:

朋友说,他想到的第一个解决方案是发射粒子。确实,使用粒子可以解决第二个问题,但同时我们必须知道单个图层粒子的最大尺寸是 500x500 像素,一旦粒子飞到近处,它就虚掉了。

使用粒子发射你的名字

除此之外,粒子发射器的设置是非常难调校的,很容易出现名字重复出现或者丢了名字的情况,因为粒子的核心就是随机,所以要让它乖乖听话就不太容易了。

解决方案

我给他的方案还是传统方案,只是上面提到的两个关键难点用表达式去解决了,下面直接全解析一遍,代码我发上来,也会有解释,但是看不懂代码的话就直接忽略,Copy 来用就行了。

一、表达式引擎

我用的所有表达式都是基于 JavaScript 语言,所以必须先在项目设置中设置表达式引擎为 JavaScript,不然就容易报错。话说回来,我无意中学了 JavaScript,虽然在程序员圈子里,js 总是被吐槽的体无完肤,但是现在很多平台像网页设计、微信小程序、APP开发和 AE表达式等都已经广泛使用,所以学习它还是非常划算的,有兴趣的小伙伴可以去看我 JS 的专栏,内容比较少,但是你们要是有问题给我留言,我一定会去更新的。

二、对三百多个名字合理分组

三百多个人名可以按照每一组 30 至 50 个名字进行分组,分组的目的第一是便于管理,因为数量太多的话,万一要修改,就要牵一发而动全身,指不定哪儿就出错了,另一方面也是为了减少运行的压力,同时渲染 300 多个 3D图层很多电脑都会跑不动。

新建一个合成,像下面这个【人名组-1】这样,然后建一个空文本图层,这里将用于生成 40 个人名。

生成人名当然不能一个图层一个图层地去编辑,而是要靠数据去驱动,关于表达式的数据驱动动画可以参考下面这个连接:

After Effects 中的表达式语言 (adobe.com)

你可以在外部写一个 Jason 来保存数据,它的好处是当你要修改数据的时候可以直接使用记事本编辑,而不需要操作 AE,坏处就是你可能不会编辑 Jason,还有你容易不小心删掉了数据文件。所以这部分我就不讲了,会用的人不需要,不会用的人讲了白讲。下面说说比较简单的方法,利用 Marker 保存文本。关于 AE 中 Marker 的用法可以看看 [AE 表达式]引用 Marker 注释 - 知乎 (zhihu.com) 这篇文章,了解一下就好,这里主要是用来保存文本。

看上图中,我在注释中填写了 40 个扯淡的假名,每个名字中间用中文逗号隔开,逗号的作用是将文本分割成数组的分隔符,它可以人任何一个打起来比较方便的不会被当作名字用的字符,比如竖线 | 或者横杠 - 。为啥我说不能用 Jason 呢?因为代码必须用英文,中文+英文标点的混合输入很容易出错,但是像上面这样只是处理字符串就不会出毛病了。

后面内容中所有用引用格式框起来的内容都是给对代码有兴趣的小伙伴看的,拿来党可以忽略。

新建一个空文本图层,然后在源文本中开启表达式控件。

thisComp.marker.key(1).comment; // 引用第一个合成标记的注释文本

可以尝试贴入上面这句代码,标记注释的文本会直接显示为文本,但是我们不是要这么使用的,而是要使用split() 方法将其转换为数组,从而得到这样的数组数据 ["张三", "李四", "王五", "甲乙", "丙丁", ...] ,然后每一个文本都可以利用图层索引数字来访问相应的数组元素。

// 从合成标记中引用文本,并使用中文逗号分隔符将其分割为数组
let _arr = thisComp.marker.key(1).comment.split(”,“);
// 使用图层索引访问数组元素
_arr[thisLayer.index - 1]

由于 JavaScript 数组的索引是从 0 开始,而 AE 图层索引是用 1 开始,所以在使用 thisLayer.index 获取当前图层索引后无必要减 1。上面这个代码搞上去就可以只显示一个名字了,然后 Ctrl+D 再复制 39 个图层,就可以让每一个图层显示一个名字。但是事情还没完,因为三百多个图层要是客户说改一下字体,或者调整一下字号,那又要改死了。所以我们又建立一个合成,名字就叫【设置和渲染】,然后建一个新的文本图层,叫做【字体样式设置】,然后在上面的代码基础上做一些调整来使所有文本都与【字体样式设置】的样式相同。

// 引用字体样式
let _fontStyle = comp('设置和渲染').layer('字体样式设置').text.sourceText.style;
// 从合成标记中引用文本,并使用中文逗号分隔符将其分割为数组
let _arr = thisComp.marker.key(1).comment.split(”,“);
// 使用源文本设置来代替直接替换内容的方法以启用字体样式
text.sourceText.style
.setFont(_fontStyle.font) // 设置字体
.setFontSize(_fontStyle.fontSize) // 设置字号
.setFillColor(_fontStyle.fillColor) // 设置字体颜色
.setText(_arr[thisLayer.index - 1]) // 使用图层索引访问数组元素

注意上面用到的 .setFont() 等方法都是链式操作,因此直到设置文本之前的换行都不能用英文分号 ; 结尾。
上面仅罗列比较常用的字体样式设置,更多的设置方法可以参考 使用表达式控件编辑文本样式和文本属性。 (adobe.com) 官方手册来按需索取,或者像下面这样直接引用所有样式!!!

// 引用字体样式
let _fontStyle = comp('设置和渲染').layer('字体样式设置').text.sourceText.style;
// 从合成标记中引用文本,并使用中文逗号分隔符将其分割为数组
let _arr = thisComp.marker.key(1).comment.split(”,“);
// 使用源文本设置来代替直接替换内容的方法以启用字体样式
_fontStyle.setText(_arr[thisLayer.index - 1]) // 使用图层索引访问数组元素

分隔符可以在【设置和渲染】中新建一个文本图层来设置,例如名为【分隔符设置'】,然后在这里引用。使用表达式的思路和编程是类似的,哪些可以统一设置的参数都可以在一个统一的合成中添加设置,然后在其它合成中引用,不过 AE 的某些内容可以在设置的时候就能预览出效果,也是不错的。

前面啰嗦这么多都是解决第一个关键难点的,源文本代码可以直接复制下面这段了,贴入代码以后别着急复制图层,下面还要解决图层的 Z轴间距问题。

// 引用字体样式
let _fontStyle = comp('设置和渲染').layer('字体样式设置').text.sourceText.style;
// 从合成标记中引用文本,并使用中文逗号分隔符将其分割为数组
let _arr = thisComp.marker.key(1).comment.split(comp('设置和渲染').layer('分隔符设置').text.sourceText);
// 使用源文本设置来代替直接替换内容的方法以启用字体样式
_fontStyle.setText(_arr[thisLayer.index - 1]) // 使用图层索引访问数组元素

三、图层文本 Z轴控制

控制 Z轴间距就是每一个图层在上一层 Z轴基础上等量递增或者递减,这个其实非常简单,你可以用加法来做,也可以用乘法来做。要统一控制增量,可以在【设置和渲染】新建一个名为【空间设置】的调整层,增加一个【表达式控制】的【滑块控制】组件,命名为【Z轴增量】,或者其它任意的名称。

如果使用每一层递进的加法,因为第一层不需要计算,这样要想只写一遍代码然后复制层,你就必须使用条件语句排除第一层。

// 从设置中索引 Z轴偏移量
let _zOffset    = comp('设置和渲染').layer('空间设置').effect('Z轴增量')("滑块");
// 从第二层开始递增
if(thisLayer.index > 1){// 获取上一个图层的位置值let _preLayerPos   = thisComp.layer(thisLayer.index - 1).transform.position.value;// 基于上一个图层进行计算[value[0], value[1], _preLayerPos[2] + _zOffset]
}else{// 第一层保持原有值value;
}

注意索引的时候如果你使用的是其它语言版本的 AE,("滑块")这个值的索引就会失效,你可以修改为你的语言版本索引别名,或者直接使用位置索引 .effect('Z轴增量')(1) 来获取,在任何语言版本中都不会出错。上面使用了 value 关键字来获取了图层原有的值,然后在原有值基础上使用偏移量计算,并不完全覆盖原有的值,一是为了方便,二是为了保留单个图层的可编辑性。AE 位置是数组数据,所以代码返回的数据也必须符合数组格式要求,修改的是数组第三个元素,即 Z 值,所以在第三个数字上做了加法。
要想忽略条件语句可以把代码写得更加简洁,只需要用单位偏移量乘以图层索引值就可以不用判断是不是第一层,而且也不需要引用上一层的位置值。还是需要注意因为 AE 图层索引是从 1 开始的,用 thisLayer.index -1 可以让第一层的乘数为 0,这样结果就不会改变了。

// 从设置中索引 Z轴偏移量
let _zOffset    = comp('设置和渲染').layer('空间设置').effect('Z轴增量')(1);
// 偏移以后的值
[value[0], value[1], value[2] + _zOffset * (thisLayer.index -1)]

上面在使用 value 关键字的时候把每个元素都用索引很麻烦的写了个遍,更简洁的方式是使用 AE 特有的矢量数学方法。

// 从设置中索引 Z轴偏移量
let _zOffset    = comp('设置和渲染').layer('空间设置').effect('Z轴增量')(1);
// 使用矢量数学方法直接让两个数组相加
add(value, [0,0, _zOffset *(thisLayer.index -1)]);

矢量数学方法可以让代码更简洁,对于值的索引修改等操作也会更方便,但是这些方法不是 JavaScript 通用方法,要想在 JavaScript 中使用类似的方法可以自己写扩展库或者寻找第三方库。AE 的这个功能真香Plus!
经过上面的操作我们已经可以让一大堆人名在 Z轴空间有序排列了,但是如果不增加平面上的偏移,镜头在其中穿梭效果是很糟糕的,下面再赋予它像粒子系统一般的随机特征。

在【空间设置】的调整层,增加一个【表达式控制】的【点控制】组件,命名为【平面离散度】。平面离散度给出了最大随机离散范围,与粒子系统中的随机效果类似,不同的是它不会移动,而是在开始时保持一个随机的固定位置。

在使用 random() 随机方法前,先要用 seedRandom(1000, true) 设置好随机模式,AE 中的 random() 方法与 JavaScript 中的 Math.random() 方法是等效的,但是 JavaScript 中没有 seedRandom() 方法;
然后在 X轴上的方向控制可以使用图层索引的奇偶判断来控制放在画面的左侧或者右侧,Y轴单纯随机偏移即可。

下面这段代码可以直接用了,它解决了第二个难点。后面我们要利用分组来扩充数量和优化运行效率。

// 从设置中索引 Z轴偏移量
let _zOffset    = comp('设置和渲染').layer('空间设置').effect('Z轴增量')(1);
// 从设置中索引 平面离散度
let _planeOffset    = comp('设置和渲染').layer('空间设置').effect("平面离散度")(1);
// 设置随机模式
seedRandom(1000, true);
// 使用图层索引的奇偶判断来控制放在画面的左侧或者右侧
let _directionX     = ((thisLayer.index)%2) ? -1 : 1;
// 创建一个储存偏移量的空数组
let _offset = [];
_offset[0]  = _directionX * _planeOffset[0] * random(); // X
_offset[1]  = _planeOffset[1] * (random() - .5); // Y
_offset[2]  = _zOffset * (thisLayer.index -1); // Z
// 使用矢量数学方法直接让两个数组相加
add(value, _offset);

贴完这部分代码就可以复制图层了,只需要 Ctrl+D 39次,复制完可以在项目中复制更多的【人名组】合成,下一步就是控制分组的 Z轴偏移。看一下这四十个图层空间分布的顶视图:

四、分组 Z轴控制

第一遍使用 Z轴偏移的时候用的方法和前面控制文本层 Z轴的方法相同,但缺陷是如果人名组中的人名数量不相同,就会出错,所以就采用了文本层中没有使用的加法方案。

先建一个合成,可以命名为【空间场景】,用单独的合成搭建空间是为了避免前面还要处理很多图层索引的偏移。然后将【人名组-1】导入到合成时间线,打开三维模式和小太阳。

第一个图层位置不动,后面每一个图层都要用文本层的单位偏移量乘以层数获得偏移量,再加上上一层的位置。

// 第一个图层位置不动
if(thisLayer.index >1){// 引用上一层let _preLayer = thisComp.layer(thisLayer.index - 1);// 用【空间设置】中的偏移量设置乘上一层图层源合成的层数,得到这一层的偏移总量let _zOffset  = comp('设置和渲染').layer('空间设置').effect('Z轴增量')(1) * _preLayer.source.numLayers;// 用上一层的位置计算当前层的位置add(_preLayer.transform.position, [0, 0, _zOffset]);
}else{value;
}

这一段代码中唯一需要解释的就是获取上一层图层源合成所包含的层数,用 source 指定源图层,然后使用 numLayers 获取图层数量即可 。

先别着急复制图层,我们还需要让每个图层只有摄像机运动到它所在的位置附近才显示,这样画面不会太混乱,看起来更加自然,渲染时计算量也能缩减很多,提高渲染效率。

在【设置和渲染】合成中添加摄像机,为了操控方便,将它绑定到一个空对象,然后导入【空间场景】合成,也是需要打开小太阳,因为不需要在这个层级中设置三维属性,所以三维开关无所谓。

在【空间设置】增加两个控件,用来控制过渡提前的时间量和过渡的速度,这样在【设置和渲染】的合成中就可以很方便的操控过渡的效果。

然后在【空间场景】合成的层中添加不透明度的表达式,引用摄像机参数和设置。

贴入下面的代码以后就可以复制图层,然后再替换图层了。

// 摄像机实际位置
let _cameraZ            = comp('设置和渲染').layer('摄像机位置绑定').transform.position[2];
// 摄像机缩放
let _cameraZoom     = comp('设置和渲染').layer('摄像机 1').cameraOption.zoom;
// 过渡提前时间量
let _transitionPre      = comp('设置和渲染').layer('空间设置').effect('组过渡提前')(1) / 10;
// 过渡速度
let _transitionSpeed    = comp('设置和渲染').layer('空间设置').effect('组过渡速度')(1) / 10;
// 过渡提前的时间量与摄像机缩放成正比
(_cameraZ + _cameraZoom * _transitionPre - transform.position[2]) * _transitionSpeed / 1000;

这部需要说的是摄像机缩放参数参与计算,它的作用是当你调节了摄像机的缩放设置,过渡效果就会跟着变化,当焦距较长时,远处的物体更大,就需要更早显示,广角镜头中远处物体很小,可以靠近它再显示,这样就不需要一次一次的手动调校参数。

做到这一步就是去修改每一组中的标记注释,填满三百多个人名就行了,回到【设置和渲染】合成调校参数和摄像机运动,再做好背景就可以渲染输出了。

彩蛋

前面说到使用粒子系统实现这个效果不现实,但是仅仅说方法的话,你首先需要一个三百个人名组成的粒子原型库,最常见的方法就是在一个文本图层中,每一帧显示一个名字,这样在粒子系统里面就可以用 sprite 发射出粒子,如果对画质要求不是很高的时候可以用这个快速方法。

因为 time 关键字获取的值是以秒为单位,就必须除以当前合成的帧率来得到当前帧索引,但是上面的代码因为 JavaScript 的彩蛋问题会出错,你可以猜一猜是什么问题,然后在评论区留言。要修正这个错误可以使用 JavaScript 四舍五入的数学方法,代码像下面这样。

// 从合成标记中获取文本并转化为数组
let _arr = thisComp.marker.key(1).comment.split(',');
// 按帧序号索引数组
_arr[Math.round(time/thisComp.frameDuration) ]

粒子的设置参考下图:

虽然粒子系统在像素上有限制,但是要做一些花样动态效果的话,也是首选了,这篇文章里面我只说到这里,因为主要是为了解决数量级和像素问题,如果有兴趣的话可以自己动手去尝试。

看,法外狂徒张三的名字多么清晰!


重要:转载需取得作者授权!

【AE表达式】300多个人名正从宇宙中飞来……相关推荐

  1. 【转载】AE表达式中英文对照

    全局对象 Comp comp(name) 用另一个名字给合成命名. Footage footage(name) 用另一个名字给脚本标志命名. Comp thisComp 描述合成内容的表达式.例如:t ...

  2. AE表达式教程 - 1、什么是AE表达式

    AE表达式是一段(一行)JavaScript代码,通过表达式可以对元素属性的值进行操作,从而得到指定的结果. 使用表达式可以自动完成一些原本需要手动k大量关键帧才能实现,甚至手动无法实现的效果. 添加 ...

  3. ae编程语言as_【微教程】从编程的思路学习AE表达式

    (这是一段引人入胜的开场白......),针对知道ae基础操作的群体的教学 授人以鱼不如授人以渔,学习表达式应该是从学会脚本语言入手,而不是通过记忆的方式记住每种效果的表达式语句.这里说的编程并非特指 ...

  4. ae制作的mg如何用到html,【教程】AE表达式常用的函数-制作MG动画必备

    我一开始接触AE是简单的套模板,后来慢慢学会自己调关键帧动画.感觉应用比较多的同样也让我很头疼的是运动.所以对表达式控制做了初步的学习.下面是一些常用的AE表达式 1.time time随着时间线的变 ...

  5. 【AE表达式】以插值方法代替关键帧

    在<[AE表达式]更好的动画时间控制>这篇文章中说过我比较不是很喜欢用关键帧来控制动画,这是有一定条件的.使用表达式控制动画更多的是强调复用性,比如你拿到我的工程文件,不管是显示的内容.尺 ...

  6. 如何学好 AE 表达式?

    --你知道世界上最难的问题是什么吗? --就是这道题. 上面的只是一个梗,不过面对知乎里或者朋友的这种发问,我真的不知道怎么回答,从上个礼拜,我就开始琢磨这个事儿:如何讲明白如何学好 AE 表达式.问 ...

  7. 【转载】AE 表达式精通大法

    本文是转自 设计树 的一篇文章,我对文章进行了重新排版,并标注了重点 原文地址 -- AE表达式精通大法 以下是正文部分: AE 表达式一直是大家学习 AE 过程中的一道坎,涉及代码网上教程资源又不多 ...

  8. [AE 表达式]函数复用技巧

    声明:我写的[AE 表达式]文章都是原创,包括文字和图片请不要以任何形式进行转载. 在 After Effects 中,表达式复用是比较麻烦的,因为表达式的作用域是当前属性,因此无法在其它位置引用,A ...

  9. ae绘图未指定错误怎么办_【教程】最全的ae表达式教学分享(实用!)表达式其实很简单...

    大部分人对ae表达式是望而却之,感觉很难,无从下手,网上的教程也是层次不齐,很难找到好的教程跟着学.于是呢,搜罗全网,整理了一份最全的最实用的ae表达式教学.也算是一个对自己的总结. 下面解决几个问题 ...

最新文章

  1. linux bash: sqlplus: command not found 错误处理
  2. 深入JavaScript与.NET Framework中的日期时间(1):基本概念与概述
  3. 调查预测:云部署将于2018年中迎来小高峰
  4. 『设计模式』电话接线员与中介者模式
  5. Teradata Fastload 使用方法
  6. SQL 拼接语句输出_一文了解Mybatis中动态SQL的实现
  7. 为什么需要字节对齐?
  8. Bit Digital反驳美国做空机构J Capital对其比特币业务的虚假指控
  9. flume和sqoop导数据的区别
  10. 机器学习 - [集成学习]Bagging算法的编程实现
  11. macOS Monterey兼容哪些Mac电脑?
  12. hadoop component summary
  13. VB语言通用基础语句
  14. 高等数学(第七版)同济大学 习题3-1 个人解答
  15. AI上推荐 之 协同过滤
  16. Caused by: java.lang.NoSuchMethodError:xxx——依赖冲突的解决
  17. 活动回顾|Apache Doris 向量化技术实现与后续规划
  18. 网络工程师十月份免费讲座
  19. matlab总路径最短问题,最短路径问题(急)
  20. P4924 [1007]魔法少女小Scarlet

热门文章

  1. vue中集成的ui组件库_Vue组件可使用Vault Flow通过Braintree集成PayPal付款
  2. 斯坦福大学-自然语言处理入门 笔记 第十四课 CGSs和PCFGs
  3. 天大博士/硕士学位论文Latex模板2021年
  4. 安卓应用在各大应用市场上架方法
  5. Arouter 跳转失败activityResumeTrigger: not whiteListed
  6. QT_QML_仿微信界面(实战)
  7. 古文选读161篇--蔡礼旭老师选
  8. [luogu P4230]连环病原体
  9. 记一个openwrt reboot异步信号处理死锁问题
  10. 算法设计与分析: 4-3 磁带最优存储问题