diff --git a/main.js b/main.js index 1ce30ab..3fd902a 100644 --- a/main.js +++ b/main.js @@ -1,7 +1,600 @@ -import { init, load, resize, draw } from "./unraveled.js"; +const extendedContext = {}; +const programs = {}; +const textures = {}; +const dynamicSizes = { fullscreen: { scale: 1 } }; +const framebuffers = {}; +const geometry = {}; +const attributes = {}; +const uniforms = {}; + +const extensionNames = [ + "oes_texture_half_float", + "oes_texture_half_float_linear", + "ext_color_buffer_half_float", + "webgl_color_buffer_float", + "oes_standard_derivatives", +]; + +const palette = [ + [0, 0, 0, 255], + [7, 33, 0, 255], + [15, 63, 2, 255], + [22, 96, 5, 255], + [38, 117, 17, 255], + [53, 137, 33, 255], + [71, 160, 48, 255], + [86, 181, 63, 255], + [104, 204, 79, 255], + [119, 224, 94, 255], + [135, 247, 109, 255], + [155, 247, 132, 255], + [175, 249, 158, 255], + [175, 249, 158, 255], + [175, 249, 158, 255], + [175, 249, 158, 255], +]; + +const fullscreen_frag_shader_source = ` + precision mediump float; + varying vec2 vUV; + uniform sampler2D tex; + void main() { + gl_FragColor = texture2D(tex, vUV); + } +`; + +const fullscreen_vert_shader_source = ` + precision mediump float; + attribute vec2 aPosition; + varying vec2 vUV; + void main() { + vUV = 0.5 * (aPosition + 1.0); + gl_Position = vec4(aPosition, 0, 1); + } +`; + +const rain_compute_shader_source = ` + precision highp float; + + #define PI 3.14159265359 + #define SQRT_2 1.4142135623730951 + #define SQRT_5 2.23606797749979 + + uniform sampler2D previousComputeState; + + uniform float numColumns, numRows; + uniform float time, tick; + uniform float fallSpeed, cycleSpeed; + uniform float glyphSequenceLength; + uniform float raindropLength; + + highp float randomFloat( const in vec2 uv ) { + const highp float a = 12.9898, b = 78.233, c = 43758.5453; + highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI ); + return fract(sin(sn) * c); + } + + float wobble(float x) { + return x + 0.3 * sin(SQRT_2 * x) + 0.2 * sin(SQRT_5 * x); + } + + float getRainBrightness(float simTime, vec2 glyphPos) { + float columnTimeOffset = randomFloat(vec2(glyphPos.x, 0.)) * 1000.; + float columnSpeedOffset = randomFloat(vec2(glyphPos.x + 0.1, 0.)) * 0.5 + 0.5; + float columnTime = columnTimeOffset + simTime * fallSpeed * columnSpeedOffset; + float rainTime = (glyphPos.y * 0.01 + columnTime) / raindropLength; + rainTime = wobble(rainTime); + return 1.0 - fract(rainTime); + } + + vec2 computeRaindrop(float simTime, vec2 glyphPos) { + float brightness = getRainBrightness(simTime, glyphPos); + float brightnessBelow = getRainBrightness(simTime, glyphPos + vec2(0., -1.)); + bool cursor = brightness > brightnessBelow; + return vec2(brightness, cursor); + } + + vec2 computeSymbol(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous) { + + float previousSymbol = previous.r; + float previousAge = previous.g; + bool resetGlyph = isFirstFrame; + if (resetGlyph) { + previousAge = randomFloat(screenPos + 0.5); + previousSymbol = floor(glyphSequenceLength * randomFloat(screenPos)); + } + float age = previousAge; + float symbol = previousSymbol; + if (mod(tick, 1.0) == 0.) { + age += cycleSpeed; + if (age >= 1.) { + symbol = floor(glyphSequenceLength * randomFloat(screenPos + simTime)); + age = fract(age); + } + } + + return vec2(symbol, age); + } + + void main() { + vec2 glyphPos = gl_FragCoord.xy; + vec2 screenPos = glyphPos / vec2(numColumns, numRows); + + vec2 raindrop = computeRaindrop(time, glyphPos); + + bool isFirstFrame = tick <= 1.; + vec4 previous = texture2D( previousComputeState, screenPos ); + vec4 previousSymbol = vec4(previous.ba, 0.0, 0.0); + vec2 symbol = computeSymbol(time, isFirstFrame, glyphPos, screenPos, previousSymbol); + gl_FragColor = vec4(raindrop, symbol); + } + +`; + +const rain_vert_shader_source = ` + precision lowp float; + + attribute vec2 aPosition; + uniform vec2 size; + varying vec2 vUV; + + void main() { + vUV = aPosition; + vec2 proportion = (size.y > size.x ? vec2(size.y / size.x, 1.) : vec2(1., size.x / size.y)); + gl_Position = vec4((aPosition - 0.5) * 2.0 * proportion, 0.0, 1.0); + } +`; + +const rain_frag_shader_source = ` + #define PI 3.14159265359 + #ifdef GL_OES_standard_derivatives + #extension GL_OES_standard_derivatives: enable + #endif + precision lowp float; + + uniform sampler2D computeState; + uniform float numColumns, numRows; + uniform sampler2D glyphMSDF; + uniform float msdfPxRange; + uniform vec2 glyphMSDFSize; + uniform float glyphSequenceLength; + uniform vec2 glyphTextureGridSize; + + varying vec2 vUV; + + float median3(vec3 i) { + return max(min(i.r, i.g), min(max(i.r, i.g), i.b)); + } + + float modI(float a, float b) { + float m = a - floor((a + 0.5) / b) * b; + return floor(m + 0.5); + } + + vec3 getBrightness(vec2 raindrop, vec2 uv) { + + float base = raindrop.r; + bool isCursor = bool(raindrop.g); + float glint = base; + + base = base * 1.1 - 0.5; + glint = glint * 2.5 - 1.5; + + return vec3( + (isCursor ? vec2(0.0, 1.0) : vec2(1.0, 0.0)) * base, + glint + ); + } + + vec2 getSymbolUV(float index) { + float symbolX = modI(index, glyphTextureGridSize.x); + float symbolY = (index - symbolX) / glyphTextureGridSize.x; + symbolY = glyphTextureGridSize.y - symbolY - 1.; + return vec2(symbolX, symbolY); + } + + vec2 getSymbol(vec2 uv, float index) { + uv = fract(uv * vec2(numColumns, numRows)); + uv = (uv + getSymbolUV(index)) / glyphTextureGridSize; + + vec2 symbol; + { + vec2 unitRange = vec2(msdfPxRange) / glyphMSDFSize; + vec2 screenTexSize = vec2(1.0) / fwidth(uv); + float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0); + + float signedDistance = median3(texture2D(glyphMSDF, uv).rgb); + float screenPxDistance = screenPxRange * (signedDistance - 0.5); + symbol.r = clamp(screenPxDistance + 0.5, 0.0, 1.0); + } + + return symbol; + } + + void main() { + vec4 data = texture2D(computeState, vUV); + vec3 brightness = getBrightness(data.rg, vUV); + vec2 symbol = getSymbol(vUV, data.b); + gl_FragColor = vec4(brightness.rg * symbol.r, brightness.b * symbol.g, 0.); + } +`; + +const bloom_high_pass_shader_source = ` + precision mediump float; + + uniform sampler2D tex; + uniform float highPassThreshold; + + varying vec2 vUV; + + void main() { + vec4 color = texture2D(tex, vUV); + + if (color.r < highPassThreshold) color.r = 0.0; + if (color.g < highPassThreshold) color.g = 0.0; + if (color.b < highPassThreshold) color.b = 0.0; + gl_FragColor = color; + } +`; + +const bloom_blur_shader_source = ` + precision mediump float; + + uniform vec2 size; + uniform sampler2D tex; + uniform vec2 direction; + + varying vec2 vUV; + + void main() { + vec2 proportion = (size.y > size.x ? vec2(size.y / size.x, 1.) : vec2(1., size.x / size.y)); + gl_FragColor = + texture2D(tex, vUV) * 0.442 + + ( + texture2D(tex, vUV + direction / max(size.y, size.x) * proportion) + + texture2D(tex, vUV - direction / max(size.y, size.x) * proportion) + ) * 0.279; + } +`; + +const bloom_combine_shader_source = ` + precision mediump float; + + uniform sampler2D pyr_0, pyr_1, pyr_2, pyr_3, pyr_4; + uniform float bloomStrength; + varying vec2 vUV; + + void main() { + vec4 total = vec4(0.); + total += texture2D(pyr_0, vUV) * 0.96549; + total += texture2D(pyr_1, vUV) * 0.92832; + total += texture2D(pyr_2, vUV) * 0.88790; + total += texture2D(pyr_3, vUV) * 0.84343; + total += texture2D(pyr_4, vUV) * 0.79370; + gl_FragColor = total * bloomStrength; + } +`; + +const palette_shader_source = ` + precision mediump float; + #define PI 3.14159265359 + + uniform sampler2D tex, bloomTex, paletteTex; + uniform float time; + varying vec2 vUV; + + highp float rand( const in vec2 uv, const in float t ) { + const highp float a = 12.9898, b = 78.233, c = 43758.5453; + highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI ); + return fract(sin(sn) * c + t); + } + + void main() { + vec4 primary = texture2D(tex, vUV); + vec4 bloom = texture2D(bloomTex, vUV); + vec4 brightness = primary + bloom - rand( gl_FragCoord.xy, time ) * 0.0167; + gl_FragColor = vec4( + texture2D( paletteTex, vec2(brightness.r, 0.0)).rgb + + min(vec3(0.756, 1.0, 0.46) * brightness.g * 2.0, vec3(1.0)), + 1.0 + ); + } +`; + +const init = (gl) => Object.assign(extendedContext, ...extensionNames.map((name) => Object.getPrototypeOf(gl.getExtension(name)))); + +const load = (gl, msdfImage, palette) => { + const buildShader = (source, isFragment) => { + const shader = gl.createShader(isFragment ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER); + gl.shaderSource(shader, source); + gl.compileShader(shader); + return shader; + }; + + const buildProgram = (vertexShader, fragmentShader) => { + const program = gl.createProgram(); + gl.attachShader(program, vertexShader); + gl.attachShader(program, fragmentShader); + gl.linkProgram(program); + return program; + }; + + const fullscreen_frag_shader = buildShader(fullscreen_frag_shader_source, true); + const fullscreen_vert_shader = buildShader(fullscreen_vert_shader_source, false); + const rain_compute_shader = buildShader(rain_compute_shader_source, true); + const rain_frag_shader = buildShader(rain_frag_shader_source, true); + const rain_vert_shader = buildShader(rain_vert_shader_source, false); + const bloom_high_pass_shader = buildShader(bloom_high_pass_shader_source, true); + const bloom_blur_shader = buildShader(bloom_blur_shader_source, true); + const bloom_combine_shader = buildShader(bloom_combine_shader_source, true); + const palette_shader = buildShader(palette_shader_source, true); + + programs.fullscreen = buildProgram(fullscreen_vert_shader, fullscreen_frag_shader); + uniforms.rain_program_tex = gl.getUniformLocation(programs.fullscreen, "tex"); + attributes.fullscreen_program_aPosition = gl.getAttribLocation(programs.fullscreen, "aPosition"); + + programs.rain_compute = buildProgram(fullscreen_vert_shader, rain_compute_shader); + attributes.rain_compute_program_aPosition = gl.getAttribLocation(programs.rain_compute, "aPosition"); + uniforms.rain_compute_program_time = gl.getUniformLocation(programs.rain_compute, "time"); + uniforms.rain_compute_program_previousComputeState = gl.getUniformLocation(programs.rain_compute, "previousComputeState"); + uniforms.rain_compute_program_tick = gl.getUniformLocation(programs.rain_compute, "tick"); + gl.useProgram(programs.rain_compute); + gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "numColumns"), 80); + gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "glyphSequenceLength"), 57); + gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "numRows"), 80); + gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "fallSpeed"), 0.3); + gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "raindropLength"), 0.75); + gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "cycleSpeed"), 0.03); + + programs.rain = buildProgram(rain_vert_shader, rain_frag_shader); + attributes.rain_program_aPosition = gl.getAttribLocation(programs.rain, "aPosition"); + uniforms.rain_program_size = gl.getUniformLocation(programs.rain, "size"); + uniforms.rain_program_computeState = gl.getUniformLocation(programs.rain, "computeState"); + uniforms.rain_program_glyphMSDF = gl.getUniformLocation(programs.rain, "glyphMSDF"); + gl.useProgram(programs.rain); + gl.uniform2f(gl.getUniformLocation(programs.rain, "glyphTextureGridSize"), 8, 8); + gl.uniform1f(gl.getUniformLocation(programs.rain, "numColumns"), 80); + gl.uniform2f(gl.getUniformLocation(programs.rain, "glyphMSDFSize"), 512, 512); + gl.uniform1f(gl.getUniformLocation(programs.rain, "numRows"), 80); + gl.uniform1f(gl.getUniformLocation(programs.rain, "msdfPxRange"), 4); + + programs.bloom_high_pass = buildProgram(fullscreen_vert_shader, bloom_high_pass_shader); + attributes.bloom_high_pass_program_aPosition = gl.getAttribLocation(programs.bloom_high_pass, "aPosition"); + uniforms.bloom_high_pass_program_tex = gl.getUniformLocation(programs.bloom_high_pass, "tex"); + gl.useProgram(programs.bloom_high_pass); + gl.uniform1f(gl.getUniformLocation(programs.bloom_high_pass, "highPassThreshold"), 0.1); + + programs.bloom_blur = buildProgram(fullscreen_vert_shader, bloom_blur_shader); + attributes.bloom_blur_program_aPosition = gl.getAttribLocation(programs.bloom_blur, "aPosition"); + uniforms.bloom_blur_program_tex = gl.getUniformLocation(programs.bloom_blur, "tex"); + uniforms.bloom_blur_program_size = gl.getUniformLocation(programs.bloom_blur, "size"); + uniforms.bloom_blur_program_direction = gl.getUniformLocation(programs.bloom_blur, "direction"); + + programs.bloom_combine = buildProgram(fullscreen_vert_shader, bloom_combine_shader); + attributes.bloom_combine_program_aPosition = gl.getAttribLocation(programs.bloom_combine, "aPosition"); + uniforms.bloom_combine_program_pyr_0 = gl.getUniformLocation(programs.bloom_combine, "pyr_0"); + uniforms.bloom_combine_program_pyr_1 = gl.getUniformLocation(programs.bloom_combine, "pyr_1"); + uniforms.bloom_combine_program_pyr_2 = gl.getUniformLocation(programs.bloom_combine, "pyr_2"); + uniforms.bloom_combine_program_pyr_3 = gl.getUniformLocation(programs.bloom_combine, "pyr_3"); + uniforms.bloom_combine_program_pyr_4 = gl.getUniformLocation(programs.bloom_combine, "pyr_4"); + gl.useProgram(programs.bloom_combine); + gl.uniform1f(gl.getUniformLocation(programs.bloom_combine, "bloomStrength"), 0.7); + + programs.palette = buildProgram(fullscreen_vert_shader, palette_shader); + attributes.palette_program_aPosition = gl.getAttribLocation(programs.palette, "aPosition"); + uniforms.palette_program_tex = gl.getUniformLocation(programs.palette, "tex"); + uniforms.palette_program_bloomTex = gl.getUniformLocation(programs.palette, "bloomTex"); + uniforms.palette_program_time = gl.getUniformLocation(programs.palette, "time"); + uniforms.palette_program_paletteTex = gl.getUniformLocation(programs.palette, "paletteTex"); + + geometry.rain = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, geometry.rain); + gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from([0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0]), gl.STATIC_DRAW); + + geometry.fullscreen = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, geometry.fullscreen); + gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from([-4, -4, 4, -4, 0, 4]), gl.STATIC_DRAW); + + const setTexParams = (texture, isLinear, data) => { + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture); + const filter = isLinear ? gl.LINEAR : gl.NEAREST; + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + if (data != null) { + if (data instanceof HTMLImageElement) { + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data); + } else { + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, data.length, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, Uint8ClampedArray.from(data.flat())); + } + } + }; + + for (let i = 0; i < 2; i++) { + const name = "rain_compute_doublebuffer_" + i; + const texture = gl.createTexture(); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, texture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 80, 80, 0, gl.RGBA, extendedContext.HALF_FLOAT_OES, null); + setTexParams(texture, false); + + const framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + + textures[name] = texture; + framebuffers[name] = framebuffer; + } + + const buildAndAddRTT = (name, scale) => { + const texture = gl.createTexture(); + dynamicSizes[name] = { scale }; + setTexParams(texture, true); + const framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); + + textures[name] = texture; + framebuffers[name] = framebuffer; + }; + + buildAndAddRTT("rain_output", 1); + for (let i = 0; i < 5; i++) { + const scale = 0.4 / 2 ** i; + buildAndAddRTT("bloom_high_pass_pyr_" + i, scale); + buildAndAddRTT("bloom_h_blur_pyr_" + i, scale); + buildAndAddRTT("bloom_v_blur_pyr_" + i, scale); + } + buildAndAddRTT("bloom_output", 1); + buildAndAddRTT("palette_output", 1); + + textures.palette = gl.createTexture(); + setTexParams(textures.palette, true, palette); + + textures.msdf = gl.createTexture(); + setTexParams(textures.msdf, true, msdfImage); + + gl.enableVertexAttribArray(0); + gl.disable(gl.DEPTH_TEST); + gl.blendFuncSeparate(1, 1, 1, 1); + gl.clearColor(0, 0, 0, 1); +}; + +const resize = (gl, width, height) => { + dynamicSizes.fullscreen.width = width; + dynamicSizes.fullscreen.height = height; + + for (var name in textures) { + const size = dynamicSizes[name]; + if (size == null) { + continue; + } + size.width = Math.floor(width * size.scale); + size.height = Math.floor(height * size.scale); + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, textures[name]); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size.width, size.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + } + + gl.useProgram(programs.rain); + gl.uniform2f(uniforms.rain_program_size, width, height); +}; + +const setViewportSizeTo = (gl, name) => { + const size = dynamicSizes[name]; + gl.viewport(0, 0, size.width, size.height); +}; + +const bindTextureTo = (gl, texName, uniformName, index) => { + gl.activeTexture(gl.TEXTURE0 + index); + gl.bindTexture(gl.TEXTURE_2D, textures[texName]); + gl.uniform1i(uniforms[uniformName], index); +}; + +const bindGeometryTo = (gl, geometryName, attributeName) => { + gl.bindBuffer(gl.ARRAY_BUFFER, geometry[geometryName]); + gl.vertexAttribPointer(attributes[attributeName], 2, gl.FLOAT, false, 0, 0); +}; + +const draw = (gl, tick, time) => { + const doubleBufferFrontName = "rain_compute_doublebuffer_" + (tick % 2); + const doubleBufferBackName = "rain_compute_doublebuffer_" + ((tick + 1) % 2); + + // rain compute + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[doubleBufferFrontName]); + gl.viewport(0, 0, 80, 80); + gl.useProgram(programs.rain_compute); + bindGeometryTo(gl, "fullscreen", "rain_compute_program_aPosition"); + bindTextureTo(gl, doubleBufferBackName, "rain_compute_program_previousComputeState", 0); + gl.uniform1f(uniforms.rain_compute_program_time, time); + gl.uniform1f(uniforms.rain_compute_program_tick, tick); + gl.drawArrays(gl.TRIANGLES, 0, 3); + + // rain + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers.rain_output); + setViewportSizeTo(gl, "rain_output"); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.enable(gl.BLEND); + gl.useProgram(programs.rain); + bindGeometryTo(gl, "rain", "rain_program_aPosition"); + + bindTextureTo(gl, doubleBufferFrontName, "rain_program_computeState", 0); + bindTextureTo(gl, "msdf", "rain_program_glyphMSDF", 1); + gl.drawArrays(gl.TRIANGLES, 0, 6); + gl.disable(gl.BLEND); + + // high pass pyramid + gl.useProgram(programs.bloom_high_pass); + gl.bindBuffer(gl.ARRAY_BUFFER, geometry.fullscreen); + gl.vertexAttribPointer(attributes.bloom_high_pass_program_aPosition, 2, gl.FLOAT, false, 0, 0); + for (let i = 0; i < 5; i++) { + const name = "bloom_high_pass_pyr_" + i; + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[name]); + const size = dynamicSizes[name]; + gl.viewport(0, 0, size.width, size.height); + const src = i === 0 ? textures.rain_output : textures["bloom_high_pass_pyr_" + (i - 1)]; + gl.activeTexture(gl.TEXTURE0); + gl.bindTexture(gl.TEXTURE_2D, src); + gl.uniform1i(uniforms.bloom_high_pass_program_tex, 0); + gl.drawArrays(gl.TRIANGLES, 0, 3); + } + + // blur pyramids + gl.useProgram(programs.bloom_blur); + gl.bindBuffer(gl.ARRAY_BUFFER, geometry.fullscreen); + gl.vertexAttribPointer(attributes.bloom_blur_program_aPosition, 2, gl.FLOAT, false, 0, 0); + for (let i = 0; i < 5; i++) { + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers["bloom_h_blur_pyr_" + i]); + const hSize = dynamicSizes["bloom_h_blur_pyr_" + i]; + gl.viewport(0, 0, hSize.width, hSize.height); + gl.uniform2f(uniforms.bloom_blur_program_size, hSize.width, hSize.height); + bindTextureTo(gl, "bloom_high_pass_pyr_" + i, "bloom_blur_program_tex", 0); + gl.uniform2f(uniforms.bloom_blur_program_direction, 1, 0); + gl.drawArrays(gl.TRIANGLES, 0, 3); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers["bloom_v_blur_pyr_" + i]); + const vSize = dynamicSizes["bloom_v_blur_pyr_" + i]; + gl.viewport(0, 0, vSize.width, vSize.height); + bindTextureTo(gl, "bloom_h_blur_pyr_" + i, "bloom_blur_program_tex", 0); + gl.uniform2f(uniforms.bloom_blur_program_direction, 0, 1); + gl.drawArrays(gl.TRIANGLES, 0, 3); + } + + // bloom combine + gl.useProgram(programs.bloom_combine); + bindGeometryTo(gl, "fullscreen", "bloom_combine_program_aPosition"); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers.bloom_output); + setViewportSizeTo(gl, "bloom_output"); + for (let i = 0; i < 5; i++) { + gl.activeTexture(gl.TEXTURE0 + i); + gl.bindTexture(gl.TEXTURE_2D, textures["bloom_v_blur_pyr_" + i]); + gl.uniform1i(uniforms["bloom_combine_program_pyr_" + i], i); + } + gl.drawArrays(gl.TRIANGLES, 0, 3); + + // palette + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers.palette_output); + setViewportSizeTo(gl, "palette_output"); + gl.useProgram(programs.palette); + bindGeometryTo(gl, "fullscreen", "palette_program_aPosition"); + bindTextureTo(gl, "rain_output", "palette_program_tex", 0); + bindTextureTo(gl, "bloom_output", "palette_program_bloomTex", 1); + gl.uniform1f(uniforms.palette_program_time, time); + bindTextureTo(gl, "palette", "palette_program_paletteTex", 2); + gl.drawArrays(gl.TRIANGLES, 0, 3); + + // upscale + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + setViewportSizeTo(gl, "fullscreen"); + gl.useProgram(programs.fullscreen); + bindGeometryTo(gl, "fullscreen", "fullscreen_program_aPosition"); + bindTextureTo(gl, "palette_output", "fullscreen_program_tex", 0); + gl.drawArrays(gl.TRIANGLES, 0, 3); +}; document.body.onload = async () => { - document.addEventListener("touchmove", (e) => e.preventDefault(), { passive: false }); const canvas = document.querySelector("canvas"); const dimensions = { width: 1, height: 1 }; @@ -21,25 +614,6 @@ document.body.onload = async () => { image.src = "msdf.png"; await image.decode(); - const palette = [ - [ 0, 0, 0, 255, ], - [ 7, 33, 0, 255, ], - [ 15, 63, 2, 255, ], - [ 22, 96, 5, 255, ], - [ 38, 117, 17, 255, ], - [ 53, 137, 33, 255, ], - [ 71, 160, 48, 255, ], - [ 86, 181, 63, 255, ], - [ 104, 204, 79, 255, ], - [ 119, 224, 94, 255, ], - [ 135, 247, 109, 255, ], - [ 155, 247, 132, 255, ], - [ 175, 249, 158, 255, ], - [ 175, 249, 158, 255, ], - [ 175, 249, 158, 255, ], - [ 175, 249, 158, 255, ], - ]; - init(gl); load(gl, image, palette); @@ -57,7 +631,6 @@ document.body.onload = async () => { draw(gl, tick, (Date.now() - start) / 1000); requestAnimationFrame(update); - } + }; update(); - }; diff --git a/unraveled.js b/unraveled.js deleted file mode 100644 index edeb53e..0000000 --- a/unraveled.js +++ /dev/null @@ -1,584 +0,0 @@ -const extendedContext = {}; -const programs = {}; -const textures = {}; -const dynamicSizes = { fullscreen: {scale: 1}}; -const framebuffers = {}; -const geometry = {}; -const attributes = {}; -const uniforms = {}; - -const extensionNames = [ - "oes_texture_half_float", - "oes_texture_half_float_linear", - "ext_color_buffer_half_float", - "webgl_color_buffer_float", - "oes_standard_derivatives", -]; - -const fullscreen_frag_shader_source = ` - precision mediump float; - varying vec2 vUV; - uniform sampler2D tex; - void main() { - gl_FragColor = texture2D(tex, vUV); - } -`; - -const fullscreen_vert_shader_source = ` - precision mediump float; - attribute vec2 aPosition; - varying vec2 vUV; - void main() { - vUV = 0.5 * (aPosition + 1.0); - gl_Position = vec4(aPosition, 0, 1); - } -`; - -const rain_compute_shader_source = ` - precision highp float; - - #define PI 3.14159265359 - #define SQRT_2 1.4142135623730951 - #define SQRT_5 2.23606797749979 - - uniform sampler2D previousComputeState; - - uniform float numColumns, numRows; - uniform float time, tick; - uniform float fallSpeed, cycleSpeed; - uniform float glyphSequenceLength; - uniform float raindropLength; - - highp float randomFloat( const in vec2 uv ) { - const highp float a = 12.9898, b = 78.233, c = 43758.5453; - highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI ); - return fract(sin(sn) * c); - } - - float wobble(float x) { - return x + 0.3 * sin(SQRT_2 * x) + 0.2 * sin(SQRT_5 * x); - } - - float getRainBrightness(float simTime, vec2 glyphPos) { - float columnTimeOffset = randomFloat(vec2(glyphPos.x, 0.)) * 1000.; - float columnSpeedOffset = randomFloat(vec2(glyphPos.x + 0.1, 0.)) * 0.5 + 0.5; - float columnTime = columnTimeOffset + simTime * fallSpeed * columnSpeedOffset; - float rainTime = (glyphPos.y * 0.01 + columnTime) / raindropLength; - rainTime = wobble(rainTime); - return 1.0 - fract(rainTime); - } - - vec2 computeRaindrop(float simTime, vec2 glyphPos) { - float brightness = getRainBrightness(simTime, glyphPos); - float brightnessBelow = getRainBrightness(simTime, glyphPos + vec2(0., -1.)); - bool cursor = brightness > brightnessBelow; - return vec2(brightness, cursor); - } - - vec2 computeSymbol(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous) { - - float previousSymbol = previous.r; - float previousAge = previous.g; - bool resetGlyph = isFirstFrame; - if (resetGlyph) { - previousAge = randomFloat(screenPos + 0.5); - previousSymbol = floor(glyphSequenceLength * randomFloat(screenPos)); - } - float age = previousAge; - float symbol = previousSymbol; - if (mod(tick, 1.0) == 0.) { - age += cycleSpeed; - if (age >= 1.) { - symbol = floor(glyphSequenceLength * randomFloat(screenPos + simTime)); - age = fract(age); - } - } - - return vec2(symbol, age); - } - - void main() { - vec2 glyphPos = gl_FragCoord.xy; - vec2 screenPos = glyphPos / vec2(numColumns, numRows); - - vec2 raindrop = computeRaindrop(time, glyphPos); - - bool isFirstFrame = tick <= 1.; - vec4 previous = texture2D( previousComputeState, screenPos ); - vec4 previousSymbol = vec4(previous.ba, 0.0, 0.0); - vec2 symbol = computeSymbol(time, isFirstFrame, glyphPos, screenPos, previousSymbol); - gl_FragColor = vec4(raindrop, symbol); - } - -`; - -const rain_vert_shader_source = ` - precision lowp float; - - attribute vec2 aPosition; - uniform vec2 size; - varying vec2 vUV; - - void main() { - vUV = aPosition; - vec2 proportion = (size.y > size.x ? vec2(size.y / size.x, 1.) : vec2(1., size.x / size.y)); - gl_Position = vec4((aPosition - 0.5) * 2.0 * proportion, 0.0, 1.0); - } -`; - -const rain_frag_shader_source = ` - #define PI 3.14159265359 - #ifdef GL_OES_standard_derivatives - #extension GL_OES_standard_derivatives: enable - #endif - precision lowp float; - - uniform sampler2D computeState; - uniform float numColumns, numRows; - uniform sampler2D glyphMSDF; - uniform float msdfPxRange; - uniform vec2 glyphMSDFSize; - uniform float glyphSequenceLength; - uniform vec2 glyphTextureGridSize; - - varying vec2 vUV; - - float median3(vec3 i) { - return max(min(i.r, i.g), min(max(i.r, i.g), i.b)); - } - - float modI(float a, float b) { - float m = a - floor((a + 0.5) / b) * b; - return floor(m + 0.5); - } - - vec3 getBrightness(vec2 raindrop, vec2 uv) { - - float base = raindrop.r; - bool isCursor = bool(raindrop.g); - float glint = base; - - base = base * 1.1 - 0.5; - glint = glint * 2.5 - 1.5; - - return vec3( - (isCursor ? vec2(0.0, 1.0) : vec2(1.0, 0.0)) * base, - glint - ); - } - - vec2 getSymbolUV(float index) { - float symbolX = modI(index, glyphTextureGridSize.x); - float symbolY = (index - symbolX) / glyphTextureGridSize.x; - symbolY = glyphTextureGridSize.y - symbolY - 1.; - return vec2(symbolX, symbolY); - } - - vec2 getSymbol(vec2 uv, float index) { - uv = fract(uv * vec2(numColumns, numRows)); - uv = (uv + getSymbolUV(index)) / glyphTextureGridSize; - - vec2 symbol; - { - vec2 unitRange = vec2(msdfPxRange) / glyphMSDFSize; - vec2 screenTexSize = vec2(1.0) / fwidth(uv); - float screenPxRange = max(0.5 * dot(unitRange, screenTexSize), 1.0); - - float signedDistance = median3(texture2D(glyphMSDF, uv).rgb); - float screenPxDistance = screenPxRange * (signedDistance - 0.5); - symbol.r = clamp(screenPxDistance + 0.5, 0.0, 1.0); - } - - return symbol; - } - - void main() { - vec4 data = texture2D(computeState, vUV); - vec3 brightness = getBrightness(data.rg, vUV); - vec2 symbol = getSymbol(vUV, data.b); - gl_FragColor = vec4(brightness.rg * symbol.r, brightness.b * symbol.g, 0.); - } -`; - -const bloom_high_pass_shader_source = ` - precision mediump float; - - uniform sampler2D tex; - uniform float highPassThreshold; - - varying vec2 vUV; - - void main() { - vec4 color = texture2D(tex, vUV); - - if (color.r < highPassThreshold) color.r = 0.0; - if (color.g < highPassThreshold) color.g = 0.0; - if (color.b < highPassThreshold) color.b = 0.0; - gl_FragColor = color; - } -`; - -const bloom_blur_shader_source = ` - precision mediump float; - - uniform vec2 size; - uniform sampler2D tex; - uniform vec2 direction; - - varying vec2 vUV; - - void main() { - vec2 proportion = (size.y > size.x ? vec2(size.y / size.x, 1.) : vec2(1., size.x / size.y)); - gl_FragColor = - texture2D(tex, vUV) * 0.442 + - ( - texture2D(tex, vUV + direction / max(size.y, size.x) * proportion) + - texture2D(tex, vUV - direction / max(size.y, size.x) * proportion) - ) * 0.279; - } -`; - -const bloom_combine_shader_source = ` - precision mediump float; - - uniform sampler2D pyr_0, pyr_1, pyr_2, pyr_3, pyr_4; - uniform float bloomStrength; - varying vec2 vUV; - - void main() { - vec4 total = vec4(0.); - total += texture2D(pyr_0, vUV) * 0.96549; - total += texture2D(pyr_1, vUV) * 0.92832; - total += texture2D(pyr_2, vUV) * 0.88790; - total += texture2D(pyr_3, vUV) * 0.84343; - total += texture2D(pyr_4, vUV) * 0.79370; - gl_FragColor = total * bloomStrength; - } -`; - -const palette_shader_source = ` - precision mediump float; - #define PI 3.14159265359 - - uniform sampler2D tex, bloomTex, paletteTex; - uniform float time; - varying vec2 vUV; - - highp float rand( const in vec2 uv, const in float t ) { - const highp float a = 12.9898, b = 78.233, c = 43758.5453; - highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI ); - return fract(sin(sn) * c + t); - } - - void main() { - vec4 primary = texture2D(tex, vUV); - vec4 bloom = texture2D(bloomTex, vUV); - vec4 brightness = primary + bloom - rand( gl_FragCoord.xy, time ) * 0.0167; - gl_FragColor = vec4( - texture2D( paletteTex, vec2(brightness.r, 0.0)).rgb - + min(vec3(0.756, 1.0, 0.46) * brightness.g * 2.0, vec3(1.0)), - 1.0 - ); - } -`; - -const init = (gl) => Object.assign(extendedContext, ...extensionNames.map(name => Object.getPrototypeOf(gl.getExtension(name)))); - -const load = (gl, msdfImage, palette) => { - - const buildShader = (source, isFragment) => { - const shader = gl.createShader(isFragment ? gl.FRAGMENT_SHADER : gl.VERTEX_SHADER); - gl.shaderSource(shader, source); - gl.compileShader(shader); - return shader; - }; - - const buildProgram = (vertexShader, fragmentShader) => { - const program = gl.createProgram(); - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); - return program; - }; - - const fullscreen_frag_shader = buildShader(fullscreen_frag_shader_source, true); - const fullscreen_vert_shader = buildShader(fullscreen_vert_shader_source, false); - const rain_compute_shader = buildShader(rain_compute_shader_source, true); - const rain_frag_shader = buildShader(rain_frag_shader_source, true); - const rain_vert_shader = buildShader(rain_vert_shader_source, false); - const bloom_high_pass_shader = buildShader(bloom_high_pass_shader_source, true); - const bloom_blur_shader = buildShader(bloom_blur_shader_source, true); - const bloom_combine_shader = buildShader(bloom_combine_shader_source, true); - const palette_shader = buildShader(palette_shader_source, true); - - programs.fullscreen = buildProgram(fullscreen_vert_shader, fullscreen_frag_shader); - uniforms.rain_program_tex = gl.getUniformLocation(programs.fullscreen, "tex"); - attributes.fullscreen_program_aPosition = gl.getAttribLocation(programs.fullscreen, "aPosition"); - - programs.rain_compute = buildProgram(fullscreen_vert_shader, rain_compute_shader); - attributes.rain_compute_program_aPosition = gl.getAttribLocation(programs.rain_compute, "aPosition"); - uniforms.rain_compute_program_time = gl.getUniformLocation(programs.rain_compute, "time"); - uniforms.rain_compute_program_previousComputeState = gl.getUniformLocation(programs.rain_compute, "previousComputeState"); - uniforms.rain_compute_program_tick = gl.getUniformLocation(programs.rain_compute, "tick"); - gl.useProgram(programs.rain_compute); - gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "numColumns"), 80); - gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "glyphSequenceLength"), 57); - gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "numRows"), 80); - gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "fallSpeed"), 0.3); - gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "raindropLength"), 0.75); - gl.uniform1f(gl.getUniformLocation(programs.rain_compute, "cycleSpeed"), 0.03); - - programs.rain = buildProgram(rain_vert_shader, rain_frag_shader); - attributes.rain_program_aPosition = gl.getAttribLocation(programs.rain, "aPosition"); - uniforms.rain_program_size = gl.getUniformLocation(programs.rain, "size"); - uniforms.rain_program_computeState = gl.getUniformLocation(programs.rain, "computeState"); - uniforms.rain_program_glyphMSDF = gl.getUniformLocation(programs.rain, "glyphMSDF"); - gl.useProgram(programs.rain); - gl.uniform2f(gl.getUniformLocation(programs.rain, "glyphTextureGridSize"), 8, 8); - gl.uniform1f(gl.getUniformLocation(programs.rain, "numColumns"), 80); - gl.uniform2f(gl.getUniformLocation(programs.rain, "glyphMSDFSize"), 512, 512); - gl.uniform1f(gl.getUniformLocation(programs.rain, "numRows"), 80); - gl.uniform1f(gl.getUniformLocation(programs.rain, "msdfPxRange"), 4); - - programs.bloom_high_pass = buildProgram(fullscreen_vert_shader, bloom_high_pass_shader); - attributes.bloom_high_pass_program_aPosition = gl.getAttribLocation(programs.bloom_high_pass, "aPosition"); - uniforms.bloom_high_pass_program_tex = gl.getUniformLocation(programs.bloom_high_pass, "tex"); - gl.useProgram(programs.bloom_high_pass); - gl.uniform1f(gl.getUniformLocation(programs.bloom_high_pass, "highPassThreshold"), 0.1); - - programs.bloom_blur = buildProgram(fullscreen_vert_shader, bloom_blur_shader); - attributes.bloom_blur_program_aPosition = gl.getAttribLocation(programs.bloom_blur, "aPosition"); - uniforms.bloom_blur_program_tex = gl.getUniformLocation(programs.bloom_blur, "tex"); - uniforms.bloom_blur_program_size = gl.getUniformLocation(programs.bloom_blur, "size"); - uniforms.bloom_blur_program_direction = gl.getUniformLocation(programs.bloom_blur, "direction"); - - programs.bloom_combine = buildProgram(fullscreen_vert_shader, bloom_combine_shader); - attributes.bloom_combine_program_aPosition = gl.getAttribLocation(programs.bloom_combine, "aPosition"); - uniforms.bloom_combine_program_pyr_0 = gl.getUniformLocation(programs.bloom_combine, "pyr_0"); - uniforms.bloom_combine_program_pyr_1 = gl.getUniformLocation(programs.bloom_combine, "pyr_1"); - uniforms.bloom_combine_program_pyr_2 = gl.getUniformLocation(programs.bloom_combine, "pyr_2"); - uniforms.bloom_combine_program_pyr_3 = gl.getUniformLocation(programs.bloom_combine, "pyr_3"); - uniforms.bloom_combine_program_pyr_4 = gl.getUniformLocation(programs.bloom_combine, "pyr_4"); - gl.useProgram(programs.bloom_combine); - gl.uniform1f(gl.getUniformLocation(programs.bloom_combine, "bloomStrength"), 0.7); - - programs.palette = buildProgram(fullscreen_vert_shader, palette_shader); - attributes.palette_program_aPosition = gl.getAttribLocation(programs.palette, "aPosition"); - uniforms.palette_program_tex = gl.getUniformLocation(programs.palette, "tex"); - uniforms.palette_program_bloomTex = gl.getUniformLocation(programs.palette, "bloomTex"); - uniforms.palette_program_time = gl.getUniformLocation(programs.palette, "time"); - uniforms.palette_program_paletteTex = gl.getUniformLocation(programs.palette, "paletteTex"); - - geometry.rain = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, geometry.rain); - gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from([0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 0]), gl.STATIC_DRAW); - - geometry.fullscreen = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, geometry.fullscreen); - gl.bufferData(gl.ARRAY_BUFFER, Float32Array.from([-4, -4, 4, -4, 0, 4]), gl.STATIC_DRAW); - - const setTexParams = (texture, isLinear, data) => { - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - const filter = isLinear ? gl.LINEAR : gl.NEAREST; - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); - if (data != null) { - if (data instanceof HTMLImageElement) { - gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, data); - } else { - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, data.length, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, Uint8ClampedArray.from(data.flat())); - } - } - } - - for (let i = 0; i < 2; i++) { - const name = "rain_compute_doublebuffer_" + i; - const texture = gl.createTexture(); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, texture); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 80, 80, 0, gl.RGBA, extendedContext.HALF_FLOAT_OES, null); - setTexParams(texture, false); - - const framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); - - textures[name] = texture; - framebuffers[name] = framebuffer; - } - - const buildAndAddRTT = (name, scale) => { - const texture = gl.createTexture(); - dynamicSizes[name] = {scale}; - setTexParams(texture, true); - const framebuffer = gl.createFramebuffer(); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); - gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); - - textures[name] = texture; - framebuffers[name] = framebuffer; - }; - - buildAndAddRTT("rain_output", 1); - for (let i = 0; i < 5; i++) { - const scale = 0.4 / (2 ** i); - buildAndAddRTT("bloom_high_pass_pyr_" + i, scale); - buildAndAddRTT("bloom_h_blur_pyr_" + i, scale); - buildAndAddRTT("bloom_v_blur_pyr_" + i, scale); - } - buildAndAddRTT("bloom_output", 1); - buildAndAddRTT("palette_output", 1); - - textures.palette = gl.createTexture(); - setTexParams(textures.palette, true, palette); - - textures.msdf = gl.createTexture(); - setTexParams(textures.msdf, true, msdfImage); - - gl.enableVertexAttribArray(0); - gl.disable(gl.DEPTH_TEST); - gl.blendFuncSeparate(1, 1, 1, 1); - gl.clearColor(0, 0, 0, 1); -}; - -const resize = (gl, width, height) => { - - dynamicSizes.fullscreen.width = width; - dynamicSizes.fullscreen.height = height; - - for (var name in textures) { - const size = dynamicSizes[name]; - if (size == null) { - continue; - } - size.width = Math.floor(width * size.scale); - size.height = Math.floor(height * size.scale); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, textures[name]); - gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size.width, size.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); - } - - gl.useProgram(programs.rain); - gl.uniform2f(uniforms.rain_program_size, width, height); -}; - -const setViewportSizeTo = (gl, name) => { - const size = dynamicSizes[name]; - gl.viewport(0, 0, size.width, size.height); -} - -const bindTextureTo = (gl, texName, uniformName, index) => { - gl.activeTexture(gl.TEXTURE0 + index); - gl.bindTexture(gl.TEXTURE_2D, textures[texName]); - gl.uniform1i(uniforms[uniformName], index); -}; - -const bindGeometryTo = (gl, geometryName, attributeName) => { - gl.bindBuffer(gl.ARRAY_BUFFER, geometry[geometryName]); - gl.vertexAttribPointer(attributes[attributeName], 2, gl.FLOAT, false, 0, 0); -}; - -const draw = (gl, tick, time) => { - - const doubleBufferFrontName = "rain_compute_doublebuffer_" + (tick % 2); - const doubleBufferBackName = "rain_compute_doublebuffer_" + ((tick + 1) % 2); - - // rain compute - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[doubleBufferFrontName]); - gl.viewport(0, 0, 80, 80); - gl.useProgram(programs.rain_compute); - bindGeometryTo(gl, "fullscreen", "rain_compute_program_aPosition"); - bindTextureTo(gl, doubleBufferBackName, "rain_compute_program_previousComputeState", 0); - gl.uniform1f(uniforms.rain_compute_program_time, time); - gl.uniform1f(uniforms.rain_compute_program_tick, tick); - gl.drawArrays(gl.TRIANGLES, 0, 3); - - // rain - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers.rain_output); - setViewportSizeTo(gl, "rain_output"); - gl.clear(gl.COLOR_BUFFER_BIT); - gl.enable(gl.BLEND); - gl.useProgram(programs.rain); - bindGeometryTo(gl, "rain", "rain_program_aPosition"); - - bindTextureTo(gl, doubleBufferFrontName, "rain_program_computeState", 0); - bindTextureTo(gl, "msdf", "rain_program_glyphMSDF", 1); - gl.drawArrays(gl.TRIANGLES, 0, 6); - gl.disable(gl.BLEND); - - // high pass pyramid - gl.useProgram(programs.bloom_high_pass); - gl.bindBuffer(gl.ARRAY_BUFFER, geometry.fullscreen); - gl.vertexAttribPointer(attributes.bloom_high_pass_program_aPosition, 2, gl.FLOAT, false, 0, 0); - for (let i = 0; i < 5; i++) { - const name = "bloom_high_pass_pyr_" + i; - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers[name]); - const size = dynamicSizes[name]; - gl.viewport(0, 0, size.width, size.height); - const src = (i === 0 ? textures.rain_output : textures["bloom_high_pass_pyr_" + (i - 1)]); - gl.activeTexture(gl.TEXTURE0); - gl.bindTexture(gl.TEXTURE_2D, src); - gl.uniform1i(uniforms.bloom_high_pass_program_tex, 0); - gl.drawArrays(gl.TRIANGLES, 0, 3); - } - - // blur pyramids - gl.useProgram(programs.bloom_blur); - gl.bindBuffer(gl.ARRAY_BUFFER, geometry.fullscreen); - gl.vertexAttribPointer(attributes.bloom_blur_program_aPosition, 2, gl.FLOAT, false, 0, 0); - for (let i = 0; i < 5; i++) { - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers["bloom_h_blur_pyr_" + i]); - const hSize = dynamicSizes["bloom_h_blur_pyr_" + i]; - gl.viewport(0, 0, hSize.width, hSize.height); - gl.uniform2f(uniforms.bloom_blur_program_size, hSize.width, hSize.height); - bindTextureTo(gl, "bloom_high_pass_pyr_" + i, "bloom_blur_program_tex", 0); - gl.uniform2f(uniforms.bloom_blur_program_direction, 1, 0); - gl.drawArrays(gl.TRIANGLES, 0, 3); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers["bloom_v_blur_pyr_" + i]); - const vSize = dynamicSizes["bloom_v_blur_pyr_" + i]; - gl.viewport(0, 0, vSize.width, vSize.height); - bindTextureTo(gl, "bloom_h_blur_pyr_" + i, "bloom_blur_program_tex", 0); - gl.uniform2f(uniforms.bloom_blur_program_direction, 0, 1); - gl.drawArrays(gl.TRIANGLES, 0, 3); - } - - // bloom combine - gl.useProgram(programs.bloom_combine); - bindGeometryTo(gl, "fullscreen", "bloom_combine_program_aPosition"); - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers.bloom_output); - setViewportSizeTo(gl, "bloom_output"); - for (let i = 0; i < 5; i++) { - gl.activeTexture(gl.TEXTURE0 + i); - gl.bindTexture(gl.TEXTURE_2D, textures["bloom_v_blur_pyr_" + i]); - gl.uniform1i(uniforms["bloom_combine_program_pyr_" + i], i); - } - gl.drawArrays(gl.TRIANGLES, 0, 3); - - // palette - gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffers.palette_output); - setViewportSizeTo(gl, "palette_output"); - gl.useProgram(programs.palette); - bindGeometryTo(gl, "fullscreen", "palette_program_aPosition"); - bindTextureTo(gl, "rain_output", "palette_program_tex", 0); - bindTextureTo(gl, "bloom_output", "palette_program_bloomTex", 1); - gl.uniform1f(uniforms.palette_program_time, time); - bindTextureTo(gl, "palette", "palette_program_paletteTex", 2); - gl.drawArrays(gl.TRIANGLES, 0, 3); - - // upscale - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - setViewportSizeTo(gl, "fullscreen"); - gl.useProgram(programs.fullscreen); - bindGeometryTo(gl, "fullscreen", "fullscreen_program_aPosition"); - bindTextureTo(gl, "palette_output", "fullscreen_program_tex", 0); - gl.drawArrays(gl.TRIANGLES, 0, 3); - -}; - -export { - init, load, resize, draw -};