PsrdNoise周期性旋转梯度 Simplex变种噪声

64 阅读6分钟

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

  1. 随机函数与白噪声

  2. 值噪声 ValueNoise

  3. 柏林噪声 Gradient Noise

  4. 多维柏林噪声

  5. 柏林噪声优化 Simplex Noise

  6. Cell Noise

  7. SmoothVoronoi 与VoroNoise

  8. Simplex Noise变种PsrdNoise(本篇)

  9. PsrdNoise 应用 FlowNoise

  10. CurlNoise

  11. FBM深入

其源码和解释可以在 Github github.com/stegu/psrdn… 查看,我也复制了一份,将 shader程序变成 gif便于查看。 可以查看 PsrdNoise原文

PsrdNoise 是一个 Simplex Noise的变种,由瑞典的一名学者Stefan Gustavson 2022年提出。其源码和解释可以在 Github github.com/stegu/psrdn… 查看。毕竟是大学老师,讲的非常详细。 本文是全文通读下来的学习记录,建议大家直接看原文

我看到这个 Noise是看到 shopify 在 24 年底一篇技术播客 shopify.engineering/how-we-buil… 里面, 在制作地球材质中,他使用了 PsrdNoise的 flow noise的用法达到了符合设计的卡通地球效果,同时性能强大。

PsrdNoise是一系列特性的缩写

  1. P: periodic 周期性的

  2. S: simplex说明这个算法是基于 simplex noise的

  3. R: rotating gradients 算法一个核心点是旋转局部噪声梯度

  4. D: derivatives 基本上名字中已经揭示了算法的核心,接下去我们将进行深入理解。

从 Simplex到 Psrd

2503010407

2503010407

上图是一个psrdNoise生成的图像,我们暂时不考虑 psrdnoise具体算法,其代码实现为

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = (fragCoord * 2.- iResolution.xy)/iResolution.y;
    
    uv *= 5.0;
    const vec2 p = vec2(0.0);
    float alpha = 0.0;
    vec2 g;
    
    float n = 0.5 + 0.5 * psrdnoise(uv, p, alpha, g);
    vec3 ncolor = vec3(n);

    // Output to screen
    fragColor = vec4(ncolor,1.0);
}

这时候会发现他有 4 个参数, 其参数作用如下

参数

作用

示例代码

uv

输入纹理坐标,决定噪声采样位置

uv *= 5.0

p

周期性参数(periodic),设置噪声的重复周期,支持独立控制 x/y 方向

vec2 p = vec2(6.0, 4.0);

alpha

