查看原文
其他

在Android地图上实现一个雨雪特效吧

Zuo 郭霖
2024-07-22


/   今日科技快讯   /

近日,谷歌将在今年秋季发布的 Pixel 9 系列迎来了新消息,包括处理器规格和跑分成绩。

据悉,Pixel 9、Pixel 9 Pro 和 Pixel 9 Pro XL 将搭载全新 Tensor G4 芯片,这将是最后一款采用部分谷歌定制方案的 Tensor 处理器。从 2025 年 Pixel 10 系列开始,谷歌将完全自主设计 Tensor 芯片(代号 Tensor G5)。而且 Tensor G5 将采用台积电的第二代 3nm 制程工艺生产,而非三星代工的 4nm 制程。

/   作者简介   /

大家好,新的一周继续努力工作!

本篇文章转自Zuo的博客,文章主要分享了如何使用如何用Android开发雨雪特效的动画,相信会对大家有所帮助!

原文地址:
https://juejin.cn/post/7372818506533355529

/   前言   /

目前主流的地图都附带了天气特效,效果如下:


除了贴图方式,是否还有别的方案?特此简单记录。

/   雪景方案   /

相比于直接贴图,可以根据地图信息和角度来绘制天气效果,以达到更好的真实感和运动感。但同时渲染又不能过于复杂而太占用性能,所以还需要合理约束渲染复杂度。

以雪景为例,目标为:

  • 缩放地图会有相机的探/缩视觉效果。
  • 雪景缩放中心与地图缩放中心一致。
  • 地图视角改变,看到的雪景视角也会跟随改变。
  • 地图的平移,雪景也会跟随平移。


一种简单思路:在广袤的地图上源源不断生成粒子,那么只需要设置相机参数就可以看到具有真实感的雪景。而缺点也很明显:地图越大,则粒子数越大,为了达到视觉效果所需粒子不计其数,性能无法承受。

另一种思路:是采用循环粒子。让粒子只诞生在 [-1,1] 的一个三维正方体里。相机始终在这个正方体的内切球表面上看向球心。一切地图上的变化如缩放平移旋转等,都以等效的方式作用在这个立方体里,营造出类似雪粒子遍布整个地图的效果。

雪生成

因为要求让所有雪粒子分布在 [-1,1] 的正方体里,所以可以让所有雪粒子诞生都发生在与地面平行的两个面中的上方的一个面。因为相机始终在该正方体的内切球里,而且相机移动的维度不会移动到内切球的赤道面及以下,所以只需要简单设置相机的FOV恰好为整个对面,就可以保证相机看不到雪的诞生,使得雪像是从遥远的天空上慢慢飘落下来一样。


雪降落

雪的降落就是依据一个简单的重力系统,给每个雪粒子赋予一个水平面上的一个初速度和垂直向下的初速度,然后基于向下的重力进行匀变速运动。

后来索性改成匀速运动,不让重力进行作用了,因为雪花自身受到与重力相当的空气阻力。

最后每帧更新粒子们的坐标。

旋转

旋转就是调整相机位置。因为相机始终在单位球壳上看向中心。即始终围绕原点旋转,因此其旋转矩阵一定是一个正交矩阵。即自身转置等于自身的逆,这个性质后面会用到,利用 mapstatus 里的 viewMatrix,只选择其中左上角3X3的正交矩阵部分,对相机坐标在单位球壳上进行调整即可。

景深

景深效果是因为透视变换。指定一个相机的FOV,就会使得相机看到的画面有近大远小的视觉效果。


地图缩放与探/缩视觉

循环粒子:即保证粒子下标始终保持在 [-1,1],如果超出范围则取模。

探/缩视觉效果:放大地图就像是往前伸头,缩小地图就像是往后缩头,利用相似三角形可以计算出边长放大两倍时,相机刚好从球壳上一点移动到球心。


相机向前移动了 1/2 个单位长度,带着球壳移动 1/2,而雪粒子分布区域还在方形区域内。所以利用循环粒子坐标,将相机背后的粒子调整到现在能看到的粒子背后。

这里说的单位长度其实是正方形边长(但正方形边长为 2 ),因此,放大两倍,等效于所有雪粒子坐标加上 1/2 个单位长度后取模。


如果放大 4 倍,相机就会下移 3/4 个单位长度,放大 10 倍就会下移 9/10,可以发现随着倍数增大,移动的距离增量越来越小,会发现放大 10 倍和放大 1000 倍调整后的雪景几乎没有区别,这并不是我们想要的视觉效果。

因此在放大上进行了优化。以两倍为基准,放大两倍是下移 1/2 个单位长度,那么放大 4 倍就是下移 1 个单位长度,也就是不变。

假设放大 k 倍。k= 4 ^ t * r,r满足r∈[1,4)。

如果 r 小于 2,那么就是直接用相似三角形去计算下移多少距离,即下移 (r-1)/r 单位长度。

如果 r 大于等于 2,则先让相机下移 1/2 个单位长度,然后让 r = r/2,回到上一步。

如图:


其中横坐标为放大倍数,纵坐标为坐标偏移量。可以看出黑色线条放大效果会更加具有变化性,更加接近一条log4曲线。

