說明
【跟月影學可視化】學習筆記。
什麼是噪聲?
實體學上,噪聲指一切不規則的信号(不一定要是聲音),比如電磁噪聲,熱噪聲,無線電傳輸時的噪聲,雷射器噪聲,光纖通信噪聲,照相機拍攝圖檔時畫面的噪聲等。
如何實作噪聲函數?
我們知道随機數是離散的,如果對離散的随機點進行插值,可以讓每個點之間的值連續過渡,然後使用 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" cnotallow="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" cnotallow="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" cnotallow="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" cnotallow="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" cnotallow="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" cnotallow="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" cnotallow="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" cnotallow="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" cnotallow="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