余额宝年年有余中的 lottie 摄像机

简介: 设计师在用 AE 设计 lottie 动画的时候,在想要实现某个方向上的透视变换的效果的时候,会使用到 AE 中的一个叫做摄像机的东西。摄像机沿着一定的轨迹运动,就能够获得一些类似 3D 的效果。今年的余额宝年年有余活动中,设计师就利用了这个特性,类似于下面这个示意。 不管是 lottie-web 还是 lottie-pixi 都是无法支持这个特性的,也没有在社区中看到有相关的解法。这次年年有余中,我们利用 oasis 引擎,使设计师设计的摄像机能够被正确的渲染出来。

图片.png

背景


设计师在用 AE 设计 lottie 动画的时候,在想要实现某个方向上的透视变换的效果的时候,会使用到 AE 中的一个叫做摄像机的东西。摄像机沿着一定的轨迹运动,就能够获得一些类似 3D 的效果。今年的余额宝年年有余活动中,设计师就利用了这个特性,类似于下面这个示意。 不管是 lottie-web 还是 lottie-pixi 都是无法支持这个特性的,也没有在社区中看到有相关的解法。这次年年有余中,我们利用 oasis 引擎,使设计师设计的摄像机能够被正确的渲染出来。

图片.png

设计师在设计这个效果的时候,是将不同的元素放置在 z 轴方向上,然后通过移动摄像机转换视角,达到了一种不断前进的动画效果。如果把上面的动画旋转一下,从 x 轴正方向往负方向看去,可以看到是下面这个样子。不同的元素摆放在 z 轴中的不同位置。

image.gif

01.gif


而摄像机(可以当成是一个眼睛 👀 ),则随着时间的流逝在空间中做位移,就能在时间序列中以不同的视角“拍”下当前的每一帧,从而形成具有透视效果的动画。


概念


下图中的红色箭头展示了摄像机的一个运动轨迹,沿着红色的轨迹运动,可以得到一个类似于文章最开头的动图的效果。

图片.png

在 AE 中,也可以看到摄像机运动的具体轨迹。

001.gif

摄像机在每一帧都在“拍照”,这个“拍照”,是一个透视投影。透视投影是为了获得接近真实三维物体的视觉效果而在二维的画布平面上绘图。简单来说,就是近大远小

这里放出经典的透视投影的模型图。

图片.png

然而物体看上去的大小,除了与它离眼睛的远近有关,还和物体本身的尺寸有关。视角fieldOfVIew )可以取代上述两者,直接比较物体看上去的大小。上面的模型图中,近裁剪平面(nearClipPlane),远裁剪平面(farClipPlane)和视角会形成一个视椎体。在视椎体内部的物体是会被投影到摄像机里的,也就是会渲染在画布上,而视椎体外的物体则会被裁剪。


为了模拟人眼近大远小这一个特性,可以利用透视投影来完成。有关透视投影的基本数学推导,在网上可以找到很多描述,这里就不提了。具体的细节可以参见维基百科上的描述。


回到 AE 中,在使用 AE 设计的有摄像机的动画中,我们至少需要依靠下面这三种数据,来描述 AE 中摄像机的运动及其“拍”下的的每一帧。


  1. 摄像机本身的位移,控制了当前摄像机在哪,也就是 👀 在哪;
  2. 摄像机朝向的角度(或看向的点),也就是 👀 朝哪里看;在 AE 设计中,可以给摄像机设置旋转角度,也可以给摄像机设置目标点,旋转角度和目标点是互斥的,如果都设置了,目标点会无效,以旋转角度为准。由于 oasis 引擎提供了  transform.lookAt 方法,因此可以推荐设计师直接给摄像机设置目标点,如果设计师设计的是旋转角度,则需要在代码中计算一下目标点。
  3. 视角,控制了可视范围有多大。


如下图所示。

图片.png

实现


在 AE 中,使用 bodymovin 插件,导出的摄像机的 json 描述,与导出其他的类型的元素的描述是一样的,只是包括最基本的位移旋转等属性,而缺少了摄像机本身的一些属性。

