写在前面
是iq,是iq啊啊啊啊啊!!!!!!
I love it😭
I love it😭
I love it😭
I love it😭
I love it😭
我现在就把这条评论裱起来😭😭😭😭😭
NOISE
不知道是不是只有北京有“吵(chāo)吵儿”(?)这个说法...总之今天的内容是关于制造噪声,嗯,视觉上那种。
White Noise就是最闹眼的,like电视花屏。每个像素的取值是完全随机的,没有相关性,概率上平均分布。
In signal processing, white noise is a random signal having equal intensity at different frequencies, giving it a constant power spectral density.
而为了生成更加贴近大自然的随机效果,前人们提出了Perlin(用于模拟自然环境,如火焰、云朵等)、Worley(用于模拟多孔结构,如纸张、木纹等)等噪声算法。 根据乐乐老师说根据wiki,由程序产生噪声的方法大致可分为两大类: | 类别 | 名称 |
---|---|---|
基于晶格的方法(Lattice based) | 又可细分为两种: 第一种是梯度噪声(Gradient noise),包括Perlin噪声, Simplex噪声,Wavelet噪声等; 第二种是Value噪声(Value noise)。 |
|
基于点的方法(Point based) | Worley噪声 |
常见的说法还有分形噪声(Fractal Noise)和fbm分形布朗运动(Fractal Brownian Motion):
分形噪声会把多个不同振幅、不同频率的octave相叠加,得到一个更加自然的噪声。而这些octave则对应了不同的来源,它可以是Gradient噪声(例如Perlin噪声)或Value噪声,也可以是一个简单的白噪声(White noise)。
Lattice Noise
进入到最常用的perlin noise之前,还是要为自己的菜打打基础。
之前图形学的作业里,尝试总结了一下:
- 生成噪波需要构造一个noise函数,该函数的输入为纹理坐标,把坐标转换成能够作为伪随机数(PRN)使用的hash值;或者能把hash值作为索引,在生成的PRN表中查找对应数值。 以下实现以Lattice Noise为例。
- 首先声明一个利用white noise生成的伪随机数(PRN)表,该表包括从0到255的所有数,在数组中随机排布。
- Noise函数首先对坐标进行取整,通过PERM(x+PERM(y+PERM(z)))获得格点的PRN值,再除以256以获得格点处的灰度值。其中PERM函数的作用是先对输入值与256取余,然后于perm数组中取PRN值。之后再对每个格点进行插值即为Value Noise。 详细实现 emmmmmm.....对也不对。 基于lattice的噪音生成算法都需要为整数格点(integer lattice)生成PRN,每一个像素对所处晶格的四个角点进行插值。生成PRN的内容和插值的方式形成了不同的噪声效果。 最简单粗暴的Value Noise就是在每个格点生成0~1的PRN,然后直接插值。
Perlin Noise
Value noise即便使用三次插值、catmull-rom等比较平滑的插值方式,还是能很辨认出原始的晶格轮廓,有明显的网格感。 为了消除这种块状的效果,在1985年Ken Perlin开发了另一种noise算法 Gradient Noise,后来也被称为Pelrin Noise。解决了如何插入随机的gradients而不是一个固定值。这些梯度值来自于一个二维的随机函数,返回一个方向(vec2 格式的向量),而不仅是一个值(float格式)。
概括来说,Perlin噪声的实现需要三个步骤:
- 定义一个晶格结构,每个晶格的顶点有一个“伪随机”的梯度向量(其实就是个向量啦)。对于二维的Perlin噪声来说,晶格结构就是一个平面网格,三维的就是一个立方体网格。
- 输入一个点(二维的话就是二维坐标,三维就是三维坐标,n维的就是n个坐标),我们找到和它相邻的那些晶格顶点(二维下有4个,三维下有8个,n维下有2^n个),计算该点到各个晶格顶点的距离向量,再分别与顶点上的梯度向量做点乘,得到2^n个点乘结果。
- 使用缓和曲线(ease curves)来计算它们的权重和。在原始的Perlin噪声实现中,缓和曲线是s(t)=3t^2−2t^3(yep就是smoothstep那个曲线),在2002年的论文6中,Perlin改进为s(t)=6t^5−15t^4+10t^3。这里简单解释一下,为什么不直接使用s(t)=t,即线性插值。直接使用的线性插值的话,它的一阶导在晶格顶点处(即t = 0或t = 1)不为0,会造成明显的不连续性。s(t)=3t^2−2t^3在一阶导满足连续性,s(t)=6t^5−15t^4+10t^3在二阶导上仍然满足连续性。
这个算法与Value Noise的主要差别是:
- 晶格点上的随机取值由一维的随机值换成了二维的随机向量(也就是所谓的梯度向量);
- 插值的四个值(2D)是四个晶格点的梯度向量 和 距像素点的距离向量 点乘的结果。 原始算法中梯度向量的产生使用了蒙特卡洛算法,筛选单位圆(球)内的向量。shader实现时可以简化一下,乐乐老师这个版本没有归一化到单位圆内,是-1~1正方形内的随机向量:
vec2 hash22(vec2 p){
p = vec2( dot(p,vec2(127.1,311.7)),
dot(p,vec2(269.5,183.3)));
return -1.0 + 2.0 * fract(sin(p)*43758.5453123);
}
float perlin_noise(vec2 p)
{
vec2 pi = floor(p);
vec2 pf = p - pi;
vec2 w = pf * pf * (3.0 - 2.0 * pf);
return mix(mix(dot(hash22(pi + vec2(0.0, 0.0)), pf - vec2(0.0, 0.0)),
dot(hash22(pi + vec2(1.0, 0.0)), pf - vec2(1.0, 0.0)), w.x),
mix(dot(hash22(pi + vec2(0.0, 1.0)), pf - vec2(0.0, 1.0)),
dot(hash22(pi + vec2(1.0, 1.0)), pf - vec2(1.0, 1.0)), w.x),
w.y);
}//-1~1
float perlin_basic(vec2 p, float dens){
return 0.5+perlin_noise(p*dens)*0.5;
}//remap to 0~1
通过对perlin noise 进行fbm,可以获得更富有变化的效果。叠加时每一次噪声的采样频率翻倍,而振幅减少一倍。
float perlin_fbm(vec2 p,float dens){
float f = 0.0;
p = p dens;
f += 1.0000 perlin_noise(p); p = 2.0 p;
f += 0.5000 perlin_noise(p); p = 2.0 p;
f += 0.2500 perlin_noise(p); p = 2.0 p;
f += 0.1250 perlin_noise(p); p = 2.0 p;
f += 0.0625 perlin_noise(p); p = 2.0 p;
return f0.5+0.5;
}
其他变体:
fbm取绝对值,Perlin把这个公式称为turbulence。。由于进行了绝对值操作,因此会在0值变化处出现不连续性,形成一些尖锐的效果。通过合适的颜色叠加,我们可以用这种噪声来模拟火焰、云朵这些物体。
在之前turbulence公式的基础上使用了一个关于表面x分量的正弦函数,这个公式可以让表面沿着x方向形成一个条纹状的结构。Perlin使用这个公式模拟了一些大理石材质。
float perlin_sinx(vec2 p,float dens,float xscale){
float f = perlin_abs(p,dens);
return sin(f 1.5 + p.x xscale);
}
应用一下
如果希望实现二维等噪音动画效果,需要使用三维的噪音,第三维对应时间变量。在shadertoy上魔改了一个传送门:
float snoise(vec3 uv, float res)
{
const vec3 s = vec3(1e0, 1e2, 1e3);
uv = res;
vec3 uv0 = floor(mod(uv, res))s;
vec3 uv1 = floor(mod(uv+vec3(1.), res))*s;
vec3 f = fract(uv); f = f*f*(3.0-2.0*f);
vec4 v = vec4(uv0.x+uv0.y+uv0.z, uv1.x+uv0.y+uv0.z,
uv0.x+uv1.y+uv0.z, uv1.x+uv1.y+uv0.z);
vec4 r = fract(sin(v*1e-1)*1e3);
float r0 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);
r = fract(sin((v + uv1.z - uv0.z)*1e-1)*1e3);
float r1 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);
return mix(r0, r1, f.z)*2.-1.;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 p = -.5 + fragCoord.xy / iResolution.xy;
p.x *= iResolution.x/iResolution.y;
float color = 6.0 - (6.*length(2.*p));
vec3 coord = vec3(atan(p.x,p.y)/6.2832+.5, length(p)*.4, .5);
for(int i = 1; i <= 7; i++)
{
float power = pow(2.0, float(i));
color += (1.5 / power) * snoise(coord + vec3(0.,-iTime*.05, iTime*.01), power*16.);
}
fragColor = vec4( sin(pow(max(color,0.),0.8))*(sin(iTime)*.1+.9)*.8,
sin(pow(max(color,.0),0.87))*(sin(iTime)*.1+.9)*.9,
(sin(pow(max(color,0.),0.78)))*(sin(iTime*2.)*.1+.9)*1.,
1.0);
}