噪声笔记#5 Worley噪声

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_38275140/article/details/84349111

Worley noise也叫Voronoi/Cellular noise,叫Voronoi是因为基于Voronoi 算法,Voronoi图,叫Worley是因为1996年Steven Worley 写了一篇名为“A Cellular Texture Basis Function”的论文。在这篇论文里,他描述了这种现在被广泛使用的程序化纹理技术。

worely噪声有别与前面几种噪声,它是随机生成几个特征点,然后每个像素点的取值是它与最近特征点之间的距离。

比如你有四个特征点,

float2 Rpoints[4];
Rpoints[0] = float2(0.83, 0.75);
Rpoints[1] = float2(0.60, 0.07);
Rpoints[2] = float2(0.28, 0.64);
Rpoints[3] = float2(0.31, 0.26);
float m_dist = 1;
for (int i = 0; i < 4;i++) {
	m_dist=min(m_dist, distance(uv, Rpoints[i]));
}

但这样有个问题,如果你生成的特征点少还好,如果需要生成大量特征点,越多计算复杂度越大, 而且这些特征点你要提前准备好,不然每个特征点都要重新计算一遍。所以Steven Worley对此进化了优化。把空间分割成网格,每个格子生成一个特征点,像素点只在自己和周围八个网格里寻找最近的特征点距离。这样无论最终有多少个特征点,每个像素只会循环算9次最短距离

for (int x = -1; x < 2;x++) {
	for (int y = -1; y < 2; y++) {				
		float2 neighbor = float2(x, y);
		//自己和周围网格的特征点
		float2 neighborP = random(i + neighbor) ;
		//距离
		float dist = distance(f,neighborP+ neighbor);
		m_dist = min(m_dist, dist);
	}
}

如图,每个网格里都有一个特征点,仔细看可以发现,每一个cell边不会超过8条,因为它只是在周围8点寻找最近距离。

3x3 Worley噪声

前面直接把最小值作为返回值使用了,当实际上求最小距离是为了得到最近特征点,我们可以根据特征点,返回一些别的值,这里我用point的xy值来中和三个预设颜色,并在计算特征点时加了动态。

