数字孪生混乱中生成世界6 细胞噪声 Worley Noise

498 阅读5分钟

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

  1. 随机函数与白噪声
  2. 值噪声 ValueNoise
  3. 柏林噪声 Gradient Noise
  4. 多维柏林噪声
  5. 柏林噪声优化 Simplex Noise
  6. Cell Noise(本篇)
  7. VoroNoise
  8. FBM深入

Cell Noise, 有的又叫 Worley Noise,基于 voronoi,由Steven Worley在1996年提出了新的算法.

Voronoi是什么

定义

给定一个由(n)个点组成的集合P=p1,p2,,pnP = {p_1, p_2, \ldots, p_n},这些点被称为种子点。Voronoi图则将整个空间划分为(n)个区域V(p1),V(p2),,V(pn)V(p_1), V(p_2), \ldots, V(p_n),其中每个区域V(pi)V(p_i)是由所有距离种子点pip_i更近于其它种子点的所有点组成的集合。即对于每个点xx在区域V(pi)V(p_i)内,满足欧几里得距离d(x,pi)<d(x,pj)d(x, p_i) < d(x, p_j) 这样说明有点过于抽象,可以参考下图理解, 下图中黄色的点为种子点,多边形便是划分出来的区域, 2405250619 voronoi

大自然

2405250218 voronoi lossy

大自然中Voronoi图样非常常见,其原因主要归结于Voronoi图的基本特性——空间分割和最近邻优化。这些特性一方面与自然界物理和生物过程的基本法则相契合,另一方面也是自然界追求效率和平衡的结果。

  1. 资源分配的效率: 在自然界中,许多生物体(如细胞、植物)需要争夺有限的资源(如光、水、营养物质)。Voronoi图自然而然地模拟了这种资源分配的过程,其中每个点(种子)代表一个获取资源的主体,而Voronoi单元代表该主体能够有效利用的区域。这种分配方式保证了每个生物体都有相对公平的资源获取机会,并且对资源的利用达到了局部最优。
  2. 自然现象的物理规律:一些自然过程,如裂纹的形成、干涸泥土的裂缝、冰的结晶等,遵循最小能量原则。Voronoi图在形成时,每个单元的边缘可以视为平衡点,即不同力量相互作用的结果。这与自然界中力和能量分布均匀的原则相一致。
  3. 生物组织和结构:在生物学中,许多生物组织的结构在微观上呈现出与Voronoi图相似的模式。例如,昆虫的眼睛、植物的叶脉、动物皮肤的斑点等。这些结构通常是由成长、细胞分裂和资源竞争等过程自然形成的,Voronoi图自然地描绘了这些过程中的空间动态和结构形态。
  4. 最近邻效应:自然界中的许多现象和互动是基于最近邻效应的,如动物的领地划分、种群的分布等。Voronoi图恰好提供了一种直观的方式来描述和模拟这种最近邻的空间关系。

2405261451 voronoi

以长颈鹿为例,长颈鹿胚胎中的黑色素分泌细胞不规则的分散分布,在怀孕过程中,这些细胞逐渐释放黑色素,最终形成了长颈鹿深色斑纹。有兴趣可以参考这篇论文Integrating Shape and Pattern in Mammalian Models,作者使用Voronoi Diagram 来模拟哺乳类动物的斑纹生成。

Voronoi 算法

暴力解法

暴力解法简单粗暴,假设有50个种子点,代码中对每个种子点进行比较,然后取最小的dist, 说先利用首先引入一个随机函数,用于产生种子点

// from https://www.shadertoy.com/view/ldl3W8
vec2 hash22( vec2 p )
{
	return fract(sin(vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))))*43758.5453);
}

增加一个运动函数,然种子点动起来, 以下我们产生了50个种子点,同时通过sin函数与时间的结合,让点的位置distortion。将点通过smmothstep画出来

	float m = 0.;
    float t = iTime*.8;
    
    for (float i = 0.; i < 50.; i++) {
        vec2 n = hash22(vec2(i));
        vec2 p = sin(n*t);
        float d = length(uv-p);
        m += smoothstep(.02, .01, d);
    }
    col += m;

最后有效果 2405263710 voronoi lossy 然后通过计算像素点到50个种子点的距离,取最小距离。

    float minDist = 100.;
    for (float i = 0.; i < 50.; i++) {
	    ...
		if (d < minDist) minDist = d;
	}
	col += minDist;

Voronoi 就出来了,而且已经挺漂亮的啦 2405264104 voronoi lossy

网格法

显而易见,上面的方法性能消耗验证,每一个像素点需要对种子点做计算。 考虑下面这个九宫格 2405265259 voronoi 假设种子点分布在不同的格子中,那么5号格子内所有点去找最近的种子点,只需要在[1,9]格子中去找。这就是优化的思路,种子点会移动,但是只会在某个格子中移动, 而格子的划分在前面sdf repeat里面已经推导过如何做, 就三步,放大,分割,坐标

    uv *= 3.;
    vec2 gv = 2.0 * (fract(uv) -.5;)
    vec2 id = floor(uv);

接下来就是为每个九宫格求出9个种子点,

    for (float y =-1.; y <=1.; y++){
    for (float x =-1.; x <=1.; x++){
        vec2 offs = vec2(x,y);
        vec2 n = hash22(id+offs);
        vec2 p = offs+sin(n*t);   
    }}

接下去的方法和暴力法一样,不过要注意的是格子索引是通过grid+offset得到的

	float d = length(gv-2.0 * p);
	if (d < minDist){
		minDist = d;
		cid = id +offs;
	}

最后我们将索引变成颜色col.rb = (cid + vec2(5.0)) / 10.0; 感觉不错啊

2405263332 voronoi

Worley Noise

Worley Noise就是用Voronoi 生成算法,最后 Noise值就是最短的那个距离, 代码如下,非常相似

float worley(vec2 p){
    vec2 n = floor( p );
    vec2 f = fract( p );

    float dis = 1.0;
    for( int j= -1; j <= 1; j++ )
        for( int i=-1; i <= 1; i++ ) {	
                vec2  g = vec2(i,j);
                vec2  o = hash22( n + g );
                vec2  delta = g + o - f;
                float d = length(delta);
                dis = min(dis,d);
    }

    return 1.0-dis;
}

资料

  1. Steven Worley
  2. The art of code: www.youtube.com/watch?v=l-0…
  3. iq: iquilezles.org/articles/vo…
  4. the book of shader: thebookofshaders.com/12/?lan=en