在之前的柏林噪声原理介绍一文中,我们介绍了什么是噪声,还介绍了在图形学中我们经常需要模拟自然界中的噪声。简单来说,自然界中的噪声虽然有随机成分,但是过渡自然。柏林噪声是通过晶格上的随机量和距离向量乘积并通过平滑插值已达到平滑过渡。而今天介绍的网格噪声是通过距离来达到平滑目的的。接下来让我们来介绍下网格噪声的原理。
网格噪声
网格噪声基于距离场。每个点的值是离所有特征点最短的距离。我们以总共有四个特征点组成的特征点集举例:
float min_dist = 1.0; // 初始化最短距离
min_dist = min(min_dist, distance(coord, point_1));
min_dist = min(min_dist, distance(coord, point_2));
min_dist = min(min_dist, distance(coord, point_3));
min_dist = min(min_dist, distance(coord, point_4));
上面中的 coord 是指屏幕坐标系归一化后的坐标。最后 min_dist 中保存的就是对应的 coord 点到 point_1 到 point_4 中的最短距离。我们可以将上面代码改写成用数组和for循环的形式:
float min_dist = 1.0;
for (int i = 0; i < 4; i++){
float dist = distantce(coord, points[ i ]);
min_dist = min(min_dist, dist);
}
提升效率
我们知道,GLSL用的是GPU并行的模式。所以用上述for循环和数组的方式来实现效率并不高。当特征点集很多时,我们需要寻找一个用到并行特性的方法来实现。
解决这个问题的一个方法就是把空间网格化。这时候,属于某个网格内的点只需要计算和临近的八个网格中的特征点以及落在该网格中的特征点的距离并取最小值即可。大致格式如下:
vec3 color = vec3(0.0); // 初始化颜色(黑)
coord *= 3.0; // scale: 网格化
vec2 i_coord = floor(coord);
vec2 f_coord = fract(coord);
float m_dist = 1.0; // 初始化最小距离
for (int y = -1; y <= 1; y++){
for (int x = -1; x <= 1; x++){
// 相邻网格,当x和y都为0时为当前网格
vec2 neighbor = vec2(float(x), float(y));
// 随机生成特征点
vec2 point = random2(i_coord + neighbor);
vec2 diff = neighbor + point - f_coord;
float dist = length(diff);
m_dist = min(m_dist, dist);
}
}
color += m_dist;
gl_FragColor = vec4(color, 1.0);
其中的 random2 函数如下:
vec2 random2( vec2 p ) {
return fract(sin(vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))))*43758.5453);
}
效果如下:
效果图