Shader "Custom/WorleyNoise2D" {
	Properties{
		_Scale("Scale",Range(5,50)) = 5
	}
	SubShader{
		Pass{
			CGPROGRAM
			#include "UnityCG.cginc"
			#pragma vertex vert
			#pragma fragment frag
			float _Scale;
			struct v2f {
				float4 pos:SV_POSITION;
				half2 uv:TEXCOORD0;
			};
			v2f vert(appdata_base v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = v.texcoord;
				return o;
			}
			float2 random(float2 p) {
				p = float2(dot(p, float2(127.1, 311.7)),
					dot(p, float2(269.5, 183.3)));

				return frac(sin(p)*43758.5453123);
			}
			float3 worleyNoise(float2 uv) {
				float2 i = floor(uv);
				float2 f = frac(uv);
				float m_dist =1;
				float2 m_point;
				for (int x = -1; x < 2;x++) {
					for (int y = -1; y < 2; y++) {
						
						float2 neighbor = float2(x, y);
						//周围的特征点
						float2 neighborP = random(i + neighbor) ;
						//动态
						neighborP = 0.5 + 0.5*cos(_Time.y + 6.2831*neighborP);
						float dist = distance(f,neighborP+ neighbor);
						if (dist<m_dist) {
							//最短距离
							m_dist = dist;
							//最近的特征点
							m_point = neighborP;
						}
					}
				}
				float3 color1 =float3(0.790, 0.320, 0.190);
				float3 color2 =float3(0.393, 0.790, 0.428);
				float3 color3 = float3(0.360, 0.644, 0.790);

				float3 color = lerp(color1, color2,m_point.x);
				color = lerp(color, color3, m_point.y);

				return color;
			}
			fixed4 frag(v2f i) :SV_Target{
				float3 noise = worleyNoise(i.uv*_Scale);
				return fixed4(noise, 1);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

2x2 Worley噪声

在 2011 年, Stefan Gustavson 优化了 Steven Worley 的算法,仅仅对一个 2x2 的矩阵作遍历(而不是 3x3 的矩阵)。这显著地减少了工作量,但是会在网格边缘制造人工痕迹。事实上这个问题在3x3上也存在,只是不明显。

è¿éåå¾çæè¿°

比如在上图的情况下,离a点最近的点是e点,但是a点不在周围的8个网格内,所以计算时不会计算E点和A点的距离,得到的自然不是真正的特殊点,这样图像边缘就会有割裂的感觉,好在3x3是这种情况很少,但是如果特征点搜寻框变成了2x2,那么就有可能出现明显的效果。所以要在效果和效率之间做出选择。

可以发现,特征点越靠近网格中心,那么出现割裂的可能性越低,所以在2x2的Worley噪声中,特征点的计算变为基于中心的一定强度的偏移扰乱,这样可以避免极端情况出现。

Shader "Custom/2x2WorleyNoise2D" {
	Properties {
		_Scale("Scale",Range(3,50)) = 10
		_Jitter("Jitter",Range(0,2)) = 0.5
	}
	SubShader{
		Pass{
			CGPROGRAM
			#include "UnityCG.cginc"
			#pragma vertex vert
			#pragma fragment frag
			float _Scale;
			float _Jitter;
			struct v2f {
				float4 pos:SV_POSITION;
				half2 uv:TEXCOORD0;
			};
			v2f vert(appdata_base v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = v.texcoord;
				return o;
			}

			float2 random(float2 p) {
				p = float2(dot(p, float2(127.1, 311.7)),
					dot(p, float2(269.5, 183.3)));

				return frac(sin(p)*43758.5453123);
			}

			float worleyNoise(float2 uv) {

				float2 i = floor(uv);
				float2 f = frac(uv);
				float m_dist = 1;
				//四个特征点,未进行扰乱前是四个网格中心
				float2 originPoint = float2(0.5, 0.5);
				float2 upPoint = float2(1.5, 0.5);
				float2 rightPoint = float2(0.5, 1.5);
				float2 urightPoint = float2(1.5, 1.5);
				//添加扰乱,因为是计算右上的四个网格,所以扰乱应该向左下(-)扰乱,这样才能避免出错
				originPoint -= random(i)*0.5*_Jitter;
				upPoint -= random(i+float2(1,0))*0.5*_Jitter;
				rightPoint -= random(i + float2(0,1))*0.5*_Jitter;
				urightPoint -= random(i + float2(1,1))*0.5*_Jitter;
				//算距离 
				float4 dis = float4(distance(f, originPoint), distance(f, upPoint), distance(f, rightPoint), distance(f, urightPoint));
				m_dist = min(min(dis.x, dis.y), min(dis.z, dis.w));
				return m_dist;
			}

			fixed4 frag(v2f i) :SV_Target{
				float v =worleyNoise(i.uv*_Scale);
				v = 1-1.5*worleyNoise(i.uv*_Scale);
				return fixed4(v, v, v, 1);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

Worley 噪声的边缘

前面的Worley噪声是的最小距离时基于欧几里得算法,所以他的距离渐变是一个圆形,但是有时候我们可能希望他的距离时按照多边形边缘渐变,

我们可以通过F2-F1得到一个近似的效果(F1是最短距离,F2是第二短距离);

但是效果不是很好,Inigo Quile在2012年写了一篇有关Voronoi borders的文章,讲了一个实现方法。

如图所示,x是像素点位置 a是离x最近的特征点,b是第二近的特征点,m是两者的中点,也是在两个cell的接触线上,紫色的线长度就是离边缘线的距离,等于\underset{mx}{\rightarrow}\underset{ab}{\rightarrow}上的投影 即:dist=dot(x-\frac{a+b}{2},normalize(b-a))

至于寻找第二近特征点,你可以在原本的9个特征点离直接找,但效果不好,这里是在基于最近特征点(不是像素点所在网格的特征点)的5x5搜寻框里寻找

Shader "Custom/3x3WorleyNoiseBorder" {
	Properties{
		_Scale("Scale",Range(5,50)) = 5
	}
	SubShader{
		Pass{
			CGPROGRAM
			#include "UnityCG.cginc"
			#pragma vertex vert
			#pragma fragment frag
			float _Scale;
			struct v2f {
				float4 pos:SV_POSITION;
				half2 uv:TEXCOORD0;
			};
			v2f vert(appdata_base v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = v.texcoord;
				return o;
			}
			float2 random(float2 p) {
				p = float2(dot(p, float2(127.1, 311.7)),
					dot(p, float2(269.5, 183.3)));

				return frac(sin(p)*43758.5453123);
			}
			float worleyNoise(float2 uv) {
				float2 i = floor(uv);
				float2 f = frac(uv);
				float m_dist = 8;
				float2 mp;
				float2 mr;
				//正常的求最短距离
				for (int x = -1; x < 2; x++)
				for (int y = -1; y < 2; y++) {

					float2 neighbor = float2(x, y);
					float2 neighborP = random(i + neighbor);
					float dist = distance(f, neighborP + neighbor);
					if (dist < m_dist.x) {
						//最短距离
						m_dist.x = dist;
						//最近的特征点网格,下面要用
						mp=neighbor;
						//最近点和像素点的距离向量,下面要用
						mr = neighborP + neighbor - f;
					}
				}
				//求边缘距离 寻找(距离(最近特征点)最近的特征点) 5x5
				float borderDis = 8;
				for (int x = -2; x < 3; x++)
				for (int y = -2; y < 3; y++) {
					float2 neighbor = float2(x, y);
					//mr=最近点和像素点距离,r=第二近和像素点距离,mr+r=2mx r-mr=ab
					float2 neighborP = random(i+mp + neighbor);
					float2 r = mp + neighborP + neighbor - f;
					if (dot(mr - r, mr - r) > 0.00001) {//排除xy=0的情况
						borderDis = min(borderDis, dot(0.5*(mr + r), normalize(r - mr)));
					}
				}
				return borderDis;
			}
			fixed4 frag(v2f i) :SV_Target{
			    float n = worleyNoise(i.uv*_Scale);
			    float3 noise = n;//1
			    noise = 1.0 - smoothstep(0.0, 0.05, n);//2
			    noise = (0.5 + .5*cos(n*64))*n;//3
			    return fixed4(noise, 1);
		    }
		ENDCG
		}
	}
	FallBack "Diffuse"
}

Voronoise

Inigo 在 Voronoi 上的实验并没有就此停止。2014 年,他写了一篇非常漂亮的文章,提出一种他称作为 voro-noise 的噪声,可以让常规噪声和 voronoi 逐渐地融合。

首先常规的噪声是连续的,但Voronoi 噪声不是连续的(边界),所以想要结合,必需让Voronoi 噪声变成连续的,Inigo提出了一种smooth voronoi,让voronoi变成连续的。总的来说,就是把原本的求最小值变成的各个特征点距离之间的加权取值,

res += 1.0/pow( d, 8.0 );
...
return pow( 1.0/res, 1.0/16.0 )

这是其中一种,文中给出了两种,d是距离的平方,距离越小,加权加的值就越多。8次方意味着最大的数占据了加权中的绝大部分。所以效果其实和正常的差不多,而且还是连续的。 return后面的运算其实就是把d给还原出来。

pow( pow(d,8), 1.0/16.0 )=\sqrt{d}  因为这里d是距离的平方,所以正好开个根号。


这里是8次方,所以会有加权的效果,但如果是res+=pow(d,n ),n=1 那就是每个距离单纯的相加, 

float ww = pow( 1.0-smoothstep(0.0,1.414,sqrt(d)), 64.0 - 63.0*v );

再看这个,v值范围在[0,1],

v=0时,ww=pow(1.0-smoothstep(0.0,1.414,sqrt(d)),64);  //Voronoi 噪声

v=1时,ww=1.0-smoothstep(0.0,1.414,sqrt(d));//可以把值看成常规噪声中每个网格的随机值,

ps:smoothstep是为了让距离在[0,1.414]里映射成[0,1],超出部分返回1,避免负数之类的 1.414≈根号2

Shader "Custom/Voronoise" {
	Properties{
		_Scale("Scale",Range(3,50)) = 10
		_Jitter("Jitter",Range(0,1)) = 0
		_Lerp("Voronoi2Noise",Range(0,1)) = 0
	}
	SubShader{
		Pass{
			CGPROGRAM
			#include "UnityCG.cginc"
			#pragma vertex vert
			#pragma fragment frag
			float _Scale;
			float _Jitter;
			float _Lerp;
			struct v2f {
				float4 pos:SV_POSITION;
				half2 uv:TEXCOORD0;
			};
			v2f vert(appdata_base v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				o.uv = v.texcoord;
				return o;
			}
			float3 hash3(float2 p) {
				float3 q = float3(dot(p, float2(127.1, 311.7)),
					dot(p, float2(269.5, 183.3)),
					dot(p, float2(419.2, 371.9)));
				return frac(sin(q)*43758.5453);
			}
			float voronoise(float2 pos, float u, float v) {
				float2 p = floor(pos);
				float2 f = frac(pos);

				float H = 1.0 + 63.0*pow(1.0 - v, 4.0);//这里决定是像Voronoi还是noise
				float vall = 0.0;
				float wall = 0.0;
				for (int j = -2; j < 3; j++) {
					for (int k = -2; k <3; k++) {
						float2 g = float2(k,j);
						//前两个随机值用于特征点的扰动
						float3 o = hash3(p + g)*float3(u, u, 1.0);
						float2 r = g-f+o.xy;//像素点和特征点距离向量
						float d = dot(r, r);//距离^2
						float ww = pow(1.0 - smoothstep(0.0, 1.414, sqrt(d)), H);
						vall += o.z*ww;
						wall += ww;
					}
				}

				return vall / wall;//归一化在(0,1)
			}
			fixed4 frag(v2f i) :SV_Target{
				float3 noise = voronoise(i.uv*_Scale,_Jitter,_Lerp);
				return fixed4(noise, 1);
			}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

 参考内容 :https://blog.csdn.net/yolon3000/article/details/78386701

https://thebookofshaders.com/12/?lan=ch

猜你喜欢

转载自blog.csdn.net/qq_38275140/article/details/84349111