NEKOPARTY STUDIO

贴图反Tile算法

字数统计: 1.2k阅读时长: 4 min
2025/06/17
loading

背景

在我这个项目里,需要制作一个下雨效果。下雨期间地面需要产生被雨点击中的波纹,目前的方案是采样一个动态贴图转换为法线图来实现的。效果如下

目标效果

采样的贴图是这样的

采样贴图示例

很明显,这一张贴图上的雨点不够多,因此需要大量平铺。但是即使贴图是能无缝平铺的,在大量平铺之后会很容易观察到贴图的重复情况,观感很差。

平铺效果

因此需要对贴图做反Tile算法,也就是Antitile。做了Antitile之后,就能明显减轻这种贴图重复的情况。效果大致如下

反Tile之后

即使还是能勉强看出有分块的情况,但是总体上已经改善许多。这是因为我这个实现非常简陋,没有使用太高级的算法,那些会在本文之后提及。

最简单的实现

为了能够做到随机旋转,我们需要生成一个随机数。这里提供一个在Shader里生成一个伪随机数的函数:

1
2
3
float rand(vec2 co){
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
}

这个方法生成的随机数并不连续,无法做到类似噪声图的平滑随机效果,但是开销很小。如果有特殊的图形要求也可以采样一个噪声图来替换这个随机数函数。

之后,我们根据平铺纹理所在的UV格子来生成一个随机数。我们将目标像素的UV向下取整即可得到这个格子对应的整数坐标,然后用这个坐标去生成随机数。这样就能保证每个UV块都能获取相同的随机数。

1
2
vec2 uv_block_coord = vec2(floor(UV.x), floor(UV.y));
float rand_base = rand(uv_block_coord);

为了保证旋转结果的正确性,我们希望UV只旋转90度的整数倍。因此我们把这个(0,1)的随机数转换为0,1,2,3这四个整数之一。

1
float rand_result = floor(rand_base * 4.0);

这样就为每个UV块生成了一个0-3之间的整数。然后只需要把它转换为角度,再将UV旋转对应角度即可。如果需要的话,再加入一个随机的水平翻转就能进一步增加复杂度。这样就能得到文章开头的效果,对于我们这个项目的要求是够用了。

但很明显可以看出,我们使用的贴图边缘是空的,也就是说不论我怎么旋转边缘都能随便拼接。当然,对于我们这个需求来说已经是非常完美的方案了。但是对于一个正常的无缝贴图,这种方式显然就会导致边缘无法无缝拼接。所以我们需要使用更先进的算法来解决这个问题。

一些非常简单的其他办法

我们现在需要消除下面的贴图Tiling情况。

Tiling Example

如果不去做那些比较复杂的Antitile算法的话,也能用一些比较简单的方式去减少贴图平铺重复的感觉。基本就是传入一个噪声图,用另一个贴图去和当前贴图混合,然后在HSV色彩空间做一些调整等。几个方法按需求配合起来使用即可。

使用噪声图的话,还有另一个不需要复杂算法的简单方法。这个方法的核心思路依然是分块然后随机旋转UV。但是可以使用Vonoroi算法(或者其他更快的算法)进行不规则分块,而不是每块都是规则的方形。然后每块Vonoroi格子内是随机的函数值,根据这个值旋转UV即可。

这样会产生另一个问题,就是旋转UV必定会在每个块的边缘产生明显的接缝。这就是这个方法的另一个核心思路。

接缝

根据相同的Vonoroi Seed,生成一个由每个点到Vonoroi块边缘距离决定的新噪声图。然后根据这个噪声图,将旋转后采样到的贴图与原始贴图混合即可消除接缝。

结果

基于Godot的噪声生成器能够非常简单的完成这个工作。当然这个就应该这么做,因为使用GPU生成Vonoroi图实在是过于昂贵,直接采样预生成的图片要快一到两个数量级。

结果

Stochastic Tiling

直接使用Stochastic算法去采样2D贴图,代替原始的UV采样即可。由于代码也是抄的就不贴在这里了。说实话,效果比我那个半吊子方法好不少。

SAntitile

CATALOG
  1. 1. 背景
  2. 2. 最简单的实现
  3. 3. 一些非常简单的其他办法
  4. 4. Stochastic Tiling