数字孪生混乱中生成世界7 SmoothVoronoi 和 VoroNoise

215 阅读4分钟

欢迎继续一起踏上 The Journey of Chaos, 关于 Shader 生成技术的一些基础性学习。噪声会分为以下几篇内容学习

  1. 随机函数与白噪声
  2. 值噪声 ValueNoise
  3. 柏林噪声 Gradient Noise
  4. 多维柏林噪声
  5. 柏林噪声优化 Simplex Noise
  6. Cell Noise
  7. SmoothVoronoi 与VoroNoise(本篇)
  8. CurlNoise
  9. 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函数引入了不连续性,因为它只选择最近的一个点,而忽略了其他点。就如下图所示 2502091355 IQ的解决方案用一个连续的函数替代 min(),使得所有点都对最终结果有贡献,但最近的点权重最大。 是不是想起了我们之前学习过的 smoothmin . 没错差不多的思想。

于是 IQ提出了两种方法 10. 基于幂次的加权平均

for {
	res += 1.0 / pow( d, 8.0 ); // 使用距离的倒数作为权重
}
pow( 1.0 / res, 1.0 / 16.0 ); // 反转加权平均
  1. 基于指数衰减的加权平均
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

2502095359

解决两个参数之后,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

2502092442