噪声笔记#2 插值和值噪声

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

白噪声实在是太随机了,随机值之间没什么相关性,而在现实中,像波浪,叶脉纹理这些在随机中又带着规律性,因此想要模拟这些的噪声(Noise)也需要在随机中带点相关性。

(1)用插值连接随机点

之前实现白噪声,每个像素块彼此完全独立,随机点之间没有联系。想要把这些随机点连接起来就需要插值。

这里列几个常用的插值方法 比如说有这样几个随机点

第一种,线性插值 :l=a*t+b*(1-t)

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 插值:利用三次方多项式来插值\small f(p0,p1,p2,p3)=\\(-\frac{1}{2}p0+\frac{3}{2}p1-\frac{3}{2}p2+\frac{1}{2}p3 )x^3+ (p0-\frac{5}{2}p1+2p2-\frac{1}{2}p3 )x^2 +(-\frac{1}{2}p0+\frac{1}{2}p2 )x +p1

PS:在找相关资料的时候发现还有一种叫三次样条插值的(Cubic Spline Interpolation),这两个应该是不一样的,不要搞混


推导过程:

\left\{\begin{matrix} f(x)=ax^3+bx^2+cx+d \\f'(x)=3x^2+2bx+c \end{matrix}\right.

x带入0,1

\left\{\begin{matrix}f(0)=d \\f'(0)=c \\f(1)=a+b+c+d \\f'(1)=3a+2b+c \end{matrix}\right.

\left\{\begin{matrix} a=2f(0)-2f(1)+f'(0)+f'(1)\\ b=-3f(0)+3f(1)-2f'(0)-f'(1) \\ c=f'(0) \\ d=f(0) \end{matrix}\right.

我们可以把f(0) f(1) 看做插值的前后两个点p1,p2,f'(0),f'(1)则可以表示曲线在这两个点上的导数,可以把它设为0,但是把它设为前后两点的斜率,可以得到更平滑的曲线。( 如p1的前后两个点p2和p0 )

带入

\left\{\begin{matrix} f(0)=p1\\ f(1)=p2 \\ f'(0)=\frac{p2-p0}{2} \\ f'(1)=\frac{p3-p1}{2} \end{matrix}\right.

\left\{\begin{matrix} a=-\frac{1}{2}p0+\frac{3}{2}p1-\frac{3}{2}p2+\frac{1}{2}p3 \\ b=p0-\frac{5}{2}p1+2p2-\frac{1}{2}p3 \\ c=-\frac{1}{2}+\frac{1}{2}p2 \\ d=p1 \end{matrix}\right.


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))));

Cubic

在第六个点可以看到和余弦插值明显的不同。


再PS:还找到一种Cubic 插值公式是:

但不知道是怎么推出来的,试了一下,效果也可以,而且这式子看起来还简单点。

cubic2

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插值公式:\small f(t)=(2t^3-3t^2+1)v0+(t^3-2t^2+t)m0+(t^3-t^2)m1+(-2*t^3+3*t^2)v1

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);

Hermite插值

emmmmm 从这张图看的话感觉和余弦插值差不多嘛

余弦+hermite

黄色三次hermite,蓝色余弦插值,本来想找点不同的。。。

(2) 2D值噪声

回到正题,值噪声之所以叫值噪声(Value Noise),就是它是在随机值(random value)之间插值得到的noise值,后面还会写到梯度噪声(Gradient Noise),就是用的别的方法得到noise值。

在一维插值是从两个点中插值,转变到二维,则是从四个点中插值

把平面想象成一块块的网格,一个网格由四个点构成,每个点都是一个固定的随机值,每个像素点都在某一个网格中,根据它对于四个点的相对位置,插值得到像素点的随机值(噪声值)

如果把noise值想象成高度的话

                  直接的线性插值                                               平滑插值

用unityshader绘制2D值噪声

值噪声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个点的插值,这里我把时间作为第三个变量,有一个动态的效果

3D值噪声

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.csdn.net/yolon3000/article/details/75145035

猜你喜欢

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