// bodymovin 导出的摄像机的 lottie json 描述
{
      "ddd": 0,
      "ind": 78,
      "ty": 13,
      "nm": "摄像机",
      "sr": 1,
      "pe": {
        "a": 0,
        "k": 1041.667,
        "ix": 1
      },
      "ks": {
        "a": {
          "a": 1,
          "k": [
            {
              "i": {
                "x": 0.667,
                "y": 1
              },
              "o": {
                "x": 0.333,
                "y": 0
              },
              "t": 104,
              "s": [
                401.104,
                928,
                4495
              ],
              "e": [
                393.104,
                360,
                4615
              ],
              "to": [
                0,
                0,
                0
              ],
              "ti": [
                0,
                0,
                0
              ]
            },
            {
              "t": 150
            }
          ],
          "ix": 1
        },
        "p": {
          "a": 1,
          "k": [
            {
              "i": {
                "x": 0.667,
                "y": 1
              },
              "o": {
                "x": 0.382,
                "y": 0
              },
              "t": 0,
              "s": [
                400,
                900,
                -970.667
              ],
              "e": [
                400,
                900,
                4000
              ],
              "to": [
                2.698,
                -1.641,
                54.22
              ],
              "ti": [
                0,
                0,
                0
              ]
            },
            {
              "t": 150
            }
          ],
          "ix": 2
        },
        "or": {
          "a": 0,
          "k": [
            0,
            0,
            0
          ],
          "ix": 7
        },
        "rx": {
          "a": 0,
          "k": 0,
          "ix": 8
        },
        "ry": {
          "a": 0,
          "k": 0,
          "ix": 9
        },
        "rz": {
          "a": 0,
          "k": 0,
          "ix": 10
        }
      },
      "ip": 0,
      "op": 450,
      "st": 0,
      "bm": 0
    }

而像下图中的 AE 设置中的一些视角等信息,是缺失的。因此,在没办法获取到这些信息的情况下,我们只好手动将我们这些缺失的信息写入。为了更好的支持 lottie 中的摄像机,需要推动 bodymovin 来修改他们的插件,或者我们给他们提 pr(TODO)。

图片.png

oasis 引擎是一个 ECS(Entity-Component-System) 架构的引擎,因此对于我们业务来说,我们的 Lottie 动画是一个 Entity,摄像机(Camera)也是一个 Entity ,我们需要给摄像机添加一个 Component 来控制相机的位置和目标点。

在具体的实现中,创建了一个 CameraScriptComponent,在 onStart 的时候拿到和设置相机的相关属性,在 onUpdate 的时候去更新相机当前的位置和目标点即可。

import { Script } from 'oasis-engine';
class CameraScript extends Script {
  onStart() {
    // 获取到 lottie json 中的相关属性
    // 并设置相机的相关参数,包括投影透视、fov、关闭裁剪、远近裁剪面等数据
    // ... 
  }
  onUpdate(deltaTime: number) {
    // 设置每一帧相机的位置和目标点
    // ... 
  }
}


上面的 json 中,"p"节点下的代表的是相机的位置描述,与其他元素的位置描述一样,它是用两条贝塞尔曲线来(位置曲线和速度曲线)描述的,通过这些曲线的起点终点和控制点获取到每一帧的摄像机位置之后,通过调用:

camera.transform.setPosition(x, y, z);

来更新相机的位置。

"a"节点下的代表的是相机的目标点描述,与"p"节点下位置描述一样,可以计算获取到每一帧的摄像机的目标点的坐标,然后调用

camera.transform.lookAt(new Vector3(x, y, z));

便可以更新相机的目标点。

需要注意的是,lottie json 中的坐标,与 oasis 中的坐标是不一样的,因此在设置的时候,需要转换一下。lottie json 中的坐标是的原点 (0, 0) 是画布的左上角,而 oasis 中的坐标的原点 (0, 0) 是画布的中心。

// lottie 的坐标 转换成 oasis 里面的坐标
function convertCoords(vector3: Vector3, w = 750, h = 1624, pixelsPerUnit = 128) {
  const result = vector3.clone();
  result.x = (result.x - w / 2) / pixelsPerUnit;
  result.y = -(result.y - h / 2) / pixelsPerUnit;
  result.z /= pixelsPerUnit;
  return result;
}

为了能让 z 方向上所有的元素,都能出现在摄像机的视野范围内,需要给摄像机设置远近裁剪面。设计师在 AE 中设计摄像机的时候,已经把摄像机和每一层的元素放置好了,因此,不需要给远近裁剪面设定一个唯一的值,只需满足一定的条件就行了,确保裁剪面的范围能够包括下所有的元素。也就是

0 < nearClipPlane < abs(距离相机最近的图层 - 相机的位置)
farClipPlane > abs(距离相机最远图层 - 相机的位置)

