Worley Noise
Worley Noise1(也称为 Voronoi noise 和 Cellular noise)是由 Steven Worley2 在 1996 年引入的一种噪声函数。
它的样子看起来就是生物细胞的样子,由一个个 cell 构成,作者本身也是叫它“Cellular Texture”。
Worley Noise 的生成算法是:将整个图像分为一个个小方格,每个小方格中会有1个特征点,每个像素点的数值,是它到周围单元格中特征点的距离的最小值。
由于每个小方格只有1个特征点,每个像素只需要检查临近的9个小方格,一共只需要对比9个点来计算出它的值。
这里有2个在游戏中的应用案例3,分别是龟裂的干旱地面和造型夸张的烟柱。

Shadertoy
在本次代码实现过程中,我主要学习自Suboptimal Engineer4和The Book of Shaders5,并在Shadertoy6上进行了具体的代码编写。
同时,在iquilezles7这个网站有很多图形学相关的知识可以进行参考。
Grid Code
进入 Shadertoy,输入下列的代码:
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec3 col = vec3(0.0);
fragColor = vec4(col,1.0);
}
此时显示纯黑色。
将坐标变为正方形方格坐标,每个方格的范围在$[-0.5, +0.5]$之间。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.y;
uv = uv * 4.0;
vec2 gird = floor(uv);
vec2 coord = fract(uv) - 0.5;
vec3 col = vec3(coord, 0);
fragColor = vec4(col,1.0);
}
根据距离每个方格边框的距离,来进行上色。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.y;
uv = uv * 4.0;
vec2 gird = floor(uv);
vec2 coord = fract(uv) - 0.5;
float distanceGrid = 2.0 * max(abs(coord.x), abs(coord.y));
vec3 col = vec3(distanceGrid);
fragColor = vec4(col,1.0);
}
只在临近边界的地方进行上色,并添加颜色为红色。
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.y;
uv = uv * 4.0;
vec2 gird = floor(uv);
vec2 coord = fract(uv) - 0.5;
float distanceGrid = smoothstep(0.9, 1.0, 2.0 * max(abs(coord.x), abs(coord.y)));
vec3 girdColor = vec3(1.0, 0, 0) * vec3(distanceGrid);
vec3 col = girdColor;
fragColor = vec4(col,1.0);
}
Cell Code
接下来做两件事:
- 根据伪随机函数,确定每个方格的点的位置
- 计算当前像素到临近9个方格点的距离,取最小值
先绘制所有方格点,每个方格中只有1个点。
vec2 random2( vec2 p )
{
// add 0.5 to avoid vec2(0, 0) return (0, 0)
return fract(sin(vec2(dot(p + 0.5, vec2(213.1,322.2)),dot(p + 0.5, vec2(513.1,312.2))))*51312.1234);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.y;
uv = uv * 4.0;
vec2 gird = floor(uv);
vec2 coord = fract(uv) - 0.5;
// Gird Color
float distanceGrid = smoothstep(0.9, 1.0, 2.0 * max(abs(coord.x), abs(coord.y)));
vec3 girdColor = vec3(1.0, 0, 0) * vec3(distanceGrid);
// Cell Color
float distancePoint = 1.0;
for (int i=-1; i<=1; i++)
{
for (int j=-1; j<=1; j++)
{
vec2 near = vec2(float(i), float(j));
vec2 point = near + 0.5 * sin(iTime + 6.2831 * random2(gird + near));
float currentDistance = length(coord - point);
distancePoint = min(currentDistance, distancePoint);
}
}
vec3 pointColor = vec3(smoothstep(0.90, 1.0, 1.0 - distancePoint));
vec3 col = girdColor + pointColor;
fragColor = vec4(col,1.0);
}
在有了距离后,只要根据距离显示颜色就可以了。
vec2 random2( vec2 p )
{
// add 0.5 to avoid vec2(0, 0) return (0, 0)
return fract(sin(vec2(dot(p + 0.5, vec2(213.1,322.2)),dot(p + 0.5, vec2(513.1,312.2))))*51312.1234);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.y;
uv = uv * 4.0;
vec2 gird = floor(uv);
vec2 coord = fract(uv) - 0.5;
// Gird Color
float distanceGrid = smoothstep(0.95, 1.0, 2.0 * max(abs(coord.x), abs(coord.y)));
vec3 girdColor = vec3(1.0, 0, 0) * vec3(distanceGrid);
// Cell Color
float distancePoint = 1.0;
for (int i=-1; i<=1; i++)
{
for (int j=-1; j<=1; j++)
{
vec2 near = vec2(float(i), float(j));
vec2 point = near + 0.5 * sin(iTime + 6.2831 * random2(gird + near));
float currentDistance = length(coord - point);
distancePoint = min(currentDistance, distancePoint);
}
}
vec3 pointColor = vec3(smoothstep(0.95, 1.0, 1.0 - distancePoint));
vec3 distanceColor = vec3(smoothstep(0.2, 2.0, 1.7 - distancePoint));
vec3 col = girdColor + pointColor + distanceColor;
fragColor = vec4(col,1.0);
}
最后,如果移除辅助网格和中点,得到 Worley Noise 的图像。
Palettes
最后通过渐变插值来上一点颜色8
vec3 palette( in float t)
{
vec3 a = vec3(0.5, 0.5, 0.5);
vec3 b = vec3(0.5, 0.5, 0.5);
vec3 c = vec3(1.0, 1.0, 1.0);
vec3 d = vec3(0.0, 0.1, 0.2);
return a + b * cos( 6.283185 * ( c * t + d ) );
}
vec2 random2( vec2 p )
{
// add 0.5 to avoid vec2(0, 0) return (0, 0)
return fract(sin(vec2(dot(p + 0.5, vec2(213.1,322.2)),dot(p + 0.5, vec2(513.1,312.2))))*51312.1234);
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord/iResolution.y;
uv = uv * 4.0;
vec2 gird = floor(uv);
vec2 coord = fract(uv) - 0.5;
// Gird Color
float distanceGrid = smoothstep(0.95, 1.0, 2.0 * max(abs(coord.x), abs(coord.y)));
vec3 girdColor = vec3(1.0, 0, 0) * vec3(distanceGrid);
// Cell Color
float distancePoint = 1.0;
for (int i=-1; i<=1; i++)
{
for (int j=-1; j<=1; j++)
{
vec2 near = vec2(float(i), float(j));
vec2 point = near + 0.5 * sin(iTime + 6.2831 * random2(gird + near));
float currentDistance = length(coord - point);
distancePoint = min(currentDistance, distancePoint);
}
}
vec3 pointColor = vec3(smoothstep(0.95, 1.0, 1.0 - distancePoint));
vec3 distanceColor = vec3(palette(smoothstep(0.2, 2.0, 1.7 - distancePoint)));
vec3 col = distanceColor;
col += girdColor + pointColor;
fragColor = vec4(col,1.0);
}
wikipedia, Worley noise, https://en.wikipedia.org/wiki/Worley_noise ↩︎
Steven Worley, (1996), A Cellular Texture Basis Function, https://cedric.cnam.fr/~cubaud/PROCEDURAL/worley.pdf ↩︎
Por Ryan Brucks, (2016) , Getting the Most Out of Noise in UE4, https://www.unrealengine.com/es-ES/tech-blog/getting-the-most-out-of-noise-in-ue4 ↩︎
Suboptimal Engineer, (2023), What is Voronoi Noise? , https://www.youtube.com/watch?v=vcfIJ5Uu6Qw&ab_channel=SuboptimalEngineer ↩︎
Patricio Gonzalez Vivo and Jen Lowe, The Book of Shaders, https://thebookofshaders.com/12/ ↩︎
Shadertoy, https://www.shadertoy.com/ ↩︎
iquilezles, float small and random, https://iquilezles.org/articles/sfrand/ ↩︎
iquilezles, palettes - 1999, https://iquilezles.org/articles/palettes/ ↩︎