《The Book of Shader》笔记 : 第七节 - 噪声与FBM -- 用噪声勾勒碧海蓝天

前言

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

在这里插入图片描述

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Babylon.js提供了一些内置的工具来生成柏林噪声、Worley噪声和分形噪声。下面是一些示例代码: 1. 生成柏林噪声: ```javascript // 创建一个柏林噪声纹理 var noiseTexture = new BABYLON.NoiseProceduralTexture("noise", 256, scene); // 设置柏林噪声的类型和参数 noiseTexture.animationSpeedFactor = 5; noiseTexture.persistence = 0.9; noiseTexture.brightness = 0.1; noiseTexture.octaves = 4; // 将柏林噪声纹理应用到一个平面上 var plane = BABYLON.Mesh.CreatePlane("plane", 10, scene); var mat = new BABYLON.StandardMaterial("mat", scene); mat.diffuseTexture = noiseTexture; plane.material = mat; ``` 2. 生成Worley噪声: ```javascript // 创建一个Worley噪声纹理 var noiseTexture = new BABYLON.WorleyNoiseProceduralTexture("noise", 256, scene); // 设置Worley噪声的类型和参数 noiseTexture.animationSpeedFactor = 5; noiseTexture.distanceFunction = BABYLON.WorleyNoiseProceduralTexture.EUCLIDIAN_DISTANCE; noiseTexture.cellsDensity = 2.0; // 将Worley噪声纹理应用到一个平面上 var plane = BABYLON.Mesh.CreatePlane("plane", 10, scene); var mat = new BABYLON.StandardMaterial("mat", scene); mat.diffuseTexture = noiseTexture; plane.material = mat; ``` 3. 生成分形噪声: ```javascript // 创建一个分形噪声纹理 var noiseTexture = new BABYLON.FractalNoiseProceduralTexture("noise", 256, scene); // 设置分形噪声的类型和参数 noiseTexture.animationSpeedFactor = 5; noiseTexture.type = BABYLON.FractalNoiseType.FBM; noiseTexture.persistence = 0.6; noiseTexture.octaves = 4; // 将分形噪声纹理应用到一个平面上 var plane = BABYLON.Mesh.CreatePlane("plane", 10, scene); var mat = new BABYLON.StandardMaterial("mat", scene); mat.diffuseTexture = noiseTexture; plane.material = mat; ``` 这些示例代码演示了如何在Babylon.js中生成柏林噪声、Worley噪声和分形噪声,并将它们应用到一个平面上。你可以通过修改参数和设置不同的纹理类型来创建自己的噪声效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值