旋转梯度参数,控制噪声特征的动态旋转(rotating gradients

float alpha = time;

g

输出梯度向量,提供噪声场的精确导数,用于动态效果(如流动、折射模拟)

vec2 g; psrdnoise(v, p, alpha, g);

理解 Psrd需要先理解 Simplex, 这个算法我之前已经写过一篇柏林噪声优化 Simplex Noise, 需要理解是 Psrd是基于 Simplex 噪声(单纯形噪声),通过六边形网格(2D 场景)生成更自然的噪声分布。 这里的单纯形网格的点. 从下面这张图可以看出每个网格的点 2503014758

周期性 periodic

2503015447 注意上图每个相方块里面的都是相似的,但是不会影响到其不同方块之间的连续性。方块是有第二个参数 P(period)控制的, 如果p.x为 0,表示在 x轴不重复。 其核心代码为下面这一段

// 5), 6) wrap to period and adjust i0, i1, i2 accordingly  if(any(greaterThan(period, vec2(0.0)))) { xw = vec3(v0.x, v1.x, v2.x); yw = vec3(v0.y, v1.y, v2.y);    if(period.x > 0.0)  xw = mod(vec3(v0.x, v1.x, v2.x), period.x); if(period.y > 0.0)  yw = mod(vec3(v0.y, v1.y, v2.y), period.y);    iu = floor(xw + 0.5*yw + 0.5); iv = floor(yw + 0.5);  } else {    iu = vec3(i0.x, i1.x, i2.x); iv = vec3(i0.y, i1.y, i2.y);  }

这里只是简单的 mod, 但是考虑 shear,所以 iu的需要增加0.5*yw. 同时为了保障周期不会影响连续性, mod需要增加 0.5.

旋转梯度

还是回到simplex noise, 在2D simplex noise中,晶格是一个等边三角形,晶格的边缘点是梯度的初始值,以晶格边缘点画一个长度为三角形边的圆,这个边缘点的影响范围在这个圆内。 2503020346

如果我们将每个边缘点的梯度以同样的速度旋转会产生什么效果呢? 这就来到了 Psrd的关键梯度旋转 期代码如下

  vec3 psi = hash*0.07482 + alpha;  vec3 gx = cos(psi); vec3 gy = sin(psi);  vec2 g0 = vec2(gx.x, gy.x);  vec2 g1 = vec2(gx.y, gy.y);  vec2 g2 = vec2(gx.z, gy.z);

g0,g1,g2表示三个点的梯度,

2503022315

2503022315

这个时候将限制去掉, 左边是 1 倍速,右边是 4 变速。 在速度越来越快能够比较清楚的看到 grid的晶体结构像一个六边形,所以作者建议要谨慎试用该参数 2503022955

动画分型

之前我们有说过 fbm, 但是因为有了 alpha参数,我们可以基于 alpha参数做一个分型,会得到非常有趣的效果。 2503023537 其分型累加的代码如下

float alpha = 0.5*iTime;float n = 0.5;n += 0.4  * psrdnoise(1.0 * uv+0.0, p* 1.0, 1.0 *alpha, g);n += 0.2  * psrdnoise(2.0 * uv+0.1, p* 2.0, 2.0 *alpha, g);n += 0.1  * psrdnoise(3.0 * uv+0.2, p* 4.0, 4.0 *alpha, g);n += 0.05 * psrdnoise(8.0 * uv+0.3, p* 8.0, 8.0 *alpha, g);n += 0.025* psrdnoise(16.0* uv+0.0, p*16.0, 16.0*alpha, g);fragColor = vec4(vec3(n),1.0);

可以看到除了做了分型累加,也做了 uv偏移,还做了旋转角的分型

梯度

2503024943 最后是参数 Gradient, 其作用是返回噪声函数的梯度。从图中可以看出,梯度是变化最大的方向。如果这一块变化很大,那么梯度的箭头就很长。 2501275409 还记得这张图吗,这是柏林噪声的 4 个点形成的曲面,换到Simplex控制点只有三个,同时可以通过函数求出点的导数,也就是说可以求出精确梯度 exact derivative,而不是通过多次计算得到的差分近视值。其核心代码如下,

// 10) Compute radial falloff  vec3 w = 0.8 - vec3(dot(x0, x0), dot(x1, x1), dot(x2, x2));  w = max(w, 0.0); vec3 w2 = w*w; vec3 w4 = w2*w2;// 11) Linear ramp along gradient (by a scalar product)  vec3 gdotx = vec3(dot(g0, x0), dot(g1, x1), dot(g2, x2));// 12), 13) Multiply and sum up noise terms  float n = dot(w4, gdotx);// 14) Compute first order partial derivatives  vec3 w3 = w2*w; vec3 dw = -8.0*w3*gdotx;  vec2 dn0 = w4.x*g0 + dw.x*x0;  vec2 dn1 = w4.y*g1 + dw.y*x1;  vec2 dn2 = w4.z*g2 + dw.z*x2;  gradient = 10.9*(dn0 + dn1 + dn2);

当然我们增加旋转,并且把 scale调整的小一点, 可以看的更加清晰 2503020631

只此 Psrd所有特性就讲完了,在这里作者通过梯度做了一些简单的颜色配置,就获得非常漂亮的图形,其配置是如果单的 梯度的 x分量>0.5 显示红, y分量>0.5 在叠加颜色,具体代码录下

    vec3 gradientcolor = vec3(0.0, 0.5+g*0.11);    vec3 bgcolor = vec3(0.0, 0.0, 0.5);    vec3 xcolor = vec3(1.0, 0.0, 0.0);    vec3 ycolor = vec3(0.0, 1.0, 0.0);    vec3 mixcolor = mix(bgcolor, xcolor, aastep(0.6, g.x));    mixcolor = mix(mixcolor, ycolor, aastep(0.6, g.y));

左侧是加了阈值的图形,右侧是通过 green和 blue颜色通道查看梯度, 左侧很漂亮吧,像虫子... 2503022554

梯度plus

程序纹理不是关于 “什么是正确的”,而是关于 “看起来不错的”,所以不要害怕尝试一些疯狂的想法!, 例如接下去这个例子就是作者完全抛弃物理规律,,做出了水面透射和波纹的效果。

2503024932

2503024932

这里作者使用了2次随机。为了更好理解,我这里拆出这两个函数

  1. 使用梯度实现uv的扭曲,从何实现水面下的效果

    float n = psrdnoise(uv, p, alpha, g);float disort = dot(g, vec2(1., 4.)) * 0.5 * clamp(-st.t, 0.0 , 1.0);return vec3(disort);

2503024306

2503024306

  1. 使用随机值作为水面的上下起伏

    float w = clamp(-st.t + 0.01*n, 0.0, 1.0);float mask = aastep(0.01,w); return vec3(mask);

2503024557

2503024557