说明
【跟月影学可视化】学习笔记。
什么是噪声?
物理学上,噪声指一切不规则的信号(不一定要是声音),比如电磁噪声,热噪声,无线电传输时的噪声,激光器噪声,光纤通信噪声,照相机拍摄图片时画面的噪声等。
如何实现噪声函数?
我们知道随机数是离散的,如果对离散的随机点进行插值,可以让每个点之间的值连续过渡,然后使用 smoothstep 或者平滑的三次样条来插值,就可以形成一条连续平滑的随机曲线。
对离散的随机值进行插值又被称为插值噪声(Value Noise)。缺点:它的值的梯度不均匀。最直观的表现就是,二维噪声图像有明显的“块状”特点,不够平滑。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>如何实现噪声函数</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; // 随机函数 float random (float x) { return fract(sin(x * 1243758.5453123)); } void main() { vec2 st = vUv - vec2(0.5); st *= 10.0; float i = floor(st.x); float f = fract(st.x); // d直接等于随机函数返回值,这样d不连续 // float d = random(i); // 线段的首尾就会连起来,得到一段连续的折线。 // float d = mix(random(i), random(i + 1.0), f); // 下面两种都得到一条连续并且平滑的曲线 // float d = mix(random(i), random(i + 1.0), smoothstep(0.0, 1.0, f)); float d = mix(random(i), random(i + 1.0), f * f * (3.0 - 2.0 * f)); gl_FragColor.rgb = (smoothstep(st.y - 0.05, st.y, d) - smoothstep(st.y, st.y + 0.05, d)) * vec3(1.0); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); </script> </body> </html>
在 2D 中,除了在一条线的两点(fract(x) 和 fract(x)+1.0)中插值,我们将在一个平面上的方形的四角(fract(st), fract(st)+vec2(1.,0.), fract(st)+vec2(0.,1.) 和 fract(st)+vec2(1.,1.))中插值。https://thebookofshaders.com/11/?lan=ch
把 st 与方形区域的四个顶点(对应四个向量)做插值,这样就能得到二维噪声。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>二维噪声</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; float random (vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123); } // 二维噪声,对st与方形区域的四个顶点插值 highp float noise(vec2 st) { vec2 i = floor(st); vec2 f = fract(st); vec2 u = f * f * (3.0 - 2.0 * f); return mix( mix( random( i + vec2(0.0,0.0) ), random( i + vec2(1.0,0.0) ), u.x), mix( random( i + vec2(0.0,1.0) ), random( i + vec2(1.0,1.0) ), u.x), u.y); } void main() { vec2 st = vUv * 20.0; gl_FragColor.rgb = vec3(noise(st)); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); </script> </body> </html>
噪声的应用
实现类似于水滴滚过物体表面的效果
结合噪声和距离场,来实现类似于水滴滚过物体表面的效果。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>实现类似于水滴滚过物体表面的效果</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; uniform float uTime; float random (vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123); } highp float noise(vec2 st) { vec2 i = floor(st); vec2 f = fract(st); vec2 u = f * f * (3.0 - 2.0 * f); return mix( mix( random( i + vec2(0.0,0.0) ), random( i + vec2(1.0,0.0) ), u.x), mix( random( i + vec2(0.0,1.0) ), random( i + vec2(1.0,1.0) ), u.x), u.y); } void main() { vec2 st = mix(vec2(-10, -10), vec2(10, 10), vUv); float d = distance(st, vec2(0)); d *= noise(uTime + st); d = smoothstep(0.0, 1.0, d) - step(1.0, d); gl_FragColor.rgb = vec3(d); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); function update(t) { renderer.uniforms.uTime = t / 1000; requestAnimationFrame(update); } update(0); </script> </body> </html>
实现类似于木头的条纹
使用不同的距离场构造方式,加上旋转噪声,构造出类似于木头的条纹。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>实现类似于木头的条纹</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; uniform float uTime; float random (vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123); } highp float noise(vec2 st) { vec2 i = floor(st); vec2 f = fract(st); vec2 u = f * f * (3.0 - 2.0 * f); return mix( mix( random( i + vec2(0.0,0.0) ), random( i + vec2(1.0,0.0) ), u.x), mix( random( i + vec2(0.0,1.0) ), random( i + vec2(1.0,1.0) ), u.x), u.y); } float lines(in vec2 pos, float b){ float scale = 10.0; pos *= scale; return smoothstep(0.0, 0.5 + b * 0.5, abs((sin(pos.x * 3.1415) + b * 2.0)) * 0.5); } vec2 rotate(vec2 v0, float ang) { float sinA = sin(ang); float cosA = cos(ang); mat3 m = mat3(cosA, -sinA, 0, sinA, cosA, 0, 0, 0, 1); return (m * vec3(v0, 1.0)).xy; } void main() { vec2 st = vUv.yx * vec2(10.0, 3.0); st = rotate(st, noise(st)); float d = lines(st, 0.5); gl_FragColor.rgb = 1.0 - vec3(d); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); function update(t) { renderer.uniforms.uTime = t / 1000; requestAnimationFrame(update); } update(0); </script> </body> </html>
梯度噪声
插值噪声的缺点可以使用另一种噪声算法来解决,梯度噪声是对随机的二维向量来插值,而不是一维的随机数。这样我们就能够获得更加平滑的噪声效果。
可以参考这个例子:https://www.shadertoy.com/view/XdXGW8
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>梯度噪声</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; 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); } // Gradient Noise by Inigo Quilez - iq/2013 // https://www.shadertoy.com/view/XdXGW8 float 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,0.0) ), f - vec2(0.0,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 ); } void main() { vec2 st = vUv * 20.0; gl_FragColor.rgb = vec3(0.5 * noise(st) + 0.5); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); </script> </body> </html>
用噪声实现云雾效果
Smooth HSV :https://www.shadertoy.com/view/MsS3Wc
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>用噪声实现云雾效果</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; // Function from Iñigo Quiles // https://www.shadertoy.com/view/MsS3Wc vec3 hsb2rgb(vec3 c){ vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 6.0)-3.0)-1.0, 0.0, 1.0); rgb = rgb * rgb * (3.0 - 2.0 * rgb); return c.z * mix(vec3(1.0), rgb, c.y); } float random (vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898,78.233)))*43758.5453123); } highp float noise(vec2 st) { vec2 i = floor(st); vec2 f = fract(st); vec2 u = f * f * (3.0 - 2.0 * f); return mix( mix( random( i + vec2(0.0,0.0) ), random( i + vec2(1.0,0.0) ), u.x), mix( random( i + vec2(0.0,1.0) ), random( i + vec2(1.0,1.0) ), u.x), u.y ); } #define OCTAVES 6 float mist(vec2 st) { //Initial values float value = 0.0; float amplitude = 0.5; // Loop of octaves for(int i = 0; i < OCTAVES; i++) { value += amplitude * noise(st); st *= 2.0; amplitude *= 0.5; } return value; } uniform float uTime; void main() { vec2 st = vUv; st.x += 0.1 * uTime; // gl_FragColor.rgb = vec3(mist(st)); gl_FragColor.rgb = hsb2rgb(vec3 (mist(st), 1.0, 1.0)); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.uniforms.uTime = 0.0; renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); function update(t) { renderer.uniforms.uTime = t / 1000; requestAnimationFrame(update); } update(0); </script> </body> </html>
Simplex Noise
Simplex Noise 是 Ken Perlin 在 2001 年的 Siggraph 会议上展示的 Simplex Noise 算法。它有更低的计算复杂度和更少的乘法运算,并且可以用更少的计算量达到更高的维度,而且它制造出的噪声非常自然。
Simplex Noise 与插值噪声以及梯度噪声的不同之处在于,它不是对四边形进行插值,而是对三角网格进行插值。
如下图:
该算法的优点:
- 它有着更低的计算复杂度和更少乘法计算。
- 它可以用更少的计算量达到更高的维度。
- 制造出的 noise 没有明显的人工痕迹。
- 有着定义得很精巧的连续的 gradients(梯度),可以大大降低计算成本。
- 特别易于硬件实现。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Simplex Noise</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; 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); } // // Description : GLSL 2D simplex noise function // Author : Ian McEwan, Ashima Arts // Maintainer : ijm // Lastmod : 20110822 (ijm) // License : // Copyright (C) 2011 Ashima Arts. All rights reserved. // Distributed under the MIT License. See LICENSE file. // https://github.com/ashima/webgl-noise // float noise(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); } void main() { vec2 st = vUv * 20.0; gl_FragColor.rgb = vec3(0.5 * noise(st) + 0.5); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); </script> </body> </html>
网格噪声
网格噪声就是将噪声与网格结合使用的一种纹理生成技术。目前被广泛应用的程序化纹理技术,用来生成随机网格类的视觉效果,可以用来模拟物体表面的晶格、晶体生长、细胞、微生物等等有趣的效果。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>网格噪声</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; uniform float uTime; vec2 random2(vec2 st){ st = vec2( dot(st,vec2(127.1,311.7)), dot(st,vec2(269.5,183.3)) ); return fract(sin(st) * 43758.5453123); } void main() { vec2 st = vUv * 10.0; float d = 1.0; vec2 i_st = floor(st); vec2 f_st = fract(st); vec2 p = random2(i_st); d = distance(f_st, p); gl_FragColor.rgb = vec3(d); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); </script> </body> </html>
上面每个网格是独立的,并且界限分明,可以计算特征点到当前网格的距离,以及计算它到周围相邻的 8 个网格的距离,然后取最小值去实现边界过渡更圆滑效果。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>网格噪声</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; // 圆滑版本 const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; uniform float uTime; vec2 random2(vec2 st){ st = vec2(dot(st,vec2(127.1,311.7)), dot(st,vec2(269.5,183.3))); return fract(sin(st) * 43758.5453123); } void main() { vec2 st = vUv * 10.0; float d = 1.0; vec2 i_st = floor(st); vec2 f_st = fract(st); for(int i = -1; i <= 1; i++) { for(int j = -1; j <= 1; j++) { vec2 neighbor = vec2(float(i), float(j)); vec2 p = random2(i_st + neighbor); p = 0.5 + 0.5 * sin(uTime + 6.2831 * p); d = min(d, distance(f_st, neighbor + p)); } } gl_FragColor.rgb = vec3(d) + step(d, 0.03); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.uniforms.uTime = 0.0; renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); </script> </body> </html>
基于这个圆滑版本我们实现一下细胞动画效果
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>网格噪声模拟生物细胞</title> <style> canvas { border: 1px dashed salmon; } </style> </head> <body> <canvas width="512" height="512"></canvas> <script src="./common/lib/gl-renderer.js"></script> <script> const vertex = ` attribute vec2 a_vertexPosition; attribute vec2 uv; varying vec2 vUv; void main() { gl_PointSize = 1.0; vUv = uv; gl_Position = vec4(a_vertexPosition, 1, 1); } `; const fragment = ` #ifdef GL_ES precision highp float; #endif varying vec2 vUv; uniform float uTime; vec2 random2(vec2 st){ st = vec2(dot(st,vec2(127.1,311.7)), dot(st,vec2(269.5,183.3))); return fract(sin(st) * 43758.5453123); } void main() { vec2 st = vUv * 10.0; float d = 1.0; vec2 i_st = floor(st); vec2 f_st = fract(st); for(int i = -1; i <= 1; i++) { for(int j = -1; j <= 1; j++) { vec2 neighbor = vec2(float(i), float(j)); vec2 p = random2(i_st + neighbor); p = 0.5 + 0.5 * sin(uTime + 6.2831 * p); d = min(d, distance(f_st, neighbor + p)); } } gl_FragColor.rgb = vec3(d) + step(d, 0.03); gl_FragColor.a = 1.0; } `; const canvas = document.querySelector("canvas"); const renderer = new GlRenderer(canvas); const program = renderer.compileSync(fragment, vertex); renderer.useProgram(program); renderer.uniforms.uTime = 0.0; renderer.setMeshData([ { positions: [ [-1, -1], [-1, 1], [1, 1], [1, -1], ], attributes: { uv: [ [0, 0], [0, 1], [1, 1], [1, 0], ], }, cells: [ [0, 1, 2], [2, 0, 3], ], }, ]); renderer.render(); function update(t) { renderer.uniforms.uTime = t / 1000; requestAnimationFrame(update); } update(0); </script> </body> </html>
拓展
网格噪声(Cellular Noise):https://thebookofshaders.com/12/?lan=ch
演示例子:https://thebookofshaders.com/edit.php#12/vorono-01.frag