<canvas id="game"></canvas>
<div id="debug"></div>
body {
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
background: #000;
}
View Compiled
/**************************************************************
* WebGL App Setup
*************************************************************/
// ⚪ Initialization
var canvas = document.getElementById('game') as HTMLCanvasElement;
var gl = canvas.getContext('webgl2');
if (!gl) {
throw new Error('Could not create WebGL Context!');
}
// 🔲 Create NDC Space Quad (attribute vec2 position)
let ndcQuad = [ 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0 ];
let indices = [ 0, 1, 2, 1, 2, 3 ];
// Create Buffers
let dataBuffer = gl.createBuffer();
let indexBuffer = gl.createBuffer();
// Bind Data/Indices to Buffers
gl.bindBuffer(gl.ARRAY_BUFFER, dataBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(ndcQuad), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
var compiledProgram, fs, size, seed, scale;
size = 256;
seed = 0;
scale = 1.0;
function createProgram(vsSource: string, fsSource: string) {
let vs = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vs, vsSource);
gl.compileShader(vs);
if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) {
console.error('An error occurred compiling the shader: ' + gl.getShaderInfoLog(vs));
console.log(vsSource);
}
let fs = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fs, fsSource);
gl.compileShader(fs);
if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) {
console.error('An error occurred compiling the shader: ' + gl.getShaderInfoLog(fs));
console.log(fsSource);
}
let program = gl.createProgram();
gl.attachShader(program, vs);
gl.attachShader(program, fs);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
}
return { vs, fs, program };
}
function deleteProgram({ vs, fs, program }) {
gl.deleteProgram(program);
gl.deleteShader(vs);
gl.deleteShader(fs);
}
let vs = `#version 300 es
/**************************************************************
* Attributes
**************************************************************/
layout(location = 0) in vec2 aPosition;
/**************************************************************
* Uniforms
**************************************************************/
// None.
/**************************************************************
* Varying
**************************************************************/
out vec2 vFragCoord;
/**************************************************************
* Main
**************************************************************/
void main()
{
vFragCoord = (0.5 * aPosition) + vec2(0.5, 0.5);
vFragCoord.y = 1.0 - vFragCoord.y;
gl_Position = vec4(aPosition, 0.0, 1.0);
}
`;
let noiseGenerators = {
whiteNoise: `
// 🔺 Sine hash by Patricio Gonzalez Vivo. Public Domain.
// http://patriciogonzalezvivo.com/2015/thebookofshaders/10/
float whiteNoise(vec2 uv)
{
return fract(sin(dot(uScale * uv.xy + vec2(float(uSeed)) ,vec2(12.9898,78.233))) * 43758.5453);
}`,
haltonNoise: `
// 🇭 Halton Noise
// Ray Tracing Gems Chapter 25
// Code provided by Electronic Arts
// Colin Barré-Brisebois, Henrik Halén, Graham Wihlidal, Andrew Lauritzen,
// Jasper Bekkers, Tomasz Stachowiak, and Johan Andersson
// Errata corrected by Alain Galvan
struct HaltonState
{
int dimension;
int sequenceIndex;
};
int mIncrement = 1;
int haltonIndex(int x, int y, int i);
void haltonInit(inout HaltonState hState, int x, int y, int path, int numPaths,
int frameId, int loop)
{
hState.dimension = 2;
hState.sequenceIndex =
haltonIndex(x, y, (frameId * numPaths + path) % (loop * numPaths));
}
float haltonSample(int dimension, int sampleIndex)
{
int base = 0;
// Use a prime number.
switch (dimension)
{
case 0:
base = 2;
break;
case 1:
base = 3;
break;
case 2:
base = 5;
break;
case 3:
base = 7;
break;
case 4:
base = 11;
break;
case 5:
base = 13;
break;
case 6:
base = 17;
break;
case 7:
base = 19;
break;
case 8:
base = 23;
break;
case 9:
base = 29;
break;
case 10:
base = 31;
break;
case 11:
base = 37;
break;
case 12:
base = 41;
break;
case 13:
base = 43;
break;
case 14:
base = 47;
break;
case 15:
base = 53;
break;
case 16:
base = 59;
break;
case 17:
base = 61;
break;
case 18:
base = 67;
break;
case 19:
base = 71;
break;
case 20:
base = 73;
break;
case 21:
base = 79;
break;
case 22:
base = 83;
break;
case 23:
base = 89;
break;
case 24:
base = 97;
break;
case 25:
base = 101;
break;
case 26:
base = 103;
break;
case 27:
base = 107;
break;
case 28:
base = 109;
break;
case 29:
base = 113;
break;
case 30:
base = 127;
break;
case 31:
base = 131;
break;
default:
base = 2;
break;
}
// Compute the radical inverse.
float a = 0.0;
float invBase = 1.0 / float(base);
for (float mult = invBase; sampleIndex != 0;
sampleIndex /= base, mult *= invBase)
{
a += float(sampleIndex % base) * mult;
}
return a;
}
float haltonNext(inout HaltonState state)
{
mIncrement = mIncrement < 1 ? 1 : mIncrement + 1;
return haltonSample(state.dimension++, state.sequenceIndex);
}
// Modified from [pbrt]
int halton2Inverse(int index, int digits)
{
index = (index << 16) | (index >> 16);
index = ((index & 0x00ff00ff) << 8) | ((index & 0xff00ff00) >> 8);
index = ((index & 0x0f0f0f0f) << 4) | ((index & 0xf0f0f0f0) >> 4);
index = ((index & 0x33333333) << 2) | ((index & 0xcccccccc) >> 2);
index = ((index & 0x55555555) << 1) | ((index & 0xaaaaaaaa) >> 1);
return index >> (32 - digits);
}
// Modified from [pbrt]
int halton3Inverse(int index, int digits)
{
int result = 0;
for (int d = 0; d < digits; ++d)
{
result = result * 3 + index % 3;
index /= 3;
}
return result;
}
// Modified from [pbrt]
int haltonIndex(int x, int y, int i)
{
return ((halton2Inverse(x % 256, 8) * 76545 +
halton3Inverse(y % 256, 6) * 110080) %
mIncrement) +
i * 186624;
}
float haltonNoise(vec2 uv)
{
HaltonState hState;
vec2 scale = uResolution * uScale;
ivec2 px = ivec2(uv * scale);
// This should be
mIncrement = 1 + px.x + int(scale.y) * px.y;
haltonInit(hState, px.x, px.y, uSeed, 255,
1, 1);
return haltonNext(hState);
}
`,
simplexNoise: `
// https://github.com/ashima/webgl-noise/blob/master/src/noise2D.glsl
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)
{
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
vec2 i = floor(v + dot(v, C.yy) );
vec2 x0 = v - i + dot(i, C.xx);
// Other corners
vec2 i1;
//i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
//i1.y = 1.0 - i1.x;
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
// x0 = x0 - 0.0 + 0.0 * C.xx ;
// x1 = x0 - i1 + 1.0 * C.xx ;
// x2 = x0 - 1.0 + 2.0 * C.xx ;
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
// Permutations
i = mod289(i); // Avoid truncation effects in permutation
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(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
m = m*m ;
m = m*m ;
// Gradients: 41 points 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;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
float simplexNoise(vec2 uv)
{
return .5 + .5 * snoise(uv * uScale + vec2(float(uSeed)));
}
`,
gradientNoise: `
// 3D gradient Noise
// MIT License
// Copyright © 2013 Inigo Quilez
// https://www.shadertoy.com/view/Xsl3Dl
vec3 hash(vec3 p)
{
p = vec3(dot(p, vec3(127.1, 311.7, 74.7)), dot(p, vec3(269.5, 183.3, 246.1)),
dot(p, vec3(113.5, 271.9, 124.6)));
return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}
float noise(in vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
return mix(
mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x),
u.y),
mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x),
u.y),
u.z);
}
const mat3 m = mat3(0.00, 0.80, 0.60, -0.80, 0.36, -0.48, -0.60, -0.48, 0.64);
float turbulance(vec3 pos, float lacunarity, float gain ) {
float f;
vec3 q = 8.0 * pos;
f = gain * noise(q);
q = m * q * 2.01 * lacunarity;
f += (gain * gain) * noise(q);
q = m * q * 2.02 * lacunarity;
f += (gain * gain * gain) * noise(q);
q = m * q * 2.03 * lacunarity;
f += (gain * gain * gain * gain) * noise(q);
q = m * q * 2.01 * lacunarity;
f = 0.5 + 0.5 * f;
return clamp(f, 0.0, 1.0);
}
float gradientNoise(vec2 uv)
{
return .5 + 2.0 * ( turbulance(vec3(uv * uScale, float(uSeed)), 1.0, .5) - .5 );
}
`,
trianglularNoise: `
// https://www.shadertoy.com/view/Mts3zM
mat2 mm2(in float a){float c = cos(a), s = sin(a);return mat2(c,-s,s,c);}
float tri(in float x){return abs(fract(x)-.5);}
vec2 tri2(in vec2 p){return vec2(tri(p.x+tri(p.y*2.)),tri(p.y+tri(p.x*2.)));}
mat2 m2 = mat2( 0.970, 0.242, -0.242, 0.970 );
//Animated triangle noise, cheap and pretty decent looking.
float trianglularNoise(in vec2 p)
{
p *= uScale;
float z=1.5;
float z2=1.5;
float rz = 0.;
vec2 bp = p;
for (float i=0.; i<=3.; i++ )
{
vec2 dg = tri2(bp*2.)*.8;
dg *= mm2(float(uSeed)*.3);
p += dg/z2;
bp *= 1.6;
z2 *= .6;
z *= 1.8;
p *= 1.2;
p*= m2;
rz+= (tri(p.x+tri(p.y)))/z;
}
return rz;
}
`,
sobolNoise: `
// Marc B. Reynolds, 2010-2015
// Public Domain under http://unlicense.org, see link for details.
//
// Sobol (low-discrepancy) sequence in 1-3D, stratified in 2-4D.
// Classic binary-reflected gray code.
//
// Documentation: http://marc-b-reynolds.github.io/shf/2016/04/18/sobol.html
#define SOBOL_TO_F32(X) (float(X)*(1.0/float((1<<24)*(1<<8))))
uint sobol_table[64] =
uint[](
0x80000000u, 0x80000000u,
0xc0000000u, 0xc0000000u,
0xa0000000u, 0x60000000u,
0xf0000000u, 0x90000000u,
0x88000000u, 0xe8000000u,
0xcc000000u, 0x5c000000u,
0xaa000000u, 0x8e000000u,
0xff000000u, 0xc5000000u,
0x80800000u, 0x68800000u,
0xc0c00000u, 0x9cc00000u,
0xa0a00000u, 0xee600000u,
0xf0f00000u, 0x55900000u,
0x88880000u, 0x80680000u,
0xcccc0000u, 0xc09c0000u,
0xaaaa0000u, 0x60ee0000u,
0xffff0000u, 0x90550000u,
0x80008000u, 0xe8808000u,
0xc000c000u, 0x5cc0c000u,
0xa000a000u, 0x8e606000u,
0xf000f000u, 0xc5909000u,
0x88008800u, 0x6868e800u,
0xcc00cc00u, 0x9c9c5c00u,
0xaa00aa00u, 0xeeee8e00u,
0xff00ff00u, 0x5555c500u,
0x80808080u, 0x8000e880u,
0xc0c0c0c0u, 0xc0005cc0u,
0xa0a0a0a0u, 0x60008e60u,
0xf0f0f0f0u, 0x9000c590u,
0x88888888u, 0xe8006868u,
0xccccccccu, 0x5c009c9cu,
0xaaaaaaaau, 0x8e00eeeeu,
0xffffffffu, 0xc5005555u
);
uint bitScanForward(uint _mask)
{
uint _index = 0u;
for (uint bitNumber = 0u; bitNumber < 32u; bitNumber++)
{
if ((_mask & uint(1u << bitNumber)) != 0u)
{
_index = bitNumber;
return _index;
}
}
return _index;
}
uint sobol_bits_flipped(uint x, uint off)
{
uint i = ~x;
uint n = i + off;
uint a = i ^ (i >> 1u);
uint b = n ^ (n >> 1u);
uint d = a ^ b;
return d;
}
void sobol_1d_init(inout uvec2 s, uint hash)
{
s.y = hash;
s.x = 0xFFFFFFFFu;
}
// state updates - normally not needed by user.
void sobol_1d_update(inout uvec2 s)
{
uint c = bitScanForward(s.x);
s.y ^= 0x80000000u >> c;
s.x -= 1u;
}
float sobol_1d_next_f32(inout uvec2 s)
{
float r = SOBOL_TO_F32(s.x);
sobol_1d_update(s);
return r;
}
float sobolNoise(vec2 p)
{
p *= uResolution * uScale;
uint seed = uint(uSeed) + uint(dot(p, vec2(12.9898,78.233)));
uvec2 sobol;
sobol_1d_init(sobol, seed);
sobol_1d_update(sobol);
return sobol_1d_next_f32(sobol);
}
`,
convolutionNoise: `
// https://www.shadertoy.com/view/3lf3z2
// 3D gradient Noise
// MIT License
// Copyright © 2013 Inigo Quilez
// https://www.shadertoy.com/view/Xsl3Dl
float hash(vec2 p)
{
return fract(sin(dot(uScale * p.xy + vec2(float(uSeed)) ,vec2(12.9898,78.233))) * 43758.5453);
}
float noise(vec2 x)
{
vec2 i = floor(x);
vec2 f = fract(x);
float a = hash(i);
float b = hash(i + vec2(1.0, 0.0));
float c = hash(i + vec2(0.0, 1.0));
float d = hash(i + vec2(1.0, 1.0));
vec2 u = f * f * (3.0 - 2.0 * f);
return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}
#define octaves 14
float turbulance(vec3 p) {
float value = 0.0;
float freq = 1.0;
float amp = 0.5;
for (int i = 0; i < octaves; i++) {
value += amp * (noise((p.xy - vec2(1.0)) * freq));
freq *= 1.9;
amp *= 0.6;
}
return value;
}
float convolutionNoise(vec2 uv)
{
vec3 p = vec3(uv, 0.0) * uScale;
float time = float(uSeed);
vec2 offset = vec2(-0.5);
vec2 aPos = vec2(sin(time * 0.005), sin(time * 0.01)) * 6.;
vec3 aScale = vec3(3.0);
float a = turbulance(p * aScale + vec3(aPos, 0.0));
vec2 bPos = vec2(sin(time * 0.01), sin(time * 0.01)) * 1.;
vec3 bScale = vec3(0.6);
float b = turbulance((p + a) * bScale + vec3(bPos, 0.0));
vec2 cPos = vec2(-0.6, -0.5) + vec2(sin(-time * 0.001), sin(time * 0.01)) * 2.;
vec3 cScale = vec3(2.6);
float c = turbulance((p + b) * cScale + vec3(cPos, 0.0));
return c;
}
`,
orbitNoise: `
// by Dave_hoskins
// https://www.shadertoy.com/view/4djSRW
vec4 hash42(vec2 p){
vec4 p4 = fract(vec4(p.xyxy)*vec4(1031,.1030,.0973,.1099));
p4 += dot(p4,p4.wzxy+19.19);
return fract((p4.xxyz+p4.yzzw)*p4.zywx);
}
// by nimitz
// https://www.shadertoy.com/view/4t3yDn
float orbitNoise(vec2 p){
p = p * uScale + vec2(float(uSeed));
vec2 ip = floor(p);
vec2 fp = fract(p) - 0.5;
float rz = 0.;
float orbitRadius = 0.5;
for (int j = -2; j <= 2; j++){
for (int i = -2; i <= 2; i++){
vec2 dp = vec2(j,i);
vec4 rn = hash42(dp + ip) - 0.5;
vec2 op = fp - dp - rn.zw*orbitRadius;
rz += smoothstep(1.4, float(0), length(op))*dot(rn.xy*1.4, op);
}
}
return rz*0.5 + 0.5;
}
`,
wangNoise: `
// https://twitter.com/jcant0n/status/1150505102354128896
uint wang_hash(uint seed)
{
seed = (seed ^ 61u) ^ (seed >> 16u);
seed *= 9u;
seed = seed ^ (seed >> 4u);
seed *= 0x27d4eb2du;
seed = seed ^ (seed >> 15u);
return seed;
}
float wangNoise(vec2 p)
{
p *= uResolution * uScale;
uint seed = uint(uSeed) + uint(dot(p, vec2(12.9898,78.233)));
return float(wang_hash(seed)) / float(0x7FFFFFFF);
}
`
};
// Effects
let curNoise = noiseGenerators.whiteNoise;
function generateFragmentShader(val) {
return `#version 300 es
precision highp float;
precision highp int;
/**************************************************************
* Uniforms
**************************************************************/
uniform vec2 uResolution;
uniform int uSeed;
uniform float uScale;
/**************************************************************
* Varying
**************************************************************/
in vec2 vFragCoord;
/**************************************************************
* Outputs
**************************************************************/
out vec4 outFragColor;
/**************************************************************
* Main
**************************************************************/
${curNoise}
#define noise ${val}
void main()
{
vec2 uv = vFragCoord;
vec4 outColor = vec4(0.0, 0.0, 0.0, 0.0);
float n = clamp(noise(uv), 0.0, 1.0);
outColor = vec4(n, n, n, 1.0);
outFragColor = outColor;
}`;
}
fs = generateFragmentShader('whiteNoise');
compiledProgram = createProgram(vs, fs);
// 📐 Draw
function draw() {
// Bind Shaders
gl.useProgram(compiledProgram.program);
// Bind Vertex Layout
let loc = gl.getAttribLocation(compiledProgram.program, 'aPosition');
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 4 * 2, 0);
gl.enableVertexAttribArray(loc);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Bind Uniforms
let bottomLayerLoc = gl.getUniformLocation(compiledProgram.program, 'uSeed');
gl.uniform1i(bottomLayerLoc, seed);
let resLoc = gl.getUniformLocation(compiledProgram.program, 'uResolution');
gl.uniform2f(resLoc, size, size);
let scaleLoc = gl.getUniformLocation(compiledProgram.program, 'uScale');
gl.uniform1f(scaleLoc, scale);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
}
let resizeHandler = () => {
canvas.width = canvas.height = size;
gl.viewport(0, 0, size, size);
draw();
};
window.addEventListener('resize', resizeHandler);
resizeHandler();
function update() {
draw();
}
function toCamelCase(s) {
return s.replace(/^([A-Z])|\s(\w)/g, function(_, p1, p2, __) {
if (p2) return p2.toUpperCase();
return p1.toLowerCase();
});
}
//DATGUI Controls
var gui = new dat.GUI({ autoPlace: false });
var params = {
Noise: 'White Noise',
Seed: 0,
Resolution: 256,
Scale: 1.0
};
var list = gui.add(params, 'Noise', [ 'White Noise', 'Halton Noise', 'Simplex Noise', 'Gradient Noise', 'Trianglular Noise', 'Convolution Noise', 'Orbit Noise', 'Wang Noise', 'Sobol Noise' ]);
var s = gui.add(params, 'Seed', 0, 255, 1);
var r = gui.add(params, 'Resolution', 16, 4096, 16);
var sx = gui.add(params, 'Scale', .1, 32.0, .5);
let datGUIChanged = (value) => {
seed = Math.floor(params.Seed);
size = params.Resolution;
scale = params.Scale;
resizeHandler();
let p = toCamelCase(params.Noise);
curNoise = noiseGenerators[p];
fs = generateFragmentShader(p);
deleteProgram(compiledProgram);
compiledProgram = createProgram(vs, fs);
update();
};
list.onChange(datGUIChanged);
s.onChange(datGUIChanged);
r.onChange(datGUIChanged);
sx.onChange(datGUIChanged);
gui.close();
var debugContainer = document.getElementById('debug');
gui.domElement.style.position = 'absolute';
gui.domElement.style.top = '0px';
gui.domElement.style.left = '0px';
debugContainer.appendChild(gui.domElement);
View Compiled
This Pen doesn't use any external CSS resources.