写在前面
之前搜索云雾,火焰,水等效果的时候,噪声一词频繁出现,因而一直想搜索相关概念。现在终于到了这章节。这两节内容从随机数讲起,而后介绍了Value noise, Perlin noise,Simplex noise噪声纹理的生成。
(预告:这章之后,理解了噪声与分形的有关概念,大概可以理解这张简单的二维云彩效果是如何生成的了。)
读完书中的内容,觉得理解起来还差点意思,搜索了一下其他前人的总结。
原地转粉的女神:谈谈图形学
书中常常出现的大神Inigo Quiles的博客:https://www.iquilezles.org/www/index.htm
看了前辈们的文章之后,我的认知再次被刷新,不禁再次感叹,图形大法博大精深。
正文
Perlin 噪声
二维情况下Perlin噪声为例。
- 首先每个网格顶点生成一个随机单位向量。
// -------- 随机函数 -------- //
vec2 random2(vec2 st) {
st = vec2(dot(st, vec2(127.1, 311.7)), dot(st, vec2(269.5, 183.3)));
return -1.0 + 2.0 * fract(sin(st) * 43758.5453123);
}
- 计算网格顶点到网格中一点(s, t)的距离向量。
- 得到网格顶点处的值,做法是用网格顶点上的随机向量点乘对应的距离向量。
- 求出(s, t)处对应的值。采用插值的方法,这里常用的插值函数是
选取三次函数的原因,该函数一阶导的导函数,在顶底点处函数值为0。相邻网格做插值时可满足一阶导连续,变化不会显得太突兀。
// -------- 2 维 Perlin Noise函数 -------- //
vec2 random2(vec2 st) {
st = vec2(dot(st, vec2(127.1, 311.7)), dot(st, vec2(269.5, 183.3)));
return -1.0 + 2.0 * fract(sin(st) * 43758.5453123);
}
float perlin_noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(
dot(random2(i + vec2(0.0)), f - vec2(0.0)),
dot(random2(i + vec2(1.0,0.0)), f - vec2(1.0, 0.0)),
u.x
),
mix(
dot(random2(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0)),
dot(random2(i + vec2(1.0,1.0)), f - vec2(1.0, 1.0)),
u.x
),
u.y
);
}
Perlin Noise分型效果演示:
Value Noise
- 同样的也是先在每个网格顶点生成一个介于[0, 1]间的随机值(这回不必生成以一个单位随机向量)。
- 直接使用顶点上的四个值,对网格中的像素点进行插值。
- // -------- 2 维 Value Noise函数 -------- //
float random21(vec2 st) {
st = vec2 (dot(st, vec2(127.1, 311.7)),dot(st, vec2(269.5,183.3)));
return -1.0 + 2.0 * fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
float value_noise(vec2 st) {
vec2 i = floor(st);
vec2 f = fract(st);
vec2 u = f * f * (3.0 - 2.0 * f);
// 先沿x方向进行一次插值,然后再沿y方向做一次插值
return mix(
mix(random2(i + vec2(0.0)), random2(i + vec2(1.0, 0.0)), u.x),
mix(random2(i + vec2(0.0, 1.0)), random2(i + vec2(1.0, 1.0)), u.x),
u.y
);
}
Value Noise分型效果演示:
Simplex Noise
Simplex 是对Perlin噪声的一种改进,该方法的计算复杂度为
在二维空间中,Simplex Noise用三个顶点构成一个网格,然后利用网格上的顶点进行插值,表示空间中任一点的值。推广到N维空间中则由N + 1 个顶点构成网格。
而之前的 Value Noise 与Perlin Noise 的复杂度为
二维空间中需要利用四个顶点进行插值,n维空间中需要利用到2 的 n 次方个顶点。
随着输入向量维度的增加,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
// First corner (x0)
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
// Other two corners (x1, x2)
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;
// Do some permutations to avoid
// truncation effects in permutation
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);
}
Simplex Noise 噪声分型效果。