Alina Ledeneva 探索了一种基于材质的伪球体技术,该技术使得无需自定义球形 UV 映射即可直接使用常规的平铺纹理。
简介
如果我说这颗星球实际上只是一张带有常规平铺纹理的平面卡片,你会怎么想?场景中并没有球形网格,也没有预先准备好的球形贴图。几何体从未发生弯曲或变成半球体。所有的体积错觉完全由材质实现:World Position Offset 始终保持平面卡片朝向摄像机,而经过变换的 UV 则让图像看起来像是映射在了一个曲面上。这一效果出自 Kamishibai Interactive 正在开发的单人叙事驱动射击游戏《TYBALT》的研发工作,该作灵感来源于《Destiny》《Marathon》《Witchfire》《SOMA》与《System Shock》。
星球视觉表现的目标之一,是让美术人员能够直接使用程序化纹理和平铺纹理,而无需将其转换为球形贴图,也无需为每个变体专门准备球形 UV 布局。在探索如何让该工作流更加灵活的过程中,我开发了这种基于材质的技术方案。本文将详细拆解该实验背后的材质逻辑与 Blueprint 逻辑。
在材质中创建伪球体
为了对纹理进行扭曲,我使用了半球的立体投影(stereographic projection)。首先,将卡片的 UV 围绕中心点重新映射,并将其视为朝向摄像机的半球的 X 和 Y 坐标。基于这两个坐标,材质会重建第三个坐标:即该点在半球上应有的深度值。
随后,这个 3D 半球位置会被立体投影到用于采样纹理的 2D 坐标中。靠近中心时坐标变化微小,但越往边缘扭曲越强。在边缘区域,这种处理会压缩图像,使其看起来像是包裹在一个背离观察者的凸曲面上。网格本身并未发生形变,材质仅改变了用于采样常规平铺 2D 纹理的坐标。仅此一点,就足以营造出基础的体积错觉。
World Position Offset 的作用
World Position Offset 只有一个职责:确保平面卡片始终朝向摄像机。材质会利用摄像机方向,在对应的摄像机坐标系中重建顶点位置。即使星球在画面中偏离中心,卡片依然保持平整并与屏幕平行。我在文章《Material-Based Mesh Rotation in UE5: Math and Logic》中更详细地介绍过这项技术。
处理纹理移动
摄像机需要围绕行星自由环绕,同时纹理需表现得如同实际附着在三维表面上。行星自身也会旋转,因此系统必须对摄像机的移动与行星朝向的变化做出一致的反应。
远离极点时,这种运动可以较容易地分解为两个 UV 偏移量。沿赤道的运动可用水平偏移表示,而沿经线的运动则表现为垂直偏移。然而靠近极点时,仅靠偏移已不够用,图像还需要在平面上进行旋转。
纹理运动采用偏移与旋转相结合的方法。垂直偏移负责处理沿经线的移动。赤道附近,水平移动也主要由偏移处理;但随着摄像机接近极点,该偏移的影响会逐渐减弱,取而代之的是围绕计算出的枢轴点进行旋转。
为何变换必须累积
摄像机可以从 A 点沿两条不同的路径移动到 D 点。
第一种情况中,它直接沿赤道移动。整个路径上主要由水平偏移完成工作,到达 D 点时我们看到的是纹理的特定区域。第二种情况中,摄像机首先沿经线从 A 移动到 B。这部分主要由垂直偏移表示,因此纹理会逐步平移。随后它沿靠近极点的上方纬线从 B 移动到 C。在此阶段,围绕可见极点位置的旋转开始占据主导。最后,摄像机沿另一条经线从 C 向下移动到 D。
尽管最后一段再次对应垂直移动,但纹理已被路径的前半部分发生平移和旋转。新的垂直移动不能简单地直接应用于原始 UV 坐标,而必须在已旋转的坐标系中进行叠加,同时保持当前的纹理位置不变。
两条路径最终都使摄像机停留在同一个 D 点,但显示的却是纹理的不同部分。这并不是球体的真实三维旋转,而是基于可平铺二维纹理构建的一种近似方案。
由于最终的纹理状态取决于路径,系统必须记住当前状态,并将每次新变化叠加在其之上。材质无法在帧间保留此类状态,因此我将流程拆分为蓝图与材质两部分。蓝图负责记录移动历史,材质则接收最终累积的二维变换矩阵。
确定纹理移动
蓝图存储两类信息。首先是累积的纹理偏移与旋转数据。其次是基于上一帧相机相对于行星的位置数据:包括相机位于赤道上方或下方的距离,以及相机正朝向行星的哪一侧。在每次更新时,蓝图会将当前状态与上一状态进行对比,判断视角的变化量,并将对应的偏移或旋转累加到当前的累积变换中。
蓝图首先计算从行星中心指向相机的方向向量,并将其转换至 Actor 的局部空间。这使得纹理的移动不仅取决于相机位置,还受行星当前朝向的影响。随后,该方向向量会被分解为纬度与经度分量。
纬度的计算基于相机方向相对于局部 +Z 轴的上仰或下俯程度。当前纬度与存储纬度的差值将作为垂直方向的纹理偏移量。
对于经度,首先将相机方向投影至赤道平面,即剔除其沿极轴的分量。剩余的水平方向指示了相机正观察行星的哪一侧。蓝图会将其与上一帧的方向进行对比,并测量两者绕局部 +Z 轴的夹角。该夹角的正负号决定移动方向,绝对值则代表经度的变化量。
靠近极点时,赤道投影会变得极短,而在极点处则会完全消失。所有经线在此交汇,导致经度无法再被可靠地计算。在该区域,系统会暂时停止更新经度,转而保留上一次有效的赤道方向。维持最后一次有效方向可防止相机穿越极地时出现纹理随机跳变。
经度变化的应用方式会随纬度不同而有所差异。远离极点时,它主要产生水平方向的偏移。随着相机逐渐靠近极点,该偏移的影响会逐渐减弱,经度分量也会平滑过渡为纹理旋转。
垂直偏移符号
在当前版本中,局部 +Z 轴被用作南北极轴,而局部 +X 轴定义了初始经度方向。当相机环绕行星顶部或底部运行时,其在屏幕空间中的向上方向可能会与行星的局部 +Z 轴反向。若纬度变化保持原有符号不变,纹理便会开始向错误的方向横穿屏幕。
蓝图会将 CameraUp 向量与行星局部 +Z 轴的世界空间方向进行比较。当两者方向相反时,系统会对垂直偏移量进行取反。此举可确保相机跨越极点后,沿经线的运动在视觉上保持连续。
寻找旋转枢轴
为了使纹理旋转效果正确,系统需要确定极点位于卡片上的具体位置。通过比较从行星中心指向摄像机的方向与行星的 Local Z 轴,系统可判断摄像机处于哪个半球。据此,系统会选定可见极点的方向,即纹理应围绕其旋转的北极或南极。
选定的极点方向首先会被转换至摄像机坐标系。该坐标系直接由摄像机朝向构建,且始终与屏幕保持平行。计算所得的点即为极点在屏幕上的对应位置。随后,系统会沿用材质在生成伪球面 UV 时所采用的立体投影原理,将该点投影至卡片的二维空间内。
最终得到一个二维坐标点,表示可见极点在卡片上的实时位置,该点即作为纹理旋转的枢轴。此枢轴并非固定不变,而是会随着摄像机移动或 Actor 旋转持续重新计算,同时综合考量已累积的偏移量与旋转角度。
合并偏移与旋转
新增的偏移量与旋转角度无法简单地独立叠加至历史数值上。若纹理此前已发生过旋转,后续的偏移方向必须顺应其当前的朝向。此外,可见极点的位置也会因摄像机位移、Actor 旋转以及已累积的偏移量而发生动态变化。
系统会将所有的运动历史统一存储为一个累积的二维变换。该变换由相对于 UV 原点的总旋转角度与总平移向量组成。举例而言,纹理可围绕任意点 A 旋转 30°。等效地,该结果也可表述为:先围绕原点 (0, 0) 旋转 30°,再施加一段平移变换,使其回到完全相同的最终位置。
首先,系统会在现有旋转与平移生效后,定位当前极点在纹理内部的坐标。接着,计算当前帧所需的微小经纬度变化量。新偏移量最初基于原始 UV 轴向进行计算。由于纹理可能已存在旋转状态,该偏移方向需先按累积角度进行旋转变换,随后再累加至总平移向量中。若部分经度变化需在极点附近转化为旋转操作,系统则会同步增加微小的旋转角度并修正平移参数,以确保旋转严格围绕计算得出的极点位置进行。
每次更新完成后,系统均会将结果以统一的格式重新存档,并将最终状态传递给材质:
- RotationTurns:围绕 UV 原点累积的总旋转角度;
- Translation:最终的二维偏移总量,既包含常规的纹理位移,也涵盖因历次围绕不同极点位置旋转所引入的附加平移。
简化版方案
该效果由多个独立层级构成:World Position Offset 用于保持平面卡片始终朝向摄像机;材质中的 UV 变换可营造体积感;蓝图负责处理连续的表面运动,它会追踪摄像机移动与 Actor 旋转,记录累积的表面状态,并将 LocalOffset 和 RotationTurns 传递给材质。
此外我还提供了一套更简化的版本,仅使用水平与垂直偏移量。该版本不累积状态,不会在极点附近旋转纹理,也无需额外的蓝图逻辑,所有运动计算均直接在材质中完成。
简化版适用于远景背景星球、装饰物件、UI 场景等摄像机活动范围有限且从不经过极点的场景。当你只需要微弱的表面流动感,且不要求纹理严格保留历史轨迹时,它同样非常适用。
若摄像机需围绕物体自由环绕、接近极点,或星球自身旋转时必须保持纹理运动的连续性,则完整版更为合适。简化版虽然灵活性较低,但配置简单得多,且完全封装在材质内部。
该效果的适用场景
- 纹理无需预先制作成球面贴图。常规球体要求表面图像严格匹配其 UV 布局,而本方案允许材质直接使用任意普通平铺 2D 纹理。无需转换为等距柱状投影图,也无需针对极点与经线做特殊适配。你可以直接复用现有的平铺纹理、程序化图案,或快速搭建的材质作为星球表面。
- 摄像机仍可围绕物体环绕并从不同角度观察。随着纹理发生位移与旋转,画面会呈现出图像真实附着于 3D 表面的错觉。
- 该效果通过移动纹理来模拟星球可见面的变化,但并未生成真实的球体几何结构。因此最终纹理状态取决于摄像机的运动历史。两条不同的运动路径可能使摄像机到达同一位置,却显示纹理的不同区域。这种路径依赖性正是底层 2D 模拟机制的一部分。