这里要注意一个问题是,若雪粒子等效移动 k 个单位长度,是沿着球心——相机连线方向移动的,也就是说需要依据ViewMatrix 计算出移动的方向向量,然后再去乘上 k 才得到雪粒子的偏移。

方向计算

设粒子移动的向量为 v,粒子当前坐标为 x,旋转矩阵为 V。则移动后为 x+v,旋转后的坐标为 V * (x + v),根据上述要求,则 V * (x + v) = V * x + [0, 0, k]。

即求出 v = V ^ − 1 ∗ [0,0,k] = V ^ T ∗ [0,0,k](因为V为正交矩阵),而对于缩小来讲,也是类似的。即想办法让放大了  k 倍的变化,与缩小为 1/k 对应的变化相反即可。

平移

平移的检测是通过记录当前屏幕中心点对应的地图位置的墨卡托坐标。在相邻两帧之间,如果地图中心点坐标发生了变化,例如上一次是 A,这一次是 B,可以认为相机移动向量为 B-A。

但是考虑到地图显示等级,大倍率视角下移动相同屏幕距离和小倍率视角下移动相同屏幕距离,两次墨卡托坐标差值是不同的,是正比于缩放比例的,所以我们需要依据 (B-A) / ratio 来进行相机的平移。

相机平移类似于缩放,等效调整雪粒子坐标后取模。只是平移时的变化向量一定沿水平方向。

总结

总的来说,在相邻两帧之间,通过去比较上一次与这一次的地图中心墨卡托坐标、上一次与这一次的ratio,可以计算出来粒子需要进行多少偏移,即平移向量和缩放向量。

对粒子进行偏移之后,让粒子下落一些距离,然后将坐标取模调整到 [-1,1]。将坐标信息传入到 shader 中,在shader 中进行 MVP 变换,此时的 Model 矩阵是单位矩阵,View 矩阵就是 ViewMatrix 中的正交分量, Projection 矩阵就是自己指定FOV对应的透视矩阵。

/   雨景方案   /

好了,接下来说下雨景的实现思路,效果见下:


之前是打算将模型抽象成一套黑盒,然后对于雨和雪,只需要改变对粒子的贴图即可。但实际操作后发现自己忽视了粒子系统的问题。

粒子系统认为每个点仅仅是一个点,不应该有三维的形状。粒子系统只会将粒子的顶点坐标投射到屏幕像素上,并以该点为中心渲染一个小正方形。

对于雪花来讲,如果直接用一个白色的小球来代表的话,不论从什么角度观察,都是一个白色的圆,所以是各向同性的。所以只需要在小正方形里渲染一个小白圆就可以。

但是对于雨滴来讲,它是一个细长的结构,所以从天空向下看雨滴和平视雨滴看到的是不一样的。所以需要额外设计来保证雨滴具有三维形状。

根据调研,为了使粒子有三维结构:

  • 第一种是对粒子进行贴图,但是贴图不是直接贴在小正方形上,而是让贴图乘上 MVP 矩阵,然后将透视的结果绘制在小正方形里。这种算法需要将 MVP 的 matrix 和 texture 都传入到 fragment shader 中,相当于每一个小像素都需要去变换,有大量的重复计算。
  • 第二种是拿数学公式模拟雨滴,可以直接在 vertex shader 中计算出来雨滴应该绘制的形状,然后将参数传入到 fragment shader 中,将对应的图形绘制在小正方形里。

显然第二种方法更好些,效率高,还能复用雪景模型盒的大部分代码。

椭圆方程

以粒子的空间坐标为椭球的球心,在 vertex shader 中进行计算时,根据该球心坐标,在 Z 轴值上加减 L/2 (半焦距,设定好的固定常数)得到两个焦点的空间坐标,将这两个焦点投影到屏幕上。

由于绘制是以球心在屏幕上的投影坐标为中心的正方形,则需要根据这两个投影后的焦点在屏幕中的位置来确定所绘制的正方形大小。


将上下焦点的投影坐标传入到 fragment shader 中,再以这两点为焦点用椭圆方程绘制一个扁平的椭圆来表示雨滴。

这里要注意一点,这样绘制的椭圆不一定就是该椭球在屏幕上的精确投影,只是当该形状及其狭长时,这样的近似会极其接近,足以满足视觉效果。


考虑风向

在 Z 值上加减 L/2 是默认雨滴是竖直方向的,如果有风时,雨滴会有一定的偏向。

将风抽象成一个模长受限的水平向量(模长最大值可以被设定),然后让风与向量 (0,0,1) 的夹角等于雨滴与 Z 轴的夹角即可。


总结

雨景部分相比于雪景主要就是解决了粒子各向异性问题。既然贴图的路不好走,又不想抛弃粒子系统去渲染大量三维物体,于是就用简易的数学公式来模拟一个三维雨滴形状,从而保证了渲染效率基本不受影响。

而且在雪景的模型盒系统的支持下,雨景还可以有俯视视角、平移、旋转、缩放等等类似的景象,具有良好的视觉效果。

推荐阅读:
我的新书,《第一行代码 第3版》已出版!
原创:写给初学者的Jetpack Compose教程,用derivedStateOf提升性能
船新版本之学习屏幕刷新机制引发的画面卡顿监控与优化的思考

欢迎关注我的公众号
学习技术或投稿


长按上图,识别图中二维码即可关注
继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存