有时候,一个 lottie 是可以用不同的速度播放的,在年年有余的小游戏中,也允许通过同时控制 lottie 的播放速度与相机的运动速度来展示出慢速或快速的动画。由于我们是给一个 Camera增加了一个 Component来更新相机的位置,因此只需要在 Script中的 onUpdate里面,获取到当前的 LottieAnimation的速度,再同步地算出当前相机的位置,更新给相机即可。


需要注意的一点是,摄像机“拍照”的近大远小的特性,会造成在远处的图片异常的大。在将 lottie 中的图片合并成雪碧图的时候,很容易就超过了 2048x2048 的大小,此时,需要将雪碧图拆分成多个(或者使用 base64的方式),否则会造成闪退。


参考链接

oasis-engine: https://github.com/oasis-engine/engine

相关文章
|
Kotlin
Kotlin中的步长rangeTo downTo until step ..
Kotlin中的步长rangeTo downTo until step ..
150 2
|
数据可视化 开发工具 git
Sourcetree
Sourcetree 是一款免费的 Git 和 Hg 客户端管理工具,同时支持 Mn 版本控制系统。它为 Windows 和 Mac 用户提供了可视化的 Git 管理界面,使得用户能够轻松地创建、克隆、提交、推送、拉取和合并等 Git 操作。Sourcetree 还具有强大的代码对比功能,可以方便地查看和合并代码更改。
481 1
|
消息中间件 存储 API
|
消息中间件 Android开发 开发者
🔍深度剖析Android内存泄漏,让你的App远离崩溃边缘,稳如老狗!🐶
【7月更文挑战第28天】在 Android 开发中,内存管理至关重要。内存泄漏可悄无声息地累积,最终导致应用崩溃或性能下滑。它通常由不正确地持有 Activity 或 Fragment 的引用引起。常见原因包括静态变量持有组件引用、非静态内部类误用、Handler 使用不当、资源未关闭及集合对象未清理。使用 Android Studio Profiler 和 LeakCanary 可检测泄漏,修复方法涉及使用弱引用、改用静态内部类、妥善管理 Handler 和及时释放资源。良好的内存管理是保证应用稳定性的基石。
362 4
|
存储 安全 算法
MD5的日常实践应用:确保数据完整性与基础安全校验
**MD5概览:** 作为过时但仍然流行的散列函数,MD5用于生成数据固定长度的散列,常用于文件完整性校验和非安全密码验证。虽因易受碰撞攻击而不适于安全用途,但在低敏感场景下仍有应用。例如,Python代码展示如何计算文件MD5校验和及模拟MD5密码验证。不过,对于高安全需求,推荐使用SHA-256等更安全的算法。【6月更文挑战第17天】
1119 1
|
消息中间件 传感器 监控
钉钉 ANR 治理最佳实践 | 定位 ANR 不再雾里看花
钉钉 ANR 治理最佳实践 | 定位 ANR 不再雾里看花
680 0
钉钉 ANR 治理最佳实践 | 定位 ANR 不再雾里看花
|
人工智能 监控 物联网
医疗大模型:数据+知识双轮驱动实现医学推理、医患问答、病历自动生成、临床决策,为未来医疗服务提供全新可能性
医疗大模型:数据+知识双轮驱动实现医学推理、医患问答、病历自动生成、临床决策,为未来医疗服务提供全新可能性【2月更文挑战第3天】
医疗大模型:数据+知识双轮驱动实现医学推理、医患问答、病历自动生成、临床决策,为未来医疗服务提供全新可能性
|
供应链 搜索推荐 API
1688商品在跨境电商中的api应用场景
随着全球电子商务的快速发展,跨境电商已经成为了一个不可忽视的市场。在跨境电商中,商品信息的获取和整合是至关重要的。1688商品API作为一种便捷、高效的数据接口,在跨境电商中得到了广泛应用。本文将详细探讨1688商品API在跨境电商中的应用场景,以期为相关从业者提供参考。
|
设计模式 前端开发 Java
从Langchain到ReAct,在大模型时代下全新的应用开发核心
什么是ReAct框架关于什么是langchain,在使用langchain的过程中,大模型给人留下最深刻的印象无疑是Agent功能。大模型会自己分析问题,选择合适的工具,最终解决问题。这个功能背后的原理就是来自ReAct框架。ReA
19922 2
从Langchain到ReAct,在大模型时代下全新的应用开发核心
|
传感器 运维 网络协议
面向 IPv6 的淘宝 App 网络技术与体验升级
面向 IPv6 的淘宝 App 网络技术与体验升级
8156 0