图形学中的噪声

一.噪声定义

大理石表面的花纹、流沙、水墨画、山脉起伏、光照的海面等,这些纹理不可预测性叫随机,但是这种随机又不同于我们常规程序中用到的随机。计算机中用机器来绘制这些图就会运用到噪声,噪声可以定义为一个随机数生成器 ;但是这种随机是有规律的随机,看似杂乱无章实则有序,因此可以构造一些丰富逼真的图片。

attachmentId-124

attachmentId-125

attachmentId-126

attachmentId-127

二.噪声的生成

一维使用noise

float rand (in float _x) {
    return fract(sin(_x)*1e4);
}

float i = floor(x);  // 整数(i 代表 integer)
float f = fract(x);  // 小数(f 代表 fraction)
y = rand(i); 

我们把float类型的x分割成整数和小数部分,然后用rand把分割出来的整数部分生成一个随机值,效果如下:

attachmentId-128

这样当x取整的值相等时,即相同的i得到的y值相同,就会看到连续的水平线

既然x是一个小数的话,那我们想到是否能够用小数部分来做一个线性插值呢
如下:

attachmentId-129

y = mix(rand(i), rand(i + 1.0), f);

因f值呈线性变化,因此会出现折线。

曲线太过于尖锐的时候我们会想到前面文章中经常使用的smoothstep来使曲线平滑的过渡,看一下效果:

attachmentId-130

y = mix(rand(i), rand(i + 1.0), smoothstep(0.,1.,f));

在应用smoothstep之后我们注意顶点的变化变的顺滑了起来。
噪声实际运用中,会发现大部分的代码会用三次多项式来代替smoothstep

float u = f * f * (3.0 - 2.0 * f );  //其实smoothstep源码就是用了这个三次多项式
y = mix(rand(i), rand(i + 1.0), u); 

三.梯度噪声

梯度噪声可用于产生连续性的纹理,所以经常用来模拟山脉,云朵等具有连续性的物质。著名的柏林噪声就是梯度噪声的代表

算法步骤

梯度噪声是通过多个随机梯度相互影响计算得到,通过梯度向量的方向与片元的位置计算噪声值。

attachmentId-131

二维梯度噪声示意图
以2d举例,主要分为四步:

1.网格生成

我们将2d平面分成mxn个大小相同的网格,具体数值取决于我们生成的纹理密度
GLSL中生成网格很简单 将要噪声化的点乘以一个scale即可 如uv *= SCALE

2.随机梯度的生成

梯度向量生成,这一步是根据第一步生成的网格的顶点来产生随机向量,四个顶点就有四个梯度向量

// 输入网格顶点位置,输出随机向量
vec2 random(vec2 p){
    return  -1.0 + 2.0 * fract(
        sin(
            vec2(
                dot(p, vec2(127.1,311.7)),
                dot(p, vec2(269.5,183.3))
            )
        ) * 43758.5453
    );
}

如上,借用三角函数sin来生成随机值,输入网格顶点坐标,输出随机变量

3.梯度点乘

这一步是通过计算四个梯度向量对当前片元点P的影响,主要先求出点P到四个顶点的距离向量,然后和对应的梯度向量进行点积。
如上图,网格内的片元点P的四个顶点距离向量为a1, a2, a3, a4。
此时将距离向量与梯度向量g1, g2, g3, g4进行点积运算:c[i] = a[i] · g[i];

4.平滑插值

得到四个点乘结果后,我们对其进行线性叠加,使用smoothstep方法来平滑网格的边界,最终得到当前片元的噪声值。

代码如下:

float noise_perlin (vec2 p) {
    vec2 i = floor(p);      // 获取当前网格索引i
    vec2 f = fract(p);      // 获取当前片元在网格内的相对位置

    float a = dot(random(i),f);         //周围四个点的梯度向量与距离向量点积运算
    float b = dot(random(i + vec2(1., 0.)),f - vec2(1., 0.));
    float c = dot(random(i + vec2(0., 1.)),f - vec2(0., 1.));
    float d = dot(random(i + vec2(1., 1.)),f - vec2(1., 1.));
    // 平滑插值
    vec2 u = smoothstep(0.,1.,f);
    // 叠加点乘结果
    return mix(mix(a,b,u.x),mix(c,d,u.x),u.y);
}

经典柏林噪声

看懂了上面的算法步骤之后,我们再来看看经典柏林噪声的计算

vec4 mod289(vec4 x)
{
  return x - floor(x * (1.0 / 289.0)) * 289.0;
}

vec4 permute(vec4 x)
{
  return mod289(((x*34.0)+10.0)*x);
}

vec4 taylorInvSqrt(vec4 r)
{
  return 1.79284291400159 - 0.85373472095314 * r;
}

vec2 fade(vec2 t) {
  return t*t*t*(t*(t*6.0-15.0)+10.0);
}

// Classic Perlin noise
float cnoise(vec2 P)
{
  vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
  vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
  Pi = mod289(Pi); // To avoid truncation effects in permutation
  vec4 ix = Pi.xzxz;    //(0,1,0,1)
  vec4 iy = Pi.yyww;    //(0,0,1,1)
  vec4 fx = Pf.xzxz;
  vec4 fy = Pf.yyww;

  vec4 i = permute(permute(ix) + iy);

  vec4 gx = fract(i * (1.0 / 41.0)) * 2.0 - 1.0 ;
  vec4 gy = abs(gx) - 0.5 ;
  vec4 tx = floor(gx + 0.5);
  gx = gx - tx;

  vec2 g00 = vec2(gx.x,gy.x);       //四个随机梯度向量  (0,0)点
  vec2 g10 = vec2(gx.y,gy.y);       //                (1,0)点
  vec2 g01 = vec2(gx.z,gy.z);       //                (0,1)点
  vec2 g11 = vec2(gx.w,gy.w);       //                (1,1)点

  vec4 norm = taylorInvSqrt(vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11)));
  g00 *= norm.x;                    //归一化 消除正方形方向性的偏差
  g01 *= norm.y;  
  g10 *= norm.z;  
  g11 *= norm.w;  

  float n00 = dot(g00, vec2(fx.x, fy.x));     //梯度向量和距离向量点乘    (0,0)点
  float n10 = dot(g10, vec2(fx.y, fy.y));     //                        (1,0)点
  float n01 = dot(g01, vec2(fx.z, fy.z));     //                        (0,1)点
  float n11 = dot(g11, vec2(fx.w, fy.w));     //                         (1,1)点

  vec2 fade_xy = fade(Pf.xy);
  vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
  float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
  return 2.3 * n_xy;
}

可以看出,经典的柏林算法的代码看起来更加规范化和可扩展化,与之前的区别是添加了梯度的归一化,可以消除正方向方向性的偏差。
应用示例:

attachmentId-132

这里下面的山脉起伏效果就是用的经典柏林噪声来实现

四.三维柏林噪声

同理,三维柏林噪声和二维类似,计算量不同而已。二维是正方形,相邻的晶格点有四个,那三维就是8个,n维有2n个。
同样,计算该点到各个晶格点的距离向量,再分别与顶点上的梯度向量做点乘,得到2n个点乘结果。然后再使用缓和曲线来计算他们的权重和。这里就不再做详细讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值