白噪声实在是太随机了,随机值之间没什么相关性,而在现实中,像波浪,叶脉纹理这些在随机中又带着规律性,因此想要模拟这些的噪声(Noise)也需要在随机中带点相关性。
(1)用插值连接随机点
之前实现白噪声,每个像素块彼此完全独立,随机点之间没有联系。想要把这些随机点连接起来就需要插值。
这里列几个常用的插值方法 比如说有这样几个随机点
第一种,线性插值 :,
float i = floor(x);
float f = fract(x);
y = mix(rand(i), rand(i + 1.0), f);//mix等价于rand(i+1.0)*f+rand(i)*(1.0-f);
第二种 余弦插值:在线性插值的基础上,把t值替换成一个余弦值,这样插值就会变得平滑
f*=3.1415926;//转化为弧度制 [0,π]
f=(1.-cos(f))*0.5;//映射在[0,1]
y = mix(rand(i), rand(i + 1.0), f);
//y = mix(f1(floor(x)), f2(floor(x) + 1.0), (1-cos(frac(x)*3.1415))*0.5;
第三种 :Cubic 插值:利用三次方多项式来插值
PS:在找相关资料的时候发现还有一种叫三次样条插值的(Cubic Spline Interpolation),这两个应该是不一样的,不要搞混
推导过程:
x带入0,1
得
我们可以把f(0) f(1) 看做插值的前后两个点p1,p2,f'(0),f'(1)则可以表示曲线在这两个点上的导数,可以把它设为0,但是把它设为前后两点的斜率,可以得到更平滑的曲线。( 如p1的前后两个点p2和p0 )
带入
得
float v0=rand(i-1.);
float v1=rand(i);
float v2=rand(i+1.);
float v3=rand(i+2.);
y=v1+f*(0.5*(v2-v0)+f*(0.5*(2.*v0-5.*v1+4.*v2-v3)+f*(0.5*(-v0+3.*v1-3.*v2+v3))));
在第六个点可以看到和余弦插值明显的不同。
再PS:还找到一种Cubic 插值公式是:
但不知道是怎么推出来的,试了一下,效果也可以,而且这式子看起来还简单点。
float v1=rand(i);
float v2=rand(i+1.);
float v3=rand(i+2.);
y=((v3-v2)-(v0-v1))*f*f*f+(2.*(v0-v1)-(v3-v2))*f*f+(v2-v0)*f+v1;
第四种:Hermite插值:smoothstep();用的就是这种插值方法(优化过)
三次Hermite插值公式:
m0,m2表示v0,v2的方向(切向量),懒得管可以设为0;
TODO 没懂它的原理,以后再来补吧
float u = f * f * (3.0 - 2.0 * f ); //-2f^3+3f^2
y = mix(rand(i), rand(i + 1.0), u);
//把这里的mix()拆开就等于m1,m2=0的三次Hermite插值
//(2f^3-3f^2+1)*rand(i)+(-2*f^3+3*t^2)*rand(i+1);
emmmmm 从这张图看的话感觉和余弦插值差不多嘛
黄色三次hermite,蓝色余弦插值,本来想找点不同的。。。
(2) 2D值噪声
回到正题,值噪声之所以叫值噪声(Value Noise),就是它是在随机值(random value)之间插值得到的noise值,后面还会写到梯度噪声(Gradient Noise),就是用的别的方法得到noise值。
在一维插值是从两个点中插值,转变到二维,则是从四个点中插值
把平面想象成一块块的网格,一个网格由四个点构成,每个点都是一个固定的随机值,每个像素点都在某一个网格中,根据它对于四个点的相对位置,插值得到像素点的随机值(噪声值)
如果把noise值想象成高度的话
直接的线性插值 平滑插值
用unityshader绘制2D值噪声
Shader "Custom/ValueNoise2D" {
Properties{
_Scale("Scale",Range(4,20)) = 10
}
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;
}
float rand(float2 st) {
return frac(sin(dot(st.xy,
float2(12.9898, 78.233)))
* 43758.5453123);
}
float mix(float a, float b, float t) {
return b*t + a*(1 - t);
}
float ValueNoise(float2 uv) {
float2 i = floor(uv);
float2 f = frac(uv);
float a = rand(i);
float b = rand(i + float2(1, 0));
float c = rand(i + float2(0, 1));
float d = rand(i + float2(1, 1));
float2 u= f*f*(3.0 - 2.0*f);//三次Hermite插值
return mix(a, b, u.x) +
(c - a)* u.y * (1.0 - u.x) +
(d - b) * u.x * u.y;
}
fixed4 frag(v2f i) :SV_Target{
half2 uv = i.uv * _Scale;
float noise = ValueNoise(uv);
return fixed4(noise, noise, noise, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
四个点的插值公式一开始看有点懵逼,但其实就是把几个mix()拆开了。
return mix(a, b, u.x) +(c - a)* u.y * (1.0 - u.x) +(d - b) * u.x * u.y;
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);//把这个拆开,就是上面的这个式子
(2) 3D值噪声
以此推类3D噪声,也就是8个点的插值,这里我把时间作为第三个变量,有一个动态的效果
Shader "Custom/ValueNoise3D" {
Properties{
_Scale("Scale",Range(4,20)) = 10
}
SubShader{
Pass{
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
float _Scale;
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;
}
float rand(float3 st) {
return frac(sin(dot(st.xyz,
float3(127.1, 311.7, 74.7)))
* 43758.5453123);
}
float mix(float a, float b, float t) {
return b*t + a*(1 - t);
}
float ValueNoise3D(float3 uvt) {
float3 i = floor(uvt);
float3 f = frac(uvt);
float a = rand(i);
float b = rand(i + float3(1, 0, 0));
float c = rand(i + float3(0, 1, 0));
float d = rand(i + float3(1, 1, 0));
float e = rand(i + float3(0, 0, 1));
float fx = rand(i + float3(1, 0, 1));
float g = rand(i + float3(0, 1, 1));
float h = rand(i + float3(1, 1, 1));
float3 u= f*f*(3.0 - 2.0*f);
float mix1 = mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
float mix2 = mix(mix(e, fx, u.x), mix(g, h, u.x), u.y);
return mix(mix1, mix2, f.z);//这里感觉用线性的f.z效果好点,也可以选择平滑后的u.z
}
fixed4 frag(v2f i) :SV_Target{
half2 uv = i.uv * _Scale;
float noise = ValueNoise3D(float3(uv.xy,_Time.y));
return fixed4(noise, noise, noise, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
PS:值噪声看起来是一块一块的,为了消除这种块状的效果,在 1985 年 Ken Perlin 开发了另一种 noise 算法 Gradient Noise(梯度噪声),也就是后面的内容。
参考内容:https://thebookofshaders.com/11/?lan=ch
http://www.paulinternet.nl/?page=bicubic
https://blog.youkuaiyun.com/yolon3000/article/details/75145035