欢迎继续一起踏上 The Journey of Chaos, 关于 Shader 生成技术的一些基础性学习。噪声会分为以下几篇内容学习
- 随机函数与白噪声
- 值噪声 ValueNoise
- 柏林噪声 Gradient Noise
- 多维柏林噪声
- 柏林噪声优化 Simplex Noise
- Cell Noise
- SmoothVoronoi 与VoroNoise(本篇)
- CurlNoise
- FBM深入
Voronoise 是 Inigo Quilez 在传统 Voronoi 图(泰森多边形)基础上提出的一种改进算法,旨在通过引入噪声函数与距离混合策略,生成更自然、更具艺术性的程序化纹理。其核心目标是通过数学优化,在保持 Voronoi 图空间划分特性的同时,消除机械化的硬边界,赋予图形“有机感”与连续性, 这篇主要是理解他的这个 Shader与Voronoise文章 iquilezles.org/articles/vo… 和平滑Voronoi iquilezles.org/articles/sm… 。通过查看他的文章,可以一窥超级大神再有了直觉之后,通过起数学思维,如何扩展成一个实现。大师的特征就是他既充满想象力,同时其直觉得到了严格理论的坚定支撑。
SmoothVoronoi
让我们在回顾一下经典的 Voronoi算法
float voronoi( in vec2 x )
{
ivec2 p = floor( x ); // 获取当前点所在的网格单元
vec2 f = fract( x ); // 获取当前点在网格单元中的相对位置
float res = 8.0; // 初始化最小距离为一个较大的值
for( int j=-1; j<=1; j++ )
for( int i=-1; i<=1; i++ )
{
ivec2 b = ivec2( i, j ); // 遍历当前单元周围的 3x3 网格
vec2 r = vec2( b ) - f + hash2f( p + b ); // 计算相对位置并加上随机偏移
float d = dot( r, r ); // 计算距离的平方,dot计算快
res = min( res, d ); // 找到最小距离
}
return sqrt( res ); // 返回平方根,已获得最短距离
}
min函数引入了不连续性,因为它只选择最近的一个点,而忽略了其他点。就如下图所示
IQ的解决方案用一个连续的函数替代 min(),使得所有点都对最终结果有贡献,但最近的点权重最大。
是不是想起了我们之前学习过的 smoothmin . 没错差不多的思想。
于是 IQ提出了两种方法 10. 基于幂次的加权平均
for {
res += 1.0 / pow( d, 8.0 ); // 使用距离的倒数作为权重
}
pow( 1.0 / res, 1.0 / 16.0 ); // 反转加权平均
- 基于指数衰减的加权平均
for {
res += exp2( -32.0 * d ); // 使用指数衰减作为权重
}
-(1.0 / 32.0) * log2( res ); // 反转加权平均
一样我认为还是可以创造出无限的 加权平均方法
Voronoi与噪声的混合
噪声和Voronoi是两种常见的基于规则生成模式的方法。噪声有多种变体,其中Perlin噪声是最常见的一种;而Voronoi也有不同的变体,其中最常见的是将域分割成一个正方形网格,并在每个单元格中放置一个特征点。
这意味着Voronoi模式实际上也是基于网格的,与噪声类似,
尽管存在这种相似性,但事实是这两种模式中网格的使用方式不同。噪声插值/平均随机值(如在价值噪声中)或梯度(如在梯度噪声中),而Voronoi计算到最近特征点的距离。
IQ在这里产生了思考,能够发对这两个生成的算法进行抽象到统一的方案?于IQ提出的 Voronoise
想法之后的推演
为了混合Voronoi和Noise,IQ设计了两个参数:一个用于控制特征点的抖动量可以理解为网格的偏移,另一个用于控制noise的值也就是插值方式。让我们将网格控制参数称为u,而插值控制器为v。
网格控制参数的设计非常简单:u=0将使用类似噪声的规则网格,而u=1将是类似Voronoi的抖动网格。因此,u值可以简单地控制抖动量。很简单明了。
v参数必须在类似噪声的双线性插值器和类似Voronoi的最小运算符之间混合。这里的主要困难是min() 操作是一个不连续的函数,不过在上一节已经做了 smooth voronoi的讲解了,这里也一样。 不过
于是对于 v参数,iq设计了一个函数, 也是 d越小影响因子越大。不过抽象除了另外一个调节参数 k,
k1 = 64.0 - 63.0*v
k2 = 1 + 63 * pow(1-v, 4)
float ww = pow( 1.0-smoothstep(0.0,1.414,sqrt(d)), k1 );
k2会让 d的影响更加均匀些,这个不好理解,但是看下面的函数图像就有点感觉了。 蓝色是 k2
解决两个参数之后,IQ写出了下面这样的代码,结构与 voronoi非常相似,但是求随机数的方法由于 valuenosie比较相似
float noise( in vec2 x, float u, float v )
{
vec2 p = floor(x);
vec2 f = fract(x);
float k = 1.0 + 63.0*pow(1.0-v,4.0);
float va = 0.0;
float wt = 0.0;
for( int j=-2; j<=2; j++ )
for( int i=-2; i<=2; i++ )
{
vec2 g = vec2( float(i), float(j) );
vec3 o = hash3( p + g )*vec3(u,u,1.0);
vec2 r = g - f + o.xy;
float d = dot(r,r);
float w = pow( 1.0-smoothstep(0.0,1.414,sqrt(d)), k );
va += w*o.z;
wt += w;
}
return va/wt;
}
于是得到下面这张噪声图,左上为 voronoi, 左下为grid, 右下为 noise, 右上为VoroNoise