数字孪生混乱中生成世界2 值噪声 ValueNoise

85 阅读5分钟

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

  1. 随机函数与白噪声

  2. 值噪声 ValueNoise(本篇)

  3. 柏林噪声 Gradient Noise

  4. 柏林噪声优化 Simplex Noise

  5. Voronoi噪声

  6. FBM深入

本篇主要是理解 ValueNoise的具体实现,包括通过 ValueNoise组成的 Fractal Noise . 主要是参考 IQ的www.shadertoy.com/view/lsf3WH 与 The art of code value noise的视频。

哎,好笨啊。 这么简单要看这么久

ValueNoise

在上一篇学习了 random函数是如何产生的,如果我们将画面画面拆成多个Tile,拆分的方法通过 对坐标进行放大然后 fract, 这个已经讲过很多次了。

           uv *= 5.;        vec2 i = floor(uv);    vec2 u = fract(uv);        vec2 gridBars = clamp(cos(uv * 6.283) * 10. - 9.9, 0., 1.); // ---^---^---    float grid = max(gridBars.x, gridBars.y)*2.0;    col += grid * 10.0;

2501242104

2501242104

接下来为每一个格子取一个 random, 也就是将坐标 i 去 random, hash21 是我们上一篇写的随机函数,表示输入为 2 维向量,输出为 float。于是就得到了下面每个格子不同灰度的图

    col += vec3(hash21(i));

2501242224

2501242224

为了简化问题,我们现在只讨论一个格子, 如下图所示, 我们只考虑格子 1 2501243826 算法是 对 1 ,2 格子进行进行 mix。代码如下

    float cell_1 = hash21(i);    float cell_2 = hash21(i+vec2(1., 0.));    c = mix(cell_1, cell_2, u.x);

可以看到在 x方向上颜色已经是连续的了,注意我这里强调颜色的连续性 2501240224

这个时候我们在对 4 个格子中 3,4 做同样的操作,最后在 y方向做一次 mix操作,代码为

    float cell_1 = hash21(i);    float cell_2 = hash21(i+vec2(1., 0.));    float cell_12 = mix(cell_1, cell_2, u.x);        float cell_4 = hash21(i+vec2(0., 1.));    float cell_3 = hash21(i+vec2(1., 1.));    float cell_34 = mix(cell_3, cell_4, u.x);        float cell_1234 = mix(cell_12, cell_34, u.y);

可以看到在 x,y两个方向都是连续的了。 2501240621

这个时候我们再将格子数量变大,可以看到一张 value noise图

    uv *= 5.;

2501240837 但是这张图很奇怪,就是能看到虽然颜色在 x,y两个方向没有跳变,但是还是能看到格子。比如图中的白线和黑线。 这是因为我们的函数虽然是连续的,也就是在不同格子之间交界处其颜色的值相等,但是我们的图像在格子的交界处变化率不一样。 这里的问题原因我在看big wings的视频的时候一开始没有明白,因为他只是用手在那里比划以下, 接下来我将用 geogebra去解释这个事情

2501242642

2501242642

我们 4 个格子的灰度值,就像上图的一样。 每个格子的颜色是跳跃的,称为不连续,接下去我们通过了 mix函数让他们练了起来, 一次 mix的效果如下 2501242902 二次 mix效果最好得出的值就像下图蓝色, E点就是连续不可导的点在图像上就是我们看到的边界 2501243135

我们使用的 mix实际上函数表示是如下,是一个线性插值

f(a, b, h) = a + h(b - a); h \in [0, 1]
\

上面的 h 的函数表达,就是一条直线

y = x
\

这个时候,我们只要使用非线性插值让边界点导数为 0,便可以去除非效果, 关于这里更有感觉的理解在这篇之前写的关于 SDF平滑合并的文章

我们选择的平滑函数很简单,就 smoothstep, 他的特点是在 0,1 的时候导数值为0, 其特征如下图所示 2501245337

于是我们给 u加上平滑函数

    u = u * u * (3. - 2.*u);

有了下面的效果 2501240055

音色与 Fractal noise

在上上篇讲基础乐理的时候,我们讨论了为什么拉动吉他会产生美妙的声音,因为有个吉他弦震动是有多个波的,我们将一个标准的正弦波作为基准,称作基波。谐波就是比基波的频率高整数倍的波。吉他弹响一根弦或钢琴按下一个键都会在基波的基础上产生多个谐波,在音乐中我们把谐波称为“泛音”。

2501244627

2501244627

如果我们把这个原理应用在噪声函数上呢? 一个三角函数为 A * sin(Bx + C)

  • A 是振幅(Amplitude),表示波峰或波谷的高度。

  • B 是频率(Frequency),通过 2π/B 可以计算出周期。

  • C 是相位(Phase),用来表示波形的水平移动。 注意不同的谐波相位可能也不一样。

我们将上面的 ValueNoise函数抽象出来, 形成

float f = 0.0;float s = 1.5;    mat2 r = mat2(cos(0.33), sin(0.33), -sin(0.33), cos(0.33));f  = 0.5 / pow(s, 0.) * valueNoise(uv); uv = s * r * uv;f += 0.5 / pow(s, 1.) * valueNoise(uv); uv = s * r * uv;f += 0.5 / pow(s, 2.) * valueNoise(uv); uv = s * r * uv;f += 0.5 / pow(s, 3.) * valueNoise(uv); uv = s * r * uv;f = 0.5 + 0.5 * f;vec3 col = vec3(f);
  1. uv 乘上 s(scale值) 进行放大,就好像对 谐波基波 频率的几倍一样

  2. 对 不同系统uv计算出来的f 进行缩小,就像高频的谐波进行振幅的调小一样

  3. uv 做旋转,就像是不同波之间的相位不一样 最后我们得到了一个像云彩一样的东西

2501241112

2501241112

这种方法图形学里面把它叫做 fractal noise

参考资料

  1. IQ Shader Value Noise www.shadertoy.com/view/lsf3WH

  2. IQ Noise Shader PlayList www.shadertoy.com/playlist/fX…

  3. The Art of Code Value Noise Explain : www.youtube.com/watch?v=zXs…