假设要生成 4 个网格,可以先在空间中指定 4 个特征点。对于每个像素点,计算它到最近特征点的距离,将这个距离当作结果值输出。
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; void main() { vec2 st = gl_FragCoord.xy/u_resolution.xy; // 先指定 4 个特征点 vec2 point[4]; point[0] = vec2(0.83, 0.75); point[1] = vec2(0.60, 0.07); point[2] = vec2(0.28, 0.64); point[3] = vec2(0.31, 0.26); // 计算像素点到 4 个特征点的最小距离 float m_dist = 1.0; for (int i = 0; i < 4; i++) { float dist = distance(st, point[i]); m_dist = min(m_dist, dist); } // 输出结果 vec3 color = vec3(0.0); color += m_dist; gl_FragColor = vec4(color, 1.0); }效果展示:
0x01 优化循环对着色器很不友好,遍历次数过多的循环会显著降低着色器的性能。在 Steven Worley 发表的一篇论文《A Cellular Texture Basis Function》中描述了优化的方法。我们可以将空间分割成网格,每个网格对应一个特征点,每个像素点只计算到相邻网格中的特征点的距离。这样,每个像素点就只需要计算到九个特征点的距离,它自身所在的网格的特征点和相邻的八个网格的特征点。
另外,还可以改用每个网格的整数坐标来构造随机的特征点。省去了手动指定特征点的麻烦,同时也带来了更多的随机性。
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; vec2 random2(vec2 pos) { float x = dot(pos, vec2(127.1, 311.7)); float y = dot(pos, vec2(269.5, 183.3)); return fract(sin(vec2(x, y)) * 43758.5453); } void main() { vec2 st = gl_FragCoord.xy / u_resolution.xy; st *= 9.0; vec2 i = floor(st); vec2 f = fract(st); float m_dist = 1.0; for (int y = -1; y <= 1; y++) { for (int x = -1; x <= 1; x++) { // 相邻格子 vec2 neighbor = vec2(float(x), float(y)); // 生成随机特征点 vec2 point = random2(i + neighbor); // 计算距离 float dist = length(neighbor + point - f); // 更新最短距离 m_dist = min(m_dist, dist); } } vec3 color = vec3(0.0); color += m_dist; gl_FragColor = vec4(color, 1.0); }效果展示:
0x02 扩展Inigo Quilez 写了一篇文章,提出了他称之为 Voro Noise 的噪声,可以将常规噪声和网格噪声组合在一起。
在 Voro Noise 中,额外使用两个参数,不妨称之为 u 和 v。其中,u 用来决定最终的噪声更像常规噪声还是网格噪声,简单来说:当 u 接近 0 时,生成的噪声更接近常规噪声;当 u 接近 1 时,生成的噪声更接近网格噪声。v 提供类似常规噪声中的线性插值和网格噪声中最短距离值的功能(ps:最短距离的算法是非连续的,在 iq 的另一篇文章 Smooth Voronoi 中提供了解决这一问题的办法)。
// 初版插值方法。其中,64 是一个连续性比较好的值,详见 Smooth Voronoi 一文。 // float ww = pow( 1.0 - smoothstep(0.0, 1.414, sqrt(d)), 64.0 - 63.0 * v); // 在初版基础上提高函数的阶数以获得更平滑的表现。 // 64.0 - 63.0 * v => 1.0 + 63.0 * pow(1.0 - v, 4.0) float ww = pow( 1.0 - smoothstep(0.0, 1.414, sqrt(d)), 1.0 + 63.0 * pow(1.0 - v, 4.0));完整代码:
#ifdef GL_ES precision mediump float; #endif uniform vec2 u_resolution; uniform vec2 u_mouse; vec3 random3( vec2 p ) { float x = dot(p, vec2(127.1, 311.7)); float y = dot(p, vec2(269.5, 183.3)); float z = dot(p, vec2(419.2, 371.9)); return fract(sin(vec3(x, y, z)) * 43758.5453); } float voroNoise(in vec2 x, float u, float v ) { vec2 i = floor(x); vec2 f = fract(x); float k = 1.0 + 63.0 * pow(1.0 - v, 4.0); // 下面两个参数用于计算加权平均值,wa 统计总值,wt 统计总单位数 float wa = 0.0; float wt = 0.0; // 扩大搜索范围,进一步提高连续性 for (int y = -2; y <= 2; y++) { for (int x = -2; x <= 2; x++) { // 相邻格子 vec2 neighbor = vec2(float(x), float(y)); // 随机生成 point,其中 point.xy 表示特征点,point.z 表示该点的灰度值 vec3 point = random3(i + neighbor) * vec3(u, u, 1.0); // 根据距离计算贡献值 float dist = length(neighbor - f + point.xy); float ww = pow(1.0 - smoothstep(0.0, 1.414, dist), k); wa += point.z * ww; wt += ww; } } return wa/wt; } void main() { vec2 st = gl_FragCoord.xy/u_resolution.xy; st *= 10.0; // 用鼠标位置控制 voroNoise 中的 u 和 v float n = voroNoise(st, u_mouse.x/u_resolution.x, u_mouse.y/u_resolution.y); gl_FragColor = vec4(vec3(n), 1.0); }效果展示:
u 从 0 到 1