diff --git a/TODO.txt b/TODO.txt index c06d4ba..dfdd23e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -2,7 +2,7 @@ TODO: WebGPU First decent rainRender - Is there a way to whittle out quadSize? + What's wrong with the other font textures? Port compute pass 100% Compute entry point can live in the same wgsl file, I think use textureLoad for texel access in render pipeline diff --git a/js/webgpu/main.js b/js/webgpu/main.js index 08200a3..b487192 100644 --- a/js/webgpu/main.js +++ b/js/webgpu/main.js @@ -37,13 +37,11 @@ export default async (canvas, config) => { // to reach the desired density, and then overlaps them const volumetric = config.volumetric; const density = volumetric && config.effect !== "none" ? config.density : 1; - const [numRows, numColumns] = [config.numColumns, config.numColumns * density]; + const gridSize = [config.numColumns * density, config.numColumns]; // The volumetric mode requires us to create a grid of quads, // rather than a single quad for our geometry - const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1]; - const numQuads = numQuadRows * numQuadColumns; - const quadSize = [1 / numQuadColumns, 1 / numQuadRows]; + const numQuads = volumetric ? gridSize[0] * gridSize[1] : 1; // Various effect-related values const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1; @@ -57,8 +55,7 @@ export default async (canvas, config) => { { name: "animationSpeed", type: "f32", value: config.animationSpeed }, { name: "glyphHeightToWidth", type: "f32", value: config.glyphHeightToWidth }, { name: "resurrectingCodeRatio", type: "f32", value: config.resurrectingCodeRatio }, - { name: "numColumns", type: "i32", value: numColumns }, - { name: "numRows", type: "i32", value: numRows }, + { name: "gridSize", type: "vec2", value: gridSize }, { name: "showComputationTexture", type: "i32", value: showComputationTexture }, // compute @@ -84,9 +81,6 @@ export default async (canvas, config) => { { name: "glyphEdgeCrop", type: "f32", value: config.glyphEdgeCrop }, { name: "isPolar", type: "i32", value: config.isPolar }, { name: "density", type: "f32", value: density }, - { name: "numQuadColumns", type: "i32", value: numQuadColumns }, - { name: "numQuadRows", type: "i32", value: numQuadRows }, - { name: "quadSize", type: "vec2", value: quadSize }, { name: "slantScale", type: "f32", value: slantScale }, { name: "slantVec", type: "vec2", value: slantVec }, { name: "volumetric", type: "i32", value: volumetric }, diff --git a/shaders/wgsl/rainRenderPass.wgsl b/shaders/wgsl/rainRenderPass.wgsl index 6cc19ab..a04f755 100644 --- a/shaders/wgsl/rainRenderPass.wgsl +++ b/shaders/wgsl/rainRenderPass.wgsl @@ -11,8 +11,7 @@ let SQRT_5:f32 = 2.23606797749979; animationSpeed : f32; glyphHeightToWidth : f32; resurrectingCodeRatio : f32; - numColumns : i32; - numRows : i32; + gridSize : vec2; showComputationTexture : i32; // compute @@ -38,9 +37,6 @@ let SQRT_5:f32 = 2.23606797749979; glyphEdgeCrop : f32; isPolar : i32; density : f32; - numQuadColumns : i32; - numQuadRows : i32; - quadSize : vec2; slantScale : f32; slantVec : vec2; volumetric : i32; @@ -110,6 +106,12 @@ fn wobble(x:f32) -> f32 { var volumetric = bool(config.volumetric); + var quadGridSize = vec2(1.0); + if (volumetric) { + quadGridSize = config.gridSize; + } + + // Convert the vertex index into its quad's position and its corner in its quad var i = i32(input.index); var quadIndex = i / NUM_VERTICES_PER_QUAD; @@ -119,46 +121,52 @@ fn wobble(x:f32) -> f32 { ); var quadPosition = vec2( - f32(quadIndex % config.numQuadColumns), - f32(quadIndex / config.numQuadColumns) + f32(quadIndex % i32(quadGridSize.x)), + f32(quadIndex / i32(quadGridSize.x)) ); - var vUV = (quadPosition + quadCorner) * config.quadSize; - var vGlyph = vec4(1.0, 0.15, randomFloat(vec2(quadPosition.x, 1.0)), 0.0); // TODO: texture2D(state, quadPosition * config.quadSize); + // Calculate the vertex's uv + var uv = (quadPosition + quadCorner) / quadGridSize; - // Calculate the world space position + // Retrieve the quad's glyph data + var vGlyph = vec4(1.0, 0.72, randomFloat(vec2(quadPosition.x, 1.0)), 0.0); // TODO: texture2D(state, quadPosition / quadGridSize); + + // Calculate the quad's depth var quadDepth = 0.0; if (volumetric && !bool(config.showComputationTexture)) { quadDepth = fract(vGlyph.b + time.seconds * config.animationSpeed * config.forwardSpeed); vGlyph.b = quadDepth; } - var position = (quadPosition * vec2(1.0, config.glyphVerticalSpacing) + quadCorner * vec2(config.density, 1.0)) * config.quadSize; - var pos = vec4((position - 0.5) * 2.0, quadDepth, 1.0); - pos.y = -pos.y; + // Calculate the vertex's world space position + var worldPosition = quadPosition * vec2(1.0, config.glyphVerticalSpacing); + worldPosition = worldPosition + quadCorner * vec2(config.density, 1.0); + worldPosition = worldPosition / quadGridSize; + worldPosition = (worldPosition - 0.5) * 2.0; + worldPosition.y = -worldPosition.y; // "Resurrected" columns are in the green channel, // and are vertically flipped (along with their glyphs) var vChannel = vec3(1.0, 0.0, 0.0); if (volumetric && randomFloat(vec2(quadPosition.x, 0.0)) < config.resurrectingCodeRatio) { - pos.y = -pos.y; + worldPosition.y = -worldPosition.y; vChannel = vec3(0.0, 1.0, 0.0); } vChannel = vec3(1.0); // TODO: remove - // Convert the world space position to screen space + // Convert the vertex's world space position to screen space + var screenPosition = vec4(worldPosition, quadDepth, 1.0); if (volumetric) { - pos.x = pos.x / config.glyphHeightToWidth; - pos = scene.camera * scene.transform * pos; + screenPosition.x = screenPosition.x / config.glyphHeightToWidth; + screenPosition = scene.camera * scene.transform * screenPosition; } else { - pos.x = pos.x * scene.screenSize.x; - pos.y = pos.y * scene.screenSize.y; + screenPosition = vec4(screenPosition.xy * scene.screenSize, screenPosition.zw); } return VertOutput( - pos, - vUV, + screenPosition, + uv, vChannel, vGlyph ); @@ -182,26 +190,26 @@ fn getSymbolUV(glyphCycle:f32) -> vec2 { var volumetric = bool(config.volumetric); var uv = input.uv; - // In normal mode, derives the current glyph and UV from vUV + // For normal mode, derive the fragment's glyph and msdf UV from its screen space position if (!volumetric) { if (bool(config.isPolar)) { - // Curved space that makes letters appear to radiate from up above + // Curve space to make the letters appear to radiate from up above uv = (uv - 0.5) * 0.5; - uv.y = uv.y - 0.5; + uv.y = uv.y + 0.5; var radius = length(uv); - var angle = atan2(uv.y, uv.x) / (2.0 * PI) + 0.5; // atan? - uv = vec2(fract(angle * 4.0 - 0.5), 1.5 * (1.0 - sqrt(radius))); + var angle = atan2(uv.y, uv.x) / (2.0 * PI) + 0.5; + uv = -vec2(fract(angle * 4.0 - 0.5), 1.5 * (1.0 - sqrt(radius))); } else { - // Applies the slant and scales space so the viewport is fully covered + // Apply the slant and a scale to space so the viewport is still fully covered by the geometry uv = vec2( - (uv.x - 0.5) * config.slantVec.x + (uv.y - 0.5) * config.slantVec.y, - (uv.y - 0.5) * config.slantVec.x - (uv.x - 0.5) * config.slantVec.y + (uv.x - 0.5) * config.slantVec.x + (uv.y - 0.5) * -config.slantVec.y, + (uv.y - 0.5) * config.slantVec.x - (uv.x - 0.5) * -config.slantVec.y ) * config.slantScale + 0.5; } uv.y = uv.y / config.glyphHeightToWidth; } - // Unpack the values from the data texture + // Retrieve values from the data texture var glyph:vec4; if (volumetric) { glyph = input.glyph; @@ -221,7 +229,7 @@ fn getSymbolUV(glyphCycle:f32) -> vec2 { } // resolve UV to cropped position of glyph in MSDF texture - var glyphUV = fract(uv * vec2(f32(config.numColumns), f32(config.numRows))); + var glyphUV = fract(uv * config.gridSize); glyphUV = glyphUV - 0.5; glyphUV = glyphUV * clamp(1.0 - config.glyphEdgeCrop, 0.0, 1.0); glyphUV = glyphUV + 0.5;