前言
看shadertoy一些shader里面,偶尔会出现名字是fbm的函数,有些云里雾里,看完这节总算大概能明白这个函数的作用了。
fbm 不是噪声,但和噪声的使用常常成对出现。这个函数将不同振幅,不同频率的噪声组合起来实现相应的效果。下面两幅图是本节中实现的两个小示例(个人感觉效果看起来差了不少,一个像云雾一个像水面,但实际上只是使用的噪声函数不同。看来有时间需要做做实验,把之前了解到的几种噪声与不同的fbm组合下,试试效果)。
此外,在本节的最后,过一下shadertoy上大神写的蓝天白云。
最后上个定义
分形布朗运动FBM(Fractal Brown Motion),是1968年Mandelbrot和Ness两人提出的一种数学模型,它主要用于描述自然界的山脉、云层、地形地貌以及模拟星球表面等不规则形状阶。 ---- <百度>
正文
一. 通常形式
在之前对比展示ValueNoise, PerlinNoise噪声效果差异的时,已经使用到了分形函数的技巧。
上面这个例子中,图2, 3, 4 分别用到了下面的三个分型函数
float noise_sum(vec2 st) {
float f = 0.0;
st *= 4.0;
f += 1.000 * noise(st); st *= 2.0;
f += 0.500 * noise(st); st *= 2.0;
f += 0.250 * noise(st); st *= 2.0;
f += 0.125 * noise(st); st *= 2.0;
f += 0.0625 * noise(st); st *= 2.0;
return f;
}
float noise_sum_abs(vec2 st) {
float f = 0.0;
st *= 4.0;
f += 1.000 * abs(noise(st)); st *= 2.0;
f += 0.500 * abs(noise(st)); st *= 2.0;
f += 0.250 * abs(noise(st)); st *= 2.0;
f += 0.125 * abs(noise(st)); st *= 2.0;
f += 0.0625 * abs(noise(st)); st *= 2.0;
return f;
}
float noise_sum_sin_abs(vec2 st) {
float f = 0.0;
st *= 16.0;
f += 1.000 * abs(value_noise(st)); st *= 2.0;
f += 0.500 * abs(value_noise(st)); st *= 2.0;
f += 0.250 * abs(value_noise(st)); st *= 2.0;
f += 0.125 * abs(value_noise(st)); st *= 2.0;
f += 0.0625 * abs(value_noise(st)); st *= 2.0;
f = sin(f + st.x / 40.0);
return f;
}
形式上,每一个迭代项都进行了相同的计算过程,区别在于振幅每次减半,频率逐次增加。
二.云雾
书中综合运用了Value Noise, FBM的技巧实现了上面那这个云雾效果。
但似乎隐隐约约还能看出一些网格的边界,所以试着将生成噪声的函数由Value Noise 改为Simplex Noise,却呈现出了下面这样的效果。(Why?)
#ifdef GL_ES
precision mediump float;
#endif
// -------- Simplex noise -------- //
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
float snoise(vec2 v) {
// Precompute values for skewed triangular grid
const vec4 C = vec4(0.211324865405187,
// (3.0-sqrt(3.0))/6.0
0.366025403784439,
// 0.5*(sqrt(3.0)-1.0)
-0.577350269189626,
// -1.0 + 2.0 * C.x
0.024390243902439);
// 1.0 / 41.0
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1 = vec2(0.0);
i1 = (x0.x > x0.y)? vec2(1.0, 0.0):vec2(0.0, 1.0);
vec2 x1 = x0.xy + C.xx - i1;
vec2 x2 = x0.xy + C.zz;
i = mod289(i);
vec3 p = permute(
permute( i.y + vec3(0.0, i1.y, 1.0))
+ i.x + vec3(0.0, i1.x, 1.0 ));
vec3 m = max(0.5 - vec3(
dot(x0,x0),
dot(x1,x1),
dot(x2,x2)
), 0.0);
m = m*m ;
m = m*m ;
// Gradients:
// 41 pts uniformly over a line, mapped onto a diamond
// The ring size 17*17 = 289 is close to a multiple
// of 41 (41*7 = 287)
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
// Normalise gradients implicitly by scaling m
// Approximation of: m *= inversesqrt(a0*a0 + h*h);
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0+h*h);
// Compute final noise value at P
vec3 g = vec3(0.0);
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * vec2(x1.x,x2.x) + h.yz * vec2(x1.y,x2.y);
return 130.0 * dot(m, g);
}
#define NUM_OCTAVES 5
float fbm(vec2 st) {
float v = 0.0;
float a = 0.5;
vec2 shift = vec2(100.0);
// 进行旋转
mat2 rot = mat2(cos(0.5), sin(0.5),
-sin(0.5), cos(0.50));
// 进行分形的迭代
for (int i = 0; i < NUM_OCTAVES; ++i) {
v+= a * snoise(st);
st = rot * st * 2.0 + shift;
a *= 0.5;
}
return v;
}
#define shadow vec3(0.101961,0.619608,0.666667)
#define hightlight vec3(0.666667,0.666667,0.498039)
void main() {
vec2 st = gl_FragCoord.xy/iResolution.xy*3.;
vec3 color = vec3(0.0);
vec2 q = vec2(0.0);
q.x = fbm(st + 0.20 * iTime);
q.y = fbm(st + 0.30 * iTime);
vec2 r = vec2(0.0);
r.x = fbm(st + 1.0 * q + vec2(1.7, 9.2) + 0.15 * iTime);
r.y = fbm(st + 1.0 * q + vec2(8.3, 2.8) + 0.126 * iTime);
float f = fbm(st + r);
color = mix(shadow,
hightlight,
clamp((f*f)*4.0,0.0,1.0));
color = mix(color,
vec3(0,0,0.164706),
clamp(length(q),0.0,1.0));
color = mix(color,
vec3(0.666667,1,1),
clamp(length(r.x),0.0,1.0));
gl_FragColor = vec4((f*f*f+.6*f*f+.5*f)*color,1.);
}
该例子中使用到的分型函数
for (int i = 0; i < NUM_OCTAVES; ++i) {
v+= a * snoise(st);
st = rot * st * 2.0 + shift;
a *= 0.5;
}
可以理解为上面这个分型函数的基础上,每一个迭代项中的p,左乘了一个旋转矩阵R,而后加上了一个偏移量shift。
(虽然把这个R和shift去掉,这里好像没有看出太明显的区别)。
另外,调整一些参数会产生不一样的效果。将与振幅相关的倍数 a 由0.5 改为 0.2
可以得到如下效果,比起上一张显得更平和。
3. 2维蓝天白云
接下来看看shadertoy上大神写的蓝天白云。
https://www.shadertoy.com/view/4tdSWr
1. 首先是该图形的噪声函数。
// -------- 生成随向量 -------- //
vec2 random22(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 noise (vec2 p) {
const float K1 = 0.366025404; // (sqrt(3)-1)/2;
const float K2 = 0.211324865; // (3-sqrt(3))/6;
vec2 i = floor(p + (p.x + p.y) * K1); // 类似整数部分 ?
vec2 a = p - i + (i.x + i.y) * K2; // 类似小数部分 ?
// 当a.x > a.y, 即点落在y = x 对角线下半部分, o = vec2(1, 0) 否则 o = vec2(0, 1)
vec2 o = (a.x > a.y)?vec2(1.0, 0.0) : vec2(0.0,1.0); // or vec2 o = 0.5 + 0.5 * vec2(sign(a.x - a.y), sign(a.y - a.x));
vec2 b = a - o + K2;
vec2 c = a - 1.0 + 2.0 * K2;
vec3 h = max(0.5 - vec3(dot(a, a), dot(b,b), dot(c,c)), 0.0);
vec3 n = h * h * h * h * vec3(dot(a, random22(i + 0.0)), dot(b, random22(i + o)), random22(i + 1.0));
return dot(n, vec3(70.0));
}
第一次看完这个噪声的代码,并不理解为什么是这样实现的。直接输出一下噪声的样子。(后来发现,这个噪声其实就是Simplex 噪声)
void main() {
vec2 p = gl_FragCoord.xy / iResolution.xy;
vec2 uv = 4.0 * p * vec2(iResolution.x/iResolution.y,1.0);
float pct = noise(uv);
vec3 color = vec3(pct);
gl_FragColor = vec4(color, 1.0);
}
可以看出这个噪声具有沿平行四边形(或者说等边三角形?)网格排布的特征(尝试过直接使用perlin noise,最终效果差多了)。
2. 分形函数
// -------- fbm 分形函数 -------- //
// f = 0.4 * noise(n) + 0.04 * noise(m * n) + 0.016 * noise(m * m * n) + …
float fbm(vec2 n) {
float total = 0.0, amplitude = 0.1; // 初始化返回值与振幅的衰减系数
for (int i = 0; i < 7; i++) {
total += noise(n) * amplitude;
n = m * n; // 乘上一个旋转矩阵
amplitude *= 0.4;
}
return total;
}
(比起噪声,这里的fbm好理解多了。)
3. Main函数
Step1
void main() {
vec2 p = gl_FragCoord.xy / iResolution.xy;
vec2 uv = p * vec2(iResolution.x/iResolution.y,1.0);
float time = iTime * speed;
float q = fbm(uv * cloudscale * 0.5);
// ridged noise shape
float r = 0.0;
uv *= cloudscale;
uv -= q - time;
float weight = 0.8;
for (int i = 0; i < 8; i++) {
r += abs(weight * noise(uv));
uv = m * uv + time;
weight *= 0.7;
}
vec3 color = vec3(r);
gl_FragColor = vec4(color, 1.0);
}
Step2 分别做四次分形
void main() {
vec2 p = gl_FragCoord.xy / iResolution.xy;
vec2 uv = p * vec2(iResolution.x/iResolution.y,1.0);
float time = iTime * speed;
float q = fbm(uv * cloudscale * 0.5);
// ridged noise shape
float r = 0.0;
uv *= cloudscale;
uv -= q - time;
float weight = 0.8;
for (int i = 0; i < 8; i++) {
r += abs(weight * noise(uv));
uv = m * uv + time;
weight *= 0.7;
}
// noise shape
// noise shape
// 又做了一次分形操作,区别在于参数weight发生了变化
float f = 0.0;
uv = p*vec2(iResolution.x/iResolution.y,1.0);
uv *= cloudscale;
uv -= q - time;
weight = 0.7;
for (int i = 0; i < 8; i++) {
f += weight * noise(uv);
uv = m * uv + time;
weight *= 0.6;
}
f *= r + f; // f = f * (r + f)
// 对noise 与 noise ridge 的颜色有分别做了一次分形
// noise color
float c = 0.0;
time = iTime * speed * 2.0;
uv = p*vec2(iResolution.x/iResolution.y,1.0);
uv *= cloudscale*2.0;
uv -= q - time;
weight = 0.4;
for (int i=0; i<7; i++){
c += weight*noise( uv );
uv = m*uv + time;
weight *= 0.6;
}
//noise ridge colour
float c1 = 0.0;
time = iTime * speed * 3.0;
uv = p*vec2(iResolution.x/iResolution.y,1.0);
uv *= cloudscale*3.0;
uv -= q - time;
weight = 0.4;
for (int i=0; i<7; i++){
c1 += abs(weight*noise( uv ));
uv = m*uv + time;
weight *= 0.6;
}
c += c1;
vec3 color = vec3(c);
gl_FragColor = vec4(color, 1.0);
}
Step3 上色
void main() {
vec2 p = gl_FragCoord.xy / iResolution.xy;
vec2 uv = p * vec2(iResolution.x/iResolution.y,1.0);
float time = iTime * speed;
float q = fbm(uv * cloudscale * 0.5);
// ridged noise shape
float r = 0.0;
uv *= cloudscale;
uv -= q - time;
float weight = 0.8;
for (int i = 0; i < 8; i++) {
r += abs(weight * noise(uv));
uv = m * uv + time;
weight *= 0.7;
}
// noise shape
// 又做了一次分形操作,区别在于参数weight发生了变化
float f = 0.0;
uv = p*vec2(iResolution.x/iResolution.y,1.0);
uv *= cloudscale;
uv -= q - time;
weight = 0.7;
for (int i = 0; i < 8; i++) {
f += weight * noise(uv);
uv = m * uv + time;
weight *= 0.6;
}
f *= r + f; // f = f * (r + f)
// 对noise 与 noise ridge 的颜色有分别做了一次分形
// noise color
float c = 0.0;
time = iTime * speed * 2.0;
uv = p*vec2(iResolution.x/iResolution.y,1.0);
uv *= cloudscale*2.0;
uv -= q - time;
weight = 0.4;
for (int i=0; i<7; i++){
c += weight*noise( uv );
uv = m*uv + time;
weight *= 0.6;
}
//noise ridge colour
float c1 = 0.0;
time = iTime * speed * 3.0;
uv = p*vec2(iResolution.x/iResolution.y,1.0);
uv *= cloudscale*3.0;
uv -= q - time;
weight = 0.4;
for (int i=0; i<7; i++){
c1 += abs(weight*noise( uv ));
uv = m*uv + time;
weight *= 0.6;
}
c += c1;
vec3 skycolor = mix(skycolor2, skycolor1, p.y);
vec3 cloudcolor = vec3(1.1, 1.1, 0.9) * clamp((clouddark + cloudlight * c), 0.0, 1.0);
f = cloudcover + cloudalpha*f*r;
vec3 result = mix(skycolor, clamp(skytint * skycolor + cloudcolor, 0.0, 1.0), clamp(f + c, 0.0,1.0));
gl_FragColor = vec4(result, 1.0);
}