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的接触线上,紫色的线长度就是离边缘线的距离,等于在
上的投影 即:
至于寻找第二近特征点,你可以在原本的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 )= 因为这里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