From 353c7380b6de76d7a659d7e1b2d206320d6bf285 Mon Sep 17 00:00:00 2001 From: Ksenia Kondrashova Date: Mon, 20 Apr 2026 23:58:43 +0400 Subject: [PATCH 1/5] first run --- bun.lock | 7 +- docs/src/app/home-thumbnails.ts | 31 +- docs/src/app/page.tsx | 2 +- docs/src/app/test/page.tsx | 143 +++- package.json | 3 +- packages/shaders/src/shader-mount.ts | 753 +++++++++--------- packages/shaders/src/shader-utils.ts | 209 +++-- packages/shaders/src/shaders/color-panels.ts | 260 +++--- packages/shaders/src/shaders/dithering.ts | 307 ++++--- packages/shaders/src/shaders/dot-grid.ts | 120 +-- packages/shaders/src/shaders/dot-orbit.ts | 146 ++-- packages/shaders/src/shaders/fluted-glass.ts | 505 ++++++------ packages/shaders/src/shaders/gem-smoke.ts | 330 ++++---- packages/shaders/src/shaders/god-rays.ts | 159 ++-- .../shaders/src/shaders/grain-gradient.ts | 325 ++++---- packages/shaders/src/shaders/halftone-cmyk.ts | 414 +++++----- packages/shaders/src/shaders/halftone-dots.ts | 398 ++++----- packages/shaders/src/shaders/heatmap.ts | 306 ++++--- .../shaders/src/shaders/image-dithering.ts | 205 +++-- packages/shaders/src/shaders/liquid-metal.ts | 442 +++++----- packages/shaders/src/shaders/mesh-gradient.ts | 147 ++-- packages/shaders/src/shaders/metaballs.ts | 120 ++- packages/shaders/src/shaders/neuro-noise.ts | 92 +-- packages/shaders/src/shaders/paper-texture.ts | 343 ++++---- packages/shaders/src/shaders/perlin-noise.ts | 231 +++--- .../shaders/src/shaders/pulsing-border.ts | 309 ++++--- packages/shaders/src/shaders/simplex-noise.ts | 113 ++- packages/shaders/src/shaders/smoke-ring.ts | 175 ++-- packages/shaders/src/shaders/spiral.ts | 105 ++- .../src/shaders/static-mesh-gradient.ts | 159 ++-- .../src/shaders/static-radial-gradient.ts | 275 +++---- packages/shaders/src/shaders/swirl.ts | 124 ++- packages/shaders/src/shaders/voronoi.ts | 164 ++-- packages/shaders/src/shaders/warp.ts | 182 +++-- packages/shaders/src/shaders/water.ts | 151 ++-- packages/shaders/src/shaders/waves.ts | 87 +- packages/shaders/src/vertex-shader.ts | 250 +++--- packages/shaders/tsconfig.json | 1 + 38 files changed, 4104 insertions(+), 3989 deletions(-) diff --git a/bun.lock b/bun.lock index cfd0bf0b0..c161d9ec4 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "name": "paper-shaders", "devDependencies": { "@types/bun": "^1.1.11", + "@webgpu/types": "^0.1.69", "esbuild": "^0.24.0", "glob": "^11.0.1", "nodemon": "^3.1.7", @@ -47,11 +48,11 @@ }, "packages/shaders": { "name": "@paper-design/shaders", - "version": "0.0.68", + "version": "0.0.76", }, "packages/shaders-react": { "name": "@paper-design/shaders-react", - "version": "0.0.68", + "version": "0.0.76", "dependencies": { "@paper-design/shaders": "workspace:*", }, @@ -475,6 +476,8 @@ "@vercel/analytics": ["@vercel/analytics@1.5.0", "", { "peerDependencies": { "@remix-run/react": "^2", "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@remix-run/react", "@sveltejs/kit", "next", "react", "svelte", "vue", "vue-router"] }, "sha512-MYsBzfPki4gthY5HnYN7jgInhAZ7Ac1cYDoRWFomwGHWEX7odTEzbtg9kf/QSo7XEsEAqlQugA6gJ2WS2DEa3g=="], + "@webgpu/types": ["@webgpu/types@0.1.69", "", {}, "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ=="], + "acorn": ["acorn@8.14.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], diff --git a/docs/src/app/home-thumbnails.ts b/docs/src/app/home-thumbnails.ts index e8c94e9ce..62eb49cc6 100644 --- a/docs/src/app/home-thumbnails.ts +++ b/docs/src/app/home-thumbnails.ts @@ -119,6 +119,7 @@ export const homeThumbnails = [ ShaderComponent: PaperTexture, image: paperTextureImg, shaderConfig: { ...paperTexturePresets[0].params, scale: 1.05 }, + alwaysLivePreview: false, }, { name: 'fluted glass', @@ -126,6 +127,7 @@ export const homeThumbnails = [ ShaderComponent: FlutedGlass, image: flutedGlassImg, shaderConfig: { ...flutedGlassPresets[0].params, scale: 1.05, distortion: 0.2 }, + alwaysLivePreview: false, }, { name: 'water', @@ -138,6 +140,7 @@ export const homeThumbnails = [ colorBack: '#e0f2ff', image: '/images/image-filters/0018.webp', }, + alwaysLivePreview: false, }, { name: 'image dithering', @@ -145,6 +148,7 @@ export const homeThumbnails = [ ShaderComponent: ImageDithering, image: imageDitheringImg, shaderConfig: { ...imageDitheringPresets[0].params, scale: 1.05 }, + alwaysLivePreview: false, }, { name: 'halftone dots', @@ -156,6 +160,7 @@ export const homeThumbnails = [ image: '/images/image-filters/0018.webp', speed: 0, }, + alwaysLivePreview: false, }, { name: 'halftone cmyk', @@ -169,6 +174,7 @@ export const homeThumbnails = [ softness: 0.5, speed: 0, }, + alwaysLivePreview: false, }, ], }, @@ -187,6 +193,7 @@ export const homeThumbnails = [ suspendWhenProcessingImage: true, image: '/images/logos/diamond.svg', }, + alwaysLivePreview: false, }, { name: 'liquid metal', @@ -199,6 +206,7 @@ export const homeThumbnails = [ suspendWhenProcessingImage: true, image: '/images/logos/diamond.svg', }, + alwaysLivePreview: false, }, { name: 'gem smoke', @@ -214,6 +222,7 @@ export const homeThumbnails = [ suspendWhenProcessingImage: true, image: '/images/logos/diamond.svg', }, + alwaysLivePreview: false, }, ], }, @@ -226,6 +235,7 @@ export const homeThumbnails = [ url: '/mesh-gradient', ShaderComponent: MeshGradient, shaderConfig: { ...meshGradientPresets[0].params }, + alwaysLivePreview: false, }, { name: 'static mesh gradient', @@ -233,6 +243,7 @@ export const homeThumbnails = [ ShaderComponent: StaticMeshGradient, image: staticMeshGradientImg, shaderConfig: { ...staticMeshGradientPresets[0].params }, + alwaysLivePreview: false, }, { name: 'static radial gradient', @@ -240,6 +251,7 @@ export const homeThumbnails = [ ShaderComponent: StaticRadialGradient, image: staticRadialGradientImg, shaderConfig: { ...staticRadialGradientPresets[0].params, radius: 0.65, offsetY: -0.03 }, + alwaysLivePreview: false, }, { name: 'dithering', @@ -250,7 +262,7 @@ export const homeThumbnails = [ shaderConfig: { ...ditheringPresets[0].params }, // This shader doesn't render consistently when // thumbnails are resized, so we live render it - alwaysLivePreview: true, + alwaysLivePreview: false, }, { name: 'grain gradient', @@ -260,7 +272,7 @@ export const homeThumbnails = [ shaderConfig: { ...grainGradientPresets[0].params, speed: 1.5 }, // This shader thumbnails look nowhere near as crisp // when resized, so we live render it to maintain the quality - alwaysLivePreview: true, + alwaysLivePreview: false, }, { name: 'dot orbit', @@ -268,6 +280,7 @@ export const homeThumbnails = [ url: '/dot-orbit', ShaderComponent: DotOrbit, shaderConfig: { ...dotOrbitPresets[0].params, scale: 0.45 }, + alwaysLivePreview: false, }, { name: 'dot grid', @@ -275,6 +288,7 @@ export const homeThumbnails = [ ShaderComponent: DotGrid, image: dotGridImg, shaderConfig: { ...dotGridPresets[0].params, gapX: 24, gapY: 24, size: 1.5, speed: 0 }, + alwaysLivePreview: false, }, { name: 'warp', @@ -282,6 +296,7 @@ export const homeThumbnails = [ ShaderComponent: Warp, image: warpImg, shaderConfig: { ...warpPresets[0].params, scale: 0.6 }, + alwaysLivePreview: false, }, { name: 'spiral', @@ -289,6 +304,7 @@ export const homeThumbnails = [ ShaderComponent: Spiral, image: spiralImg, shaderConfig: { ...spiralPresets[0].params, scale: 0.5, speed: 2 }, + alwaysLivePreview: false, }, { name: 'swirl', @@ -296,6 +312,7 @@ export const homeThumbnails = [ ShaderComponent: Swirl, image: swirlImg, shaderConfig: { ...swirlPresets[0].params }, + alwaysLivePreview: false, }, { name: 'waves', @@ -303,6 +320,7 @@ export const homeThumbnails = [ ShaderComponent: Waves, image: wavesImg, shaderConfig: { ...wavesPresets[0].params, speed: 0, scale: 0.55, spacing: 1.2 }, + alwaysLivePreview: false, }, { name: 'neuro noise', @@ -310,6 +328,7 @@ export const homeThumbnails = [ url: '/neuro-noise', ShaderComponent: NeuroNoise, shaderConfig: { ...neuroNoisePresets[0].params, scale: 0.8 }, + alwaysLivePreview: false, }, { name: 'perlin', @@ -317,6 +336,7 @@ export const homeThumbnails = [ ShaderComponent: PerlinNoise, image: perlinNoiseImg, shaderConfig: { ...perlinNoisePresets[0].params, scale: 0.8, speed: 0.2 }, + alwaysLivePreview: false, }, { name: 'simplex noise', @@ -327,6 +347,7 @@ export const homeThumbnails = [ ...simplexNoisePresets[0].params, scale: 0.4, }, + alwaysLivePreview: false, }, { name: 'voronoi', @@ -334,6 +355,7 @@ export const homeThumbnails = [ ShaderComponent: Voronoi, image: voronoiImg, shaderConfig: { ...voronoiPresets[0].params, scale: 0.35 }, + alwaysLivePreview: false, }, { name: 'pulsing border', @@ -341,6 +363,7 @@ export const homeThumbnails = [ ShaderComponent: PulsingBorder, image: pulsingBorderImg, shaderConfig: { ...pulsingBorderPresets[0].params }, + alwaysLivePreview: false, }, { name: 'metaballs', @@ -348,6 +371,7 @@ export const homeThumbnails = [ url: '/metaballs', ShaderComponent: Metaballs, shaderConfig: { ...metaballsPresets[0].params, scale: 1, count: 8, speed: 1.5 }, + alwaysLivePreview: false, }, { name: 'color panels', @@ -355,6 +379,7 @@ export const homeThumbnails = [ ShaderComponent: ColorPanels, image: colorPanelsImg, shaderConfig: { ...colorPanelsPresets[0].params, scale: 0.75, speed: 2 }, + alwaysLivePreview: false, }, { name: 'smoke ring', @@ -362,6 +387,7 @@ export const homeThumbnails = [ url: '/smoke-ring', ShaderComponent: SmokeRing, shaderConfig: { ...smokeRingPresets[0].params, scale: 0.8, speed: 1 }, + alwaysLivePreview: false, }, { name: 'god rays', @@ -369,6 +395,7 @@ export const homeThumbnails = [ ShaderComponent: GodRays, image: godRaysImg, shaderConfig: { ...godRaysPresets[0].params, offsetY: -0.7, speed: 1.25 }, + alwaysLivePreview: false, }, ], }, diff --git a/docs/src/app/page.tsx b/docs/src/app/page.tsx index b950df4fc..482dd8048 100644 --- a/docs/src/app/page.tsx +++ b/docs/src/app/page.tsx @@ -117,7 +117,7 @@ function ShaderItem({ /> )} - {shaderVisibility !== 'hidden' && shaderConfig.speed !== 0 && ( + {shaderVisibility !== 'hidden' && ( +
{children}
+
+ {name} +
+ + ); +} export default function TestPage() { - return ( -
- - - -
- ); + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); } diff --git a/package.json b/package.json index c038123ba..2d3e43818 100644 --- a/package.json +++ b/package.json @@ -20,10 +20,11 @@ }, "devDependencies": { "@types/bun": "^1.1.11", + "@webgpu/types": "^0.1.69", "esbuild": "^0.24.0", + "glob": "^11.0.1", "nodemon": "^3.1.7", "npm-run-all": "^4.1.5", - "glob": "^11.0.1", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.14", "rimraf": "^6.0.1", diff --git a/packages/shaders/src/shader-mount.ts b/packages/shaders/src/shader-mount.ts index b7c1183df..23b3ad72f 100644 --- a/packages/shaders/src/shader-mount.ts +++ b/packages/shaders/src/shader-mount.ts @@ -5,65 +5,57 @@ const DEFAULT_MAX_PIXEL_COUNT: number = 1920 * 1080 * 4; export class ShaderMount { public parentElement: PaperShaderElement; public canvasElement: HTMLCanvasElement; - private gl: WebGL2RenderingContext; - private program: WebGLProgram | null = null; - private uniformLocations: Record = {}; - /** The fragment shader that we are using */ + + // WebGPU state + private device: GPUDevice | null = null; + private context: GPUCanvasContext | null = null; + private pipeline: GPURenderPipeline | null = null; + private vertexBuffer: GPUBuffer | null = null; + private uniformBuffer: GPUBuffer | null = null; + private uniformData: ArrayBuffer | null = null; + private uniformDataView: DataView | null = null; + private uniformLayout: Map = new Map(); + private uniformBufferSize: number = 0; + private bindGroup: GPUBindGroup | null = null; + private bindGroupLayout: GPUBindGroupLayout | null = null; + private textureBindGroup: GPUBindGroup | null = null; + private textureBindGroupLayout: GPUBindGroupLayout | null = null; + private presentationFormat: GPUTextureFormat = 'bgra8unorm'; + + // Texture state + private textures: Map = new Map(); + private gpuSamplers: Map = new Map(); + private textureUnitMap: Map = new Map(); + private fragmentShader: string; - /** Stores the RAF for the render loop */ private rafId: number | null = null; - /** Time of the last rendered frame */ private lastRenderTime = 0; - /** Total time that we have played any animation, passed as a uniform to the shader for time-based VFX */ private currentFrame = 0; - /** The speed that we progress through animation time (multiplies by delta time every update). Allows negatives to play in reverse. If set to 0, rAF will stop entirely so static shaders have no recurring performance costs */ private speed = 0; - /** Actual speed used that accounts for document visibility (we pause the shader if the tab is hidden) */ private currentSpeed = 0; - /** Uniforms that are provided by the user for the specific shader being mounted (not including uniforms that this Mount adds, like time and resolution) */ private providedUniforms: ShaderMountUniforms; - /** Names of the uniforms that should have mipmaps generated for them */ private mipmaps: string[] = []; - /** Just a sanity check to make sure frames don't run after we're disposed */ private hasBeenDisposed = false; - /** If the resolution of the canvas has changed since the last render */ private resolutionChanged = true; - /** Store textures that are provided by the user */ - private textures: Map = new Map(); private minPixelRatio; private maxPixelCount; private isSafari = isSafari(); private uniformCache: Record = {}; - private textureUnitMap: Map = new Map(); private ownerDocument: Document; + private isReady = false; constructor( - /** The div you'd like to mount the shader to. The shader will match its size. */ parentElement: HTMLElement, fragmentShader: string, uniforms: ShaderMountUniforms, - webGlContextAttributes?: WebGLContextAttributes, - /** The speed of the animation, or 0 to stop it. Supports negative values to play in reverse. */ + /** @deprecated WebGL context attributes param is ignored in the WebGPU renderer. Kept for API compatibility. */ + _contextAttributes?: WebGLContextAttributes, speed = 0, - /** Pass a frame to offset the starting u_time value and give deterministic results*/ frame = 0, - /** - * The minimum pixel ratio to render at, defaults to 2. - * May be reduced to improve performance or increased together with `maxPixelCount` to improve antialiasing. - */ minPixelRatio = 2, - /** - * The maximum amount of physical device pixels to render for the shader, - * by default it's 1920 * 1080 * 2x dpi (per each side) = 8,294,400 pixels of a 4K screen. - * Actual DOM size of the canvas can be larger, it will just lose quality after this. - * - * May be reduced to improve performance or increased to improve quality on high-resolution screens. - */ maxPixelCount: number = DEFAULT_MAX_PIXEL_COUNT, - /** Names of the uniforms that should have mipmaps generated for them */ mipmaps: string[] = [] ) { - // nodeType check instead of `instanceof` to work across document boundaries (iframes, PiP windows) if (parentElement?.nodeType === 1) { this.parentElement = parentElement as PaperShaderElement; } else { @@ -79,92 +71,210 @@ export class ShaderMount { this.ownerDocument.head.prepend(styleElement); } - // Create the canvas element and mount it into the provided element const canvasElement = this.ownerDocument.createElement('canvas'); this.canvasElement = canvasElement; this.parentElement.prepend(canvasElement); this.fragmentShader = fragmentShader; this.providedUniforms = uniforms; this.mipmaps = mipmaps; - // Base our starting animation time on the provided frame value this.currentFrame = frame; this.minPixelRatio = minPixelRatio; this.maxPixelCount = maxPixelCount; - const gl = canvasElement.getContext('webgl2', webGlContextAttributes); - if (!gl) { - throw new Error('Paper Shaders: WebGL is not supported in this browser'); + if (typeof navigator === 'undefined' || !navigator.gpu) { + throw new Error('Paper Shaders: WebGPU is not supported in this browser'); + } + + // Parse uniform layout synchronously so writeUniform works before GPU init completes + const layoutResult = parseUniformLayout(this.fragmentShader); + this.uniformLayout = layoutResult.fields; + this.uniformBufferSize = Math.max(layoutResult.totalSize, 16); + this.uniformData = new ArrayBuffer(this.uniformBufferSize); + this.uniformDataView = new DataView(this.uniformData); + + this.initGPU().catch((err) => { + console.error('Paper Shaders: Failed to initialize WebGPU:', err); + }); + } + + private async initGPU() { + const adapter = await navigator.gpu.requestAdapter(); + if (!adapter) { + throw new Error('Paper Shaders: Failed to get GPU adapter'); + } + + this.device = await adapter.requestDevice(); + this.device.lost.then((info) => { + console.error(`Paper Shaders: WebGPU device lost - ${info.message}`); + this.isReady = false; + }); + + this.context = this.canvasElement.getContext('webgpu') as GPUCanvasContext; + if (!this.context) { + throw new Error('Paper Shaders: Failed to get WebGPU context'); } - this.gl = gl; - this.initProgram(); - this.setupPositionAttribute(); - // Grab the locations of the uniforms in the fragment shader - this.setupUniforms(); - // Put the user provided values into the uniforms + this.presentationFormat = navigator.gpu.getPreferredCanvasFormat(); + this.context.configure({ + device: this.device, + format: this.presentationFormat, + alphaMode: 'premultiplied', + }); + + this.uniformBuffer = this.device.createBuffer({ + size: this.uniformBufferSize, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, + }); + + this.createVertexBuffer(); + + const shaderCode = this.fragmentShader + '\n' + vertexShaderSource; + const module = this.device.createShaderModule({ code: shaderCode }); + + const compilationInfo = await module.getCompilationInfo(); + for (const message of compilationInfo.messages) { + if (message.type === 'error') { + console.error(`Paper Shaders: Shader compilation error: ${message.message} at line ${message.lineNum}`); + } + } + + this.createPipeline(module); + this.createBindGroups(); + // Clear cache so all values are re-applied to the GPU buffer + this.uniformCache = {}; this.setUniformValues(this.providedUniforms); - // Set up the resize observer to handle window resizing and set u_resolution + + this.isReady = true; + this.setupResizeObserver(); - // Set up the visual viewport change listener to handle zoom changes (pinch zoom and classic browser zoom) visualViewport?.addEventListener('resize', this.handleVisualViewportChange); + this.ownerDocument.addEventListener('visibilitychange', this.handleDocumentVisibilityChange); - // Set the animation speed after everything is ready to go - this.setSpeed(speed); + this.setSpeed(this.speed); - // Mark parent element as paper shader mount this.parentElement.setAttribute('data-paper-shader', ''); - - // Add the shaderMount instance to the div mount element to make it easily accessible this.parentElement.paperShaderMount = this; + } - // Listen for document visibility changes to pause the shader when the tab is hidden - this.ownerDocument.addEventListener('visibilitychange', this.handleDocumentVisibilityChange); + private createVertexBuffer() { + const positions = new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]); + this.vertexBuffer = this.device!.createBuffer({ + size: positions.byteLength, + usage: GPUBufferUsage.VERTEX, + mappedAtCreation: true, + }); + new Float32Array(this.vertexBuffer.getMappedRange()).set(positions); + this.vertexBuffer.unmap(); } - private initProgram = () => { - const program = createProgram(this.gl, vertexShaderSource, this.fragmentShader); - if (!program) return; - this.program = program; - }; + private createPipeline(module: GPUShaderModule) { + // Group 0: Uniform buffer + const uniformEntries: GPUBindGroupLayoutEntry[] = [ + { + binding: 0, + visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, + buffer: { type: 'uniform' as const }, + }, + ]; + this.bindGroupLayout = this.device!.createBindGroupLayout({ entries: uniformEntries }); + + // Group 1: Textures (dynamic based on shader) + const textureEntries = this.parseTextureBindings(); + const bindGroupLayouts: GPUBindGroupLayout[] = [this.bindGroupLayout]; + + if (textureEntries.length > 0) { + this.textureBindGroupLayout = this.device!.createBindGroupLayout({ entries: textureEntries }); + bindGroupLayouts.push(this.textureBindGroupLayout); + } - private setupPositionAttribute = () => { - const positionAttributeLocation = this.gl.getAttribLocation(this.program!, 'a_position'); - const positionBuffer = this.gl.createBuffer(); - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, positionBuffer); - const positions = [-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]; - this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW); - this.gl.enableVertexAttribArray(positionAttributeLocation); - this.gl.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0); - }; + const pipelineLayout = this.device!.createPipelineLayout({ bindGroupLayouts }); + + this.pipeline = this.device!.createRenderPipeline({ + layout: pipelineLayout, + vertex: { + module, + entryPoint: 'vs_main', + buffers: [ + { + arrayStride: 8, + attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' as const }], + }, + ], + }, + fragment: { + module, + entryPoint: 'fs_main', + targets: [{ format: this.presentationFormat }], + }, + primitive: { topology: 'triangle-list' as const }, + }); + } - private setupUniforms = () => { - // Create a map to store all uniform locations - const uniformLocations: Record = { - u_time: this.gl.getUniformLocation(this.program!, 'u_time'), - u_pixelRatio: this.gl.getUniformLocation(this.program!, 'u_pixelRatio'), - u_resolution: this.gl.getUniformLocation(this.program!, 'u_resolution'), - }; + private parseTextureBindings(): GPUBindGroupLayoutEntry[] { + const entries: GPUBindGroupLayoutEntry[] = []; + const regex = /@group\(1\)\s*@binding\((\d+)\)\s*var\s+(\w+)\s*:\s*(texture_2d|sampler)/g; + let match; + while ((match = regex.exec(this.fragmentShader)) !== null) { + const binding = parseInt(match[1]!); + const type = match[3]!; + if (type === 'texture_2d') { + entries.push({ + binding, + visibility: GPUShaderStage.FRAGMENT, + texture: { sampleType: 'float' as const }, + }); + } else if (type === 'sampler') { + entries.push({ + binding, + visibility: GPUShaderStage.FRAGMENT, + sampler: { type: 'filtering' as const }, + }); + } + } + return entries; + } - // Add locations for all provided uniforms - Object.entries(this.providedUniforms).forEach(([key, value]) => { - uniformLocations[key] = this.gl.getUniformLocation(this.program!, key); + private createBindGroups() { + this.bindGroup = this.device!.createBindGroup({ + layout: this.bindGroupLayout!, + entries: [{ binding: 0, resource: { buffer: this.uniformBuffer! } }], + }); + } - // For texture uniforms, also look for the aspect ratio uniform - if (value instanceof HTMLImageElement) { - const aspectRatioUniformName = `${key}AspectRatio`; - uniformLocations[aspectRatioUniformName] = this.gl.getUniformLocation(this.program!, aspectRatioUniformName); + private rebuildTextureBindGroup() { + if (!this.textureBindGroupLayout || !this.device) return; + + const entries: GPUBindGroupEntry[] = []; + const regex = /@group\(1\)\s*@binding\((\d+)\)\s*var\s+(\w+)\s*:\s*(texture_2d|sampler)/g; + let match; + while ((match = regex.exec(this.fragmentShader)) !== null) { + const binding = parseInt(match[1]!); + const varName = match[2]!; + const type = match[3]!; + + if (type === 'texture_2d') { + const uniformName = varName.replace('_tex', ''); + const texture = this.textures.get(uniformName); + if (texture) { + entries.push({ binding, resource: texture.createView() }); + } + } else if (type === 'sampler') { + const uniformName = varName.replace('_samp', ''); + const sampler = this.gpuSamplers.get(uniformName); + if (sampler) { + entries.push({ binding, resource: sampler }); + } } - }); + } - this.uniformLocations = uniformLocations; - }; + if (entries.length > 0 && entries.length === this.parseTextureBindings().length) { + this.textureBindGroup = this.device.createBindGroup({ + layout: this.textureBindGroupLayout, + entries, + }); + } + } - /** - * The scale that we should render at. - * - Used to target 2x rendering even on 1x screens for better antialiasing - * - Prevents the virtual resolution from going beyond the maximum resolution - * - Accounts for the page zoom level so we render in physical device pixels rather than CSS pixels - */ private renderScale = 1; private parentWidth = 0; private parentHeight = 0; @@ -194,62 +304,34 @@ export class ShaderMount { this.resizeObserver.observe(this.parentElement); }; - // Visual viewport resize handler, mainly used to react to browser zoom changes. - // Resize observer by itself does not react to pinch zoom, and although it usually - // reacts to classic browser zoom, it's not guaranteed in edge cases. - // Since timing between visual viewport changes and resize observer is complex - // and because we'd like to know the device pixel sizes of elements, we just restart - // the observer to get a guaranteed fresh callback regardless if it would have triggered or not. private handleVisualViewportChange = () => { this.resizeObserver?.disconnect(); this.setupResizeObserver(); - - // In case of debugging timing, from here on: - // - animation frame 1: a paint after the visual viewport resize - // - animation frame 2: a paint after the resize observer has been handled, if it was ever triggered }; - /** Resize handler for when the container div changes size or the max pixel count changes and we want to resize our canvas to match */ private handleResize = () => { - // Aim to render at least as many pixels as physically displayed - // This will overshoot when the user zooms out, but that's acceptable - let targetPixelWidth = 0; let targetPixelHeight = 0; - // If window.devicePixelRatio is below 1, it's safe to say the browser is just zoomed out - // We can use 1 as the minimum value not to upscale it needlessly to meet the min pixel ratio param const dpr = Math.max(1, window.devicePixelRatio); const pinchZoom = visualViewport?.scale ?? 1; if (this.devicePixelsSupported) { - // Use the real pixel size if we know it, plus meet the min pixel ratio requirement and add in pinch zoom const scaleToMeetMinPixelRatio = Math.max(1, this.minPixelRatio / dpr); targetPixelWidth = this.parentDevicePixelWidth * scaleToMeetMinPixelRatio * pinchZoom; targetPixelHeight = this.parentDevicePixelHeight * scaleToMeetMinPixelRatio * pinchZoom; } else { - // Otherwise try to approximate the element size in device pixels using devicePixelRatio. - // (devicePixelRatio is imprecise and element's width/height may be fractional CSS sizes, not real pixels). let targetRenderScale = Math.max(dpr, this.minPixelRatio) * pinchZoom; if (this.isSafari) { - // As of 2025, Safari reports physical devicePixelRatio, but other browsers add the current zoom level: - // https://bugs.webkit.org/show_bug.cgi?id=124862 - // - // In Safari we need to factor in the zoom level manually in order to set the target resolution. - // To avoid sidebars upscaling the target resolution, set a minimum zoom level of 1. - // This will render at higher resolution when zoomed out, but that's fine. - // (We mostly care about maintaining good quality when zoomed in). const zoomLevel = bestGuessBrowserZoom(this.ownerDocument); targetRenderScale *= Math.max(1, zoomLevel); } - // Rounding the client width/height since they may be fractional in CSS layout values targetPixelWidth = Math.round(this.parentWidth) * targetRenderScale; targetPixelHeight = Math.round(this.parentHeight) * targetRenderScale; } - // Prevent the total rendered pixel count from exceeding maxPixelCount const maxPixelCountHeadroom = Math.sqrt(this.maxPixelCount) / Math.sqrt(targetPixelWidth * targetPixelHeight); const scaleToMeetMaxPixelCount = Math.min(1, maxPixelCountHeadroom); const newWidth = Math.round(targetPixelWidth * scaleToMeetMaxPixelCount); @@ -259,54 +341,73 @@ export class ShaderMount { if ( this.canvasElement.width !== newWidth || this.canvasElement.height !== newHeight || - this.renderScale !== newRenderScale // Usually, only render scale changes when the user zooms in/out + this.renderScale !== newRenderScale ) { this.renderScale = newRenderScale; this.canvasElement.width = newWidth; this.canvasElement.height = newHeight; this.resolutionChanged = true; - this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height); - // this is necessary to avoid flashes while resizing (the next scheduled render will set uniforms) this.render(performance.now()); } }; private render = (currentTime: number) => { - if (this.hasBeenDisposed) return; + if (this.hasBeenDisposed || !this.isReady) return; - if (this.program === null) { - console.warn('Tried to render before program or gl was initialized'); + if (!this.pipeline || !this.device || !this.context) { + console.warn('Paper Shaders: Tried to render before GPU was initialized'); return; } - // Calculate the delta time + if (this.canvasElement.width === 0 || this.canvasElement.height === 0) return; + const dt = currentTime - this.lastRenderTime; this.lastRenderTime = currentTime; - // Increase the total animation time by dt * animationSpeed if (this.currentSpeed !== 0) { this.currentFrame += dt * this.currentSpeed; } - // Clear the canvas - this.gl.clear(this.gl.COLOR_BUFFER_BIT); - - // Update uniforms - this.gl.useProgram(this.program); + this.writeUniform('u_time', this.currentFrame * 0.001); - // Update the time uniform - this.gl.uniform1f(this.uniformLocations.u_time!, this.currentFrame * 0.001); - - // If the resolution has changed, we need to update the uniform if (this.resolutionChanged) { - this.gl.uniform2f(this.uniformLocations.u_resolution!, this.gl.canvas.width, this.gl.canvas.height); - this.gl.uniform1f(this.uniformLocations.u_pixelRatio!, this.renderScale); + this.writeUniform('u_resolution', [this.canvasElement.width, this.canvasElement.height]); + this.writeUniform('u_pixelRatio', this.renderScale); this.resolutionChanged = false; } - this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); + this.device.queue.writeBuffer(this.uniformBuffer!, 0, this.uniformData!); + + let textureView: GPUTextureView; + try { + textureView = this.context.getCurrentTexture().createView(); + } catch { + return; + } + + const commandEncoder = this.device.createCommandEncoder(); + const renderPass = commandEncoder.beginRenderPass({ + colorAttachments: [ + { + view: textureView, + clearValue: { r: 0, g: 0, b: 0, a: 0 }, + loadOp: 'clear' as const, + storeOp: 'store' as const, + }, + ], + }); + + renderPass.setPipeline(this.pipeline); + renderPass.setBindGroup(0, this.bindGroup!); + if (this.textureBindGroup) { + renderPass.setBindGroup(1, this.textureBindGroup); + } + renderPass.setVertexBuffer(0, this.vertexBuffer!); + renderPass.draw(6); + renderPass.end(); + + this.device.queue.submit([commandEncoder.finish()]); - // Loop if we're animating if (this.currentSpeed !== 0) { this.requestRender(); } else { @@ -321,174 +422,138 @@ export class ShaderMount { this.rafId = requestAnimationFrame(this.render); }; - /** Creates a texture from an image and sets it into a uniform value */ + private writeUniform(name: string, value: number | number[] | number[][]) { + const field = this.uniformLayout.get(name); + if (!field || !this.uniformDataView) return; + + if (typeof value === 'number') { + this.uniformDataView.setFloat32(field.offset, value, true); + } else if (Array.isArray(value)) { + if (Array.isArray(value[0])) { + const flat = (value as number[][]).flat(); + for (let i = 0; i < flat.length; i++) { + this.uniformDataView.setFloat32(field.offset + i * 4, flat[i]!, true); + } + } else { + const arr = value as number[]; + for (let i = 0; i < arr.length; i++) { + this.uniformDataView.setFloat32(field.offset + i * 4, arr[i]!, true); + } + } + } + } + private setTextureUniform = (uniformName: string, image: HTMLImageElement): void => { if (!image.complete || image.naturalWidth === 0) { throw new Error(`Paper Shaders: image for uniform ${uniformName} must be fully loaded`); } + if (!this.device) return; + if (image.naturalWidth === 0 || image.naturalHeight === 0) return; - // Clean up existing texture if present + // Clean up existing texture const existingTexture = this.textures.get(uniformName); if (existingTexture) { - this.gl.deleteTexture(existingTexture); + existingTexture.destroy(); } - // Get texture unit - if (!this.textureUnitMap.has(uniformName)) { - this.textureUnitMap.set(uniformName, this.textureUnitMap.size); - } - const textureUnit = this.textureUnitMap.get(uniformName)!; - // Activate correct texture unit before creating the texture - this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit); - - // Create and set up the new texture - const texture = this.gl.createTexture(); - this.gl.bindTexture(this.gl.TEXTURE_2D, texture); - - // Set texture parameters - this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE); - this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE); - this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); - this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); - - // Upload image to texture - this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, image); - - // Generate mipmaps if the uniform is in the mipmaps list - if (this.mipmaps.includes(uniformName)) { - this.gl.generateMipmap(this.gl.TEXTURE_2D); - this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR_MIPMAP_LINEAR); - } + const texture = this.device.createTexture({ + size: [image.naturalWidth, image.naturalHeight, 1], + format: 'rgba8unorm', + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); - const error = this.gl.getError(); - if (error !== this.gl.NO_ERROR || texture === null) { - console.error('Paper Shaders: WebGL error when uploading texture:', error); - return; - } + this.device.queue.copyExternalImageToTexture({ source: image }, { texture }, [ + image.naturalWidth, + image.naturalHeight, + ]); - // Store the texture this.textures.set(uniformName, texture); - // Set up texture unit and uniform - const location = this.uniformLocations[uniformName]; - if (location) { - this.gl.uniform1i(location, textureUnit); - - // Calculate and set the aspect ratio uniform - const aspectRatioUniformName = `${uniformName}AspectRatio`; - const aspectRatioLocation = this.uniformLocations[aspectRatioUniformName]; - if (aspectRatioLocation) { - const aspectRatio = image.naturalWidth / image.naturalHeight; - this.gl.uniform1f(aspectRatioLocation, aspectRatio); - } + if (!this.gpuSamplers.has(uniformName)) { + const useMipmaps = this.mipmaps.includes(uniformName); + this.gpuSamplers.set( + uniformName, + this.device.createSampler({ + magFilter: 'linear', + minFilter: 'linear', + mipmapFilter: useMipmaps ? 'linear' : undefined, + addressModeU: 'clamp-to-edge', + addressModeV: 'clamp-to-edge', + }) + ); + } + + // Write aspect ratio uniform + const aspectRatioUniformName = `${uniformName}AspectRatio`; + if (this.uniformLayout.has(aspectRatioUniformName)) { + this.writeUniform(aspectRatioUniformName, image.naturalWidth / image.naturalHeight); } + + this.rebuildTextureBindGroup(); }; - /** Utility: recursive equality test for all the uniforms */ private areUniformValuesEqual = (a: any, b: any): boolean => { if (a === b) return true; if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) { - return a.every((val, i) => this.areUniformValuesEqual(val, (b as any)[i])); + return a.every((val: any, i: number) => this.areUniformValuesEqual(val, (b as any)[i])); } return false; }; - /** Sets the provided uniform values into the WebGL program, can be a partial list of uniforms that have changed */ private setUniformValues = (updatedUniforms: ShaderMountUniforms) => { - this.gl.useProgram(this.program); Object.entries(updatedUniforms).forEach(([key, value]) => { - // Grab the value to use in the uniform cache let cacheValue: ShaderMountUniforms[keyof ShaderMountUniforms] | string = value; if (value instanceof HTMLImageElement) { - // Images use their src for the cache value to save memory cacheValue = `${value.src.slice(0, 200)}|${value.naturalWidth}x${value.naturalHeight}`; } - // Check if the uniform value has changed and, if not, bail early to avoid extra work if (this.areUniformValuesEqual(this.uniformCache[key], cacheValue)) return; - // Update the uniform cache if we are still here this.uniformCache[key] = cacheValue; - const location = this.uniformLocations[key]; - if (!location) { - console.warn(`Uniform location for ${key} not found`); - return; - } - if (value instanceof HTMLImageElement) { - // Texture case, requires a good amount of code so it gets its own function: this.setTextureUniform(key, value); } else if (Array.isArray(value)) { - // Array case let flatArray: number[] | null = null; - let valueLength: number | null = null; - // If it's an array of same-sized arrays, flatten it down so we can set the uniform if (value[0] !== undefined && Array.isArray(value[0])) { const firstChildLength = value[0].length; if (value.every((arr) => (arr as number[]).length === firstChildLength)) { - // Array of same-sized arrays case, flattens the array sets it flatArray = value.flat(); - valueLength = firstChildLength; } else { - console.warn(`All child arrays must be the same length for ${key}`); + console.warn(`Paper Shaders: All child arrays must be the same length for ${key}`); return; } } else { - // Array of primitive values case, supports 2, 3, 4, 9, 16 length arrays flatArray = value as number[]; - valueLength = flatArray.length; } - // Set the uniform based on array length... supports 2, 3, 4, 9, 16 length arrays of primitive values - // or arbitrary length arrays of arrays - switch (valueLength) { - case 2: - this.gl.uniform2fv(location, flatArray); - break; - case 3: - this.gl.uniform3fv(location, flatArray); - break; - case 4: - this.gl.uniform4fv(location, flatArray); - break; - case 9: - this.gl.uniformMatrix3fv(location, false, flatArray); - break; - case 16: - this.gl.uniformMatrix4fv(location, false, flatArray); - break; - default: - console.warn(`Unsupported uniform array length: ${valueLength}`); + if (flatArray) { + this.writeUniform(key, flatArray); } } else if (typeof value === 'number') { - // Number case, supports floats and ints - this.gl.uniform1f(location, value); + this.writeUniform(key, value); } else if (typeof value === 'boolean') { - // Boolean case, supports true and false - this.gl.uniform1i(location, value ? 1 : 0); - } else { - // May happen on the server for SSR when undefined images are passed in - console.warn(`Unsupported uniform type for ${key}: ${typeof value}`); + this.writeUniform(key, value ? 1 : 0); + } else if (value !== undefined) { + console.warn(`Paper Shaders: Unsupported uniform type for ${key}: ${typeof value}`); } }); }; - /** Gets the current total animation time from 0ms */ public getCurrentFrame = (): number => { return this.currentFrame; }; - /** Set a frame to get a deterministic result, frames are literally just milliseconds from zero since the animation started */ public setFrame = (newFrame: number): void => { this.currentFrame = newFrame; this.lastRenderTime = performance.now(); this.render(performance.now()); }; - /** Set an animation speed (or 0 to stop animation) */ public setSpeed = (newSpeed = 1): void => { - // Set the new animation speed this.speed = newSpeed; this.setCurrentSpeed(this.ownerDocument.hidden ? 0 : newSpeed); }; @@ -497,37 +562,29 @@ export class ShaderMount { this.currentSpeed = newSpeed; if (this.rafId === null && newSpeed !== 0) { - // Moving from 0 to animating, kick off a new rAF loop this.lastRenderTime = performance.now(); this.rafId = requestAnimationFrame(this.render); } if (this.rafId !== null && newSpeed === 0) { - // Moving from animating to not animating, cancel the rAF loop cancelAnimationFrame(this.rafId); this.rafId = null; } }; - /** Set the maximum pixel count for the shader, this will limit the number of pixels that will be rendered */ public setMaxPixelCount = (newMaxPixelCount: number = DEFAULT_MAX_PIXEL_COUNT): void => { this.maxPixelCount = newMaxPixelCount; - this.handleResize(); }; - /** Set the minimum pixel ratio for the shader */ public setMinPixelRatio = (newMinPixelRatio: number = 2): void => { this.minPixelRatio = newMinPixelRatio; - this.handleResize(); }; - /** Update the uniforms that are provided by the outside shader, can be a partial set with only the uniforms that have changed */ public setUniforms = (newUniforms: ShaderMountUniforms): void => { this.setUniformValues(newUniforms); this.providedUniforms = { ...this.providedUniforms, ...newUniforms }; - this.render(performance.now()); }; @@ -535,36 +592,23 @@ export class ShaderMount { this.setCurrentSpeed(this.ownerDocument.hidden ? 0 : this.speed); }; - /** Dispose of the shader mount, cleaning up all of the WebGL resources */ public dispose = (): void => { - // Immediately mark as disposed to prevent future renders from leaking in this.hasBeenDisposed = true; - // Cancel the rAF loop if (this.rafId !== null) { cancelAnimationFrame(this.rafId); this.rafId = null; } - if (this.gl && this.program) { - // Clean up all textures - this.textures.forEach((texture) => { - this.gl.deleteTexture(texture); - }); - this.textures.clear(); - - this.gl.deleteProgram(this.program); - this.program = null; - - // Reset the WebGL context - this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null); - this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null); - this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null); - this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null); + this.textures.forEach((texture) => { + texture.destroy(); + }); + this.textures.clear(); - // Clear any errors - this.gl.getError(); - } + this.vertexBuffer?.destroy(); + this.uniformBuffer?.destroy(); + this.pipeline = null; + this.device = null; if (this.resizeObserver) { this.resizeObserver.disconnect(); @@ -574,76 +618,94 @@ export class ShaderMount { visualViewport?.removeEventListener('resize', this.handleVisualViewportChange); this.ownerDocument.removeEventListener('visibilitychange', this.handleDocumentVisibilityChange); - this.uniformLocations = {}; + this.uniformLayout.clear(); - // Remove the shader from the div wrapper element this.canvasElement.remove(); - // Free up the reference to self to enable garbage collection delete this.parentElement.paperShaderMount; }; } -function createShader(gl: WebGL2RenderingContext, type: number, source: string): WebGLShader | null { - const shader = gl.createShader(type); - if (!shader) return null; - - gl.shaderSource(shader, source); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader)); - gl.deleteShader(shader); - return null; - } +// --- Uniform buffer layout parser --- - return shader; +interface UniformFieldInfo { + offset: number; + size: number; + type: string; + arrayCount?: number; + arrayStride?: number; } -function createProgram( - gl: WebGL2RenderingContext, - vertexShaderSource: string, - fragmentShaderSource: string -): WebGLProgram | null { - const format = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT); - const precision = format ? format.precision : null; - // MEDIUM_FLOAT precision can be 10, 16 or 23 bits depending on device; - // Shaders fail on 10 bit (and 16 bit is hard to test) => we force 23-bit by switching to highp - if (precision && precision < 23) { - vertexShaderSource = vertexShaderSource.replace(/precision\s+(lowp|mediump)\s+float;/g, 'precision highp float;'); - fragmentShaderSource = fragmentShaderSource - .replace(/precision\s+(lowp|mediump)\s+float/g, 'precision highp float') - .replace(/\b(uniform|varying|attribute)\s+(lowp|mediump)\s+(\w+)/g, '$1 highp $3'); +function parseUniformLayout(wgslSource: string): { + fields: Map; + totalSize: number; +} { + const fields = new Map(); + const structMatch = wgslSource.match(/struct\s+Uniforms\s*\{([\s\S]*?)\}/); + if (!structMatch) return { fields, totalSize: 0 }; + + const body = structMatch[1]!; + const memberRegex = /(\w+)\s*:\s*(?:array\s*<\s*(\w+)\s*,\s*(\d+)\s*>|(\w+))/g; + let offset = 0; + let match; + + while ((match = memberRegex.exec(body)) !== null) { + const name = match[1]!; + const isArray = match[2] !== undefined; + const elemType = (isArray ? match[2] : match[4])!; + const arrayCount = isArray ? parseInt(match[3]!) : undefined; + const typeInfo = getWgslTypeInfo(elemType); + + if (isArray && arrayCount !== undefined) { + const arrayStride = Math.max(typeInfo.size, 16); + const arrayAlign = Math.max(typeInfo.align, 16); + offset = alignTo(offset, arrayAlign); + fields.set(name, { offset, size: arrayStride * arrayCount, type: elemType, arrayCount, arrayStride }); + offset += arrayStride * arrayCount; + } else { + offset = alignTo(offset, typeInfo.align); + fields.set(name, { offset, size: typeInfo.size, type: elemType }); + offset += typeInfo.size; + } } - const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource); - const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource); - - if (!vertexShader || !fragmentShader) return null; - - const program = gl.createProgram(); - if (!program) return null; + return { fields, totalSize: alignTo(offset, 16) }; +} - gl.attachShader(program, vertexShader); - gl.attachShader(program, fragmentShader); - gl.linkProgram(program); +function alignTo(offset: number, alignment: number): number { + return Math.ceil(offset / alignment) * alignment; +} - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(program)); - gl.deleteProgram(program); - gl.deleteShader(vertexShader); - gl.deleteShader(fragmentShader); - return null; +function getWgslTypeInfo(type: string): { size: number; align: number } { + switch (type) { + case 'f32': + case 'i32': + case 'u32': + return { size: 4, align: 4 }; + case 'vec2f': + case 'vec2i': + case 'vec2u': + return { size: 8, align: 8 }; + case 'vec3f': + case 'vec3i': + case 'vec3u': + return { size: 12, align: 16 }; + case 'vec4f': + case 'vec4i': + case 'vec4u': + return { size: 16, align: 16 }; + case 'mat2x2f': + return { size: 16, align: 8 }; + case 'mat3x3f': + return { size: 48, align: 16 }; + case 'mat4x4f': + return { size: 64, align: 16 }; + default: + return { size: 4, align: 4 }; } - - // Clean up shaders after successful linking - gl.detachShader(program, vertexShader); - gl.detachShader(program, fragmentShader); - gl.deleteShader(vertexShader); - gl.deleteShader(fragmentShader); - - return program; } +// --- Style and types (unchanged) --- + const defaultStyle = `@layer paper-shaders { :where([data-paper-shader]) { isolation: isolate; @@ -663,22 +725,14 @@ const defaultStyle = `@layer paper-shaders { } }`; -/** The parent `
` element that has a ShaderMount available on it */ export interface PaperShaderElement extends HTMLElement { paperShaderMount: ShaderMount | undefined; } -/** Check if an element is a Paper shader element */ export function isPaperShaderElement(element: HTMLElement): element is PaperShaderElement { return 'paperShaderMount' in element; } -/** - * Uniform types that we support to be auto-mapped into the fragment shader - * - * We accept undefined as a convenience for server rendering, when some things may be undefined - * We just skip setting the uniform if it's undefined. This allows the shader mount to still take up space during server rendering - */ export interface ShaderMountUniforms { [key: string]: boolean | number | number[] | number[][] | HTMLImageElement | undefined; } @@ -695,13 +749,6 @@ export type ShaderPreset = { export type ImageShaderPreset = { name: string; - /** - * Params for the shader excluding the image. - * Image is excluded as it isn't considered a preset, - * e.g. when switching between presets it shouldn't switch the image. - * - * While we exclude images from presets they should still be set with a default prop value so the code-first usage of shaders remains great. - */ params: Required>; }; @@ -710,54 +757,24 @@ function isSafari() { return ua.includes('safari') && !ua.includes('chrome') && !ua.includes('android'); } -// Zoom level can be estimated comparing the browser's outerWidth and the viewport width. -// It's nowhere near perfect because it's affected by the presence of browser sidebars, -// like a vertical web inspector or Arc's sidebar. Also, both outerWidth and innerWidth -// are integers, which would almost never give us a perfect ratio at face values. -// -// Still, this is pretty accurate in the vast majority of cases. -// -// Note 1: -// Avoid innerWidth, use visualViewport.width instead. -// - innerWidth is affected by pinch zoom in Safari, but not other browsers. -// visualViewport.width works consistently in all browsers. -// - innerWidth is rounded to integer, but not visualViewport.width. -// - visualViewport.width is affected by hard scrollbars, so they need to be added manually -// -// Note 2: -// Opening a sidebar in Safari like web inspector or bookmarks will throw off the zoom -// level detection and result in a larger target resolution. Not a concern in real-world usage -// with Safari, but we'd rather not try to detect zoom levels with other browsers -// (e.g. Arc always has a sidebar, which affects outerWidth vs visualViewport.width). function bestGuessBrowserZoom(doc: Document) { const viewportScale = visualViewport?.scale ?? 1; const viewportWidth = visualViewport?.width ?? window.innerWidth; const scrollbarWidth = window.innerWidth - doc.documentElement.clientWidth; const innerWidth = viewportScale * viewportWidth + scrollbarWidth; - // outerWidth and innerWidth are always integers so we won't often get the original zoom ratio - // E.g. given a 125% zoom, outerWidth = 1657, innerWidth = 1325, 1657 / 1325 = 1.2505660377 - // We check for common zoom levels and return the closest one if found. - const ratio = outerWidth / innerWidth; const zoomPercentageRounded = Math.round(100 * ratio); - // All zoom levels divisible by 5% if (zoomPercentageRounded % 5 === 0) { return zoomPercentageRounded / 100; } - - // 33% zoom if (zoomPercentageRounded === 33) { return 1 / 3; } - - // 67% zoom if (zoomPercentageRounded === 67) { return 2 / 3; } - - // 133% zoom if (zoomPercentageRounded === 133) { return 4 / 3; } diff --git a/packages/shaders/src/shader-utils.ts b/packages/shaders/src/shader-utils.ts index c816fc716..481eebd51 100644 --- a/packages/shaders/src/shader-utils.ts +++ b/packages/shaders/src/shader-utils.ts @@ -1,132 +1,171 @@ -// language=GLSL +// Shared WGSL definitions used by all shaders + +export const systemUniformFields = ` + u_resolution: vec2f, + u_pixelRatio: f32, + u_time: f32, + u_imageAspectRatio: f32, + u_originX: f32, + u_originY: f32, + u_worldWidth: f32, + u_worldHeight: f32, + u_fit: f32, + u_scale: f32, + u_rotation: f32, + u_offsetX: f32, + u_offsetY: f32, +`; + +export const vertexOutputStruct = ` +struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) v_objectUV: vec2f, + @location(1) v_objectBoxSize: vec2f, + @location(2) v_responsiveUV: vec2f, + @location(3) v_responsiveBoxGivenSize: vec2f, + @location(4) v_patternUV: vec2f, + @location(5) v_patternBoxSize: vec2f, + @location(6) v_imageUV: vec2f, +} +`; + +// WGSL utility functions + export const declarePI = ` -#define TWO_PI 6.28318530718 -#define PI 3.14159265358979323846 +const TWO_PI: f32 = 6.28318530718; +const PI: f32 = 3.14159265358979323846; `; -// language=GLSL export const rotation2 = ` -vec2 rotate(vec2 uv, float th) { - return mat2(cos(th), sin(th), -sin(th), cos(th)) * uv; +fn rotate(uv: vec2f, th: f32) -> vec2f { + return mat2x2f(cos(th), sin(th), -sin(th), cos(th)) * uv; } `; -// language=GLSL export const proceduralHash11 = ` - float hash11(float p) { - p = fract(p * 0.3183099) + 0.1; - p *= p + 19.19; - return fract(p * p); - } +fn hash11(p_in: f32) -> f32 { + var p = fract(p_in * 0.3183099) + 0.1; + p *= p + 19.19; + return fract(p * p); +} `; -// language=GLSL export const proceduralHash21 = ` - float hash21(vec2 p) { - p = fract(p * vec2(0.3183099, 0.3678794)) + 0.1; - p += dot(p, p + 19.19); - return fract(p.x * p.y); - } +fn hash21(p_in: vec2f) -> f32 { + var p = fract(p_in * vec2f(0.3183099, 0.3678794)) + vec2f(0.1); + p += vec2f(dot(p, p + vec2f(19.19))); + return fract(p.x * p.y); +} `; -// language=GLSL export const proceduralHash22 = ` - vec2 hash22(vec2 p) { - p = fract(p * vec2(0.3183099, 0.3678794)) + 0.1; - p += dot(p, p.yx + 19.19); - return fract(vec2(p.x * p.y, p.x + p.y)); - } +fn hash22(p_in: vec2f) -> vec2f { + var p = fract(p_in * vec2f(0.3183099, 0.3678794)) + vec2f(0.1); + p += vec2f(dot(p, p.yx + vec2f(19.19))); + return fract(vec2f(p.x * p.y, p.x + p.y)); +} `; -// language=GLSL export const textureRandomizerR = ` - float randomR(vec2 p) { - vec2 uv = floor(p) / 100. + .5; - return texture(u_noiseTexture, fract(uv)).r; - } +fn randomR(p: vec2f) -> f32 { + let uv = floor(p) / 100.0 + vec2f(0.5); + return textureSampleLevel(u_noiseTexture_tex, u_noiseTexture_samp, fract(uv), 0.0).r; +} `; -// language=GLSL export const textureRandomizerGB = ` - vec2 randomGB(vec2 p) { - vec2 uv = floor(p) / 100. + .5; - return texture(u_noiseTexture, fract(uv)).gb; - } +fn randomGB(p: vec2f) -> vec2f { + let uv = floor(p) / 100.0 + vec2f(0.5); + return textureSampleLevel(u_noiseTexture_tex, u_noiseTexture_samp, fract(uv), 0.0).gb; +} `; -// language=GLSL export const colorBandingFix = ` - color += 1. / 256. * (fract(sin(dot(.014 * gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453123) - .5); + color += vec3f(1.0 / 256.0 * (fract(sin(dot(0.014 * input.position.xy, vec2f(12.9898, 78.233))) * 43758.5453123) - 0.5)); +`; + +export const glslMod = ` +fn glsl_mod_f32(x: f32, y: f32) -> f32 { + return x - y * floor(x / y); +} +fn glsl_mod_vec2(x: vec2f, y: f32) -> vec2f { + return x - vec2f(y) * floor(x / vec2f(y)); +} +fn glsl_mod_vec3(x: vec3f, y: f32) -> vec3f { + return x - vec3f(y) * floor(x / vec3f(y)); +} `; -// language=GLSL export const simplexNoise = ` -vec3 permute(vec3 x) { return mod(((x * 34.0) + 1.0) * x, 289.0); } -float snoise(vec2 v) { - const vec4 C = vec4(0.211324865405187, 0.366025403784439, +fn permute3(x: vec3f) -> vec3f { return glsl_mod_vec3(((x * 34.0) + vec3f(1.0)) * x, 289.0); } +fn snoise(v: vec2f) -> f32 { + let C = vec4f(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439); - vec2 i = floor(v + dot(v, C.yy)); - vec2 x0 = v - i + dot(i, C.xx); - vec2 i1; - i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); - vec4 x12 = x0.xyxy + C.xxzz; - x12.xy -= i1; - i = mod(i, 289.0); - 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); + let i = floor(v + vec2f(dot(v, C.yy))); + let x0 = v - i + vec2f(dot(i, C.xx)); + var i1: vec2f; + if (x0.x > x0.y) { i1 = vec2f(1.0, 0.0); } else { i1 = vec2f(0.0, 1.0); } + var x12 = x0.xyxy + C.xxzz; + x12 = vec4f(x12.xy - i1, x12.zw); + let i_mod = glsl_mod_vec2(i, 289.0); + let p = permute3(permute3(vec3f(i_mod.y) + vec3f(0.0, i1.y, 1.0)) + + vec3f(i_mod.x) + vec3f(0.0, i1.x, 1.0)); + var m = max(vec3f(0.5) - vec3f(dot(x0, x0), dot(x12.xy, x12.xy), + dot(x12.zw, x12.zw)), vec3f(0.0)); m = m * m; m = m * m; - 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; + let x_val = 2.0 * fract(p * C.www) - vec3f(1.0); + let h = abs(x_val) - vec3f(0.5); + let ox = floor(x_val + vec3f(0.5)); + let a0 = x_val - ox; m *= 1.79284291400159 - 0.85373472095314 * (a0 * a0 + h * h); - vec3 g; - g.x = a0.x * x0.x + h.x * x0.y; - g.yz = a0.yz * x12.xz + h.yz * x12.yw; + let g = vec3f( + a0.x * x0.x + h.x * x0.y, + a0.y * x12.x + h.y * x12.y, + a0.z * x12.z + h.z * x12.w + ); return 130.0 * dot(m, g); } `; -// language=GLSL export const fiberNoise = ` -float fiberRandom(vec2 p) { - vec2 uv = floor(p) / 100.; - return texture(u_noiseTexture, fract(uv)).b; +fn fiberRandom(p: vec2f) -> f32 { + let uv = floor(p) / 100.0; + return textureSampleLevel(u_noiseTexture_tex, u_noiseTexture_samp, fract(uv), 0.0).b; } -float fiberValueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = fiberRandom(i); - float b = fiberRandom(i + vec2(1.0, 0.0)); - float c = fiberRandom(i + vec2(0.0, 1.0)); - float d = fiberRandom(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); +fn fiberValueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = fiberRandom(i); + let b = fiberRandom(i + vec2f(1.0, 0.0)); + let c = fiberRandom(i + vec2f(0.0, 1.0)); + let d = fiberRandom(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } -float fiberNoiseFbm(in vec2 n, vec2 seedOffset) { - float total = 0.0, amplitude = 1.; - for (int i = 0; i < 4; i++) { - n = rotate(n, .7); +fn fiberNoiseFbm(n_in: vec2f, seedOffset: vec2f) -> f32 { + var n = n_in; + var total: f32 = 0.0; + var amplitude: f32 = 1.0; + for (var i: i32 = 0; i < 4; i++) { + n = rotate(n, 0.7); total += fiberValueNoise(n + seedOffset) * amplitude; - n *= 2.; + n *= 2.0; amplitude *= 0.6; } return total; } -float fiberNoise(vec2 uv, vec2 seedOffset) { - float epsilon = 0.001; - float n1 = fiberNoiseFbm(uv + vec2(epsilon, 0.0), seedOffset); - float n2 = fiberNoiseFbm(uv - vec2(epsilon, 0.0), seedOffset); - float n3 = fiberNoiseFbm(uv + vec2(0.0, epsilon), seedOffset); - float n4 = fiberNoiseFbm(uv - vec2(0.0, epsilon), seedOffset); - return length(vec2(n1 - n2, n3 - n4)) / (2.0 * epsilon); +fn fiberNoise(uv: vec2f, seedOffset: vec2f) -> f32 { + let epsilon: f32 = 0.001; + let n1 = fiberNoiseFbm(uv + vec2f(epsilon, 0.0), seedOffset); + let n2 = fiberNoiseFbm(uv - vec2f(epsilon, 0.0), seedOffset); + let n3 = fiberNoiseFbm(uv + vec2f(0.0, epsilon), seedOffset); + let n4 = fiberNoiseFbm(uv - vec2f(0.0, epsilon), seedOffset); + return length(vec2f(n1 - n2, n3 - n4)) / (2.0 * epsilon); } `; diff --git a/packages/shaders/src/shaders/color-panels.ts b/packages/shaders/src/shaders/color-panels.ts index db765cfba..5b1be2a5c 100644 --- a/packages/shaders/src/shaders/color-panels.ts +++ b/packages/shaders/src/shaders/color-panels.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, colorBandingFix } from '../shader-utils.js'; export const colorPanelsMeta = { maxColorCount: 7, @@ -44,111 +44,107 @@ export const colorPanelsMeta = { * */ -// language=GLSL -export const colorPanelsFragmentShader: string = `#version 300 es -precision lowp float; - -uniform float u_time; -uniform mediump float u_scale; - -uniform vec4 u_colors[${ colorPanelsMeta.maxColorCount }]; -uniform float u_colorsCount; -uniform vec4 u_colorBack; -uniform float u_density; -uniform float u_angle1; -uniform float u_angle2; -uniform float u_length; -uniform bool u_edges; -uniform float u_blur; -uniform float u_fadeIn; -uniform float u_fadeOut; -uniform float u_gradient; - -in vec2 v_objectUV; +export const colorPanelsFragmentShader: string = ` +struct Uniforms { + ${ systemUniformFields } + u_colorsCount: f32, + u_density: f32, + u_angle1: f32, + u_angle2: f32, + u_length: f32, + u_edges: f32, + u_blur: f32, + u_fadeIn: f32, + u_fadeOut: f32, + u_gradient: f32, + u_colorBack: vec4f, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; -out vec4 fragColor; +${ vertexOutputStruct } ${ declarePI } -const float zLimit = .5; +const zLimit: f32 = 0.5; -vec2 getPanel(float angle, vec2 uv, float invLength, float aa) { - float sinA = sin(angle); - float cosA = cos(angle); +fn getPanel(angle: f32, uv: vec2f, invLength: f32, aa: f32) -> vec2f { + let sinA = sin(angle); + let cosA = cos(angle); - float denom = sinA - uv.y * cosA; - if (abs(denom) < .01) return vec2(0.); + let denom = sinA - uv.y * cosA; + if (abs(denom) < 0.01) { return vec2f(0.0); } - float z = uv.y / denom; + let z = uv.y / denom; - if (z <= 0. || z > zLimit) return vec2(0.); + if (z <= 0.0 || z > zLimit) { return vec2f(0.0); } - float zRatio = z / zLimit; - float panelMap = 1. - zRatio; - float x = uv.x * (cosA * z + 1.) * invLength; + let zRatio = z / zLimit; + var panelMap = 1.0 - zRatio; + let x = uv.x * (cosA * z + 1.0) * invLength; - float zOffset = zRatio - .5; - float left = -.5 + zOffset * u_angle1; - float right = .5 - zOffset * u_angle2; - float blurX = aa + 2. * panelMap * u_blur; + let zOffset = zRatio - 0.5; + let left = -0.5 + zOffset * u.u_angle1; + let right = 0.5 - zOffset * u.u_angle2; + let blurX = aa + 2.0 * panelMap * u.u_blur; - float leftEdge1 = left - blurX; - float leftEdge2 = left + .25 * blurX; - float rightEdge1 = right - .25 * blurX; - float rightEdge2 = right + blurX; + let leftEdge1 = left - blurX; + let leftEdge2 = left + 0.25 * blurX; + let rightEdge1 = right - 0.25 * blurX; + let rightEdge2 = right + blurX; - float panel = smoothstep(leftEdge1, leftEdge2, x) * (1.0 - smoothstep(rightEdge1, rightEdge2, x)); - panel *= mix(0., panel, smoothstep(0., .01 / max(u_scale, 1e-6), panelMap)); + var panel = smoothstep(leftEdge1, leftEdge2, x) * (1.0 - smoothstep(rightEdge1, rightEdge2, x)); + panel *= mix(0.0, panel, smoothstep(0.0, 0.01 / max(u.u_scale, 1e-6), panelMap)); - float midScreen = abs(sinA); - if (u_edges == true) { - panelMap = mix(.99, panelMap, panel * clamp(panelMap / (.15 * (1. - pow(midScreen, .1))), 0.0, 1.0)); - } else if (midScreen < .07) { - panel *= (midScreen * 15.); + let midScreen = abs(sinA); + if (u.u_edges > 0.5) { + panelMap = mix(0.99, panelMap, panel * clamp(panelMap / (0.15 * (1.0 - pow(midScreen, 0.1))), 0.0, 1.0)); + } else if (midScreen < 0.07) { + panel *= (midScreen * 15.0); } - return vec2(panel, panelMap); + return vec2f(panel, panelMap); } -vec4 blendColor(vec4 colorA, float panelMask, float panelMap) { - float fade = 1. - smoothstep(.97 - .97 * u_fadeIn, 1., panelMap); +fn blendColor(colorA: vec4f, panelMask: f32, panelMap: f32) -> vec4f { + var fade = 1.0 - smoothstep(0.97 - 0.97 * u.u_fadeIn, 1.0, panelMap); - fade *= smoothstep(-.2 * (1. - u_fadeOut), u_fadeOut, panelMap); + fade *= smoothstep(-0.2 * (1.0 - u.u_fadeOut), u.u_fadeOut, panelMap); - vec3 blendedRGB = mix(vec3(0.), colorA.rgb, fade); - float blendedAlpha = mix(0., colorA.a, fade); + let blendedRGB = mix(vec3f(0.0), colorA.rgb, fade); + let blendedAlpha = mix(0.0, colorA.a, fade); - return vec4(blendedRGB, blendedAlpha) * panelMask; + return vec4f(blendedRGB, blendedAlpha) * panelMask; } -void main() { - vec2 uv = v_objectUV; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var uv = input.v_objectUV; uv *= 1.25; - float t = .02 * u_time; + var t = 0.02 * u.u_time; t = fract(t); - bool reverseTime = (t < 0.5); + let reverseTime = (t < 0.5); - vec3 color = vec3(0.); - float opacity = 0.; + var color = vec3f(0.0); + var opacity: f32 = 0.0; - float aa = .005 / u_scale; - int colorsCount = int(u_colorsCount); + let aa = 0.005 / u.u_scale; + let colorsCount = i32(u.u_colorsCount); - vec4 premultipliedColors[${ colorPanelsMeta.maxColorCount }]; - for (int i = 0; i < ${ colorPanelsMeta.maxColorCount }; i++) { - if (i >= colorsCount) break; - vec4 c = u_colors[i]; - c.rgb *= c.a; + var premultipliedColors: array; + for (var i: i32 = 0; i < ${ colorPanelsMeta.maxColorCount }; i++) { + if (i >= colorsCount) { break; } + var c = u.u_colors[i]; + c = vec4f(c.rgb * c.a, c.a); premultipliedColors[i] = c; } - float invLength = 1.5 / max(u_length, .001); + let invLength = 1.5 / max(u.u_length, 0.001); - float totalColorWeight = 0.; - int panelsNumber = 12; + var totalColorWeight: f32 = 0.0; + var panelsNumber: i32 = 12; - float densityNormalizer = 1.; + var densityNormalizer: f32 = 1.0; if (colorsCount == 4) { panelsNumber = 16; densityNormalizer = 1.34; @@ -160,98 +156,98 @@ void main() { densityNormalizer = 1.17; } - float fPanelsNumber = float(panelsNumber); + let fPanelsNumber = f32(panelsNumber); - float totalPanelsShape = 0.; - float panelGrad = 1. - clamp(u_gradient, 0., 1.); + var totalPanelsShape: f32 = 0.0; + let panelGrad = 1.0 - clamp(u.u_gradient, 0.0, 1.0); - for (int set = 0; set < 2; set++) { - bool isForward = (set == 0 && !reverseTime) || (set == 1 && reverseTime); - if (!isForward) continue; + for (var setIdx: i32 = 0; setIdx < 2; setIdx++) { + let isForward = (setIdx == 0 && !reverseTime) || (setIdx == 1 && reverseTime); + if (!isForward) { continue; } - for (int i = 0; i <= 20; i++) { - if (i >= panelsNumber) break; + for (var i: i32 = 0; i <= 20; i++) { + if (i >= panelsNumber) { break; } - int idx = panelsNumber - 1 - i; + let idx = panelsNumber - 1 - i; - float offset = float(idx) / fPanelsNumber; - if (set == 1) { - offset += .5; + var offset = f32(idx) / fPanelsNumber; + if (setIdx == 1) { + offset += 0.5; } - float densityFract = densityNormalizer * fract(t + offset); - float angleNorm = densityFract / u_density; - if (densityFract >= .5 || angleNorm >= .3) continue; + let densityFract = densityNormalizer * fract(t + offset); + var angleNorm = densityFract / u.u_density; + if (densityFract >= 0.5 || angleNorm >= 0.3) { continue; } - float smoothDensity = clamp((.5 - densityFract) / .1, 0., 1.) * clamp(densityFract / .01, 0., 1.); - float smoothAngle = clamp((.3 - angleNorm) / .05, 0., 1.); - if (smoothDensity * smoothAngle < .001) continue; + let smoothDensity = clamp((0.5 - densityFract) / 0.1, 0.0, 1.0) * clamp(densityFract / 0.01, 0.0, 1.0); + let smoothAngle = clamp((0.3 - angleNorm) / 0.05, 0.0, 1.0); + if (smoothDensity * smoothAngle < 0.001) { continue; } - if (angleNorm > .5) { + if (angleNorm > 0.5) { angleNorm = 0.5; } - vec2 panel = getPanel(angleNorm * TWO_PI + PI, uv, invLength, aa); - if (panel[0] <= .001) continue; - float panelMask = panel[0] * smoothDensity * smoothAngle; - float panelMap = panel[1]; + let panel = getPanel(angleNorm * TWO_PI + PI, uv, invLength, aa); + if (panel[0] <= 0.001) { continue; } + let panelMask = panel[0] * smoothDensity * smoothAngle; + let panelMap = panel[1]; - int colorIdx = idx % colorsCount; - int nextColorIdx = (idx + 1) % colorsCount; + let colorIdx = idx % colorsCount; + let nextColorIdx = (idx + 1) % colorsCount; - vec4 colorA = premultipliedColors[colorIdx]; - vec4 colorB = premultipliedColors[nextColorIdx]; + var colorA = premultipliedColors[colorIdx]; + let colorB = premultipliedColors[nextColorIdx]; - colorA = mix(colorA, colorB, max(0., smoothstep(.0, .45, panelMap) - panelGrad)); - vec4 blended = blendColor(colorA, panelMask, panelMap); - color = blended.rgb + color * (1. - blended.a); - opacity = blended.a + opacity * (1. - blended.a); + colorA = mix(colorA, colorB, max(0.0, smoothstep(0.0, 0.45, panelMap) - panelGrad)); + let blended = blendColor(colorA, panelMask, panelMap); + color = blended.rgb + color * (1.0 - blended.a); + opacity = blended.a + opacity * (1.0 - blended.a); } - for (int i = 0; i <= 20; i++) { - if (i >= panelsNumber) break; + for (var i: i32 = 0; i <= 20; i++) { + if (i >= panelsNumber) { break; } - int idx = panelsNumber - 1 - i; + let idx = panelsNumber - 1 - i; - float offset = float(idx) / fPanelsNumber; - if (set == 0) { - offset += .5; + var offset = f32(idx) / fPanelsNumber; + if (setIdx == 0) { + offset += 0.5; } - float densityFract = densityNormalizer * fract(-t + offset); - float angleNorm = -densityFract / u_density; - if (densityFract >= .5 || angleNorm < -.3) continue; + let densityFract = densityNormalizer * fract(-t + offset); + let angleNorm = -densityFract / u.u_density; + if (densityFract >= 0.5 || angleNorm < -0.3) { continue; } - float smoothDensity = clamp((.5 - densityFract) / .1, 0., 1.) * clamp(densityFract / .01, 0., 1.); - float smoothAngle = clamp((angleNorm + .3) / .05, 0., 1.); - if (smoothDensity * smoothAngle < .001) continue; + let smoothDensity = clamp((0.5 - densityFract) / 0.1, 0.0, 1.0) * clamp(densityFract / 0.01, 0.0, 1.0); + let smoothAngle = clamp((angleNorm + 0.3) / 0.05, 0.0, 1.0); + if (smoothDensity * smoothAngle < 0.001) { continue; } - vec2 panel = getPanel(angleNorm * TWO_PI + PI, uv, invLength, aa); - float panelMask = panel[0] * smoothDensity * smoothAngle; - if (panelMask <= .001) continue; - float panelMap = panel[1]; + let panel = getPanel(angleNorm * TWO_PI + PI, uv, invLength, aa); + let panelMask = panel[0] * smoothDensity * smoothAngle; + if (panelMask <= 0.001) { continue; } + let panelMap = panel[1]; - int colorIdx = (colorsCount - (idx % colorsCount)) % colorsCount; - if (colorIdx < 0) colorIdx += colorsCount; - int nextColorIdx = (colorIdx + 1) % colorsCount; + var colorIdx = (colorsCount - (idx % colorsCount)) % colorsCount; + if (colorIdx < 0) { colorIdx += colorsCount; } + let nextColorIdx = (colorIdx + 1) % colorsCount; - vec4 colorA = premultipliedColors[colorIdx]; - vec4 colorB = premultipliedColors[nextColorIdx]; + var colorA = premultipliedColors[colorIdx]; + let colorB = premultipliedColors[nextColorIdx]; - colorA = mix(colorA, colorB, max(0., smoothstep(.0, .45, panelMap) - panelGrad)); - vec4 blended = blendColor(colorA, panelMask, panelMap); - color = blended.rgb + color * (1. - blended.a); - opacity = blended.a + opacity * (1. - blended.a); + colorA = mix(colorA, colorB, max(0.0, smoothstep(0.0, 0.45, panelMap) - panelGrad)); + let blended = blendColor(colorA, panelMask, panelMap); + color = blended.rgb + color * (1.0 - blended.a); + opacity = blended.a + opacity * (1.0 - blended.a); } } - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; color = color + bgColor * (1.0 - opacity); - opacity = opacity + u_colorBack.a * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); ${ colorBandingFix } - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/dithering.ts b/packages/shaders/src/shaders/dithering.ts index 9c9707ab4..b7f9ba6a8 100644 --- a/packages/shaders/src/shaders/dithering.ts +++ b/packages/shaders/src/shaders/dithering.ts @@ -3,13 +3,13 @@ import { type ShaderSizingParams, type ShaderSizingUniforms, } from '../shader-sizing.js'; -import { simplexNoise, declarePI, proceduralHash11, proceduralHash21 } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, simplexNoise, declarePI, proceduralHash11, proceduralHash21, glslMod } from '../shader-utils.js'; /** * Animated 2-color dithering over multiple pattern sources (noise, warp, dots, waves, ripple, swirl, sphere). * * SIZING NOTE: This shader performs sizing in the fragment shader (not vertex shader) to keep - * u_pxSize in consistent actual pixels. The pixel grid is computed from gl_FragCoord before any + * u_pxSize in consistent actual pixels. The pixel grid is computed from input.position before any * transforms, so scaling/rotating only affects the underlying pattern shape. * No vertex shader outputs (v_objectUV, v_patternUV, etc.) are used. * @@ -34,247 +34,226 @@ import { simplexNoise, declarePI, proceduralHash11, proceduralHash21 } from '../ * * */ -// language=GLSL -export const ditheringFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform vec2 u_resolution; -uniform float u_pixelRatio; -uniform float u_originX; -uniform float u_originY; -uniform float u_worldWidth; -uniform float u_worldHeight; -uniform float u_fit; -uniform float u_scale; -uniform float u_rotation; -uniform float u_offsetX; -uniform float u_offsetY; - -uniform float u_pxSize; -uniform vec4 u_colorBack; -uniform vec4 u_colorFront; -uniform float u_shape; -uniform float u_type; - -out vec4 fragColor; - -${ simplexNoise } -${ declarePI } -${ proceduralHash11 } -${ proceduralHash21 } - -float getSimplexNoise(vec2 uv, float t) { - float noise = .5 * snoise(uv - vec2(0., .3 * t)); - noise += .5 * snoise(2. * uv + vec2(0., .32 * t)); - - return noise; +export const ditheringFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_pxSize: f32, + u_colorBack: vec4f, + u_colorFront: vec4f, + u_shape: f32, + u_type: f32, } +@group(0) @binding(0) var u: Uniforms; -const int bayer2x2[4] = int[4](0, 2, 3, 1); -const int bayer4x4[16] = int[16]( -0, 8, 2, 10, -12, 4, 14, 6, -3, 11, 1, 9, -15, 7, 13, 5 +${vertexOutputStruct} + +${glslMod} +${simplexNoise} +${declarePI} +${proceduralHash11} +${proceduralHash21} + +fn getSimplexNoise(uv: vec2f, t: f32) -> f32 { + var noiseVal = 0.5 * snoise(uv - vec2f(0.0, 0.3 * t)); + noiseVal += 0.5 * snoise(2.0 * uv + vec2f(0.0, 0.32 * t)); + return noiseVal; +} + +const bayer2x2 = array(0, 2, 3, 1); +const bayer4x4 = array( + 0, 8, 2, 10, + 12, 4, 14, 6, + 3, 11, 1, 9, + 15, 7, 13, 5 ); -const int bayer8x8[64] = int[64]( -0, 32, 8, 40, 2, 34, 10, 42, -48, 16, 56, 24, 50, 18, 58, 26, -12, 44, 4, 36, 14, 46, 6, 38, -60, 28, 52, 20, 62, 30, 54, 22, -3, 35, 11, 43, 1, 33, 9, 41, -51, 19, 59, 27, 49, 17, 57, 25, -15, 47, 7, 39, 13, 45, 5, 37, -63, 31, 55, 23, 61, 29, 53, 21 +const bayer8x8 = array( + 0, 32, 8, 40, 2, 34, 10, 42, + 48, 16, 56, 24, 50, 18, 58, 26, + 12, 44, 4, 36, 14, 46, 6, 38, + 60, 28, 52, 20, 62, 30, 54, 22, + 3, 35, 11, 43, 1, 33, 9, 41, + 51, 19, 59, 27, 49, 17, 57, 25, + 15, 47, 7, 39, 13, 45, 5, 37, + 63, 31, 55, 23, 61, 29, 53, 21 ); -float getBayerValue(vec2 uv, int size) { - ivec2 pos = ivec2(fract(uv / float(size)) * float(size)); - int index = pos.y * size + pos.x; +fn getBayerValue(uv: vec2f, size: i32) -> f32 { + let pos = vec2i(fract(uv / f32(size)) * f32(size)); + let index = pos.y * size + pos.x; if (size == 2) { - return float(bayer2x2[index]) / 4.0; + return f32(bayer2x2[index]) / 4.0; } else if (size == 4) { - return float(bayer4x4[index]) / 16.0; + return f32(bayer4x4[index]) / 16.0; } else if (size == 8) { - return float(bayer8x8[index]) / 64.0; + return f32(bayer8x8[index]) / 64.0; } return 0.0; } +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + let t = 0.5 * u.u_time; -void main() { - float t = .5 * u_time; - - float pxSize = u_pxSize * u_pixelRatio; - vec2 pxSizeUV = gl_FragCoord.xy - .5 * u_resolution; + let pxSize = u.u_pxSize * u.u_pixelRatio; + var pxSizeUV = input.position.xy - 0.5 * u.u_resolution; pxSizeUV /= pxSize; - vec2 canvasPixelizedUV = (floor(pxSizeUV) + .5) * pxSize; - vec2 normalizedUV = canvasPixelizedUV / u_resolution; - - vec2 ditheringNoiseUV = canvasPixelizedUV; - vec2 shapeUV = normalizedUV; - - vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5); - vec2 givenBoxSize = vec2(u_worldWidth, u_worldHeight); - givenBoxSize = max(givenBoxSize, vec2(1.)) * u_pixelRatio; - float r = u_rotation * PI / 180.; - mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r)); - vec2 graphicOffset = vec2(-u_offsetX, u_offsetY); - - float patternBoxRatio = givenBoxSize.x / givenBoxSize.y; - vec2 boxSize = vec2( - (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x, - (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y + let canvasPixelizedUV = (floor(pxSizeUV) + vec2f(0.5)) * pxSize; + let normalizedUV = canvasPixelizedUV / u.u_resolution; + + let ditheringNoiseUV = canvasPixelizedUV; + var shapeUV = normalizedUV; + + let boxOrigin = vec2f(0.5 - u.u_originX, u.u_originY - 0.5); + var givenBoxSize = vec2f(u.u_worldWidth, u.u_worldHeight); + givenBoxSize = max(givenBoxSize, vec2f(1.0)) * u.u_pixelRatio; + let r = u.u_rotation * PI / 180.0; + let graphicRotation = mat2x2f(cos(r), sin(r), -sin(r), cos(r)); + + let patternBoxRatio = givenBoxSize.x / givenBoxSize.y; + let boxSize = vec2f( + select(givenBoxSize.x, u.u_resolution.x, u.u_worldWidth == 0.0), + select(givenBoxSize.y, u.u_resolution.y, u.u_worldHeight == 0.0) ); - - if (u_shape > 3.5) { - vec2 objectBoxSize = vec2(0.); + + if (u.u_shape > 3.5) { + var objectBoxSize = vec2f(0.0); // fit = none objectBoxSize.x = min(boxSize.x, boxSize.y); - if (u_fit == 1.) { // fit = contain - objectBoxSize.x = min(u_resolution.x, u_resolution.y); - } else if (u_fit == 2.) { // fit = cover - objectBoxSize.x = max(u_resolution.x, u_resolution.y); + if (u.u_fit == 1.0) { // fit = contain + objectBoxSize.x = min(u.u_resolution.x, u.u_resolution.y); + } else if (u.u_fit == 2.0) { // fit = cover + objectBoxSize.x = max(u.u_resolution.x, u.u_resolution.y); } objectBoxSize.y = objectBoxSize.x; - vec2 objectWorldScale = u_resolution.xy / objectBoxSize; + let objectWorldScale = u.u_resolution.xy / objectBoxSize; shapeUV *= objectWorldScale; - shapeUV += boxOrigin * (objectWorldScale - 1.); - shapeUV += vec2(-u_offsetX, u_offsetY); - shapeUV /= u_scale; + shapeUV += boxOrigin * (objectWorldScale - vec2f(1.0)); + shapeUV += vec2f(-u.u_offsetX, u.u_offsetY); + shapeUV /= u.u_scale; shapeUV = graphicRotation * shapeUV; } else { - vec2 patternBoxSize = vec2(0.); + var patternBoxSize = vec2f(0.0); // fit = none patternBoxSize.x = patternBoxRatio * min(boxSize.x / patternBoxRatio, boxSize.y); - float patternWorldNoFitBoxWidth = patternBoxSize.x; - if (u_fit == 1.) { // fit = contain - patternBoxSize.x = patternBoxRatio * min(u_resolution.x / patternBoxRatio, u_resolution.y); - } else if (u_fit == 2.) { // fit = cover - patternBoxSize.x = patternBoxRatio * max(u_resolution.x / patternBoxRatio, u_resolution.y); + let patternWorldNoFitBoxWidth = patternBoxSize.x; + if (u.u_fit == 1.0) { // fit = contain + patternBoxSize.x = patternBoxRatio * min(u.u_resolution.x / patternBoxRatio, u.u_resolution.y); + } else if (u.u_fit == 2.0) { // fit = cover + patternBoxSize.x = patternBoxRatio * max(u.u_resolution.x / patternBoxRatio, u.u_resolution.y); } patternBoxSize.y = patternBoxSize.x / patternBoxRatio; - vec2 patternWorldScale = u_resolution.xy / patternBoxSize; + let patternWorldScale = u.u_resolution.xy / patternBoxSize; - shapeUV += vec2(-u_offsetX, u_offsetY) / patternWorldScale; + shapeUV += vec2f(-u.u_offsetX, u.u_offsetY) / patternWorldScale; shapeUV += boxOrigin; shapeUV -= boxOrigin / patternWorldScale; - shapeUV *= u_resolution.xy; - shapeUV /= u_pixelRatio; - if (u_fit > 0.) { + shapeUV *= u.u_resolution.xy; + shapeUV /= u.u_pixelRatio; + if (u.u_fit > 0.0) { shapeUV *= (patternWorldNoFitBoxWidth / patternBoxSize.x); } - shapeUV /= u_scale; + shapeUV /= u.u_scale; shapeUV = graphicRotation * shapeUV; shapeUV += boxOrigin / patternWorldScale; shapeUV -= boxOrigin; - shapeUV += .5; + shapeUV += vec2f(0.5); } - float shape = 0.; - if (u_shape < 1.5) { + var shape: f32 = 0.0; + if (u.u_shape < 1.5) { // Simplex noise - shapeUV *= .001; + shapeUV *= 0.001; shape = 0.5 + 0.5 * getSimplexNoise(shapeUV, t); shape = smoothstep(0.3, 0.9, shape); - } else if (u_shape < 2.5) { + } else if (u.u_shape < 2.5) { // Warp - shapeUV *= .003; + shapeUV *= 0.003; - for (float i = 1.0; i < 6.0; i++) { + for (var i: f32 = 1.0; i < 6.0; i += 1.0) { shapeUV.x += 0.6 / i * cos(i * 2.5 * shapeUV.y + t); shapeUV.y += 0.6 / i * cos(i * 1.5 * shapeUV.x + t); } - shape = .15 / max(0.001, abs(sin(t - shapeUV.y - shapeUV.x))); - shape = smoothstep(0.02, 1., shape); + shape = 0.15 / max(0.001, abs(sin(t - shapeUV.y - shapeUV.x))); + shape = smoothstep(0.02, 1.0, shape); - } else if (u_shape < 3.5) { + } else if (u.u_shape < 3.5) { // Dots - shapeUV *= .05; + shapeUV *= 0.05; - float stripeIdx = floor(2. * shapeUV.x / TWO_PI); - float rand = hash11(stripeIdx * 10.); - rand = sign(rand - .5) * pow(.1 + abs(rand), .4); - shape = sin(shapeUV.x) * cos(shapeUV.y - 5. * rand * t); - shape = pow(abs(shape), 6.); + let stripeIdx = floor(2.0 * shapeUV.x / TWO_PI); + var rand = hash11(stripeIdx * 10.0); + rand = sign(rand - 0.5) * pow(0.1 + abs(rand), 0.4); + shape = sin(shapeUV.x) * cos(shapeUV.y - 5.0 * rand * t); + shape = pow(abs(shape), 6.0); - } else if (u_shape < 4.5) { + } else if (u.u_shape < 4.5) { // Sine wave - shapeUV *= 4.; + shapeUV *= 4.0; - float wave = cos(.5 * shapeUV.x - 2. * t) * sin(1.5 * shapeUV.x + t) * (.75 + .25 * cos(3. * t)); - shape = 1. - smoothstep(-1., 1., shapeUV.y + wave); + let wave = cos(0.5 * shapeUV.x - 2.0 * t) * sin(1.5 * shapeUV.x + t) * (0.75 + 0.25 * cos(3.0 * t)); + shape = 1.0 - smoothstep(-1.0, 1.0, shapeUV.y + wave); - } else if (u_shape < 5.5) { + } else if (u.u_shape < 5.5) { // Ripple - float dist = length(shapeUV); - float waves = sin(pow(dist, 1.7) * 7. - 3. * t) * .5 + .5; + let dist = length(shapeUV); + let waves = sin(pow(dist, 1.7) * 7.0 - 3.0 * t) * 0.5 + 0.5; shape = waves; - } else if (u_shape < 6.5) { + } else if (u.u_shape < 6.5) { // Swirl - float l = length(shapeUV); - float angle = 6. * atan(shapeUV.y, shapeUV.x) + 4. * t; - float twist = 1.2; - float offset = 1. / pow(max(l, 1e-6), twist) + angle / TWO_PI; - float mid = smoothstep(0., 1., pow(l, twist)); - shape = mix(0., fract(offset), mid); + let l = length(shapeUV); + let angle = 6.0 * atan2(shapeUV.y, shapeUV.x) + 4.0 * t; + let twist: f32 = 1.2; + let offset = 1.0 / pow(max(l, 1e-6), twist) + angle / TWO_PI; + let mid = smoothstep(0.0, 1.0, pow(l, twist)); + shape = mix(0.0, fract(offset), mid); } else { // Sphere - shapeUV *= 2.; + shapeUV *= 2.0; - float d = 1. - pow(length(shapeUV), 2.); - vec3 pos = vec3(shapeUV, sqrt(max(0., d))); - vec3 lightPos = normalize(vec3(cos(1.5 * t), .8, sin(1.25 * t))); - shape = .5 + .5 * dot(lightPos, pos); - shape *= step(0., d); + let d = 1.0 - pow(length(shapeUV), 2.0); + let pos = vec3f(shapeUV, sqrt(max(0.0, d))); + let lightPos = normalize(vec3f(cos(1.5 * t), 0.8, sin(1.25 * t))); + shape = 0.5 + 0.5 * dot(lightPos, pos); + shape *= step(0.0, d); } + let typeVal = i32(floor(u.u_type)); + var dithering: f32 = 0.0; - int type = int(floor(u_type)); - float dithering = 0.0; - - switch (type) { - case 1: { - dithering = step(hash21(ditheringNoiseUV), shape); - } break; - case 2: + if (typeVal == 1) { + dithering = step(hash21(ditheringNoiseUV), shape); + } else if (typeVal == 2) { dithering = getBayerValue(pxSizeUV, 2); - break; - case 3: + } else if (typeVal == 3) { dithering = getBayerValue(pxSizeUV, 4); - break; - default : + } else { dithering = getBayerValue(pxSizeUV, 8); - break; } - dithering -= .5; - float res = step(.5, shape + dithering); + dithering -= 0.5; + let res = step(0.5, shape + dithering); - vec3 fgColor = u_colorFront.rgb * u_colorFront.a; - float fgOpacity = u_colorFront.a; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - float bgOpacity = u_colorBack.a; + let fgColor = u.u_colorFront.rgb * u.u_colorFront.a; + let fgOpacity = u.u_colorFront.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + let bgOpacity = u.u_colorBack.a; - vec3 color = fgColor * res; - float opacity = fgOpacity * res; + var color = fgColor * res; + var opacity = fgOpacity * res; - color += bgColor * (1. - opacity); - opacity += bgOpacity * (1. - opacity); + color += bgColor * (1.0 - opacity); + opacity += bgOpacity * (1.0 - opacity); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/dot-grid.ts b/packages/shaders/src/shaders/dot-grid.ts index 8a53183e7..c6ec41216 100644 --- a/packages/shaders/src/shaders/dot-grid.ts +++ b/packages/shaders/src/shaders/dot-grid.ts @@ -1,5 +1,5 @@ import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, simplexNoise } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, glslMod, simplexNoise } from '../shader-utils.js'; /** * Static grid pattern made of circles, diamonds, squares or triangles. @@ -34,97 +34,97 @@ import { declarePI, simplexNoise } from '../shader-utils.js'; * */ -// language=GLSL -export const dotGridFragmentShader: string = `#version 300 es -precision mediump float; - -uniform vec4 u_colorBack; -uniform vec4 u_colorFill; -uniform vec4 u_colorStroke; -uniform float u_dotSize; -uniform float u_gapX; -uniform float u_gapY; -uniform float u_strokeWidth; -uniform float u_sizeRange; -uniform float u_opacityRange; -uniform float u_shape; - -in vec2 v_patternUV; +export const dotGridFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_dotSize: f32, + u_gapX: f32, + u_gapY: f32, + u_strokeWidth: f32, + u_sizeRange: f32, + u_opacityRange: f32, + u_shape: f32, + u_colorBack: vec4f, + u_colorFill: vec4f, + u_colorStroke: vec4f, +} +@group(0) @binding(0) var u: Uniforms; -out vec4 fragColor; +${vertexOutputStruct} ${ declarePI } +${ glslMod } ${ simplexNoise } -float polygon(vec2 p, float N, float rot) { - float a = atan(p.x, p.y) + rot; - float r = TWO_PI / float(N); +fn polygon(p: vec2f, N: f32, rot: f32) -> f32 { + let a = atan2(p.x, p.y) + rot; + let r = TWO_PI / N; - return cos(floor(.5 + a / r) * r - a) * length(p); + return cos(floor(0.5 + a / r) * r - a) * length(p); } -void main() { +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { - // x100 is a default multiplier between vertex and fragmant shaders - // we use it to avoid UV presision issues - vec2 shape_uv = 100. * v_patternUV; + // x100 is a default multiplier between vertex and fragment shaders + // we use it to avoid UV precision issues + let shape_uv = 100.0 * input.v_patternUV; - vec2 gap = max(abs(vec2(u_gapX, u_gapY)), vec2(1e-6)); - vec2 grid = fract(shape_uv / gap) + 1e-4; - vec2 grid_idx = floor(shape_uv / gap); - float sizeRandomizer = .5 + .8 * snoise(2. * vec2(grid_idx.x * 100., grid_idx.y)); - float opacity_randomizer = .5 + .7 * snoise(2. * vec2(grid_idx.y, grid_idx.x)); + let gap = max(abs(vec2f(u.u_gapX, u.u_gapY)), vec2f(1e-6)); + let grid = fract(shape_uv / gap) + vec2f(1e-4); + let grid_idx = floor(shape_uv / gap); + let sizeRandomizer = 0.5 + 0.8 * snoise(2.0 * vec2f(grid_idx.x * 100.0, grid_idx.y)); + let opacity_randomizer = 0.5 + 0.7 * snoise(2.0 * vec2f(grid_idx.y, grid_idx.x)); - vec2 center = vec2(0.5) - 1e-3; - vec2 p = (grid - center) * vec2(u_gapX, u_gapY); + let center = vec2f(0.5) - vec2f(1e-3); + var p = (grid - center) * vec2f(u.u_gapX, u.u_gapY); - float baseSize = u_dotSize * (1. - sizeRandomizer * u_sizeRange); - float strokeWidth = u_strokeWidth * (1. - sizeRandomizer * u_sizeRange); + let baseSize = u.u_dotSize * (1.0 - sizeRandomizer * u.u_sizeRange); + var strokeWidth = u.u_strokeWidth * (1.0 - sizeRandomizer * u.u_sizeRange); - float dist; - if (u_shape < 0.5) { + var dist: f32; + if (u.u_shape < 0.5) { // Circle dist = length(p); - } else if (u_shape < 1.5) { + } else if (u.u_shape < 1.5) { // Diamond strokeWidth *= 1.5; - dist = polygon(1.5 * p, 4., .25 * PI); - } else if (u_shape < 2.5) { + dist = polygon(1.5 * p, 4.0, 0.25 * PI); + } else if (u.u_shape < 2.5) { // Square - dist = polygon(1.03 * p, 4., 1e-3); + dist = polygon(1.03 * p, 4.0, 1e-3); } else { // Triangle strokeWidth *= 1.5; - p = p * 2. - 1.; - p *= .9; - p.y = 1. - p.y; - p.y -= .75 * baseSize; - dist = polygon(p, 3., 1e-3); + p = p * 2.0 - vec2f(1.0); + p *= 0.9; + p.y = 1.0 - p.y; + p.y -= 0.75 * baseSize; + dist = polygon(p, 3.0, 1e-3); } - float edgeWidth = fwidth(dist); - float shapeOuter = 1. - smoothstep(baseSize - edgeWidth, baseSize + edgeWidth, dist - strokeWidth); - float shapeInner = 1. - smoothstep(baseSize - edgeWidth, baseSize + edgeWidth, dist); - float stroke = shapeOuter - shapeInner; + let edgeWidth = fwidth(dist); + let shapeOuter = 1.0 - smoothstep(baseSize - edgeWidth, baseSize + edgeWidth, dist - strokeWidth); + var shapeInner = 1.0 - smoothstep(baseSize - edgeWidth, baseSize + edgeWidth, dist); + var stroke = shapeOuter - shapeInner; - float dotOpacity = max(0., 1. - opacity_randomizer * u_opacityRange); + let dotOpacity = max(0.0, 1.0 - opacity_randomizer * u.u_opacityRange); stroke *= dotOpacity; shapeInner *= dotOpacity; - stroke *= u_colorStroke.a; - shapeInner *= u_colorFill.a; + stroke *= u.u_colorStroke.a; + shapeInner *= u.u_colorFill.a; - vec3 color = vec3(0.); - color += stroke * u_colorStroke.rgb; - color += shapeInner * u_colorFill.rgb; - color += (1. - shapeInner - stroke) * u_colorBack.rgb * u_colorBack.a; + var color = vec3f(0.0); + color += stroke * u.u_colorStroke.rgb; + color += shapeInner * u.u_colorFill.rgb; + color += (1.0 - shapeInner - stroke) * u.u_colorBack.rgb * u.u_colorBack.a; - float opacity = 0.; + var opacity: f32 = 0.0; opacity += stroke; opacity += shapeInner; - opacity += (1. - opacity) * u_colorBack.a; + opacity += (1.0 - opacity) * u.u_colorBack.a; - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/dot-orbit.ts b/packages/shaders/src/shaders/dot-orbit.ts index 3b4053b70..d9f57af8b 100644 --- a/packages/shaders/src/shaders/dot-orbit.ts +++ b/packages/shaders/src/shaders/dot-orbit.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, rotation2, textureRandomizerR, textureRandomizerGB } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, textureRandomizerR, textureRandomizerGB } from '../shader-utils.js'; export const dotOrbitMeta = { maxColorCount: 10, @@ -40,50 +40,46 @@ export const dotOrbitMeta = { * */ -// language=GLSL -export const dotOrbitFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform sampler2D u_noiseTexture; - -uniform vec4 u_colorBack; -uniform vec4 u_colors[${ dotOrbitMeta.maxColorCount }]; -uniform float u_colorsCount; -uniform float u_stepsPerColor; -uniform float u_size; -uniform float u_sizeRange; -uniform float u_spreading; - -in vec2 v_patternUV; +export const dotOrbitFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_stepsPerColor: f32, + u_size: f32, + u_sizeRange: f32, + u_spreading: f32, + u_colorBack: vec4f, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; +@group(1) @binding(0) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(1) var u_noiseTexture_samp: sampler; -out vec4 fragColor; +${vertexOutputStruct} ${ declarePI } ${ rotation2 } ${ textureRandomizerR } ${ textureRandomizerGB } - -vec3 voronoiShape(vec2 uv, float time) { - vec2 i_uv = floor(uv); - vec2 f_uv = fract(uv); - - float spreading = .25 * clamp(u_spreading, 0., 1.); - - float minDist = 1.; - vec2 randomizer = vec2(0.); - for (int y = -1; y <= 1; y++) { - for (int x = -1; x <= 1; x++) { - vec2 tileOffset = vec2(float(x), float(y)); - vec2 rand = randomGB(i_uv + tileOffset); - vec2 cellCenter = vec2(.5 + 1e-4); - cellCenter += spreading * cos(time + TWO_PI * rand); - cellCenter -= .5; - cellCenter = rotate(cellCenter, randomR(vec2(rand.x, rand.y)) + .1 * time); - cellCenter += .5; - float dist = length(tileOffset + cellCenter - f_uv); +fn voronoiShape(uv: vec2f, time: f32) -> vec3f { + let i_uv = floor(uv); + let f_uv = fract(uv); + + let spreading = 0.25 * clamp(u.u_spreading, 0.0, 1.0); + + var minDist: f32 = 1.0; + var randomizer = vec2f(0.0); + for (var y: i32 = -1; y <= 1; y++) { + for (var x: i32 = -1; x <= 1; x++) { + let tileOffset = vec2f(f32(x), f32(y)); + let rand = randomGB(i_uv + tileOffset); + var cellCenter = vec2f(0.5 + 1e-4); + cellCenter += spreading * cos(vec2f(time) + TWO_PI * rand); + cellCenter -= vec2f(0.5); + cellCenter = rotate(cellCenter, randomR(vec2f(rand.x, rand.y)) + 0.1 * time); + cellCenter += vec2f(0.5); + let dist = length(tileOffset + cellCenter - f_uv); if (dist < minDist) { minDist = dist; randomizer = rand; @@ -91,62 +87,62 @@ vec3 voronoiShape(vec2 uv, float time) { } } - return vec3(minDist, randomizer); + return vec3f(minDist, randomizer); } -void main() { +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { - vec2 shape_uv = v_patternUV; + var shape_uv = input.v_patternUV; shape_uv *= 1.5; - const float firstFrameOffset = -10.; - float t = u_time + firstFrameOffset; + let firstFrameOffset: f32 = -10.0; + let t = u.u_time + firstFrameOffset; - vec3 voronoi = voronoiShape(shape_uv, t) + 1e-4; + let voronoi = voronoiShape(shape_uv, t) + vec3f(1e-4); - float radius = .25 * clamp(u_size, 0., 1.) - .5 * clamp(u_sizeRange, 0., 1.) * voronoi[2]; - float dist = voronoi[0]; - float edgeWidth = fwidth(dist); - float dots = 1. - smoothstep(radius - edgeWidth, radius + edgeWidth, dist); + let radius = 0.25 * clamp(u.u_size, 0.0, 1.0) - 0.5 * clamp(u.u_sizeRange, 0.0, 1.0) * voronoi[2]; + let dist = voronoi[0]; + let edgeWidth = fwidth(dist); + let dots = 1.0 - smoothstep(radius - edgeWidth, radius + edgeWidth, dist); - float shape = voronoi[1]; + let shape = voronoi[1]; - float mixer = shape * (u_colorsCount - 1.); - mixer = (shape - .5 / u_colorsCount) * u_colorsCount; - float steps = max(1., u_stepsPerColor); + var mixer = shape * (u.u_colorsCount - 1.0); + mixer = (shape - 0.5 / u.u_colorsCount) * u.u_colorsCount; + let steps = max(1.0, u.u_stepsPerColor); - vec4 gradient = u_colors[0]; - gradient.rgb *= gradient.a; - for (int i = 1; i < ${ dotOrbitMeta.maxColorCount }; i++) { - if (i >= int(u_colorsCount)) break; - float localT = clamp(mixer - float(i - 1), 0.0, 1.0); + var gradient = u.u_colors[0]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); + for (var i: i32 = 1; i < ${ dotOrbitMeta.maxColorCount }; i++) { + if (i >= i32(u.u_colorsCount)) { break; } + var localT = clamp(mixer - f32(i - 1), 0.0, 1.0); localT = round(localT * steps) / steps; - vec4 c = u_colors[i]; - c.rgb *= c.a; + var c = u.u_colors[i]; + c = vec4f(c.rgb * c.a, c.a); gradient = mix(gradient, c, localT); } - if ((mixer < 0.) || (mixer > (u_colorsCount - 1.))) { - float localT = mixer + 1.; - if (mixer > (u_colorsCount - 1.)) { - localT = mixer - (u_colorsCount - 1.); + if ((mixer < 0.0) || (mixer > (u.u_colorsCount - 1.0))) { + var localT2 = mixer + 1.0; + if (mixer > (u.u_colorsCount - 1.0)) { + localT2 = mixer - (u.u_colorsCount - 1.0); } - localT = round(localT * steps) / steps; - vec4 cFst = u_colors[0]; - cFst.rgb *= cFst.a; - vec4 cLast = u_colors[int(u_colorsCount - 1.)]; - cLast.rgb *= cLast.a; - gradient = mix(cLast, cFst, localT); + localT2 = round(localT2 * steps) / steps; + var cFst = u.u_colors[0]; + cFst = vec4f(cFst.rgb * cFst.a, cFst.a); + var cLast = u.u_colors[i32(u.u_colorsCount - 1.0)]; + cLast = vec4f(cLast.rgb * cLast.a, cLast.a); + gradient = mix(cLast, cFst, localT2); } - vec3 color = gradient.rgb * dots; - float opacity = gradient.a * dots; + let color_dot = gradient.rgb * dots; + var opacity = gradient.a * dots; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - color = color + bgColor * (1. - opacity); - opacity = opacity + u_colorBack.a * (1. - opacity); + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + var color = color_dot + bgColor * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/fluted-glass.ts b/packages/shaders/src/shaders/fluted-glass.ts index 695da1150..f71dd4fe9 100644 --- a/packages/shaders/src/shaders/fluted-glass.ts +++ b/packages/shaders/src/shaders/fluted-glass.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, proceduralHash21 } from '../shader-utils.js'; /** * Fluted glass image filter that transforms an image into streaked, ribbed distortions, @@ -50,336 +50,337 @@ import { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js'; * */ -// language=GLSL -export const flutedGlassFragmentShader: string = `#version 300 es -precision mediump float; - -uniform vec2 u_resolution; -uniform float u_pixelRatio; -uniform float u_rotation; - -uniform vec4 u_colorBack; -uniform vec4 u_colorShadow; -uniform vec4 u_colorHighlight; - -uniform sampler2D u_image; -uniform float u_imageAspectRatio; - -uniform float u_size; -uniform float u_shadows; -uniform float u_angle; -uniform float u_stretch; -uniform float u_shape; -uniform float u_distortion; -uniform float u_highlights; -uniform float u_distortionShape; -uniform float u_shift; -uniform float u_blur; -uniform float u_edges; -uniform float u_marginLeft; -uniform float u_marginRight; -uniform float u_marginTop; -uniform float u_marginBottom; -uniform float u_grainMixer; -uniform float u_grainOverlay; - -in vec2 v_imageUV; - -out vec4 fragColor; - -${ declarePI } -${ rotation2 } -${ proceduralHash21 } - -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = hash21(i); - float b = hash21(i + vec2(1.0, 0.0)); - float c = hash21(i + vec2(0.0, 1.0)); - float d = hash21(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); +export const flutedGlassFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorBack: vec4f, + u_colorShadow: vec4f, + u_colorHighlight: vec4f, + u_size: f32, + u_shadows: f32, + u_angle: f32, + u_stretch: f32, + u_shape: f32, + u_distortion: f32, + u_highlights: f32, + u_distortionShape: f32, + u_shift: f32, + u_blur: f32, + u_edges: f32, + u_marginLeft: f32, + u_marginRight: f32, + u_marginTop: f32, + u_marginBottom: f32, + u_grainMixer: f32, + u_grainOverlay: f32, } +@group(0) @binding(0) var u: Uniforms; -float getUvFrame(vec2 uv, float softness) { - float aax = 2. * fwidth(uv.x); - float aay = 2. * fwidth(uv.y); - float left = smoothstep(0., aax + softness, uv.x); - float right = 1. - smoothstep(1. - softness - aax, 1., uv.x); - float bottom = smoothstep(0., aay + softness, uv.y); - float top = 1. - smoothstep(1. - softness - aay, 1., uv.y); - return left * right * bottom * top; +${vertexOutputStruct} + +@group(1) @binding(0) var u_image_tex: texture_2d; +@group(1) @binding(1) var u_image_samp: sampler; + +${declarePI} +${rotation2} +${proceduralHash21} + +fn fwidth_f32(x: f32) -> f32 { + return abs(dpdx(x)) + abs(dpdy(x)); +} +fn fwidth_vec2(v: vec2f) -> vec2f { + return abs(dpdx(v)) + abs(dpdy(v)); } -const int MAX_RADIUS = 50; -vec4 samplePremultiplied(sampler2D tex, vec2 uv) { - vec4 c = texture(tex, uv); - c.rgb *= c.a; - return c; +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = hash21(i); + let b = hash21(i + vec2f(1.0, 0.0)); + let c = hash21(i + vec2f(0.0, 1.0)); + let d = hash21(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } -vec4 getBlur(sampler2D tex, vec2 uv, vec2 texelSize, vec2 dir, float sigma) { - if (sigma <= .5) return texture(tex, uv); - int radius = int(min(float(MAX_RADIUS), ceil(3.0 * sigma))); - float twoSigma2 = 2.0 * sigma * sigma; - float gaussianNorm = 1.0 / sqrt(TWO_PI * sigma * sigma); +fn getUvFrame(uv: vec2f, softness: f32) -> f32 { + let aax = 2.0 * fwidth_f32(uv.x); + let aay = 2.0 * fwidth_f32(uv.y); + let left = smoothstep(0.0, aax + softness, uv.x); + let right = 1.0 - smoothstep(1.0 - softness - aax, 1.0, uv.x); + let bottom = smoothstep(0.0, aay + softness, uv.y); + let top = 1.0 - smoothstep(1.0 - softness - aay, 1.0, uv.y); + return left * right * bottom * top; +} + +const MAX_RADIUS: i32 = 50; +fn samplePremultiplied(uv: vec2f) -> vec4f { + let c = textureSampleLevel(u_image_tex, u_image_samp, uv, 0.0); + return vec4f(c.rgb * c.a, c.a); +} +fn getBlur(uv: vec2f, texelSize: vec2f, dir: vec2f, sigma: f32) -> vec4f { + if (sigma <= 0.5) { return textureSampleLevel(u_image_tex, u_image_samp, uv, 0.0); } + let radius = i32(min(f32(MAX_RADIUS), ceil(3.0 * sigma))); - vec4 sum = samplePremultiplied(tex, uv) * gaussianNorm; - float weightSum = gaussianNorm; + let twoSigma2 = 2.0 * sigma * sigma; + let gaussianNorm = 1.0 / sqrt(TWO_PI * sigma * sigma); - for (int i = 1; i <= MAX_RADIUS; i++) { - if (i > radius) break; + var sum = samplePremultiplied(uv) * gaussianNorm; + var weightSum = gaussianNorm; - float x = float(i); - float w = exp(-(x * x) / twoSigma2) * gaussianNorm; + for (var i: i32 = 1; i <= MAX_RADIUS; i++) { + if (i <= radius) { + let x = f32(i); + let w = exp(-(x * x) / twoSigma2) * gaussianNorm; - vec2 offset = dir * texelSize * x; - vec4 s1 = samplePremultiplied(tex, uv + offset); - vec4 s2 = samplePremultiplied(tex, uv - offset); + let offset = dir * texelSize * x; + let s1 = samplePremultiplied(uv + offset); + let s2 = samplePremultiplied(uv - offset); - sum += (s1 + s2) * w; - weightSum += 2.0 * w; + sum += (s1 + s2) * w; + weightSum += 2.0 * w; + } } - vec4 result = sum / weightSum; - if (result.a > 0.) { - result.rgb /= result.a; + var result = sum / weightSum; + if (result.a > 0.0) { + result = vec4f(result.rgb / result.a, result.a); } return result; } -vec2 rotateAspect(vec2 p, float a, float aspect) { - p.x *= aspect; +fn rotateAspect(p_in: vec2f, a: f32, aspect: f32) -> vec2f { + var p = p_in; + p = vec2f(p.x * aspect, p.y); p = rotate(p, a); - p.x /= aspect; + p = vec2f(p.x / aspect, p.y); return p; } -float smoothFract(float x) { - float f = fract(x); - float w = fwidth(x); +fn smoothFract(x: f32) -> f32 { + let f = fract(x); + let w = fwidth_f32(x); - float edge = abs(f - 0.5) - 0.5; - float band = smoothstep(-w, w, edge); + let edge = abs(f - 0.5) - 0.5; + let band = smoothstep(-w, w, edge); return mix(f, 1.0 - f, band); } -void main() { +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { - float patternRotation = -u_angle * PI / 180.; - float patternSize = mix(200., 5., u_size); + let patternRotation = -u.u_angle * PI / 180.0; + let patternSize = mix(200.0, 5.0, u.u_size); - vec2 uv = v_imageUV; + var uv = input.v_imageUV; - vec2 uvMask = gl_FragCoord.xy / u_resolution.xy; - vec2 sw = vec2(.005); - vec4 margins = vec4(u_marginLeft, u_marginTop, u_marginRight, u_marginBottom); - float mask = + let uvMask = input.position.xy / u.u_resolution.xy; + let sw = vec2f(0.005); + let margins = vec4f(u.u_marginLeft, u.u_marginTop, u.u_marginRight, u.u_marginBottom); + let mask = smoothstep(margins[0], margins[0] + sw.x, uvMask.x + sw.x) * smoothstep(margins[2], margins[2] + sw.x, 1.0 - uvMask.x + sw.x) * smoothstep(margins[1], margins[1] + sw.y, uvMask.y + sw.y) * smoothstep(margins[3], margins[3] + sw.y, 1.0 - uvMask.y + sw.y); - float maskOuter = + let maskOuter = smoothstep(margins[0] - sw.x, margins[0], uvMask.x + sw.x) * smoothstep(margins[2] - sw.x, margins[2], 1.0 - uvMask.x + sw.x) * smoothstep(margins[1] - sw.y, margins[1], uvMask.y + sw.y) * smoothstep(margins[3] - sw.y, margins[3], 1.0 - uvMask.y + sw.y); - float maskStroke = maskOuter - mask; - float maskInner = - smoothstep(margins[0] - 2. * sw.x, margins[0], uvMask.x) * - smoothstep(margins[2] - 2. * sw.x, margins[2], 1.0 - uvMask.x) * - smoothstep(margins[1] - 2. * sw.y, margins[1], uvMask.y) * - smoothstep(margins[3] - 2. * sw.y, margins[3], 1.0 - uvMask.y); - float maskStrokeInner = maskInner - mask; - - uv -= .5; + let maskStroke = maskOuter - mask; + let maskInner = + smoothstep(margins[0] - 2.0 * sw.x, margins[0], uvMask.x) * + smoothstep(margins[2] - 2.0 * sw.x, margins[2], 1.0 - uvMask.x) * + smoothstep(margins[1] - 2.0 * sw.y, margins[1], uvMask.y) * + smoothstep(margins[3] - 2.0 * sw.y, margins[3], 1.0 - uvMask.y); + let maskStrokeInner = maskInner - mask; + + uv -= vec2f(0.5); uv *= patternSize; - uv = rotateAspect(uv, patternRotation, u_imageAspectRatio); + uv = rotateAspect(uv, patternRotation, u.u_imageAspectRatio); - float curve = 0.; - float patternY = uv.y / u_imageAspectRatio; - if (u_shape > 4.5) { + var curve: f32 = 0.0; + let patternY = uv.y / u.u_imageAspectRatio; + if (u.u_shape > 4.5) { // pattern - curve = .5 + .5 * sin(.5 * PI * uv.x) * cos(.5 * PI * patternY); - } else if (u_shape > 3.5) { + curve = 0.5 + 0.5 * sin(0.5 * PI * uv.x) * cos(0.5 * PI * patternY); + } else if (u.u_shape > 3.5) { // zigzag - curve = 10. * abs(fract(.1 * patternY) - .5); - } else if (u_shape > 2.5) { + curve = 10.0 * abs(fract(0.1 * patternY) - 0.5); + } else if (u.u_shape > 2.5) { // wave - curve = 4. * sin(.23 * patternY); - } else if (u_shape > 1.5) { + curve = 4.0 * sin(0.23 * patternY); + } else if (u.u_shape > 1.5) { // lines irregular - curve = .5 + .5 * sin(.5 * uv.x) * sin(1.7 * uv.x); + curve = 0.5 + 0.5 * sin(0.5 * uv.x) * sin(1.7 * uv.x); } else { // lines } - vec2 UvToFract = uv + curve; - vec2 fractOrigUV = fract(uv); - vec2 floorOrigUV = floor(uv); + let UvToFract = uv + vec2f(curve); + var fractOrigUV = fract(uv); + var floorOrigUV = floor(uv); - float x = smoothFract(UvToFract.x); - float xNonSmooth = fract(UvToFract.x) + .0001; + var x = smoothFract(UvToFract.x); + let xNonSmooth = fract(UvToFract.x) + 0.0001; - float highlightsWidth = 2. * max(.001, fwidth(UvToFract.x)); - highlightsWidth += 2. * maskStrokeInner; - float highlights = smoothstep(0., highlightsWidth, xNonSmooth); - highlights *= smoothstep(1., 1. - highlightsWidth, xNonSmooth); - highlights = 1. - highlights; - highlights *= u_highlights; - highlights = clamp(highlights, 0., 1.); + var highlightsWidth = 2.0 * max(0.001, fwidth_f32(UvToFract.x)); + highlightsWidth += 2.0 * maskStrokeInner; + var highlights = smoothstep(0.0, highlightsWidth, xNonSmooth); + highlights *= smoothstep(1.0, 1.0 - highlightsWidth, xNonSmooth); + highlights = 1.0 - highlights; + highlights *= u.u_highlights; + highlights = clamp(highlights, 0.0, 1.0); highlights *= mask; - float shadows = pow(x, 1.3); - float distortion = 0.; - float fadeX = 1.; - float frameFade = 0.; - - float aa = fwidth(xNonSmooth); - aa = max(aa, fwidth(uv.x)); - aa = max(aa, fwidth(UvToFract.x)); - aa = max(aa, .0001); - - if (u_distortionShape == 1.) { - distortion = -pow(1.5 * x, 3.); - distortion += (.5 - u_shift); - - frameFade = pow(1.5 * x, 3.); - aa = max(.2, aa); - aa += mix(.2, 0., u_size); - fadeX = smoothstep(0., aa, xNonSmooth) * smoothstep(1., 1. - aa, xNonSmooth); - distortion = mix(.5, distortion, fadeX); - } else if (u_distortionShape == 2.) { - distortion = 2. * pow(x, 2.); - distortion -= (.5 + u_shift); - - frameFade = pow(abs(x - .5), 4.); - aa = max(.2, aa); - aa += mix(.2, 0., u_size); - fadeX = smoothstep(0., aa, xNonSmooth) * smoothstep(1., 1. - aa, xNonSmooth); - distortion = mix(.5, distortion, fadeX); - frameFade = mix(1., frameFade, .5 * fadeX); - } else if (u_distortionShape == 3.) { - distortion = pow(2. * (xNonSmooth - .5), 6.); - distortion -= .25; - distortion -= u_shift; - - frameFade = 1. - 2. * pow(abs(x - .4), 2.); - aa = .15; - aa += mix(.1, 0., u_size); - fadeX = smoothstep(0., aa, xNonSmooth) * smoothstep(1., 1. - aa, xNonSmooth); - frameFade = mix(1., frameFade, fadeX); - - } else if (u_distortionShape == 4.) { + var shadows = pow(x, 1.3); + var distortion: f32 = 0.0; + var fadeX: f32 = 1.0; + var frameFade: f32 = 0.0; + + var aa = fwidth_f32(xNonSmooth); + aa = max(aa, fwidth_f32(uv.x)); + aa = max(aa, fwidth_f32(UvToFract.x)); + aa = max(aa, 0.0001); + + if (u.u_distortionShape == 1.0) { + distortion = -pow(1.5 * x, 3.0); + distortion += (0.5 - u.u_shift); + + frameFade = pow(1.5 * x, 3.0); + aa = max(0.2, aa); + aa += mix(0.2, 0.0, u.u_size); + fadeX = smoothstep(0.0, aa, xNonSmooth) * smoothstep(1.0, 1.0 - aa, xNonSmooth); + distortion = mix(0.5, distortion, fadeX); + } else if (u.u_distortionShape == 2.0) { + distortion = 2.0 * pow(x, 2.0); + distortion -= (0.5 + u.u_shift); + + frameFade = pow(abs(x - 0.5), 4.0); + aa = max(0.2, aa); + aa += mix(0.2, 0.0, u.u_size); + fadeX = smoothstep(0.0, aa, xNonSmooth) * smoothstep(1.0, 1.0 - aa, xNonSmooth); + distortion = mix(0.5, distortion, fadeX); + frameFade = mix(1.0, frameFade, 0.5 * fadeX); + } else if (u.u_distortionShape == 3.0) { + distortion = pow(2.0 * (xNonSmooth - 0.5), 6.0); + distortion -= 0.25; + distortion -= u.u_shift; + + frameFade = 1.0 - 2.0 * pow(abs(x - 0.4), 2.0); + aa = 0.15; + aa += mix(0.1, 0.0, u.u_size); + fadeX = smoothstep(0.0, aa, xNonSmooth) * smoothstep(1.0, 1.0 - aa, xNonSmooth); + frameFade = mix(1.0, frameFade, fadeX); + + } else if (u.u_distortionShape == 4.0) { x = xNonSmooth; - distortion = sin((x + .25) * TWO_PI); - shadows = .5 + .5 * asin(distortion) / (.5 * PI); - distortion *= .5; - distortion -= u_shift; - frameFade = .5 + .5 * sin(x * TWO_PI); - } else if (u_distortionShape == 5.) { - distortion -= pow(abs(x), .2) * x; - distortion += .33; - distortion -= 3. * u_shift; - distortion *= .33; - - frameFade = .3 * (smoothstep(.0, 1., x)); + distortion = sin((x + 0.25) * TWO_PI); + shadows = 0.5 + 0.5 * asin(distortion) / (0.5 * PI); + distortion *= 0.5; + distortion -= u.u_shift; + frameFade = 0.5 + 0.5 * sin(x * TWO_PI); + } else if (u.u_distortionShape == 5.0) { + distortion -= pow(abs(x), 0.2) * x; + distortion += 0.33; + distortion -= 3.0 * u.u_shift; + distortion *= 0.33; + + frameFade = 0.3 * (smoothstep(0.0, 1.0, x)); shadows = pow(x, 2.5); - aa = max(.1, aa); - aa += mix(.1, 0., u_size); - fadeX = smoothstep(0., aa, xNonSmooth) * smoothstep(1., 1. - aa, xNonSmooth); + aa = max(0.1, aa); + aa += mix(0.1, 0.0, u.u_size); + fadeX = smoothstep(0.0, aa, xNonSmooth) * smoothstep(1.0, 1.0 - aa, xNonSmooth); distortion *= fadeX; } - vec2 dudx = dFdx(v_imageUV); - vec2 dudy = dFdy(v_imageUV); - vec2 grainUV = v_imageUV - .5; - grainUV *= (.8 / vec2(length(dudx), length(dudy))); - grainUV += .5; - float grain = valueNoise(grainUV); - grain = smoothstep(.4, .7, grain); - grain *= u_grainMixer; - distortion = mix(distortion, 0., grain); - - shadows = min(shadows, 1.); + let dudx = dpdx(input.v_imageUV); + let dudy = dpdy(input.v_imageUV); + var grainUV = input.v_imageUV - vec2f(0.5); + grainUV *= (0.8 / vec2f(length(dudx), length(dudy))); + grainUV += vec2f(0.5); + let grain = valueNoise(grainUV); + let grainSmooth = smoothstep(0.4, 0.7, grain); + let grainMixed = grainSmooth * u.u_grainMixer; + distortion = mix(distortion, 0.0, grainMixed); + + shadows = min(shadows, 1.0); shadows += maskStrokeInner; shadows *= mask; - shadows = min(shadows, 1.); - shadows *= pow(u_shadows, 2.); - shadows = clamp(shadows, 0., 1.); + shadows = min(shadows, 1.0); + shadows *= pow(u.u_shadows, 2.0); + shadows = clamp(shadows, 0.0, 1.0); - distortion *= 3. * u_distortion; - frameFade *= u_distortion; + distortion *= 3.0 * u.u_distortion; + frameFade *= u.u_distortion; - fractOrigUV.x += distortion; - floorOrigUV = rotateAspect(floorOrigUV, -patternRotation, u_imageAspectRatio); - fractOrigUV = rotateAspect(fractOrigUV, -patternRotation, u_imageAspectRatio); + fractOrigUV = vec2f(fractOrigUV.x + distortion, fractOrigUV.y); + floorOrigUV = rotateAspect(floorOrigUV, -patternRotation, u.u_imageAspectRatio); + fractOrigUV = rotateAspect(fractOrigUV, -patternRotation, u.u_imageAspectRatio); uv = (floorOrigUV + fractOrigUV) / patternSize; - uv += pow(maskStroke, 4.); + uv += vec2f(pow(maskStroke, 4.0)); - uv += vec2(.5); + uv += vec2f(0.5); - uv = mix(v_imageUV, uv, smoothstep(0., .7, mask)); - float blur = mix(0., 50., u_blur); - blur = mix(0., blur, smoothstep(.5, 1., mask)); + uv = mix(input.v_imageUV, uv, smoothstep(0.0, 0.7, mask)); + var blur = mix(0.0, 50.0, u.u_blur); + blur = mix(0.0, blur, smoothstep(0.5, 1.0, mask)); - float edgeDistortion = mix(.0, .04, u_edges); - edgeDistortion += .06 * frameFade * u_edges; + var edgeDistortion = mix(0.0, 0.04, u.u_edges); + edgeDistortion += 0.06 * frameFade * u.u_edges; edgeDistortion *= mask; - float frame = getUvFrame(uv, edgeDistortion); + let frame = getUvFrame(uv, edgeDistortion); - float stretch = 1. - smoothstep(0., .5, xNonSmooth) * smoothstep(1., 1. - .5, xNonSmooth); - stretch = pow(stretch, 2.); + var stretch = 1.0 - smoothstep(0.0, 0.5, xNonSmooth) * smoothstep(1.0, 1.0 - 0.5, xNonSmooth); + stretch = pow(stretch, 2.0); stretch *= mask; - stretch *= getUvFrame(uv, .1 + .05 * mask * frameFade); - uv.y = mix(uv.y, .5, u_stretch * stretch); - - vec4 image = getBlur(u_image, uv, 1. / u_resolution / u_pixelRatio, vec2(0., 1.), blur); - image.rgb *= image.a; - vec4 backColor = u_colorBack; - backColor.rgb *= backColor.a; - vec4 highlightColor = u_colorHighlight; - highlightColor.rgb *= highlightColor.a; - vec4 shadowColor = u_colorShadow; - - vec3 color = highlightColor.rgb * highlights; - float opacity = highlightColor.a * highlights; - - shadows = mix(shadows * shadowColor.a, 0., highlights); - color = mix(color, shadowColor.rgb * shadowColor.a, .5 * shadows); - color += .5 * pow(shadows, .5) * shadowColor.rgb; + stretch *= getUvFrame(uv, 0.1 + 0.05 * mask * frameFade); + uv = vec2f(uv.x, mix(uv.y, 0.5, u.u_stretch * stretch)); + + var image = getBlur(uv, 1.0 / u.u_resolution / u.u_pixelRatio, vec2f(0.0, 1.0), blur); + image = vec4f(image.rgb * image.a, image.a); + var backColor = u.u_colorBack; + backColor = vec4f(backColor.rgb * backColor.a, backColor.a); + var highlightColor = u.u_colorHighlight; + highlightColor = vec4f(highlightColor.rgb * highlightColor.a, highlightColor.a); + let shadowColor = u.u_colorShadow; + + var color = highlightColor.rgb * highlights; + var opacity = highlightColor.a * highlights; + + shadows = mix(shadows * shadowColor.a, 0.0, highlights); + color = mix(color, shadowColor.rgb * shadowColor.a, 0.5 * shadows); + color += 0.5 * pow(shadows, 0.5) * shadowColor.rgb; opacity += shadows; - color = clamp(color, vec3(0.), vec3(1.)); - opacity = clamp(opacity, 0., 1.); + color = clamp(color, vec3f(0.0), vec3f(1.0)); + opacity = clamp(opacity, 0.0, 1.0); - color += image.rgb * (1. - opacity) * frame; - opacity += image.a * (1. - opacity) * frame; + color += image.rgb * (1.0 - opacity) * frame; + opacity += image.a * (1.0 - opacity) * frame; - color += backColor.rgb * (1. - opacity); - opacity += backColor.a * (1. - opacity); + color += backColor.rgb * (1.0 - opacity); + opacity += backColor.a * (1.0 - opacity); - float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.)); - grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5); + var grainOverlay = valueNoise(rotate(grainUV, 1.0) + vec2f(3.0)); + grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.0) + vec2f(-1.0)), 0.5); grainOverlay = pow(grainOverlay, 1.3); - float grainOverlayV = grainOverlay * 2. - 1.; - vec3 grainOverlayColor = vec3(step(0., grainOverlayV)); - float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV); - grainOverlayStrength = pow(grainOverlayStrength, .8); + let grainOverlayV = grainOverlay * 2.0 - 1.0; + let grainOverlayColor = vec3f(step(0.0, grainOverlayV)); + var grainOverlayStrength = u.u_grainOverlay * abs(grainOverlayV); + grainOverlayStrength = pow(grainOverlayStrength, 0.8); grainOverlayStrength *= mask; - color = mix(color, grainOverlayColor, .35 * grainOverlayStrength); + color = mix(color, grainOverlayColor, 0.35 * grainOverlayStrength); - opacity += .5 * grainOverlayStrength; - opacity = clamp(opacity, 0., 1.); + opacity += 0.5 * grainOverlayStrength; + opacity = clamp(opacity, 0.0, 1.0); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/gem-smoke.ts b/packages/shaders/src/shaders/gem-smoke.ts index 56b5608a1..a5c29b035 100644 --- a/packages/shaders/src/shaders/gem-smoke.ts +++ b/packages/shaders/src/shaders/gem-smoke.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import type { ShaderSizingParams, ShaderSizingUniforms } from '../shader-sizing.js'; -import { rotation2, declarePI } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, rotation2, declarePI } from '../shader-utils.js'; export const gemSmokeMeta = { maxColorCount: 6, @@ -48,242 +48,228 @@ export const gemSmokeMeta = { * */ -// language=GLSL -export const gemSmokeFragmentShader: string = `#version 300 es -precision mediump float; - -in mediump vec2 v_imageUV; -in mediump vec2 v_objectUV; -in mediump vec2 v_responsiveUV; -in mediump vec2 v_responsiveBoxGivenSize; -out vec4 fragColor; - -// Image -uniform sampler2D u_image; -uniform float u_imageAspectRatio; - -// Canvas -uniform vec2 u_resolution; -uniform float u_time; - -// Colors -uniform vec4 u_colors[${gemSmokeMeta.maxColorCount}]; -uniform float u_colorsCount; -uniform vec4 u_colorBack; -uniform vec4 u_colorInner; - -// Effect controls -uniform float u_innerDistortion; -uniform float u_outerDistortion; -uniform float u_outerGlow; -uniform float u_innerGlow; -uniform float u_offset; -uniform float u_angle; -uniform float u_size; - -// Shape controls -uniform float u_shape; -uniform bool u_isImage; +export const gemSmokeFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_colorBack: vec4f, + u_colorInner: vec4f, + u_innerDistortion: f32, + u_outerDistortion: f32, + u_outerGlow: f32, + u_innerGlow: f32, + u_offset: f32, + u_angle: f32, + u_size: f32, + u_shape: f32, + u_isImage: f32, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; + +${vertexOutputStruct} + +@group(1) @binding(0) var u_image_tex: texture_2d; +@group(1) @binding(1) var u_image_samp: sampler; ${ declarePI } ${ rotation2 } // 9x9 Gaussian blur on R and G channels -vec2 gaussBlur9x9RG(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius) { - vec2 texel = 1.0 / vec2(textureSize(tex, 0)); - vec2 r = max(radius, 0.0) * texel; +fn gaussBlur9x9RG(uv: vec2f, radius_in: f32) -> vec2f { + let texel = 1.0 / vec2f(textureDimensions(u_image_tex, 0)); + let r = max(radius_in, 0.0) * texel; // Pascal's row 8: sum = 256, 2D norm = 65536 - const float k[9] = float[9](1.0, 8.0, 28.0, 56.0, 70.0, 56.0, 28.0, 8.0, 1.0); - vec2 sum = vec2(0.0); - - for (int j = -4; j <= 4; ++j) { - float wy = k[j + 4]; - for (int i = -4; i <= 4; ++i) { - float w = k[i + 4] * wy; - vec2 off = vec2(float(i) * r.x, float(j) * r.y); - sum += w * texture(tex, uv + off).rg; + let k = array(1.0, 8.0, 28.0, 56.0, 70.0, 56.0, 28.0, 8.0, 1.0); + var blur_sum = vec2f(0.0); + + for (var j: i32 = -4; j <= 4; j++) { + let wy = k[j + 4]; + for (var i: i32 = -4; i <= 4; i++) { + let w = k[i + 4] * wy; + let off = vec2f(f32(i) * r.x, f32(j) * r.y); + let s = textureSampleLevel(u_image_tex, u_image_samp, uv + off, 0.0); + blur_sum += w * s.rg; } } - return sum / 65536.0; + return blur_sum / 65536.0; } -float sst(float a, float b, float x) { +fn sst(a: f32, b: f32, x: f32) -> f32 { return smoothstep(a, b, x); } -void main() { - float time = u_time; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + let time = u.u_time; - float roundness = 0.; - float imgAlpha = 0.; + var roundness: f32 = 0.0; + var imgAlpha: f32 = 0.0; - if (u_isImage == true) { + if (u.u_isImage > 0.5) { // Image sampling (UV scaled inward to account for padding) - vec2 imageUV = v_imageUV; - imageUV -= .5; - imageUV *= .95; - imageUV += .5; - - vec2 dudx = dFdx(v_imageUV); - vec2 dudy = dFdy(v_imageUV); + var imageUV = input.v_imageUV; + imageUV -= vec2f(0.5); + imageUV *= 0.95; + imageUV += vec2f(0.5); // Blurred image: x = roundness, y = alpha - vec2 blurred = gaussBlur9x9RG(u_image, imageUV, dudx, dudy, 10.); - roundness = 1. - blurred.x; - vec2 texelA = 1.0 / vec2(textureSize(u_image, 0)); - const float k3[3] = float[3](1.0, 2.0, 1.0); - for (int j = -1; j <= 1; ++j) { - for (int i = -1; i <= 1; ++i) { - imgAlpha += k3[i + 1] * k3[j + 1] * texture(u_image, imageUV + vec2(float(i) * texelA.x, float(j) * texelA.y)).g; + let blurred = gaussBlur9x9RG(imageUV, 10.0); + roundness = 1.0 - blurred.x; + let texelA = 1.0 / vec2f(textureDimensions(u_image_tex, 0)); + let k3 = array(1.0, 2.0, 1.0); + for (var j: i32 = -1; j <= 1; j++) { + for (var i: i32 = -1; i <= 1; i++) { + imgAlpha += k3[i + 1] * k3[j + 1] * textureSampleLevel(u_image_tex, u_image_samp, imageUV + vec2f(f32(i) * texelA.x, f32(j) * texelA.y), 0.0).g; } } imgAlpha /= 16.0; } else { - vec2 uv = v_objectUV + .5; - uv.y = 1. - uv.y; - float edge = 0.; + var uv = input.v_objectUV + vec2f(0.5); + uv = vec2f(uv.x, 1.0 - uv.y); + var edge: f32 = 0.0; - if (u_shape < 1.) { + if (u.u_shape < 1.0) { // full-fill on canvas - vec2 borderUV = v_responsiveUV + .5; - vec2 mask = min(borderUV, 1. - borderUV); - vec2 pixel_thickness = min(250. / v_responsiveBoxGivenSize, vec2(.5)); - float maskX = smoothstep(0.0, pixel_thickness.x, mask.x); - float maskY = smoothstep(0.0, pixel_thickness.y, mask.y); - maskX = pow(maskX, .25); - maskY = pow(maskY, .25); - edge = clamp(1. - maskX * maskY, 0., 1.); - } else if (u_shape < 2.) { + let borderUV = input.v_responsiveUV + vec2f(0.5); + let mask_val = min(borderUV, vec2f(1.0) - borderUV); + let pixel_thickness = min(250.0 / input.v_responsiveBoxGivenSize, vec2f(0.5)); + var maskX = smoothstep(0.0, pixel_thickness.x, mask_val.x); + var maskY = smoothstep(0.0, pixel_thickness.y, mask_val.y); + maskX = pow(maskX, 0.25); + maskY = pow(maskY, 0.25); + edge = clamp(1.0 - maskX * maskY, 0.0, 1.0); + } else if (u.u_shape < 2.0) { // circle - vec2 shapeUV = uv - .5; - shapeUV *= .67; - edge = pow(clamp(3. * length(shapeUV), 0., 1.), 18.); - } else if (u_shape < 3.) { + var shapeUV = uv - vec2f(0.5); + shapeUV *= 0.67; + edge = pow(clamp(3.0 * length(shapeUV), 0.0, 1.0), 18.0); + } else if (u.u_shape < 3.0) { // daisy - vec2 shapeUV = uv - .5; + var shapeUV = uv - vec2f(0.5); shapeUV *= 1.68; - float r = length(shapeUV) * 2.; - float a = atan(shapeUV.y, shapeUV.x) + .2; - r *= (1. + .05 * sin(3. * a + 2. * time)); - float f = abs(cos(a * 3.)); - edge = smoothstep(f, f + .7, r); + var r = length(shapeUV) * 2.0; + let a = atan2(shapeUV.y, shapeUV.x) + 0.2; + r *= (1.0 + 0.05 * sin(3.0 * a + 2.0 * time)); + let f = abs(cos(a * 3.0)); + edge = smoothstep(f, f + 0.7, r); edge *= edge; - } else if (u_shape < 4.) { + } else if (u.u_shape < 4.0) { // diamond - vec2 shapeUV = uv - .5; - shapeUV = rotate(shapeUV, .25 * PI); + var shapeUV = uv - vec2f(0.5); + shapeUV = rotate(shapeUV, 0.25 * PI); shapeUV *= 1.42; - shapeUV += .5; - vec2 mask = min(shapeUV, 1. - shapeUV); - vec2 pixel_thickness = vec2(.15); - float maskX = smoothstep(0.0, pixel_thickness.x, mask.x); - float maskY = smoothstep(0.0, pixel_thickness.y, mask.y); - maskX = pow(maskX, .25); - maskY = pow(maskY, .25); - edge = clamp(1. - maskX * maskY, 0., 1.); - } else if (u_shape < 5.) { + shapeUV += vec2f(0.5); + let mask_val = min(shapeUV, vec2f(1.0) - shapeUV); + let pixel_thickness = vec2f(0.15); + var maskX = smoothstep(0.0, pixel_thickness.x, mask_val.x); + var maskY = smoothstep(0.0, pixel_thickness.y, mask_val.y); + maskX = pow(maskX, 0.25); + maskY = pow(maskY, 0.25); + edge = clamp(1.0 - maskX * maskY, 0.0, 1.0); + } else if (u.u_shape < 5.0) { // metaballs - vec2 shapeUV = uv - .5; + var shapeUV = uv - vec2f(0.5); shapeUV *= 1.3; - edge = 0.; - for (int i = 0; i < 5; i++) { - float fi = float(i); - float speed = 1.5 + 2./3. * sin(fi * 12.345); - float angle = -fi * 1.5; - vec2 dir1 = vec2(cos(angle), sin(angle)); - vec2 dir2 = vec2(cos(angle + 1.57), sin(angle + 1.)); - vec2 traj = .4 * (dir1 * sin(time * speed + fi * 1.23) + dir2 * cos(time * (speed * 0.7) + fi * 2.17)); - float d = length(shapeUV + traj); + edge = 0.0; + for (var i: i32 = 0; i < 5; i++) { + let fi = f32(i); + let speed = 1.5 + 2.0 / 3.0 * sin(fi * 12.345); + let mb_angle = -fi * 1.5; + let dir1 = vec2f(cos(mb_angle), sin(mb_angle)); + let dir2 = vec2f(cos(mb_angle + 1.57), sin(mb_angle + 1.0)); + let traj = 0.4 * (dir1 * sin(time * speed + fi * 1.23) + dir2 * cos(time * (speed * 0.7) + fi * 2.17)); + let d = length(shapeUV + traj); edge += pow(1.0 - clamp(d, 0.0, 1.0), 4.0); } - edge = 1. - smoothstep(.65, .9, edge); - edge = pow(edge, 4.); + edge = 1.0 - smoothstep(0.65, 0.9, edge); + edge = pow(edge, 4.0); } - imgAlpha = 1. - smoothstep(.9 - 2. * fwidth(edge), .9, edge); - roundness = 1. - edge; + let fw_edge = abs(dpdx(edge)) + abs(dpdy(edge)); + imgAlpha = 1.0 - smoothstep(0.9 - 2.0 * fw_edge, 0.9, edge); + roundness = 1.0 - edge; } -// Smoke UV setup - vec2 smokeUV = v_objectUV; - smokeUV = rotate(smokeUV, u_angle * PI / 180.); - smokeUV *= mix(4., 1., u_size); + // Smoke UV setup + var smokeUV = input.v_objectUV; + smokeUV = rotate(smokeUV, u.u_angle * PI / 180.0); + smokeUV *= mix(4.0, 1.0, u.u_size); // Two swirl paths: inner (shape-masked) and outer (free), each with independent distortion - vec2 innerUV = smokeUV; - vec2 outerUV = smokeUV; + var innerUV = smokeUV; + var outerUV = smokeUV; // Vertical displacement — applied independently to inner and outer - innerUV.y += u_innerDistortion * (1. - sst(0., 1., length(.4 * innerUV))); - innerUV.y -= .4 * u_innerDistortion; - innerUV.y += .7 * u_offset * roundness; - - outerUV.y += u_outerDistortion * (1. - sst(0., 1., length(.4 * outerUV))); - outerUV.y -= .4 * u_outerDistortion; - - float innerSwirl = u_innerDistortion * roundness; - float outerSwirl = u_outerDistortion; - - for (int i = 1; i < 5; i++) { - float fi = float(i); - - float stretchIn = max(length(dFdx(innerUV)), length(dFdy(innerUV))); - float dampenIn = 1. / (1. + stretchIn * 8.); - float sIn = innerSwirl * dampenIn; - innerUV.x += sIn / fi * cos(time + fi * 2.9 * innerUV.y); - innerUV.y += sIn / fi * cos(time + fi * 1.5 * innerUV.x); - - float stretchOut = max(length(dFdx(outerUV)), length(dFdy(outerUV))); - float dampenOut = 1. / (1. + stretchOut * 8.); - float sOut = outerSwirl * dampenOut; - outerUV.x += sOut / fi * cos(time + fi * 2.9 * outerUV.y); - outerUV.y += sOut / fi * cos(time + fi * 1.5 * outerUV.x); + innerUV = vec2f(innerUV.x, innerUV.y + u.u_innerDistortion * (1.0 - sst(0.0, 1.0, length(0.4 * innerUV)))); + innerUV = vec2f(innerUV.x, innerUV.y - 0.4 * u.u_innerDistortion); + innerUV = vec2f(innerUV.x, innerUV.y + 0.7 * u.u_offset * roundness); + + outerUV = vec2f(outerUV.x, outerUV.y + u.u_outerDistortion * (1.0 - sst(0.0, 1.0, length(0.4 * outerUV)))); + outerUV = vec2f(outerUV.x, outerUV.y - 0.4 * u.u_outerDistortion); + + let innerSwirl = u.u_innerDistortion * roundness; + let outerSwirl = u.u_outerDistortion; + + for (var i: i32 = 1; i < 5; i++) { + let fi = f32(i); + + let stretchIn = max(length(dpdx(innerUV)), length(dpdy(innerUV))); + let dampenIn = 1.0 / (1.0 + stretchIn * 8.0); + let sIn = innerSwirl * dampenIn; + innerUV = vec2f(innerUV.x + sIn / fi * cos(time + fi * 2.9 * innerUV.y), innerUV.y); + innerUV = vec2f(innerUV.x, innerUV.y + sIn / fi * cos(time + fi * 1.5 * innerUV.x)); + + let stretchOut = max(length(dpdx(outerUV)), length(dpdy(outerUV))); + let dampenOut = 1.0 / (1.0 + stretchOut * 8.0); + let sOut = outerSwirl * dampenOut; + outerUV = vec2f(outerUV.x + sOut / fi * cos(time + fi * 2.9 * outerUV.y), outerUV.y); + outerUV = vec2f(outerUV.x, outerUV.y + sOut / fi * cos(time + fi * 1.5 * outerUV.x)); } // Smoke shapes from swirl fields - float innerShape = exp(-1.5 * dot(innerUV, innerUV)); - float outerShape = exp(-1.5 * dot(outerUV, outerUV)); + let innerShape_val = exp(-1.5 * dot(innerUV, innerUV)); + let outerShape_val = exp(-1.5 * dot(outerUV, outerUV)); // Visibility masks - float outerMask = pow(u_outerGlow, 2.) * (1. - imgAlpha); - float innerMask = (.01 + .99 * u_innerGlow) * imgAlpha; + let outerMask = pow(u.u_outerGlow, 2.0) * (1.0 - imgAlpha); + let innerMask = (0.01 + 0.99 * u.u_innerGlow) * imgAlpha; - innerShape *= innerMask; - outerShape *= outerMask; + var innerShape = innerShape_val * innerMask; + var outerShape = outerShape_val * outerMask; // Color gradient - float mixer = (innerShape + outerShape) * u_colorsCount; - vec4 gradient = u_colors[0]; - gradient.rgb *= gradient.a; + let mixer = (innerShape + outerShape) * u.u_colorsCount; + var gradient = u.u_colors[0]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); - float smokeMask = 0.; - for (int i = 1; i < ${gemSmokeMeta.maxColorCount + 1}; i++) { - if (i > int(u_colorsCount)) break; + var smokeMask: f32 = 0.0; + for (var i: i32 = 1; i < ${gemSmokeMeta.maxColorCount + 1}; i++) { + if (i > i32(u.u_colorsCount)) { break; } - float m = sst(0., 1., clamp(mixer - float(i - 1), 0., 1.)); - if (i == 1) smokeMask = m; + let m = sst(0.0, 1.0, clamp(mixer - f32(i - 1), 0.0, 1.0)); + if (i == 1) { smokeMask = m; } - vec4 c = u_colors[i - 1]; - c.rgb *= c.a; + var c = u.u_colors[i - 1]; + c = vec4f(c.rgb * c.a, c.a); gradient = mix(gradient, c, m); } // Compositing (premultiplied alpha, front-to-back) - vec3 color = gradient.rgb * smokeMask; - float opacity = gradient.a * smokeMask; + var color = gradient.rgb * smokeMask; + var opacity = gradient.a * smokeMask; - float innerOpacity = u_colorInner.a * imgAlpha; - vec3 innerColor = u_colorInner.rgb * innerOpacity; + let innerOpacity = u.u_colorInner.a * imgAlpha; + let innerColor = u.u_colorInner.rgb * innerOpacity; color += innerColor * (1.0 - opacity); opacity += innerOpacity * (1.0 - opacity); - vec3 backColor = u_colorBack.rgb * u_colorBack.a; + let backColor = u.u_colorBack.rgb * u.u_colorBack.a; color += backColor * (1.0 - opacity); - opacity += u_colorBack.a * (1.0 - opacity); + opacity += u.u_colorBack.a * (1.0 - opacity); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/god-rays.ts b/packages/shaders/src/shaders/god-rays.ts index 828cc38c3..72ada91b3 100644 --- a/packages/shaders/src/shaders/god-rays.ts +++ b/packages/shaders/src/shaders/god-rays.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, rotation2, textureRandomizerR, colorBandingFix, proceduralHash11 } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, textureRandomizerR, colorBandingFix, proceduralHash11 } from '../shader-utils.js'; export const godRaysMeta = { maxColorCount: 5, @@ -44,123 +44,120 @@ export const godRaysMeta = { * */ -// language=GLSL -export const godRaysFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform sampler2D u_noiseTexture; - -uniform vec4 u_colorBack; -uniform vec4 u_colorBloom; -uniform vec4 u_colors[${ godRaysMeta.maxColorCount }]; -uniform float u_colorsCount; - -uniform float u_density; -uniform float u_spotty; -uniform float u_midSize; -uniform float u_midIntensity; -uniform float u_intensity; -uniform float u_bloom; - -in vec2 v_objectUV; +export const godRaysFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_density: f32, + u_spotty: f32, + u_midSize: f32, + u_midIntensity: f32, + u_intensity: f32, + u_bloom: f32, + u_colorBack: vec4f, + u_colorBloom: vec4f, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; +@group(1) @binding(0) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(1) var u_noiseTexture_samp: sampler; -out vec4 fragColor; +${vertexOutputStruct} ${ declarePI } ${ rotation2 } ${ textureRandomizerR } -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = randomR(i); - float b = randomR(i + vec2(1.0, 0.0)); - float c = randomR(i + vec2(0.0, 1.0)); - float d = randomR(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); + +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = randomR(i); + let b = randomR(i + vec2f(1.0, 0.0)); + let c = randomR(i + vec2f(0.0, 1.0)); + let d = randomR(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } ${ proceduralHash11 } -float raysShape(vec2 uv, float r, float freq, float intensity, float radius) { - float a = atan(uv.y, uv.x); - vec2 left = vec2(a * freq, r); - vec2 right = vec2(fract(a / TWO_PI) * TWO_PI * freq, r); - float n_left = pow(valueNoise(left), intensity); - float n_right = pow(valueNoise(right), intensity); - float shape = mix(n_right, n_left, smoothstep(-.15, .15, uv.x)); +fn raysShape(uv: vec2f, r: f32, freq: f32, intensity_val: f32, radius: f32) -> f32 { + let a = atan2(uv.y, uv.x); + let left = vec2f(a * freq, r); + let right = vec2f(fract(a / TWO_PI) * TWO_PI * freq, r); + let n_left = pow(valueNoise(left), intensity_val); + let n_right = pow(valueNoise(right), intensity_val); + let shape = mix(n_right, n_left, smoothstep(-0.15, 0.15, uv.x)); return shape; } -void main() { - vec2 shape_uv = v_objectUV; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + let shape_uv = input.v_objectUV; - float t = .2 * u_time; + let t = 0.2 * u.u_time; - float radius = length(shape_uv); - float spots = 6.5 * abs(u_spotty); + let radius = length(shape_uv); + let spots = 6.5 * abs(u.u_spotty); - float intensity = 4. - 3. * clamp(u_intensity, 0., 1.); + let intensity = 4.0 - 3.0 * clamp(u.u_intensity, 0.0, 1.0); - float delta = 1. - smoothstep(0., 1., radius); + let delta = 1.0 - smoothstep(0.0, 1.0, radius); - float midSize = 10. * abs(u_midSize); - float ms_lo = 0.02 * midSize; - float ms_hi = max(midSize, 1e-6); - float middleShape = pow(u_midIntensity, 0.3) * (1. - smoothstep(ms_lo, ms_hi, 3.0 * radius)); + let midSize = 10.0 * abs(u.u_midSize); + let ms_lo = 0.02 * midSize; + let ms_hi = max(midSize, 1e-6); + var middleShape = pow(u.u_midIntensity, 0.3) * (1.0 - smoothstep(ms_lo, ms_hi, 3.0 * radius)); middleShape = pow(middleShape, 5.0); - vec3 accumColor = vec3(0.0); - float accumAlpha = 0.0; + var accumColor = vec3f(0.0); + var accumAlpha: f32 = 0.0; - for (int i = 0; i < ${ godRaysMeta.maxColorCount }; i++) { - if (i >= int(u_colorsCount)) break; + for (var i: i32 = 0; i < ${ godRaysMeta.maxColorCount }; i++) { + if (i >= i32(u.u_colorsCount)) { break; } - vec2 rotatedUV = rotate(shape_uv, float(i) + 1.0); + let rotatedUV = rotate(shape_uv, f32(i) + 1.0); - float r1 = radius * (1.0 + 0.4 * float(i)) - 3.0 * t; - float r2 = 0.5 * radius * (1.0 + spots) - 2.0 * t; - float density = 6. * u_density + step(.5, u_density) * pow(4.5 * (u_density - .5), 4.); - float f = mix(1.0, 3.0 + 0.5 * float(i), hash11(float(i) * 15.)) * density; + let r1 = radius * (1.0 + 0.4 * f32(i)) - 3.0 * t; + let r2 = 0.5 * radius * (1.0 + spots) - 2.0 * t; + let density_val = 6.0 * u.u_density + step(0.5, u.u_density) * pow(4.5 * (u.u_density - 0.5), 4.0); + let f = mix(1.0, 3.0 + 0.5 * f32(i), hash11(f32(i) * 15.0)) * density_val; - float ray = raysShape(rotatedUV, r1, 5.0 * f, intensity, radius); + var ray = raysShape(rotatedUV, r1, 5.0 * f, intensity, radius); ray *= raysShape(rotatedUV, r2, 4.0 * f, intensity, radius); - ray += (1. + 4. * ray) * middleShape; + ray += (1.0 + 4.0 * ray) * middleShape; ray = clamp(ray, 0.0, 1.0); - float srcAlpha = u_colors[i].a * ray; - vec3 srcColor = u_colors[i].rgb * srcAlpha; + let srcAlpha = u.u_colors[i].a * ray; + let srcColor = u.u_colors[i].rgb * srcAlpha; - vec3 alphaBlendColor = accumColor + (1.0 - accumAlpha) * srcColor; - float alphaBlendAlpha = accumAlpha + (1.0 - accumAlpha) * srcAlpha; + let alphaBlendColor = accumColor + (1.0 - accumAlpha) * srcColor; + let alphaBlendAlpha = accumAlpha + (1.0 - accumAlpha) * srcAlpha; - vec3 addBlendColor = accumColor + srcColor; - float addBlendAlpha = accumAlpha + srcAlpha; + let addBlendColor = accumColor + srcColor; + let addBlendAlpha = accumAlpha + srcAlpha; - accumColor = mix(alphaBlendColor, addBlendColor, u_bloom); - accumAlpha = mix(alphaBlendAlpha, addBlendAlpha, u_bloom); + accumColor = mix(alphaBlendColor, addBlendColor, u.u_bloom); + accumAlpha = mix(alphaBlendAlpha, addBlendAlpha, u.u_bloom); } - float overlayAlpha = u_colorBloom.a; - vec3 overlayColor = u_colorBloom.rgb * overlayAlpha; + let overlayAlpha = u.u_colorBloom.a; + let overlayColor = u.u_colorBloom.rgb * overlayAlpha; - vec3 colorWithOverlay = accumColor + accumAlpha * overlayColor; - accumColor = mix(accumColor, colorWithOverlay, u_bloom); + let colorWithOverlay = accumColor + accumAlpha * overlayColor; + accumColor = mix(accumColor, colorWithOverlay, u.u_bloom); - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; - vec3 color = accumColor + (1. - accumAlpha) * bgColor; - float opacity = accumAlpha + (1. - accumAlpha) * u_colorBack.a; - color = clamp(color, 0., 1.); - opacity = clamp(opacity, 0., 1.); + var color = accumColor + (1.0 - accumAlpha) * bgColor; + var opacity = accumAlpha + (1.0 - accumAlpha) * u.u_colorBack.a; + color = clamp(color, vec3f(0.0), vec3f(1.0)); + opacity = clamp(opacity, 0.0, 1.0); ${ colorBandingFix } - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/grain-gradient.ts b/packages/shaders/src/shaders/grain-gradient.ts index 8d7ec57d6..84a41aaec 100644 --- a/packages/shaders/src/shaders/grain-gradient.ts +++ b/packages/shaders/src/shaders/grain-gradient.ts @@ -5,8 +5,11 @@ import { type ShaderSizingUniforms, } from '../shader-sizing.js'; import { + systemUniformFields, + vertexOutputStruct, simplexNoise, declarePI, + glslMod, rotation2, textureRandomizerR, proceduralHash11, @@ -19,7 +22,7 @@ export const grainGradientMeta = { /** * Multi-color gradients with grainy, noise-textured distortion available in 7 animated abstract forms. * - * Note: grains are calculated using gl_FragCoord & u_resolution, meaning grains don't react to scaling and fit + * Note: grains are calculated using input.position & u_resolution, meaning grains don't react to scaling and fit * * Fragment shader uniforms: * - u_time (float): Animation time @@ -64,71 +67,56 @@ export const grainGradientMeta = { * */ -// language=GLSL -export const grainGradientFragmentShader: string = `#version 300 es -precision lowp float; - -uniform mediump float u_time; -uniform mediump vec2 u_resolution; -uniform mediump float u_pixelRatio; - -uniform sampler2D u_noiseTexture; - -uniform vec4 u_colorBack; -uniform vec4 u_colors[${ grainGradientMeta.maxColorCount }]; -uniform float u_colorsCount; -uniform float u_softness; -uniform float u_intensity; -uniform float u_noise; -uniform float u_shape; - -uniform mediump float u_originX; -uniform mediump float u_originY; -uniform mediump float u_worldWidth; -uniform mediump float u_worldHeight; -uniform mediump float u_fit; - -uniform mediump float u_scale; -uniform mediump float u_rotation; -uniform mediump float u_offsetX; -uniform mediump float u_offsetY; +export const grainGradientFragmentShader: string = ` +struct Uniforms { + ${ systemUniformFields } + u_colorsCount: f32, + u_softness: f32, + u_intensity: f32, + u_noise: f32, + u_shape: f32, + u_colorBack: vec4f, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; -in vec2 v_objectUV; -in vec2 v_patternUV; -in vec2 v_objectBoxSize; -in vec2 v_patternBoxSize; +@group(1) @binding(0) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(1) var u_noiseTexture_samp: sampler; -out vec4 fragColor; +${ vertexOutputStruct } ${ declarePI } +${ glslMod } ${ simplexNoise } ${ rotation2 } ${ textureRandomizerR } -float valueNoiseR(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = randomR(i); - float b = randomR(i + vec2(1.0, 0.0)); - float c = randomR(i + vec2(0.0, 1.0)); - float d = randomR(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); +fn valueNoiseR(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = randomR(i); + let b = randomR(i + vec2f(1.0, 0.0)); + let c = randomR(i + vec2f(0.0, 1.0)); + let d = randomR(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } -vec4 fbmR(vec2 n0, vec2 n1, vec2 n2, vec2 n3) { - float amplitude = 0.2; - vec4 total = vec4(0.); - for (int i = 0; i < 3; i++) { +fn fbmR(n0_in: vec2f, n1_in: vec2f, n2_in: vec2f, n3_in: vec2f) -> vec4f { + var amplitude: f32 = 0.2; + var total = vec4f(0.0); + var n0 = n0_in; + var n1 = n1_in; + var n2 = n2_in; + var n3 = n3_in; + for (var i: i32 = 0; i < 3; i++) { n0 = rotate(n0, 0.3); n1 = rotate(n1, 0.3); n2 = rotate(n2, 0.3); n3 = rotate(n3, 0.3); - total.x += valueNoiseR(n0) * amplitude; - total.y += valueNoiseR(n1) * amplitude; - total.z += valueNoiseR(n2) * amplitude; - total.z += valueNoiseR(n3) * amplitude; + total = vec4f(total.x + valueNoiseR(n0) * amplitude, total.y + valueNoiseR(n1) * amplitude, total.z + valueNoiseR(n2) * amplitude, total.w); + total = vec4f(total.x, total.y, total.z + valueNoiseR(n3) * amplitude, total.w); n0 *= 1.99; n1 *= 1.99; n2 *= 1.99; @@ -140,200 +128,201 @@ vec4 fbmR(vec2 n0, vec2 n1, vec2 n2, vec2 n3) { ${ proceduralHash11 } -vec2 truchet(vec2 uv, float idx){ - idx = fract(((idx - .5) * 2.)); +fn truchet(uv_in: vec2f, idx_in: f32) -> vec2f { + var uv = uv_in; + var idx = fract(((idx_in - 0.5) * 2.0)); if (idx > 0.75) { - uv = vec2(1.0) - uv; + uv = vec2f(1.0) - uv; } else if (idx > 0.5) { - uv = vec2(1.0 - uv.x, uv.y); + uv = vec2f(1.0 - uv.x, uv.y); } else if (idx > 0.25) { - uv = 1.0 - vec2(1.0 - uv.x, uv.y); + uv = vec2f(1.0) - vec2f(1.0 - uv.x, uv.y); } return uv; } -void main() { +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { - const float firstFrameOffset = 7.; - float t = .1 * (u_time + firstFrameOffset); + let firstFrameOffset: f32 = 7.0; + var t: f32 = 0.1 * (u.u_time + firstFrameOffset); - vec2 shape_uv = vec2(0.); - vec2 grain_uv = vec2(0.); + var shape_uv = vec2f(0.0); + var grain_uv = vec2f(0.0); - float r = u_rotation * PI / 180.; - float cr = cos(r); - float sr = sin(r); - mat2 graphicRotation = mat2(cr, sr, -sr, cr); - vec2 graphicOffset = vec2(-u_offsetX, u_offsetY); + let r = u.u_rotation * PI / 180.0; + let cr = cos(r); + let sr = sin(r); + let graphicRotation = mat2x2f(cr, sr, -sr, cr); + let graphicOffset = vec2f(-u.u_offsetX, u.u_offsetY); - if (u_shape > 3.5) { - shape_uv = v_objectUV; + if (u.u_shape > 3.5) { + shape_uv = input.v_objectUV; grain_uv = shape_uv; // apply inverse transform to grain_uv so it respects the originXY grain_uv = transpose(graphicRotation) * grain_uv; - grain_uv *= u_scale; + grain_uv *= u.u_scale; grain_uv -= graphicOffset; - grain_uv *= v_objectBoxSize; - grain_uv *= .7; + grain_uv *= input.v_objectBoxSize; + grain_uv *= 0.7; } else { - shape_uv = .5 * v_patternUV; - grain_uv = 100. * v_patternUV; + shape_uv = 0.5 * input.v_patternUV; + grain_uv = 100.0 * input.v_patternUV; // apply inverse transform to grain_uv so it respects the originXY grain_uv = transpose(graphicRotation) * grain_uv; - grain_uv *= u_scale; - if (u_fit > 0.) { - vec2 givenBoxSize = vec2(u_worldWidth, u_worldHeight); - givenBoxSize = max(givenBoxSize, vec2(1.)) * u_pixelRatio; - float patternBoxRatio = givenBoxSize.x / givenBoxSize.y; - vec2 patternBoxGivenSize = vec2( - (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x, - (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y + grain_uv *= u.u_scale; + if (u.u_fit > 0.0) { + var givenBoxSize = vec2f(u.u_worldWidth, u.u_worldHeight); + givenBoxSize = max(givenBoxSize, vec2f(1.0)) * u.u_pixelRatio; + var patternBoxRatio = givenBoxSize.x / givenBoxSize.y; + let patternBoxGivenSize = vec2f( + select(givenBoxSize.x, u.u_resolution.x, u.u_worldWidth == 0.0), + select(givenBoxSize.y, u.u_resolution.y, u.u_worldHeight == 0.0) ); patternBoxRatio = patternBoxGivenSize.x / patternBoxGivenSize.y; - float patternBoxNoFitBoxWidth = patternBoxRatio * min(patternBoxGivenSize.x / patternBoxRatio, patternBoxGivenSize.y); - grain_uv /= (patternBoxNoFitBoxWidth / v_patternBoxSize.x); + let patternBoxNoFitBoxWidth = patternBoxRatio * min(patternBoxGivenSize.x / patternBoxRatio, patternBoxGivenSize.y); + grain_uv /= (patternBoxNoFitBoxWidth / input.v_patternBoxSize.x); } - vec2 patternBoxScale = u_resolution.xy / v_patternBoxSize; + let patternBoxScale = u.u_resolution.xy / input.v_patternBoxSize; grain_uv -= graphicOffset / patternBoxScale; grain_uv *= 1.6; } - float shape = 0.; + var shape: f32 = 0.0; - if (u_shape < 1.5) { + if (u.u_shape < 1.5) { // Sine wave - float wave = cos(.5 * shape_uv.x - 4. * t) * sin(1.5 * shape_uv.x + 2. * t) * (.75 + .25 * cos(6. * t)); - shape = 1. - smoothstep(-1., 1., shape_uv.y + wave); + let wave = cos(0.5 * shape_uv.x - 4.0 * t) * sin(1.5 * shape_uv.x + 2.0 * t) * (0.75 + 0.25 * cos(6.0 * t)); + shape = 1.0 - smoothstep(-1.0, 1.0, shape_uv.y + wave); - } else if (u_shape < 2.5) { + } else if (u.u_shape < 2.5) { // Grid (dots) - float stripeIdx = floor(2. * shape_uv.x / TWO_PI); - float rand = hash11(stripeIdx * 100.); - rand = sign(rand - .5) * pow(4. * abs(rand), .3); - shape = sin(shape_uv.x) * cos(shape_uv.y - 5. * rand * t); - shape = pow(abs(shape), 4.); + let stripeIdx = floor(2.0 * shape_uv.x / TWO_PI); + var rand = hash11(stripeIdx * 100.0); + rand = sign(rand - 0.5) * pow(4.0 * abs(rand), 0.3); + shape = sin(shape_uv.x) * cos(shape_uv.y - 5.0 * rand * t); + shape = pow(abs(shape), 4.0); - } else if (u_shape < 3.5) { + } else if (u.u_shape < 3.5) { // Truchet pattern - float n2 = valueNoiseR(shape_uv * .4 - 3.75 * t); - shape_uv.x += 10.; - shape_uv *= .6; + var n2 = valueNoiseR(shape_uv * 0.4 - 3.75 * t); + shape_uv = vec2f(shape_uv.x + 10.0, shape_uv.y); + shape_uv *= 0.6; - vec2 tile = truchet(fract(shape_uv), randomR(floor(shape_uv))); + let tile = truchet(fract(shape_uv), randomR(floor(shape_uv))); - float distance1 = length(tile); - float distance2 = length(tile - vec2(1.)); + let distance1 = length(tile); + let distance2 = length(tile - vec2f(1.0)); - n2 -= .5; - n2 *= .1; - shape = smoothstep(.2, .55, distance1 + n2) * (1. - smoothstep(.45, .8, distance1 - n2)); - shape += smoothstep(.2, .55, distance2 + n2) * (1. - smoothstep(.45, .8, distance2 - n2)); + n2 -= 0.5; + n2 *= 0.1; + shape = smoothstep(0.2, 0.55, distance1 + n2) * (1.0 - smoothstep(0.45, 0.8, distance1 - n2)); + shape += smoothstep(0.2, 0.55, distance2 + n2) * (1.0 - smoothstep(0.45, 0.8, distance2 - n2)); shape = pow(shape, 1.5); - } else if (u_shape < 4.5) { + } else if (u.u_shape < 4.5) { // Corners - shape_uv *= .6; - vec2 outer = vec2(.5); + shape_uv *= 0.6; + let outer = vec2f(0.5); - vec2 bl = smoothstep(vec2(0.), outer, shape_uv + vec2(.1 + .1 * sin(3. * t), .2 - .1 * sin(5.25 * t))); - vec2 tr = smoothstep(vec2(0.), outer, 1. - shape_uv); - shape = 1. - bl.x * bl.y * tr.x * tr.y; + var bl = smoothstep(vec2f(0.0), outer, shape_uv + vec2f(0.1 + 0.1 * sin(3.0 * t), 0.2 - 0.1 * sin(5.25 * t))); + var tr = smoothstep(vec2f(0.0), outer, vec2f(1.0) - shape_uv); + shape = 1.0 - bl.x * bl.y * tr.x * tr.y; shape_uv = -shape_uv; - bl = smoothstep(vec2(0.), outer, shape_uv + vec2(.1 + .1 * sin(3. * t), .2 - .1 * cos(5.25 * t))); - tr = smoothstep(vec2(0.), outer, 1. - shape_uv); + bl = smoothstep(vec2f(0.0), outer, shape_uv + vec2f(0.1 + 0.1 * sin(3.0 * t), 0.2 - 0.1 * cos(5.25 * t))); + tr = smoothstep(vec2f(0.0), outer, vec2f(1.0) - shape_uv); shape -= bl.x * bl.y * tr.x * tr.y; - shape = 1. - smoothstep(0., 1., shape); + shape = 1.0 - smoothstep(0.0, 1.0, shape); - } else if (u_shape < 5.5) { + } else if (u.u_shape < 5.5) { // Ripple - shape_uv *= 2.; - float dist = length(.4 * shape_uv); - float waves = sin(pow(dist, 1.2) * 5. - 3. * t) * .5 + .5; + shape_uv *= 2.0; + let dist = length(0.4 * shape_uv); + let waves = sin(pow(dist, 1.2) * 5.0 - 3.0 * t) * 0.5 + 0.5; shape = waves; - } else if (u_shape < 6.5) { + } else if (u.u_shape < 6.5) { // Blob - t *= 2.; + t *= 2.0; - vec2 f1_traj = .25 * vec2(1.3 * sin(t), .2 + 1.3 * cos(.6 * t + 4.)); - vec2 f2_traj = .2 * vec2(1.2 * sin(-t), 1.3 * sin(1.6 * t)); - vec2 f3_traj = .25 * vec2(1.7 * cos(-.6 * t), cos(-1.6 * t)); - vec2 f4_traj = .3 * vec2(1.4 * cos(.8 * t), 1.2 * sin(-.6 * t - 3.)); + let f1_traj = 0.25 * vec2f(1.3 * sin(t), 0.2 + 1.3 * cos(0.6 * t + 4.0)); + let f2_traj = 0.2 * vec2f(1.2 * sin(-t), 1.3 * sin(1.6 * t)); + let f3_traj = 0.25 * vec2f(1.7 * cos(-0.6 * t), cos(-1.6 * t)); + let f4_traj = 0.3 * vec2f(1.4 * cos(0.8 * t), 1.2 * sin(-0.6 * t - 3.0)); - shape = .5 * pow(1. - clamp(0., 1., length(shape_uv + f1_traj)), 5.); - shape += .5 * pow(1. - clamp(0., 1., length(shape_uv + f2_traj)), 5.); - shape += .5 * pow(1. - clamp(0., 1., length(shape_uv + f3_traj)), 5.); - shape += .5 * pow(1. - clamp(0., 1., length(shape_uv + f4_traj)), 5.); + shape = 0.5 * pow(1.0 - clamp(length(shape_uv + f1_traj), 0.0, 1.0), 5.0); + shape += 0.5 * pow(1.0 - clamp(length(shape_uv + f2_traj), 0.0, 1.0), 5.0); + shape += 0.5 * pow(1.0 - clamp(length(shape_uv + f3_traj), 0.0, 1.0), 5.0); + shape += 0.5 * pow(1.0 - clamp(length(shape_uv + f4_traj), 0.0, 1.0), 5.0); - shape = smoothstep(.0, .9, shape); - float edge = smoothstep(.25, .3, shape); - shape = mix(.0, shape, edge); + shape = smoothstep(0.0, 0.9, shape); + let edge = smoothstep(0.25, 0.3, shape); + shape = mix(0.0, shape, edge); } else { // Sphere - shape_uv *= 2.; - float d = 1. - pow(length(shape_uv), 2.); - vec3 pos = vec3(shape_uv, sqrt(max(d, 0.))); - vec3 lightPos = normalize(vec3(cos(1.5 * t), .8, sin(1.25 * t))); - shape = .5 + .5 * dot(lightPos, pos); - shape *= step(0., d); + shape_uv *= 2.0; + let d = 1.0 - pow(length(shape_uv), 2.0); + let pos = vec3f(shape_uv, sqrt(max(d, 0.0))); + let lightPos = normalize(vec3f(cos(1.5 * t), 0.8, sin(1.25 * t))); + shape = 0.5 + 0.5 * dot(lightPos, pos); + shape *= step(0.0, d); } - float baseNoise = snoise(grain_uv * .5); - vec4 fbmVals = fbmR( - .002 * grain_uv + 10., - .003 * grain_uv, - .001 * grain_uv, - rotate(.4 * grain_uv, 2.) + let baseNoise = snoise(grain_uv * 0.5); + let fbmVals = fbmR( + 0.002 * grain_uv + vec2f(10.0), + 0.003 * grain_uv, + 0.001 * grain_uv, + rotate(0.4 * grain_uv, 2.0) ); - float grainDist = baseNoise * snoise(grain_uv * .2) - fbmVals.x - fbmVals.y; - float rawNoise = .75 * baseNoise - fbmVals.w - fbmVals.z; - float noise = clamp(rawNoise, 0., 1.); + let grainDist = baseNoise * snoise(grain_uv * 0.2) - fbmVals.x - fbmVals.y; + let rawNoise = 0.75 * baseNoise - fbmVals.w - fbmVals.z; + let noise = clamp(rawNoise, 0.0, 1.0); - shape += u_intensity * 2. / u_colorsCount * (grainDist + .5); - shape += u_noise * 10. / u_colorsCount * noise; + shape += u.u_intensity * 2.0 / u.u_colorsCount * (grainDist + 0.5); + shape += u.u_noise * 10.0 / u.u_colorsCount * noise; - float aa = fwidth(shape); + let aa = fwidth(shape); - shape = clamp(shape - .5 / u_colorsCount, 0., 1.); - float totalShape = smoothstep(0., u_softness + 2. * aa, clamp(shape * u_colorsCount, 0., 1.)); - float mixer = shape * (u_colorsCount - 1.); + shape = clamp(shape - 0.5 / u.u_colorsCount, 0.0, 1.0); + let totalShape = smoothstep(0.0, u.u_softness + 2.0 * aa, clamp(shape * u.u_colorsCount, 0.0, 1.0)); + let mixer = shape * (u.u_colorsCount - 1.0); - int cntStop = int(u_colorsCount) - 1; - vec4 gradient = u_colors[0]; - gradient.rgb *= gradient.a; - for (int i = 1; i < ${ grainGradientMeta.maxColorCount }; i++) { - if (i > cntStop) break; + let cntStop = i32(u.u_colorsCount) - 1; + var gradient = u.u_colors[0]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); + for (var i: i32 = 1; i < ${ grainGradientMeta.maxColorCount }; i++) { + if (i > cntStop) { break; } - float localT = clamp(mixer - float(i - 1), 0., 1.); - localT = smoothstep(.5 - .5 * u_softness - aa, .5 + .5 * u_softness + aa, localT); + var localT = clamp(mixer - f32(i - 1), 0.0, 1.0); + localT = smoothstep(0.5 - 0.5 * u.u_softness - aa, 0.5 + 0.5 * u.u_softness + aa, localT); - vec4 c = u_colors[i]; - c.rgb *= c.a; + var c = u.u_colors[i]; + c = vec4f(c.rgb * c.a, c.a); gradient = mix(gradient, c, localT); } - vec3 color = gradient.rgb * totalShape; - float opacity = gradient.a * totalShape; + var color = gradient.rgb * totalShape; + var opacity = gradient.a * totalShape; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; color = color + bgColor * (1.0 - opacity); - opacity = opacity + u_colorBack.a * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/halftone-cmyk.ts b/packages/shaders/src/shaders/halftone-cmyk.ts index 8d1bc4a0b..7e905466a 100644 --- a/packages/shaders/src/shaders/halftone-cmyk.ts +++ b/packages/shaders/src/shaders/halftone-cmyk.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI } from '../shader-utils.js'; /** * CMYK halftone printing effect applied to images with customizable dot patterns @@ -50,280 +50,292 @@ import { declarePI } from '../shader-utils.js'; * */ -// language=GLSL -export const halftoneCmykFragmentShader: string = `#version 300 es -precision mediump float; - -uniform sampler2D u_image; -uniform float u_imageAspectRatio; - -uniform vec4 u_colorBack; -uniform vec4 u_colorC; -uniform vec4 u_colorM; -uniform vec4 u_colorY; -uniform vec4 u_colorK; -uniform float u_size; -uniform float u_minDot; -uniform float u_contrast; -uniform float u_grainSize; -uniform float u_grainMixer; -uniform float u_grainOverlay; -uniform float u_gridNoise; -uniform float u_softness; -uniform float u_floodC; -uniform float u_floodM; -uniform float u_floodY; -uniform float u_floodK; -uniform float u_gainC; -uniform float u_gainM; -uniform float u_gainY; -uniform float u_gainK; -uniform float u_type; -uniform sampler2D u_noiseTexture; - -in vec2 v_imageUV; -out vec4 fragColor; - -const float shiftC = -.5; -const float shiftM = -.25; -const float shiftY = .2; -const float shiftK = 0.; - -// Precomputed sin/cos for rotation angles (15°, 75°, 0°, 45°) -const float cosC = 0.9659258; const float sinC = 0.2588190; // 15° -const float cosM = 0.2588190; const float sinM = 0.9659258; // 75° -const float cosY = 1.0; const float sinY = 0.0; // 0° -const float cosK = 0.7071068; const float sinK = 0.7071068; // 45° - -${ declarePI } - -vec2 randomRG(vec2 p) { - vec2 uv = floor(p) / 100. + .5; - return texture(u_noiseTexture, fract(uv)).rg; +export const halftoneCmykFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorBack: vec4f, + u_colorC: vec4f, + u_colorM: vec4f, + u_colorY: vec4f, + u_colorK: vec4f, + u_size: f32, + u_minDot: f32, + u_contrast: f32, + u_grainSize: f32, + u_grainMixer: f32, + u_grainOverlay: f32, + u_gridNoise: f32, + u_softness: f32, + u_floodC: f32, + u_floodM: f32, + u_floodY: f32, + u_floodK: f32, + u_gainC: f32, + u_gainM: f32, + u_gainY: f32, + u_gainK: f32, + u_type: f32, } -vec3 hash23(vec2 p) { - vec3 p3 = fract(vec3(p.xyx) * vec3(0.3183099, 0.3678794, 0.3141592)) + 0.1; - p3 += dot(p3, p3.yzx + 19.19); - return fract(vec3(p3.x * p3.y, p3.y * p3.z, p3.z * p3.x)); +@group(0) @binding(0) var u: Uniforms; + +@group(1) @binding(0) var u_image_tex: texture_2d; +@group(1) @binding(1) var u_image_samp: sampler; +@group(1) @binding(2) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(3) var u_noiseTexture_samp: sampler; + +${vertexOutputStruct} + +${declarePI} + +const shiftC: f32 = -0.5; +const shiftM: f32 = -0.25; +const shiftY: f32 = 0.2; +const shiftK: f32 = 0.0; + +// Precomputed sin/cos for rotation angles (15deg, 75deg, 0deg, 45deg) +const cosC: f32 = 0.9659258; const sinC: f32 = 0.2588190; // 15deg +const cosM: f32 = 0.2588190; const sinM: f32 = 0.9659258; // 75deg +const cosY_c: f32 = 1.0; const sinY_c: f32 = 0.0; // 0deg +const cosK: f32 = 0.7071068; const sinK: f32 = 0.7071068; // 45deg + +fn fwidth_f32(v: f32) -> f32 { + return abs(dpdx(v)) + abs(dpdy(v)); +} + +fn randomRG(p: vec2f) -> vec2f { + let uv = floor(p) / 100.0 + vec2f(0.5); + return textureSampleLevel(u_noiseTexture_tex, u_noiseTexture_samp, fract(uv), 0.0).rg; } -float sst(float edge0, float edge1, float x) { +fn hash23(p: vec2f) -> vec3f { + var p3 = fract(vec3f(p.x, p.y, p.x) * vec3f(0.3183099, 0.3678794, 0.3141592)) + vec3f(0.1); + p3 += vec3f(dot(p3, vec3f(p3.y, p3.z, p3.x) + vec3f(19.19))); + return fract(vec3f(p3.x * p3.y, p3.y * p3.z, p3.z * p3.x)); +} + +fn sst(edge0: f32, edge1: f32, x: f32) -> f32 { return smoothstep(edge0, edge1, x); } -vec3 valueNoise3(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - vec3 a = hash23(i); - vec3 b = hash23(i + vec2(1.0, 0.0)); - vec3 c = hash23(i + vec2(0.0, 1.0)); - vec3 d = hash23(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - vec3 x1 = mix(a, b, u.x); - vec3 x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); +fn valueNoise3(st: vec2f) -> vec3f { + let i = floor(st); + let f = fract(st); + let a = hash23(i); + let b = hash23(i + vec2f(1.0, 0.0)); + let c = hash23(i + vec2f(0.0, 1.0)); + let d = hash23(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } -float getUvFrame(vec2 uv, vec2 pad) { - float left = smoothstep(-pad.x, 0., uv.x); - float right = smoothstep(1. + pad.x, 1., uv.x); - float bottom = smoothstep(-pad.y, 0., uv.y); - float top = smoothstep(1. + pad.y, 1., uv.y); +fn getUvFrame(uv: vec2f, pad: vec2f) -> f32 { + let left = smoothstep(-pad.x, 0.0, uv.x); + let right = smoothstep(1.0 + pad.x, 1.0, uv.x); + let bottom = smoothstep(-pad.y, 0.0, uv.y); + let top = smoothstep(1.0 + pad.y, 1.0, uv.y); return left * right * bottom * top; } -vec4 RGBAtoCMYK(vec4 rgba) { - float k = 1. - max(max(rgba.r, rgba.g), rgba.b); - float denom = 1. - k; - vec3 cmy = vec3(0.); +fn RGBAtoCMYK(rgba: vec4f) -> vec4f { + let k = 1.0 - max(max(rgba.r, rgba.g), rgba.b); + let denom = 1.0 - k; + var cmy = vec3f(0.0); if (denom > 1e-5) { - cmy = (1. - rgba.rgb - vec3(k)) / denom; + cmy = (vec3f(1.0) - rgba.rgb - vec3f(k)) / denom; } - return vec4(cmy, k) * rgba.a; + return vec4f(cmy, k) * rgba.a; } -vec3 applyContrast(vec3 rgb) { - return clamp((rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0); +fn applyContrast(rgb: vec3f) -> vec3f { + return clamp((rgb - vec3f(0.5)) * u.u_contrast + vec3f(0.5), vec3f(0.0), vec3f(1.0)); } // Single-component CMYK extractors with contrast built-in, alpha-aware -float getCyan(vec4 rgba) { - vec3 c = clamp((rgba.rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0); - float maxRGB = max(max(c.r, c.g), c.b); - return (maxRGB > 1e-5 ? (maxRGB - c.r) / maxRGB : 0.) * rgba.a; +fn getCyan(rgba: vec4f) -> f32 { + let c = clamp((rgba.rgb - vec3f(0.5)) * u.u_contrast + vec3f(0.5), vec3f(0.0), vec3f(1.0)); + let maxRGB = max(max(c.r, c.g), c.b); + return select(0.0, (maxRGB - c.r) / maxRGB, maxRGB > 1e-5) * rgba.a; } -float getMagenta(vec4 rgba) { - vec3 c = clamp((rgba.rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0); - float maxRGB = max(max(c.r, c.g), c.b); - return (maxRGB > 1e-5 ? (maxRGB - c.g) / maxRGB : 0.) * rgba.a; +fn getMagenta(rgba: vec4f) -> f32 { + let c = clamp((rgba.rgb - vec3f(0.5)) * u.u_contrast + vec3f(0.5), vec3f(0.0), vec3f(1.0)); + let maxRGB = max(max(c.r, c.g), c.b); + return select(0.0, (maxRGB - c.g) / maxRGB, maxRGB > 1e-5) * rgba.a; } -float getYellow(vec4 rgba) { - vec3 c = clamp((rgba.rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0); - float maxRGB = max(max(c.r, c.g), c.b); - return (maxRGB > 1e-5 ? (maxRGB - c.b) / maxRGB : 0.) * rgba.a; +fn getYellow(rgba: vec4f) -> f32 { + let c = clamp((rgba.rgb - vec3f(0.5)) * u.u_contrast + vec3f(0.5), vec3f(0.0), vec3f(1.0)); + let maxRGB = max(max(c.r, c.g), c.b); + return select(0.0, (maxRGB - c.b) / maxRGB, maxRGB > 1e-5) * rgba.a; } -float getBlack(vec4 rgba) { - vec3 c = clamp((rgba.rgb - 0.5) * u_contrast + 0.5, 0.0, 1.0); - return (1. - max(max(c.r, c.g), c.b)) * rgba.a; +fn getBlack(rgba: vec4f) -> f32 { + let c = clamp((rgba.rgb - vec3f(0.5)) * u.u_contrast + vec3f(0.5), vec3f(0.0), vec3f(1.0)); + return (1.0 - max(max(c.r, c.g), c.b)) * rgba.a; } -vec2 cellCenterPos(vec2 uv, vec2 cellOffset, float channelIdx) { - vec2 cellCenter = floor(uv) + .5 + cellOffset; - return cellCenter + (randomRG(cellCenter + channelIdx * 50.) - .5) * u_gridNoise; +fn cellCenterPos(uv: vec2f, cellOffset: vec2f, channelIdx: f32) -> vec2f { + let cellCenter = floor(uv) + vec2f(0.5) + cellOffset; + return cellCenter + (randomRG(cellCenter + vec2f(channelIdx * 50.0)) - vec2f(0.5)) * u.u_gridNoise; } -vec2 gridToImageUV(vec2 cellCenter, float cosA, float sinA, float shift, vec2 pad) { - vec2 uvGrid = mat2(cosA, -sinA, sinA, cosA) * (cellCenter - shift); - return uvGrid * pad + 0.5; +fn gridToImageUV(cellCenter: vec2f, cosA: f32, sinA: f32, shift: f32, pad: vec2f) -> vec2f { + let uvGrid = mat2x2f(cosA, -sinA, sinA, cosA) * (cellCenter - vec2f(shift)); + return uvGrid * pad + vec2f(0.5); } -void colorMask(vec2 pos, vec2 cellCenter, float rad, float transparency, float grain, float channelAddon, float channelgain, float generalComp, bool isJoined, inout float outMask) { - float dist = length(pos - cellCenter); +fn colorMask(pos: vec2f, cellCenter: vec2f, rad: f32, transparency: f32, grain: f32, channelAddon: f32, channelgain: f32, generalComp: f32, isJoined: bool) -> f32 { + let dist = length(pos - cellCenter); - float radius = rad; - radius *= (1. + generalComp); - radius += (.15 + channelgain * radius); - radius = max(0., radius); - radius = mix(0., radius, transparency); + var radius = rad; + radius *= (1.0 + generalComp); + radius += (0.15 + channelgain * radius); + radius = max(0.0, radius); + radius = mix(0.0, radius, transparency); radius += channelAddon; - radius *= (1. - grain); + radius *= (1.0 - grain); - float mask = 1. - sst(0., radius, dist); + var mask = 1.0 - sst(0.0, radius, dist); if (isJoined) { // ink or sharp (joined) mask = pow(mask, 1.2); } else { // dots (separate) - mask = sst(.5 - .5 * u_softness, .51 + .49 * u_softness, mask); + mask = sst(0.5 - 0.5 * u.u_softness, 0.51 + 0.49 * u.u_softness, mask); } - mask *= mix(1., mix(.5, 1., 1.5 * radius), u_softness); - outMask += mask; + mask *= mix(1.0, mix(0.5, 1.0, 1.5 * radius), u.u_softness); + return mask; } -vec3 applyInk(vec3 paper, vec3 inkColor, float cov) { - vec3 inkEffect = mix(vec3(1.0), inkColor, clamp(cov, 0.0, 1.0)); +fn applyInk(paper: vec3f, inkColor: vec3f, cov: f32) -> vec3f { + let inkEffect = mix(vec3f(1.0), inkColor, clamp(cov, 0.0, 1.0)); return paper * inkEffect; } -void main() { - vec2 uv = v_imageUV; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + let uv = input.v_imageUV; - float cellsPerSide = mix(400.0, 7.0, pow(u_size, 0.7)); - float cellSizeY = 1.0 / cellsPerSide; - vec2 pad = cellSizeY * vec2(1.0 / u_imageAspectRatio, 1.0); - vec2 uvGrid = (uv - .5) / pad; - float insideImageBox = getUvFrame(uv, pad); + let cellsPerSide = mix(400.0, 7.0, pow(u.u_size, 0.7)); + let cellSizeY = 1.0 / cellsPerSide; + let pad = cellSizeY * vec2f(1.0 / u.u_imageAspectRatio, 1.0); + let uvGrid = (uv - vec2f(0.5)) / pad; + var insideImageBox = getUvFrame(uv, pad); - float generalComp = .1 * u_softness + .1 * u_gridNoise + .1 * (1. - step(0.5, u_type)) * (1.5 - u_softness); + let generalComp = 0.1 * u.u_softness + 0.1 * u.u_gridNoise + 0.1 * (1.0 - step(0.5, u.u_type)) * (1.5 - u.u_softness); - vec2 uvC = mat2(cosC, sinC, -sinC, cosC) * uvGrid + shiftC; - vec2 uvM = mat2(cosM, sinM, -sinM, cosM) * uvGrid + shiftM; - vec2 uvY = mat2(cosY, sinY, -sinY, cosY) * uvGrid + shiftY; - vec2 uvK = mat2(cosK, sinK, -sinK, cosK) * uvGrid + shiftK; + let uvC = mat2x2f(cosC, sinC, -sinC, cosC) * uvGrid + vec2f(shiftC); + let uvM = mat2x2f(cosM, sinM, -sinM, cosM) * uvGrid + vec2f(shiftM); + let uvY_val = mat2x2f(cosY_c, sinY_c, -sinY_c, cosY_c) * uvGrid + vec2f(shiftY); + let uvK = mat2x2f(cosK, sinK, -sinK, cosK) * uvGrid + vec2f(shiftK); - vec2 grainSize = mix(2000., 200., u_grainSize) * vec2(1., 1. / u_imageAspectRatio); - vec2 grainUV = (v_imageUV - .5) * grainSize + .5; - vec3 noiseValues = valueNoise3(grainUV); - float grain = sst(.55, 1., noiseValues.r); - grain *= u_grainMixer; + let grainSizeVal = mix(2000.0, 200.0, u.u_grainSize) * vec2f(1.0, 1.0 / u.u_imageAspectRatio); + let grainUV = (input.v_imageUV - vec2f(0.5)) * grainSizeVal + vec2f(0.5); + let noiseValues = valueNoise3(grainUV); + var grain = sst(0.55, 1.0, noiseValues.r); + grain *= u.u_grainMixer; - vec4 outMask = vec4(0.); - bool isJoined = u_type > 0.5; + var outMask = vec4f(0.0); + let isJoined = u.u_type > 0.5; - if (u_type < 1.5) { + if (u.u_type < 1.5) { // dots or ink: per-cell color sampling - for (int dy = -1; dy <= 1; dy++) { - for (int dx = -1; dx <= 1; dx++) { - vec2 cellOffset = vec2(float(dx), float(dy)); - - vec2 cellCenterC = cellCenterPos(uvC, cellOffset, 0.); - vec4 texC = texture(u_image, gridToImageUV(cellCenterC, cosC, sinC, shiftC, pad)); - colorMask(uvC, cellCenterC, getCyan(texC), insideImageBox * texC.a, grain, u_floodC, u_gainC, generalComp, isJoined, outMask[0]); - - vec2 cellCenterM = cellCenterPos(uvM, cellOffset, 1.); - vec4 texM = texture(u_image, gridToImageUV(cellCenterM, cosM, sinM, shiftM, pad)); - colorMask(uvM, cellCenterM, getMagenta(texM), insideImageBox * texM.a, grain, u_floodM, u_gainM, generalComp, isJoined, outMask[1]); - - vec2 cellCenterY = cellCenterPos(uvY, cellOffset, 2.); - vec4 texY = texture(u_image, gridToImageUV(cellCenterY, cosY, sinY, shiftY, pad)); - colorMask(uvY, cellCenterY, getYellow(texY), insideImageBox * texY.a, grain, u_floodY, u_gainY, generalComp, isJoined, outMask[2]); - - vec2 cellCenterK = cellCenterPos(uvK, cellOffset, 3.); - vec4 texK = texture(u_image, gridToImageUV(cellCenterK, cosK, sinK, shiftK, pad)); - colorMask(uvK, cellCenterK, getBlack(texK), insideImageBox * texK.a, grain, u_floodK, u_gainK, generalComp, isJoined, outMask[3]); + for (var dy: i32 = -1; dy <= 1; dy++) { + for (var dx: i32 = -1; dx <= 1; dx++) { + let cellOffset = vec2f(f32(dx), f32(dy)); + + let cellCenterC_val = cellCenterPos(uvC, cellOffset, 0.0); + let texC = textureSampleLevel(u_image_tex, u_image_samp, gridToImageUV(cellCenterC_val, cosC, sinC, shiftC, pad), 0.0); + let maskC = colorMask(uvC, cellCenterC_val, getCyan(texC), insideImageBox * texC.a, grain, u.u_floodC, u.u_gainC, generalComp, isJoined); + outMask = vec4f(outMask.x + maskC, outMask.y, outMask.z, outMask.w); + + let cellCenterM_val = cellCenterPos(uvM, cellOffset, 1.0); + let texM = textureSampleLevel(u_image_tex, u_image_samp, gridToImageUV(cellCenterM_val, cosM, sinM, shiftM, pad), 0.0); + let maskM = colorMask(uvM, cellCenterM_val, getMagenta(texM), insideImageBox * texM.a, grain, u.u_floodM, u.u_gainM, generalComp, isJoined); + outMask = vec4f(outMask.x, outMask.y + maskM, outMask.z, outMask.w); + + let cellCenterY_val = cellCenterPos(uvY_val, cellOffset, 2.0); + let texY = textureSampleLevel(u_image_tex, u_image_samp, gridToImageUV(cellCenterY_val, cosY_c, sinY_c, shiftY, pad), 0.0); + let maskY = colorMask(uvY_val, cellCenterY_val, getYellow(texY), insideImageBox * texY.a, grain, u.u_floodY, u.u_gainY, generalComp, isJoined); + outMask = vec4f(outMask.x, outMask.y, outMask.z + maskY, outMask.w); + + let cellCenterK_val = cellCenterPos(uvK, cellOffset, 3.0); + let texK = textureSampleLevel(u_image_tex, u_image_samp, gridToImageUV(cellCenterK_val, cosK, sinK, shiftK, pad), 0.0); + let maskK = colorMask(uvK, cellCenterK_val, getBlack(texK), insideImageBox * texK.a, grain, u.u_floodK, u.u_gainK, generalComp, isJoined); + outMask = vec4f(outMask.x, outMask.y, outMask.z, outMask.w + maskK); } } } else { // sharp: direct px color sampling - vec4 tex = texture(u_image, uv); - tex.rgb = applyContrast(tex.rgb); - insideImageBox *= tex.a; - vec4 cmykOriginal = RGBAtoCMYK(tex); - for (int dy = -1; dy <= 1; dy++) { - for (int dx = -1; dx <= 1; dx++) { - vec2 cellOffset = vec2(float(dx), float(dy)); - - colorMask(uvC, cellCenterPos(uvC, cellOffset, 0.), cmykOriginal.x, insideImageBox, grain, u_floodC, u_gainC, generalComp, isJoined, outMask[0]); - colorMask(uvM, cellCenterPos(uvM, cellOffset, 1.), cmykOriginal.y, insideImageBox, grain, u_floodM, u_gainM, generalComp, isJoined, outMask[1]); - colorMask(uvY, cellCenterPos(uvY, cellOffset, 2.), cmykOriginal.z, insideImageBox, grain, u_floodY, u_gainY, generalComp, isJoined, outMask[2]); - colorMask(uvK, cellCenterPos(uvK, cellOffset, 3.), cmykOriginal.w, insideImageBox, grain, u_floodK, u_gainK, generalComp, isJoined, outMask[3]); + let tex = textureSampleLevel(u_image_tex, u_image_samp, uv, 0.0); + let texContrasted = vec4f(applyContrast(tex.rgb), tex.a); + insideImageBox *= texContrasted.a; + let cmykOriginal = RGBAtoCMYK(texContrasted); + for (var dy: i32 = -1; dy <= 1; dy++) { + for (var dx: i32 = -1; dx <= 1; dx++) { + let cellOffset = vec2f(f32(dx), f32(dy)); + + let maskC = colorMask(uvC, cellCenterPos(uvC, cellOffset, 0.0), cmykOriginal.x, insideImageBox, grain, u.u_floodC, u.u_gainC, generalComp, isJoined); + outMask = vec4f(outMask.x + maskC, outMask.y, outMask.z, outMask.w); + let maskM = colorMask(uvM, cellCenterPos(uvM, cellOffset, 1.0), cmykOriginal.y, insideImageBox, grain, u.u_floodM, u.u_gainM, generalComp, isJoined); + outMask = vec4f(outMask.x, outMask.y + maskM, outMask.z, outMask.w); + let maskY = colorMask(uvY_val, cellCenterPos(uvY_val, cellOffset, 2.0), cmykOriginal.z, insideImageBox, grain, u.u_floodY, u.u_gainY, generalComp, isJoined); + outMask = vec4f(outMask.x, outMask.y, outMask.z + maskY, outMask.w); + let maskK = colorMask(uvK, cellCenterPos(uvK, cellOffset, 3.0), cmykOriginal.w, insideImageBox, grain, u.u_floodK, u.u_gainK, generalComp, isJoined); + outMask = vec4f(outMask.x, outMask.y, outMask.z, outMask.w + maskK); } } } - float shape; - - float C = outMask[0]; - float M = outMask[1]; - float Y = outMask[2]; - float K = outMask[3]; + var C_val = outMask.x; + var M_val = outMask.y; + var Y_val = outMask.z; + var K_val = outMask.w; if (isJoined) { // ink or sharp: apply threshold for joined dots - float th = .5; - float sLeft = th * u_softness; - float sRight = (1. - th) * u_softness + .01; - C = smoothstep(th - sLeft - fwidth(C), th + sRight, C); - M = smoothstep(th - sLeft - fwidth(M), th + sRight, M); - Y = smoothstep(th - sLeft - fwidth(Y), th + sRight, Y); - K = smoothstep(th - sLeft - fwidth(K), th + sRight, K); + let th: f32 = 0.5; + let sLeft = th * u.u_softness; + let sRight = (1.0 - th) * u.u_softness + 0.01; + C_val = smoothstep(th - sLeft - fwidth_f32(C_val), th + sRight, C_val); + M_val = smoothstep(th - sLeft - fwidth_f32(M_val), th + sRight, M_val); + Y_val = smoothstep(th - sLeft - fwidth_f32(Y_val), th + sRight, Y_val); + K_val = smoothstep(th - sLeft - fwidth_f32(K_val), th + sRight, K_val); } - C *= u_colorC.a; - M *= u_colorM.a; - Y *= u_colorY.a; - K *= u_colorK.a; + C_val *= u.u_colorC.a; + M_val *= u.u_colorM.a; + Y_val *= u.u_colorY.a; + K_val *= u.u_colorK.a; - vec3 ink = vec3(1.); - ink = applyInk(ink, u_colorK.rgb, K); - ink = applyInk(ink, u_colorC.rgb, C); - ink = applyInk(ink, u_colorM.rgb, M); - ink = applyInk(ink, u_colorY.rgb, Y); + var ink = vec3f(1.0); + ink = applyInk(ink, u.u_colorK.rgb, K_val); + ink = applyInk(ink, u.u_colorC.rgb, C_val); + ink = applyInk(ink, u.u_colorM.rgb, M_val); + ink = applyInk(ink, u.u_colorY.rgb, Y_val); - shape = clamp(max(max(C, M), max(Y, K)), 0., 1.); + let shape = clamp(max(max(C_val, M_val), max(Y_val, K_val)), 0.0, 1.0); - vec3 color = u_colorBack.rgb * u_colorBack.a; + var color = u.u_colorBack.rgb * u.u_colorBack.a; - float opacity = u_colorBack.a; + var opacity = u.u_colorBack.a; color = mix(color, ink, shape); opacity += shape; - opacity = clamp(opacity, 0., 1.); + opacity = clamp(opacity, 0.0, 1.0); - float grainOverlay = mix(noiseValues.g, noiseValues.b, .5); + var grainOverlay = mix(noiseValues.g, noiseValues.b, 0.5); grainOverlay = pow(grainOverlay, 1.3); - float grainOverlayV = grainOverlay * 2. - 1.; - vec3 grainOverlayColor = vec3(step(0., grainOverlayV)); - float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV); - grainOverlayStrength = pow(grainOverlayStrength, .8); - color = mix(color, grainOverlayColor, .5 * grainOverlayStrength); + let grainOverlayV = grainOverlay * 2.0 - 1.0; + let grainOverlayColor = vec3f(step(0.0, grainOverlayV)); + var grainOverlayStrength = u.u_grainOverlay * abs(grainOverlayV); + grainOverlayStrength = pow(grainOverlayStrength, 0.8); + color = mix(color, grainOverlayColor, 0.5 * grainOverlayStrength); - opacity += .5 * grainOverlayStrength; - opacity = clamp(opacity, 0., 1.); + opacity += 0.5 * grainOverlayStrength; + opacity = clamp(opacity, 0.0, 1.0); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/halftone-dots.ts b/packages/shaders/src/shaders/halftone-dots.ts index b7c734565..950b56fbc 100644 --- a/packages/shaders/src/shaders/halftone-dots.ts +++ b/packages/shaders/src/shaders/halftone-dots.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, proceduralHash21, glslMod } from '../shader-utils.js'; /** * A halftone-dot image filter featuring customizable grids, color palettes, and dot styles. @@ -40,303 +40,313 @@ import { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js'; * */ -// language=GLSL -export const halftoneDotsFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_rotation; - -uniform float u_time; - -uniform vec4 u_colorFront; -uniform vec4 u_colorBack; -uniform float u_radius; -uniform float u_contrast; - -uniform sampler2D u_image; -uniform float u_imageAspectRatio; - -uniform float u_size; -uniform float u_grainMixer; -uniform float u_grainOverlay; -uniform float u_grainSize; -uniform float u_grid; -uniform bool u_originalColors; -uniform bool u_inverted; -uniform float u_type; - -in vec2 v_imageUV; - -out vec4 fragColor; - -${ declarePI } -${ rotation2 } -${ proceduralHash21 } - -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = hash21(i); - float b = hash21(i + vec2(1.0, 0.0)); - float c = hash21(i + vec2(0.0, 1.0)); - float d = hash21(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); +export const halftoneDotsFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorFront: vec4f, + u_colorBack: vec4f, + u_radius: f32, + u_contrast: f32, + u_size: f32, + u_grainMixer: f32, + u_grainOverlay: f32, + u_grainSize: f32, + u_grid: f32, + u_originalColors: f32, + u_inverted: f32, + u_type: f32, } +@group(0) @binding(0) var u: Uniforms; -float lst(float edge0, float edge1, float x) { +@group(1) @binding(0) var u_image_tex: texture_2d; +@group(1) @binding(1) var u_image_samp: sampler; + +${vertexOutputStruct} + +${declarePI} +${rotation2} +${proceduralHash21} +${glslMod} + +struct LumBallResult { + ball: f32, + ballColor: vec4f, +} + +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = hash21(i); + let b = hash21(i + vec2f(1.0, 0.0)); + let c = hash21(i + vec2f(0.0, 1.0)); + let d = hash21(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); +} + +fn lst(edge0: f32, edge1: f32, x: f32) -> f32 { return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); } -float sst(float edge0, float edge1, float x) { +fn sst(edge0: f32, edge1: f32, x: f32) -> f32 { return smoothstep(edge0, edge1, x); } -float getCircle(vec2 uv, float r, float baseR) { - r = mix(.25 * baseR, 0., r); - float d = length(uv - .5); - float aa = fwidth(d); - return 1. - smoothstep(r - aa, r + aa, d); +fn fwidth_f32(v: f32) -> f32 { + return abs(dpdx(v)) + abs(dpdy(v)); } -float getCell(vec2 uv) { - float insideX = step(0.0, uv.x) * (1.0 - step(1.0, uv.x)); - float insideY = step(0.0, uv.y) * (1.0 - step(1.0, uv.y)); +fn getCircle(uv: vec2f, r_in: f32, baseR: f32) -> f32 { + let r = mix(0.25 * baseR, 0.0, r_in); + let d = length(uv - vec2f(0.5)); + let aa = fwidth_f32(d); + return 1.0 - smoothstep(r - aa, r + aa, d); +} + +fn getCell(uv: vec2f) -> f32 { + let insideX = step(0.0, uv.x) * (1.0 - step(1.0, uv.x)); + let insideY = step(0.0, uv.y) * (1.0 - step(1.0, uv.y)); return insideX * insideY; } -float getCircleWithHole(vec2 uv, float r, float baseR) { - float cell = getCell(uv); +fn getCircleWithHole(uv: vec2f, r_in: f32, baseR: f32) -> f32 { + let cell = getCell(uv); - r = mix(.75 * baseR, 0., r); - float rMod = mod(r, .5); + let r = mix(0.75 * baseR, 0.0, r_in); + let rMod = glsl_mod_f32(r, 0.5); - float d = length(uv - .5); - float aa = fwidth(d); - float circle = 1. - smoothstep(rMod - aa, rMod + aa, d); - if (r < .5) { + let d = length(uv - vec2f(0.5)); + let aa = fwidth_f32(d); + let circle = 1.0 - smoothstep(rMod - aa, rMod + aa, d); + if (r < 0.5) { return circle; } else { return cell - circle; } } -float getGooeyBall(vec2 uv, float r, float baseR) { - float d = length(uv - .5); - float sizeRadius = .3; - if (u_grid == 1.) { - sizeRadius = .42; +fn getGooeyBall(uv: vec2f, r: f32, baseR: f32) -> f32 { + var d = length(uv - vec2f(0.5)); + var sizeRadius = 0.3; + if (u.u_grid == 1.0) { + sizeRadius = 0.42; } - sizeRadius = mix(sizeRadius * baseR, 0., r); - d = 1. - sst(0., sizeRadius, d); + sizeRadius = mix(sizeRadius * baseR, 0.0, r); + d = 1.0 - sst(0.0, sizeRadius, d); - d = pow(d, 2. + baseR); + d = pow(d, 2.0 + baseR); return d; } -float getSoftBall(vec2 uv, float r, float baseR) { - float d = length(uv - .5); - float sizeRadius = clamp(baseR, 0., 1.); - sizeRadius = mix(.5 * sizeRadius, 0., r); - d = 1. - lst(0., sizeRadius, d); - float powRadius = 1. - lst(0., 2., baseR); - d = pow(d, 4. + 3. * powRadius); +fn getSoftBall(uv: vec2f, r: f32, baseR: f32) -> f32 { + var d = length(uv - vec2f(0.5)); + let sizeRadius_raw = clamp(baseR, 0.0, 1.0); + let sizeRadius = mix(0.5 * sizeRadius_raw, 0.0, r); + d = 1.0 - lst(0.0, sizeRadius, d); + let powRadius = 1.0 - lst(0.0, 2.0, baseR); + d = pow(d, 4.0 + 3.0 * powRadius); return d; } -float getUvFrame(vec2 uv, vec2 pad) { - float aa = 0.0001; +fn getUvFrame(uv: vec2f, pad: vec2f) -> f32 { + let aa: f32 = 0.0001; - float left = smoothstep(-pad.x, -pad.x + aa, uv.x); - float right = smoothstep(1.0 + pad.x, 1.0 + pad.x - aa, uv.x); - float bottom = smoothstep(-pad.y, -pad.y + aa, uv.y); - float top = smoothstep(1.0 + pad.y, 1.0 + pad.y - aa, uv.y); + let left = smoothstep(-pad.x, -pad.x + aa, uv.x); + let right = smoothstep(1.0 + pad.x, 1.0 + pad.x - aa, uv.x); + let bottom = smoothstep(-pad.y, -pad.y + aa, uv.y); + let top = smoothstep(1.0 + pad.y, 1.0 + pad.y - aa, uv.y); return left * right * bottom * top; } -float sigmoid(float x, float k) { +fn sigmoid(x: f32, k: f32) -> f32 { return 1.0 / (1.0 + exp(-k * (x - 0.5))); } -float getLumAtPx(vec2 uv, float contrast) { - vec4 tex = texture(u_image, uv); - vec3 color = vec3( - sigmoid(tex.r, contrast), - sigmoid(tex.g, contrast), - sigmoid(tex.b, contrast) +fn getLumAtPx(uv: vec2f, contrast: f32) -> f32 { + let tex = textureSampleLevel(u_image_tex, u_image_samp, uv, 0.0); + let color = vec3f( + sigmoid(tex.r, contrast), + sigmoid(tex.g, contrast), + sigmoid(tex.b, contrast) ); - float lum = dot(vec3(0.2126, 0.7152, 0.0722), color); - lum = mix(1., lum, tex.a); - lum = u_inverted ? (1. - lum) : lum; + var lum = dot(vec3f(0.2126, 0.7152, 0.0722), color); + lum = mix(1.0, lum, tex.a); + lum = select(lum, 1.0 - lum, u.u_inverted > 0.5); return lum; } -float getLumBall(vec2 p, vec2 pad, vec2 inCellOffset, float contrast, float baseR, float stepSize, out vec4 ballColor) { - p += inCellOffset; - vec2 uv_i = floor(p); - vec2 uv_f = fract(p); - vec2 samplingUV = (uv_i + .5 - inCellOffset) * pad + vec2(.5); - float outOfFrame = getUvFrame(samplingUV, pad * stepSize); +fn getLumBall(p_in: vec2f, pad: vec2f, inCellOffset: vec2f, contrast: f32, baseR: f32, stepSize: f32) -> LumBallResult { + let p = p_in + inCellOffset; + let uv_i = floor(p); + let uv_f = fract(p); + let samplingUV = (uv_i + vec2f(0.5) - inCellOffset) * pad + vec2f(0.5); + let outOfFrame = getUvFrame(samplingUV, pad * stepSize); - float lum = getLumAtPx(samplingUV, contrast); - ballColor = texture(u_image, samplingUV); - ballColor.rgb *= ballColor.a; + let lum = getLumAtPx(samplingUV, contrast); + var ballColor = textureSampleLevel(u_image_tex, u_image_samp, samplingUV, 0.0); + ballColor = vec4f(ballColor.rgb * ballColor.a, ballColor.a); ballColor *= outOfFrame; - float ball = 0.; - if (u_type == 0.) { + var ball: f32 = 0.0; + if (u.u_type == 0.0) { // classic ball = getCircle(uv_f, lum, baseR); - } else if (u_type == 1.) { + } else if (u.u_type == 1.0) { // gooey ball = getGooeyBall(uv_f, lum, baseR); - } else if (u_type == 2.) { + } else if (u.u_type == 2.0) { // holes ball = getCircleWithHole(uv_f, lum, baseR); - } else if (u_type == 3.) { + } else if (u.u_type == 3.0) { // soft ball = getSoftBall(uv_f, lum, baseR); } - return ball * outOfFrame; + return LumBallResult(ball * outOfFrame, ballColor); } -void main() { +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { - float stepMultiplier = 1.; - if (u_type == 0.) { + var stepMultiplier: f32 = 1.0; + if (u.u_type == 0.0) { // classic - stepMultiplier = 2.; - } else if (u_type == 1. || u_type == 3.) { + stepMultiplier = 2.0; + } else if (u.u_type == 1.0 || u.u_type == 3.0) { // gooey & soft - stepMultiplier = 6.; + stepMultiplier = 6.0; } - float cellsPerSide = mix(300., 7., pow(u_size, .7)); + var cellsPerSide = mix(300.0, 7.0, pow(u.u_size, 0.7)); cellsPerSide /= stepMultiplier; - float cellSizeY = 1. / cellsPerSide; - vec2 pad = cellSizeY * vec2(1. / u_imageAspectRatio, 1.); - if (u_type == 1. && u_grid == 1.) { + let cellSizeY = 1.0 / cellsPerSide; + var pad = cellSizeY * vec2f(1.0 / u.u_imageAspectRatio, 1.0); + if (u.u_type == 1.0 && u.u_grid == 1.0) { // gooey diagonal grid works differently - pad *= .7; + pad *= 0.7; } - vec2 uv = v_imageUV; - uv -= vec2(.5); + var uv = input.v_imageUV; + uv -= vec2f(0.5); uv /= pad; - float contrast = mix(0., 15., pow(u_contrast, 1.5)); - float baseRadius = u_radius; - if (u_originalColors == true) { - contrast = mix(.1, 4., pow(u_contrast, 2.)); - baseRadius = 2. * pow(.5 * u_radius, .3); + var contrast = mix(0.0, 15.0, pow(u.u_contrast, 1.5)); + var baseRadius = u.u_radius; + if (u.u_originalColors > 0.5) { + contrast = mix(0.1, 4.0, pow(u.u_contrast, 2.0)); + baseRadius = 2.0 * pow(0.5 * u.u_radius, 0.3); } - float totalShape = 0.; - vec3 totalColor = vec3(0.); - float totalOpacity = 0.; - - vec4 ballColor; - float shape; - float stepSize = 1. / stepMultiplier; - for (float x = -0.5; x < 0.5; x += stepSize) { - for (float y = -0.5; y < 0.5; y += stepSize) { - vec2 offset = vec2(x, y); - - if (u_grid == 1.) { - float rowIndex = floor((y + .5) / stepSize); - float colIndex = floor((x + .5) / stepSize); - if (stepSize == 1.) { - rowIndex = floor(uv.y + y + 1.); - if (u_type == 1.) { - colIndex = floor(uv.x + x + 1.); + var totalShape: f32 = 0.0; + var totalColor = vec3f(0.0); + var totalOpacity: f32 = 0.0; + + let stepSize = 1.0 / stepMultiplier; + let numSteps = i32(stepMultiplier); + for (var xi: i32 = 0; xi < numSteps; xi++) { + let x = f32(xi) * stepSize - 0.5; + for (var yi: i32 = 0; yi < numSteps; yi++) { + let y = f32(yi) * stepSize - 0.5; + var offset = vec2f(x, y); + + var skipCell = false; + if (u.u_grid == 1.0) { + var rowIndex = floor((y + 0.5) / stepSize); + var colIndex = floor((x + 0.5) / stepSize); + if (stepSize == 1.0) { + rowIndex = floor(uv.y + y + 1.0); + if (u.u_type == 1.0) { + colIndex = floor(uv.x + x + 1.0); } } - if (u_type == 1.) { - if (mod(rowIndex + colIndex, 2.) == 1.) { - continue; + if (u.u_type == 1.0) { + if (glsl_mod_f32(rowIndex + colIndex, 2.0) == 1.0) { + skipCell = true; } } else { - if (mod(rowIndex, 2.) == 1.) { - offset.x += .5 * stepSize; + if (glsl_mod_f32(rowIndex, 2.0) == 1.0) { + offset = vec2f(offset.x + 0.5 * stepSize, offset.y); } } } - shape = getLumBall(uv, pad, offset, contrast, baseRadius, stepSize, ballColor); - totalColor += ballColor.rgb * shape; - totalShape += shape; - totalOpacity += shape; + let result = getLumBall(uv, pad, offset, contrast, baseRadius, stepSize); + if (!skipCell) { + let shape = result.ball; + let ballColor = result.ballColor; + totalColor += ballColor.rgb * shape; + totalShape += shape; + totalOpacity += shape; + } } } - const float eps = 1e-4; + let eps: f32 = 1e-4; totalColor /= max(totalShape, eps); totalOpacity /= max(totalShape, eps); - float finalShape = 0.; - if (u_type == 0.) { - finalShape = min(1., totalShape); - } else if (u_type == 1.) { - float aa = fwidth(totalShape); - float th = .5; + var finalShape: f32 = 0.0; + if (u.u_type == 0.0) { + finalShape = min(1.0, totalShape); + } else if (u.u_type == 1.0) { + let aa = fwidth_f32(totalShape); + let th = 0.5; finalShape = smoothstep(th - aa, th + aa, totalShape); - } else if (u_type == 2.) { - finalShape = min(1., totalShape); - } else if (u_type == 3.) { + } else if (u.u_type == 2.0) { + finalShape = min(1.0, totalShape); + } else if (u.u_type == 3.0) { finalShape = totalShape; } - vec2 grainSize = mix(2000., 200., u_grainSize) * vec2(1., 1. / u_imageAspectRatio); - vec2 grainUV = v_imageUV - .5; - grainUV *= grainSize; - grainUV += .5; - float grain = valueNoise(grainUV); - grain = smoothstep(.55, .7 + .2 * u_grainMixer, grain); - grain *= u_grainMixer; - finalShape = mix(finalShape, 0., grain); + let grainSizeVal = mix(2000.0, 200.0, u.u_grainSize) * vec2f(1.0, 1.0 / u.u_imageAspectRatio); + var grainUV = input.v_imageUV - vec2f(0.5); + grainUV *= grainSizeVal; + grainUV += vec2f(0.5); + var grain = valueNoise(grainUV); + grain = smoothstep(0.55, 0.7 + 0.2 * u.u_grainMixer, grain); + grain *= u.u_grainMixer; + finalShape = mix(finalShape, 0.0, grain); - vec3 color = vec3(0.); - float opacity = 0.; + var color = vec3f(0.0); + var opacity: f32 = 0.0; - if (u_originalColors == true) { + if (u.u_originalColors > 0.5) { color = totalColor * finalShape; opacity = totalOpacity * finalShape; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - color = color + bgColor * (1. - opacity); - opacity = opacity + u_colorBack.a * (1. - opacity); + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + color = color + bgColor * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); } else { - vec3 fgColor = u_colorFront.rgb * u_colorFront.a; - float fgOpacity = u_colorFront.a; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - float bgOpacity = u_colorBack.a; + let fgColor = u.u_colorFront.rgb * u.u_colorFront.a; + let fgOpacity = u.u_colorFront.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + let bgOpacity = u.u_colorBack.a; color = fgColor * finalShape; opacity = fgOpacity * finalShape; - color += bgColor * (1. - opacity); - opacity += bgOpacity * (1. - opacity); + color += bgColor * (1.0 - opacity); + opacity += bgOpacity * (1.0 - opacity); } - float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.)); - grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5); + var grainOverlay = valueNoise(rotate(grainUV, 1.0) + vec2f(3.0)); + grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.0) + vec2f(-1.0)), 0.5); grainOverlay = pow(grainOverlay, 1.3); - float grainOverlayV = grainOverlay * 2. - 1.; - vec3 grainOverlayColor = vec3(step(0., grainOverlayV)); - float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV); - grainOverlayStrength = pow(grainOverlayStrength, .8); - color = mix(color, grainOverlayColor, .5 * grainOverlayStrength); + let grainOverlayV = grainOverlay * 2.0 - 1.0; + let grainOverlayColor = vec3f(step(0.0, grainOverlayV)); + var grainOverlayStrength = u.u_grainOverlay * abs(grainOverlayV); + grainOverlayStrength = pow(grainOverlayStrength, 0.8); + color = mix(color, grainOverlayColor, 0.5 * grainOverlayStrength); - opacity += .5 * grainOverlayStrength; - opacity = clamp(opacity, 0., 1.); + opacity += 0.5 * grainOverlayStrength; + opacity = clamp(opacity, 0.0, 1.0); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/heatmap.ts b/packages/shaders/src/shaders/heatmap.ts index f2d2b955c..5aa837045 100644 --- a/packages/shaders/src/shaders/heatmap.ts +++ b/packages/shaders/src/shaders/heatmap.ts @@ -1,6 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import type { ShaderSizingParams, ShaderSizingUniforms } from '../shader-sizing.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, glslMod } from '../shader-utils.js'; export const heatmapMeta = { maxColorCount: 10, @@ -43,256 +44,253 @@ export const heatmapMeta = { * */ -// language=GLSL -export const heatmapFragmentShader: string = `#version 300 es -precision highp float; - -in mediump vec2 v_imageUV; -in mediump vec2 v_objectUV; -out vec4 fragColor; - -uniform sampler2D u_image; -uniform float u_time; -uniform mediump float u_imageAspectRatio; - -uniform vec4 u_colorBack; -uniform vec4 u_colors[${ heatmapMeta.maxColorCount }]; -uniform float u_colorsCount; - -uniform float u_angle; -uniform float u_noise; -uniform float u_innerGlow; -uniform float u_outerGlow; -uniform float u_contour; - -#define TWO_PI 6.28318530718 -#define PI 3.14159265358979323846 - -float getImgFrame(vec2 uv, float th) { - float frame = 1.; - frame *= smoothstep(0., th, uv.y); - frame *= 1. - smoothstep(1. - th, 1., uv.y); - frame *= smoothstep(0., th, uv.x); - frame *= 1. - smoothstep(1. - th, 1., uv.x); +export const heatmapFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorBack: vec4f, + u_colorsCount: f32, + u_angle: f32, + u_noise: f32, + u_innerGlow: f32, + u_outerGlow: f32, + u_contour: f32, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; + +${vertexOutputStruct} + +@group(1) @binding(0) var u_image_tex: texture_2d; +@group(1) @binding(1) var u_image_samp: sampler; + +${declarePI} +${glslMod} + +fn getImgFrame(uv: vec2f, th: f32) -> f32 { + var frame: f32 = 1.0; + frame *= smoothstep(0.0, th, uv.y); + frame *= 1.0 - smoothstep(1.0 - th, 1.0, uv.y); + frame *= smoothstep(0.0, th, uv.x); + frame *= 1.0 - smoothstep(1.0 - th, 1.0, uv.x); return frame; } -float circle(vec2 uv, vec2 c, vec2 r) { - return 1. - smoothstep(r[0], r[1], length(uv - c)); +fn circle(uv: vec2f, c: vec2f, r: vec2f) -> f32 { + return 1.0 - smoothstep(r[0], r[1], length(uv - c)); } -float lst(float edge0, float edge1, float x) { +fn lst(edge0: f32, edge1: f32, x: f32) -> f32 { return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); } -float sst(float edge0, float edge1, float x) { +fn sst(edge0: f32, edge1: f32, x: f32) -> f32 { return smoothstep(edge0, edge1, x); } -float shadowShape(vec2 uv, float t, float contour) { - vec2 scaledUV = uv; +fn shadowShape(uv: vec2f, t: f32, contour: f32) -> f32 { + var scaledUV = uv; // base shape tranjectory - float posY = mix(-1., 2., t); + let posY = mix(-1.0, 2.0, t); // scaleX when it's moving down - scaledUV.y -= .5; - float mainCircleScale = sst(0., .8, posY) * lst(1.4, .9, posY); - scaledUV *= vec2(1., 1. + 1.5 * mainCircleScale); - scaledUV.y += .5; + scaledUV = vec2f(scaledUV.x, scaledUV.y - 0.5); + let mainCircleScale = sst(0.0, 0.8, posY) * lst(1.4, 0.9, posY); + scaledUV *= vec2f(1.0, 1.0 + 1.5 * mainCircleScale); + scaledUV = vec2f(scaledUV.x, scaledUV.y + 0.5); // base shape - float innerR = .4; - float outerR = 1. - .3 * (sst(.1, .2, t) * (1. - sst(.2, .5, t))); - float s = circle(scaledUV, vec2(.5, posY - .2), vec2(innerR, outerR)); - float shapeSizing = sst(.2, .3, t) * sst(.6, .3, t); + let innerR: f32 = 0.4; + let outerR = 1.0 - 0.3 * (sst(0.1, 0.2, t) * (1.0 - sst(0.2, 0.5, t))); + var s = circle(scaledUV, vec2f(0.5, posY - 0.2), vec2f(innerR, outerR)); + let shapeSizing = sst(0.2, 0.3, t) * sst(0.6, 0.3, t); s = pow(s, 1.4); s *= 1.2; // flat gradient to take over the shadow shape - float topFlattener = 0.; { - float pos = posY - uv.y; - float edge = 1.2; - topFlattener = lst(-.4, 0., pos) * (1. - sst(.0, edge, pos)); - topFlattener = pow(topFlattener, 3.); - float topFlattenerMixer = (1. - sst(.0, .3, pos)); + let pos = posY - uv.y; + let edge: f32 = 1.2; + var topFlattener = lst(-0.4, 0.0, pos) * (1.0 - sst(0.0, edge, pos)); + topFlattener = pow(topFlattener, 3.0); + let topFlattenerMixer = (1.0 - sst(0.0, 0.3, pos)); s = mix(topFlattener, s, topFlattenerMixer); } // apple right circle { - float visibility = sst(.6, .7, t) * (1. - sst(.8, .9, t)); - float angle = -2. -t * TWO_PI; - float rightCircle = circle(uv, vec2(.95 - .2 * cos(angle), .4 - .1 * sin(angle)), vec2(.15, .3)); + let visibility = sst(0.6, 0.7, t) * (1.0 - sst(0.8, 0.9, t)); + let angle = -2.0 - t * TWO_PI; + var rightCircle = circle(uv, vec2f(0.95 - 0.2 * cos(angle), 0.4 - 0.1 * sin(angle)), vec2f(0.15, 0.3)); rightCircle *= visibility; - s = mix(s, 0., rightCircle); + s = mix(s, 0.0, rightCircle); } // apple top circle { - float topCircle = circle(uv, vec2(.5, .19), vec2(.05, .25)); - topCircle += 2. * contour * circle(uv, vec2(.5, .19), vec2(.2, .5)); - float visibility = .55 * sst(.2, .3, t) * (1. - sst(.3, .45, t)); + var topCircle = circle(uv, vec2f(0.5, 0.19), vec2f(0.05, 0.25)); + topCircle += 2.0 * contour * circle(uv, vec2f(0.5, 0.19), vec2f(0.2, 0.5)); + let visibility = 0.55 * sst(0.2, 0.3, t) * (1.0 - sst(0.3, 0.45, t)); topCircle *= visibility; - s = mix(s, 0., topCircle); + s = mix(s, 0.0, topCircle); } - float leafMask = circle(uv, vec2(.53, .13), vec2(.08, .19)); - leafMask = mix(leafMask, 0., 1. - sst(.4, .54, uv.x)); - leafMask = mix(0., leafMask, sst(.0, .2, uv.y)); - leafMask *= (sst(.5, 1.1, posY) * sst(1.5, 1.3, posY)); + var leafMask = circle(uv, vec2f(0.53, 0.13), vec2f(0.08, 0.19)); + leafMask = mix(leafMask, 0.0, 1.0 - sst(0.4, 0.54, uv.x)); + leafMask = mix(0.0, leafMask, sst(0.0, 0.2, uv.y)); + leafMask *= (sst(0.5, 1.1, posY) * sst(1.5, 1.3, posY)); s += leafMask; // apple bottom circle { - float visibility = sst(.0, .4, t) * (1. - sst(.6, .8, t)); - s = mix(s, 0., visibility * circle(uv, vec2(.52, .92), vec2(.09, .25))); + let visibility = sst(0.0, 0.4, t) * (1.0 - sst(0.6, 0.8, t)); + s = mix(s, 0.0, visibility * circle(uv, vec2f(0.52, 0.92), vec2f(0.09, 0.25))); } // random balls that are invisible if apple logo is selected { - float pos = sst(.0, .6, t) * (1. - sst(.6, 1., t)); - s = mix(s, .5, circle(uv, vec2(.0, 1.2 - .5 * pos), vec2(.1, .3))); - s = mix(s, .0, circle(uv, vec2(1., .5 + .5 * pos), vec2(.1, .3))); + let pos = sst(0.0, 0.6, t) * (1.0 - sst(0.6, 1.0, t)); + s = mix(s, 0.5, circle(uv, vec2f(0.0, 1.2 - 0.5 * pos), vec2f(0.1, 0.3))); + s = mix(s, 0.0, circle(uv, vec2f(1.0, 0.5 + 0.5 * pos), vec2f(0.1, 0.3))); - s = mix(s, 1., circle(uv, vec2(.95, .2 + .2 * sst(.3, .4, t) * sst(.7, .5, t)), vec2(.07, .22))); - s = mix(s, 1., circle(uv, vec2(.95, .2 + .2 * sst(.3, .4, t) * (1. - sst(.5, .7, t))), vec2(.07, .22))); - s /= max(1e-4, sst(1., .85, uv.y)); + s = mix(s, 1.0, circle(uv, vec2f(0.95, 0.2 + 0.2 * sst(0.3, 0.4, t) * sst(0.7, 0.5, t)), vec2f(0.07, 0.22))); + s = mix(s, 1.0, circle(uv, vec2f(0.95, 0.2 + 0.2 * sst(0.3, 0.4, t) * (1.0 - sst(0.5, 0.7, t))), vec2f(0.07, 0.22))); + s /= max(1e-4, sst(1.0, 0.85, uv.y)); } - s = clamp(0., 1., s); + s = clamp(s, 0.0, 1.0); return s; } -float blurEdge3x3(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius, float centerSample) { - vec2 texel = 1.0 / vec2(textureSize(tex, 0)); - vec2 r = radius * texel; +fn blurEdge3x3(uv: vec2f, radius: f32, centerSample: f32) -> f32 { + let texDim = vec2f(textureDimensions(u_image_tex, 0)); + let texel = 1.0 / texDim; + let r = radius * texel; - float w1 = 1.0, w2 = 2.0, w4 = 4.0; - float norm = 16.0; - float sum = w4 * centerSample; + let w1: f32 = 1.0; + let w2: f32 = 2.0; + let w4: f32 = 4.0; + let norm: f32 = 16.0; + var sum = w4 * centerSample; - sum += w2 * textureGrad(tex, uv + vec2(0.0, -r.y), dudx, dudy).g; - sum += w2 * textureGrad(tex, uv + vec2(0.0, r.y), dudx, dudy).g; - sum += w2 * textureGrad(tex, uv + vec2(-r.x, 0.0), dudx, dudy).g; - sum += w2 * textureGrad(tex, uv + vec2(r.x, 0.0), dudx, dudy).g; + sum += w2 * textureSampleLevel(u_image_tex, u_image_samp, uv + vec2f(0.0, -r.y), 0.0).g; + sum += w2 * textureSampleLevel(u_image_tex, u_image_samp, uv + vec2f(0.0, r.y), 0.0).g; + sum += w2 * textureSampleLevel(u_image_tex, u_image_samp, uv + vec2f(-r.x, 0.0), 0.0).g; + sum += w2 * textureSampleLevel(u_image_tex, u_image_samp, uv + vec2f(r.x, 0.0), 0.0).g; - sum += w1 * textureGrad(tex, uv + vec2(-r.x, -r.y), dudx, dudy).g; - sum += w1 * textureGrad(tex, uv + vec2(r.x, -r.y), dudx, dudy).g; - sum += w1 * textureGrad(tex, uv + vec2(-r.x, r.y), dudx, dudy).g; - sum += w1 * textureGrad(tex, uv + vec2(r.x, r.y), dudx, dudy).g; + sum += w1 * textureSampleLevel(u_image_tex, u_image_samp, uv + vec2f(-r.x, -r.y), 0.0).g; + sum += w1 * textureSampleLevel(u_image_tex, u_image_samp, uv + vec2f(r.x, -r.y), 0.0).g; + sum += w1 * textureSampleLevel(u_image_tex, u_image_samp, uv + vec2f(-r.x, r.y), 0.0).g; + sum += w1 * textureSampleLevel(u_image_tex, u_image_samp, uv + vec2f(r.x, r.y), 0.0).g; return sum / norm; } -void main() { - vec2 uv = v_objectUV + .5; - uv.y = 1. - uv.y; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var uv = input.v_objectUV + vec2f(0.5); + uv = vec2f(uv.x, 1.0 - uv.y); - vec2 imgUV = v_imageUV; - imgUV -= .5; + var imgUV = input.v_imageUV; + imgUV -= vec2f(0.5); imgUV *= 0.5714285714285714; - imgUV += .5; - float imgSoftFrame = getImgFrame(imgUV, .03); + imgUV += vec2f(0.5); + let imgSoftFrame = getImgFrame(imgUV, 0.03); - vec4 img = texture(u_image, imgUV); - vec2 dudx = dFdx(imgUV); - vec2 dudy = dFdy(imgUV); + var img = textureSampleLevel(u_image_tex, u_image_samp, imgUV, 0.0); - if (img.a == 0.) { - fragColor = u_colorBack; - return; + if (img.a == 0.0) { + return u.u_colorBack; } - float t = .1 * u_time; - t -= .3; + var t = 0.1 * u.u_time; + t -= 0.3; - float tCopy = t + 1. / 3.; - float tCopy2 = t + 2. / 3.; + var tCopy = t + 1.0 / 3.0; + var tCopy2 = t + 2.0 / 3.0; - t = mod(t, 1.); - tCopy = mod(tCopy, 1.); - tCopy2 = mod(tCopy2, 1.); + t = glsl_mod_f32(t, 1.0); + tCopy = glsl_mod_f32(tCopy, 1.0); + tCopy2 = glsl_mod_f32(tCopy2, 1.0); - vec2 animationUV = imgUV - vec2(.5); - float angle = -u_angle * PI / 180.; - float cosA = cos(angle); - float sinA = sin(angle); - animationUV = vec2( + var animationUV = imgUV - vec2f(0.5); + let angle = -u.u_angle * PI / 180.0; + let cosA = cos(angle); + let sinA = sin(angle); + animationUV = vec2f( animationUV.x * cosA - animationUV.y * sinA, animationUV.x * sinA + animationUV.y * cosA - ) + vec2(.5); + ) + vec2f(0.5); - float shape = img[0]; + let shape = img[0]; - img[1] = blurEdge3x3(u_image, imgUV, dudx, dudy, 8., img[1]); + let img1_blurred = blurEdge3x3(imgUV, 8.0, img[1]); + img = vec4f(img[0], img1_blurred, img[2], img[3]); - float outerBlur = 1. - mix(1., img[1], shape); - float innerBlur = mix(img[1], 0., shape); - float contour = mix(img[2], 0., shape); + var outerBlur = 1.0 - mix(1.0, img[1], shape); + let innerBlur = mix(img[1], 0.0, shape); + let contour_val = mix(img[2], 0.0, shape); outerBlur *= imgSoftFrame; - float shadow = shadowShape(animationUV, t, innerBlur); - float shadowCopy = shadowShape(animationUV, tCopy, innerBlur); - float shadowCopy2 = shadowShape(animationUV, tCopy2, innerBlur); + let shadow = shadowShape(animationUV, t, innerBlur); + let shadowCopy = shadowShape(animationUV, tCopy, innerBlur); + let shadowCopy2 = shadowShape(animationUV, tCopy2, innerBlur); - float inner = .8 + .8 * innerBlur; - inner = mix(inner, 0., shadow); - inner = mix(inner, 0., shadowCopy); - inner = mix(inner, 0., shadowCopy2); + var inner = 0.8 + 0.8 * innerBlur; + inner = mix(inner, 0.0, shadow); + inner = mix(inner, 0.0, shadowCopy); + inner = mix(inner, 0.0, shadowCopy2); - inner *= mix(0., 2., u_innerGlow); + inner *= mix(0.0, 2.0, u.u_innerGlow); - inner += (u_contour * 2.) * contour; - inner = min(1., inner); - inner *= (1. - shape); + inner += (u.u_contour * 2.0) * contour_val; + inner = min(1.0, inner); + inner *= (1.0 - shape); - float outer = 0.; + var outer: f32 = 0.0; { - t *= 3.; - t = mod(t - .1, 1.); + t *= 3.0; + t = glsl_mod_f32(t - 0.1, 1.0); - outer = .9 * pow(outerBlur, .8); - float y = mod(animationUV.y - t, 1.); - float animatedMask = sst(.3, .65, y) * (1. - sst(.65, 1., y)); - animatedMask = .5 + animatedMask; + outer = 0.9 * pow(outerBlur, 0.8); + let y = glsl_mod_f32(animationUV.y - t, 1.0); + var animatedMask = sst(0.3, 0.65, y) * (1.0 - sst(0.65, 1.0, y)); + animatedMask = 0.5 + animatedMask; outer *= animatedMask; - outer *= mix(0., 5., pow(u_outerGlow, 2.)); + outer *= mix(0.0, 5.0, pow(u.u_outerGlow, 2.0)); outer *= imgSoftFrame; } inner = pow(inner, 1.2); - float heat = clamp(inner + outer, 0., 1.); + var heat = clamp(inner + outer, 0.0, 1.0); - heat += (.005 + .35 * u_noise) * (fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453123) - .5); + heat += (0.005 + 0.35 * u.u_noise) * (fract(sin(dot(uv, vec2f(12.9898, 78.233))) * 43758.5453123) - 0.5); - float mixer = heat * u_colorsCount; - vec4 gradient = u_colors[0]; - gradient.rgb *= gradient.a; - float outerShape = 0.; - for (int i = 1; i < ${ heatmapMeta.maxColorCount + 1 }; i++) { - if (i > int(u_colorsCount)) break; - float m = clamp(mixer - float(i - 1), 0., 1.); + let mixer = heat * u.u_colorsCount; + var gradient = u.u_colors[0]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); + var outerShape: f32 = 0.0; + for (var i: i32 = 1; i < ${heatmapMeta.maxColorCount + 1}; i++) { + if (i > i32(u.u_colorsCount)) { break; } + let m = clamp(mixer - f32(i - 1), 0.0, 1.0); if (i == 1) { outerShape = m; } - vec4 c = u_colors[i - 1]; - c.rgb *= c.a; + var c = u.u_colors[i - 1]; + c = vec4f(c.rgb * c.a, c.a); gradient = mix(gradient, c, m); } - vec3 color = gradient.rgb * outerShape; - float opacity = gradient.a * outerShape; + var color = gradient.rgb * outerShape; + var opacity = gradient.a * outerShape; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; color = color + bgColor * (1.0 - opacity); - opacity = opacity + u_colorBack.a * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); - color += .02 * (fract(sin(dot(uv + 1., vec2(12.9898, 78.233))) * 43758.5453123) - .5); + color += vec3f(0.02 * (fract(sin(dot(uv + vec2f(1.0), vec2f(12.9898, 78.233))) * 43758.5453123) - 0.5)); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/image-dithering.ts b/packages/shaders/src/shaders/image-dithering.ts index 3f789fe70..ad6719e31 100644 --- a/packages/shaders/src/shaders/image-dithering.ts +++ b/packages/shaders/src/shaders/image-dithering.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { proceduralHash21, declarePI } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, proceduralHash21, declarePI } from '../shader-utils.js'; /** * A dithering image filter with support for 4 dithering modes and multiple color palettes @@ -37,94 +37,80 @@ import { proceduralHash21, declarePI } from '../shader-utils.js'; * */ -// language=GLSL -export const imageDitheringFragmentShader: string = `#version 300 es -precision mediump float; - -uniform vec2 u_resolution; -uniform float u_pixelRatio; -uniform float u_originX; -uniform float u_originY; -uniform float u_worldWidth; -uniform float u_worldHeight; -uniform float u_fit; - -uniform float u_scale; -uniform float u_rotation; -uniform float u_offsetX; -uniform float u_offsetY; - -uniform vec4 u_colorFront; -uniform vec4 u_colorBack; -uniform vec4 u_colorHighlight; - -uniform sampler2D u_image; -uniform float u_imageAspectRatio; - -uniform float u_type; -uniform float u_pxSize; -uniform bool u_originalColors; -uniform bool u_inverted; -uniform float u_colorSteps; +export const imageDitheringFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorFront: vec4f, + u_colorBack: vec4f, + u_colorHighlight: vec4f, + u_type: f32, + u_pxSize: f32, + u_originalColors: f32, + u_inverted: f32, + u_colorSteps: f32, +} +@group(0) @binding(0) var u: Uniforms; -out vec4 fragColor; +${vertexOutputStruct} +@group(1) @binding(0) var u_image_tex: texture_2d; +@group(1) @binding(1) var u_image_samp: sampler; -${ proceduralHash21 } -${ declarePI } +${proceduralHash21} +${declarePI} -float getUvFrame(vec2 uv, vec2 pad) { - float aa = 0.0001; +fn getUvFrame(uv: vec2f, pad: vec2f) -> f32 { + let aa: f32 = 0.0001; - float left = smoothstep(-pad.x, -pad.x + aa, uv.x); - float right = smoothstep(1.0 + pad.x, 1.0 + pad.x - aa, uv.x); - float bottom = smoothstep(-pad.y, -pad.y + aa, uv.y); - float top = smoothstep(1.0 + pad.y, 1.0 + pad.y - aa, uv.y); + let left = smoothstep(-pad.x, -pad.x + aa, uv.x); + let right = smoothstep(1.0 + pad.x, 1.0 + pad.x - aa, uv.x); + let bottom = smoothstep(-pad.y, -pad.y + aa, uv.y); + let top = smoothstep(1.0 + pad.y, 1.0 + pad.y - aa, uv.y); return left * right * bottom * top; } -vec2 getImageUV(vec2 uv) { - vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5); - float r = u_rotation * PI / 180.; - mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r)); - vec2 graphicOffset = vec2(-u_offsetX, u_offsetY); - - vec2 imageBoxSize; - if (u_fit == 1.) { // contain - imageBoxSize.x = min(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio; - } else if (u_fit == 2.) { // cover - imageBoxSize.x = max(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio; +fn getImageUV(uv_in: vec2f) -> vec2f { + let boxOrigin = vec2f(0.5 - u.u_originX, u.u_originY - 0.5); + let r = u.u_rotation * PI / 180.0; + let graphicRotation = mat2x2f(cos(r), sin(r), -sin(r), cos(r)); + let graphicOffset = vec2f(-u.u_offsetX, u.u_offsetY); + + var imageBoxSize: vec2f; + if (u.u_fit == 1.0) { // contain + imageBoxSize = vec2f(min(u.u_resolution.x / u.u_imageAspectRatio, u.u_resolution.y) * u.u_imageAspectRatio, 0.0); + } else if (u.u_fit == 2.0) { // cover + imageBoxSize = vec2f(max(u.u_resolution.x / u.u_imageAspectRatio, u.u_resolution.y) * u.u_imageAspectRatio, 0.0); } else { - imageBoxSize.x = min(10.0, 10.0 / u_imageAspectRatio * u_imageAspectRatio); + imageBoxSize = vec2f(min(10.0, 10.0 / u.u_imageAspectRatio * u.u_imageAspectRatio), 0.0); } - imageBoxSize.y = imageBoxSize.x / u_imageAspectRatio; - vec2 imageBoxScale = u_resolution.xy / imageBoxSize; + imageBoxSize = vec2f(imageBoxSize.x, imageBoxSize.x / u.u_imageAspectRatio); + let imageBoxScale = u.u_resolution.xy / imageBoxSize; - vec2 imageUV = uv; + var imageUV = uv_in; imageUV *= imageBoxScale; - imageUV += boxOrigin * (imageBoxScale - 1.); + imageUV += boxOrigin * (imageBoxScale - vec2f(1.0)); imageUV += graphicOffset; - imageUV /= u_scale; - imageUV.x *= u_imageAspectRatio; + imageUV /= u.u_scale; + imageUV = vec2f(imageUV.x * u.u_imageAspectRatio, imageUV.y); imageUV = graphicRotation * imageUV; - imageUV.x /= u_imageAspectRatio; + imageUV = vec2f(imageUV.x / u.u_imageAspectRatio, imageUV.y); - imageUV += .5; - imageUV.y = 1. - imageUV.y; + imageUV += vec2f(0.5); + imageUV = vec2f(imageUV.x, 1.0 - imageUV.y); return imageUV; } -const int bayer2x2[4] = int[4](0, 2, 3, 1); -const int bayer4x4[16] = int[16]( +const bayer2x2 = array(0, 2, 3, 1); +const bayer4x4 = array( 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 ); -const int bayer8x8[64] = int[64]( +const bayer8x8 = array( 0, 32, 8, 40, 2, 34, 10, 42, 48, 16, 56, 24, 50, 18, 58, 26, 12, 44, 4, 36, 14, 46, 6, 38, @@ -135,90 +121,85 @@ const int bayer8x8[64] = int[64]( 63, 31, 55, 23, 61, 29, 53, 21 ); -float getBayerValue(vec2 uv, int size) { - ivec2 pos = ivec2(fract(uv / float(size)) * float(size)); - int index = pos.y * size + pos.x; +fn getBayerValue(uv: vec2f, size: i32) -> f32 { + let pos = vec2i(fract(uv / f32(size)) * f32(size)); + let index = pos.y * size + pos.x; if (size == 2) { - return float(bayer2x2[index]) / 4.0; + return f32(bayer2x2[index]) / 4.0; } else if (size == 4) { - return float(bayer4x4[index]) / 16.0; + return f32(bayer4x4[index]) / 16.0; } else if (size == 8) { - return float(bayer8x8[index]) / 64.0; + return f32(bayer8x8[index]) / 64.0; } return 0.0; } -void main() { +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { - float pxSize = u_pxSize * u_pixelRatio; - vec2 pxSizeUV = gl_FragCoord.xy - .5 * u_resolution; + let pxSize = u.u_pxSize * u.u_pixelRatio; + var pxSizeUV = input.position.xy - 0.5 * u.u_resolution; pxSizeUV /= pxSize; - vec2 canvasPixelizedUV = (floor(pxSizeUV) + .5) * pxSize; - vec2 normalizedUV = canvasPixelizedUV / u_resolution; + let canvasPixelizedUV = (floor(pxSizeUV) + vec2f(0.5)) * pxSize; + let normalizedUV = canvasPixelizedUV / u.u_resolution; - vec2 imageUV = getImageUV(normalizedUV); - vec2 ditheringNoiseUV = canvasPixelizedUV; - vec4 image = texture(u_image, imageUV); - float frame = getUvFrame(imageUV, pxSize / u_resolution); + let imageUV = getImageUV(normalizedUV); + let ditheringNoiseUV = canvasPixelizedUV; + let image = textureSampleLevel(u_image_tex, u_image_samp, imageUV, 0.0); + let frame = getUvFrame(imageUV, pxSize / u.u_resolution); - int type = int(floor(u_type)); - float dithering = 0.0; + let type_val = i32(floor(u.u_type)); + var dithering: f32 = 0.0; - float lum = dot(vec3(.2126, .7152, .0722), image.rgb); - lum = u_inverted ? (1. - lum) : lum; + let lum_raw = dot(vec3f(0.2126, 0.7152, 0.0722), image.rgb); + let lum = select(lum_raw, 1.0 - lum_raw, u.u_inverted > 0.5); - switch (type) { - case 1: { - dithering = step(hash21(ditheringNoiseUV), lum); - } break; - case 2: + if (type_val == 1) { + dithering = step(hash21(ditheringNoiseUV), lum); + } else if (type_val == 2) { dithering = getBayerValue(pxSizeUV, 2); - break; - case 3: + } else if (type_val == 3) { dithering = getBayerValue(pxSizeUV, 4); - break; - default : + } else { dithering = getBayerValue(pxSizeUV, 8); - break; } - float colorSteps = max(floor(u_colorSteps), 1.); - vec3 color = vec3(0.0); - float opacity = 1.; + let colorSteps = max(floor(u.u_colorSteps), 1.0); + var color = vec3f(0.0); + var opacity: f32 = 1.0; - dithering -= .5; - float brightness = clamp(lum + dithering / colorSteps, 0.0, 1.0); + dithering -= 0.5; + var brightness = clamp(lum + dithering / colorSteps, 0.0, 1.0); brightness = mix(0.0, brightness, frame); brightness = mix(0.0, brightness, image.a); - float quantLum = floor(brightness * colorSteps + 0.5) / colorSteps; + var quantLum = floor(brightness * colorSteps + 0.5) / colorSteps; quantLum = mix(0.0, quantLum, frame); - if (u_originalColors == true) { - vec3 normColor = image.rgb / max(lum, 0.001); + if (u.u_originalColors > 0.5) { + let normColor = image.rgb / max(lum, 0.001); color = normColor * quantLum; - float quantAlpha = floor(image.a * colorSteps + 0.5) / colorSteps; - opacity = mix(quantLum, 1., quantAlpha); + let quantAlpha = floor(image.a * colorSteps + 0.5) / colorSteps; + opacity = mix(quantLum, 1.0, quantAlpha); } else { - vec3 fgColor = u_colorFront.rgb * u_colorFront.a; - float fgOpacity = u_colorFront.a; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - float bgOpacity = u_colorBack.a; - vec3 hlColor = u_colorHighlight.rgb * u_colorHighlight.a; - float hlOpacity = u_colorHighlight.a; + let fgColor = u.u_colorFront.rgb * u.u_colorFront.a; + var fgOpacity = u.u_colorFront.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + let bgOpacity = u.u_colorBack.a; + let hlColor = u.u_colorHighlight.rgb * u.u_colorHighlight.a; + let hlOpacity = u.u_colorHighlight.a; - fgColor = mix(fgColor, hlColor, step(1.02 - .02 * u_colorSteps, brightness)); - fgOpacity = mix(fgOpacity, hlOpacity, step(1.02 - .02 * u_colorSteps, brightness)); + let fgColorMixed = mix(fgColor, hlColor, step(1.02 - 0.02 * u.u_colorSteps, brightness)); + fgOpacity = mix(fgOpacity, hlOpacity, step(1.02 - 0.02 * u.u_colorSteps, brightness)); - color = fgColor * quantLum; + color = fgColorMixed * quantLum; opacity = fgOpacity * quantLum; color += bgColor * (1.0 - opacity); opacity += bgOpacity * (1.0 - opacity); } - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/liquid-metal.ts b/packages/shaders/src/shaders/liquid-metal.ts index e392bcd4a..757383ac8 100644 --- a/packages/shaders/src/shaders/liquid-metal.ts +++ b/packages/shaders/src/shaders/liquid-metal.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, rotation2, simplexNoise, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, glslMod, simplexNoise, colorBandingFix } from '../shader-utils.js'; /** * Futuristic liquid metal material applied to uploaded logo or abstract shape. @@ -46,327 +46,327 @@ import { declarePI, rotation2, simplexNoise, colorBandingFix } from '../shader-u * */ -// language=GLSL -export const liquidMetalFragmentShader: string = `#version 300 es -precision mediump float; - -uniform sampler2D u_image; -uniform float u_imageAspectRatio; - -uniform vec2 u_resolution; -uniform float u_time; - -uniform vec4 u_colorBack; -uniform vec4 u_colorTint; - -uniform float u_softness; -uniform float u_repetition; -uniform float u_shiftRed; -uniform float u_shiftBlue; -uniform float u_distortion; -uniform float u_contour; -uniform float u_angle; - -uniform float u_shape; -uniform bool u_isImage; +export const liquidMetalFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorBack: vec4f, + u_colorTint: vec4f, + u_softness: f32, + u_repetition: f32, + u_shiftRed: f32, + u_shiftBlue: f32, + u_distortion: f32, + u_contour: f32, + u_angle: f32, + u_shape: f32, + u_isImage: f32, +} +@group(0) @binding(0) var u: Uniforms; -in vec2 v_objectUV; -in vec2 v_responsiveUV; -in vec2 v_responsiveBoxGivenSize; -in vec2 v_imageUV; +${vertexOutputStruct} -out vec4 fragColor; +@group(1) @binding(0) var u_image_tex: texture_2d; +@group(1) @binding(1) var u_image_samp: sampler; ${ declarePI } ${ rotation2 } +${ glslMod } ${ simplexNoise } -float getColorChanges(float c1, float c2, float stripe_p, vec3 w, float blur, float bump, float tint) { +fn getColorChanges(c1: f32, c2: f32, stripe_p: f32, w: vec3f, blur: f32, bump_in: f32, tint: f32) -> f32 { - float ch = mix(c2, c1, smoothstep(.0, 2. * blur, stripe_p)); + var ch = mix(c2, c1, smoothstep(0.0, 2.0 * blur, stripe_p)); - float border = w[0]; - ch = mix(ch, c2, smoothstep(border, border + 2. * blur, stripe_p)); + var border = w[0]; + ch = mix(ch, c2, smoothstep(border, border + 2.0 * blur, stripe_p)); - if (u_isImage == true) { - bump = smoothstep(.2, .8, bump); + var bump = bump_in; + if (u.u_isImage > 0.5) { + bump = smoothstep(0.2, 0.8, bump); } - border = w[0] + .4 * (1. - bump) * w[1]; - ch = mix(ch, c1, smoothstep(border, border + 2. * blur, stripe_p)); + border = w[0] + 0.4 * (1.0 - bump) * w[1]; + ch = mix(ch, c1, smoothstep(border, border + 2.0 * blur, stripe_p)); - border = w[0] + .5 * (1. - bump) * w[1]; - ch = mix(ch, c2, smoothstep(border, border + 2. * blur, stripe_p)); + border = w[0] + 0.5 * (1.0 - bump) * w[1]; + ch = mix(ch, c2, smoothstep(border, border + 2.0 * blur, stripe_p)); border = w[0] + w[1]; - ch = mix(ch, c1, smoothstep(border, border + 2. * blur, stripe_p)); + ch = mix(ch, c1, smoothstep(border, border + 2.0 * blur, stripe_p)); - float gradient_t = (stripe_p - w[0] - w[1]) / w[2]; - float gradient = mix(c1, c2, smoothstep(0., 1., gradient_t)); - ch = mix(ch, gradient, smoothstep(border, border + .5 * blur, stripe_p)); + let gradient_t = (stripe_p - w[0] - w[1]) / w[2]; + let gradient_val = mix(c1, c2, smoothstep(0.0, 1.0, gradient_t)); + ch = mix(ch, gradient_val, smoothstep(border, border + 0.5 * blur, stripe_p)); // Tint color is applied with color burn blending - ch = mix(ch, 1. - min(1., (1. - ch) / max(tint, 0.0001)), u_colorTint.a); + ch = mix(ch, 1.0 - min(1.0, (1.0 - ch) / max(tint, 0.0001)), u.u_colorTint.a); return ch; } -float getImgFrame(vec2 uv, float th) { - float frame = 1.; - frame *= smoothstep(0., th, uv.y); - frame *= 1.0 - smoothstep(1. - th, 1., uv.y); - frame *= smoothstep(0., th, uv.x); - frame *= 1.0 - smoothstep(1. - th, 1., uv.x); +fn getImgFrame(uv: vec2f, th: f32) -> f32 { + var frame: f32 = 1.0; + frame *= smoothstep(0.0, th, uv.y); + frame *= 1.0 - smoothstep(1.0 - th, 1.0, uv.y); + frame *= smoothstep(0.0, th, uv.x); + frame *= 1.0 - smoothstep(1.0 - th, 1.0, uv.x); return frame; } -float blurEdge3x3(sampler2D tex, vec2 uv, vec2 dudx, vec2 dudy, float radius, float centerSample) { - vec2 texel = 1.0 / vec2(textureSize(tex, 0)); - vec2 r = radius * texel; +fn blurEdge3x3(uv: vec2f, dudx_v: vec2f, dudy_v: vec2f, radius: f32, centerSample: f32) -> f32 { + let texel = 1.0 / vec2f(textureDimensions(u_image_tex, 0)); + let r = radius * texel; - float w1 = 1.0, w2 = 2.0, w4 = 4.0; - float norm = 16.0; - float sum = w4 * centerSample; + let w1: f32 = 1.0; + let w2: f32 = 2.0; + let w4: f32 = 4.0; + let norm: f32 = 16.0; + var blur_sum = w4 * centerSample; - sum += w2 * textureGrad(tex, uv + vec2(0.0, -r.y), dudx, dudy).r; - sum += w2 * textureGrad(tex, uv + vec2(0.0, r.y), dudx, dudy).r; - sum += w2 * textureGrad(tex, uv + vec2(-r.x, 0.0), dudx, dudy).r; - sum += w2 * textureGrad(tex, uv + vec2(r.x, 0.0), dudx, dudy).r; + blur_sum += w2 * textureSampleGrad(u_image_tex, u_image_samp, uv + vec2f(0.0, -r.y), dudx_v, dudy_v).r; + blur_sum += w2 * textureSampleGrad(u_image_tex, u_image_samp, uv + vec2f(0.0, r.y), dudx_v, dudy_v).r; + blur_sum += w2 * textureSampleGrad(u_image_tex, u_image_samp, uv + vec2f(-r.x, 0.0), dudx_v, dudy_v).r; + blur_sum += w2 * textureSampleGrad(u_image_tex, u_image_samp, uv + vec2f(r.x, 0.0), dudx_v, dudy_v).r; - sum += w1 * textureGrad(tex, uv + vec2(-r.x, -r.y), dudx, dudy).r; - sum += w1 * textureGrad(tex, uv + vec2(r.x, -r.y), dudx, dudy).r; - sum += w1 * textureGrad(tex, uv + vec2(-r.x, r.y), dudx, dudy).r; - sum += w1 * textureGrad(tex, uv + vec2(r.x, r.y), dudx, dudy).r; + blur_sum += w1 * textureSampleGrad(u_image_tex, u_image_samp, uv + vec2f(-r.x, -r.y), dudx_v, dudy_v).r; + blur_sum += w1 * textureSampleGrad(u_image_tex, u_image_samp, uv + vec2f(r.x, -r.y), dudx_v, dudy_v).r; + blur_sum += w1 * textureSampleGrad(u_image_tex, u_image_samp, uv + vec2f(-r.x, r.y), dudx_v, dudy_v).r; + blur_sum += w1 * textureSampleGrad(u_image_tex, u_image_samp, uv + vec2f(r.x, r.y), dudx_v, dudy_v).r; - return sum / norm; + return blur_sum / norm; } -float lst(float edge0, float edge1, float x) { +fn lst(edge0: f32, edge1: f32, x: f32) -> f32 { return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); } -void main() { +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { - const float firstFrameOffset = 2.8; - float t = .3 * (u_time + firstFrameOffset); + let firstFrameOffset: f32 = 2.8; + var t = 0.3 * (u.u_time + firstFrameOffset); - vec2 uv = v_imageUV; - vec2 dudx = dFdx(v_imageUV); - vec2 dudy = dFdy(v_imageUV); - vec4 img = textureGrad(u_image, uv, dudx, dudy); + var uv = input.v_imageUV; + let dudx_v = dpdx(input.v_imageUV); + let dudy_v = dpdy(input.v_imageUV); + let img = textureSampleGrad(u_image_tex, u_image_samp, uv, dudx_v, dudy_v); - if (u_isImage == false) { - uv = v_objectUV + .5; - uv.y = 1. - uv.y; + if (u.u_isImage < 0.5) { + uv = input.v_objectUV + vec2f(0.5); + uv = vec2f(uv.x, 1.0 - uv.y); } - float cycleWidth = u_repetition; - float edge = 0.; - float contOffset = 1.; - - vec2 rotatedUV = uv - vec2(.5); - float angle = (-u_angle + 70.) * PI / 180.; - float cosA = cos(angle); - float sinA = sin(angle); - rotatedUV = vec2( - rotatedUV.x * cosA - rotatedUV.y * sinA, - rotatedUV.x * sinA + rotatedUV.y * cosA - ) + vec2(.5); - - if (u_isImage == true) { - float edgeRaw = img.r; - edge = blurEdge3x3(u_image, uv, dudx, dudy, 6., edgeRaw); + var cycleWidth = u.u_repetition; + var edge: f32 = 0.0; + var contOffset: f32 = 1.0; + + var rotatedUV = uv - vec2f(0.5); + let rot_angle = (-u.u_angle + 70.0) * PI / 180.0; + let cosA = cos(rot_angle); + let sinA = sin(rot_angle); + rotatedUV = vec2f( + rotatedUV.x * cosA - rotatedUV.y * sinA, + rotatedUV.x * sinA + rotatedUV.y * cosA + ) + vec2f(0.5); + + if (u.u_isImage > 0.5) { + let edgeRaw = img.r; + edge = blurEdge3x3(uv, dudx_v, dudy_v, 6.0, edgeRaw); edge = pow(edge, 1.6); - edge *= mix(0.0, 1.0, smoothstep(0.0, 0.4, u_contour)); + edge *= mix(0.0, 1.0, smoothstep(0.0, 0.4, u.u_contour)); } else { - if (u_shape < 1.) { + if (u.u_shape < 1.0) { // full-fill on canvas - vec2 borderUV = v_responsiveUV + .5; - float ratio = v_responsiveBoxGivenSize.x / v_responsiveBoxGivenSize.y; - vec2 mask = min(borderUV, 1. - borderUV); - vec2 pixel_thickness = min(250. / v_responsiveBoxGivenSize, vec2(.5)); - float maskX = smoothstep(0.0, pixel_thickness.x, mask.x); - float maskY = smoothstep(0.0, pixel_thickness.y, mask.y); - maskX = pow(maskX, .25); - maskY = pow(maskY, .25); - edge = clamp(1. - maskX * maskY, 0., 1.); - - uv = v_responsiveUV; - if (ratio > 1.) { - uv.y /= ratio; + let borderUV = input.v_responsiveUV + vec2f(0.5); + let ratio = input.v_responsiveBoxGivenSize.x / input.v_responsiveBoxGivenSize.y; + let mask_val = min(borderUV, vec2f(1.0) - borderUV); + let pixel_thickness = min(250.0 / input.v_responsiveBoxGivenSize, vec2f(0.5)); + var maskX = smoothstep(0.0, pixel_thickness.x, mask_val.x); + var maskY = smoothstep(0.0, pixel_thickness.y, mask_val.y); + maskX = pow(maskX, 0.25); + maskY = pow(maskY, 0.25); + edge = clamp(1.0 - maskX * maskY, 0.0, 1.0); + + uv = input.v_responsiveUV; + if (ratio > 1.0) { + uv = vec2f(uv.x, uv.y / ratio); } else { - uv.x *= ratio; + uv = vec2f(uv.x * ratio, uv.y); } - uv += .5; - uv.y = 1. - uv.y; + uv += vec2f(0.5); + uv = vec2f(uv.x, 1.0 - uv.y); - cycleWidth *= 2.; + cycleWidth *= 2.0; contOffset = 1.5; - } else if (u_shape < 2.) { + } else if (u.u_shape < 2.0) { // circle - vec2 shapeUV = uv - .5; - shapeUV *= .67; - edge = pow(clamp(3. * length(shapeUV), 0., 1.), 18.); - } else if (u_shape < 3.) { + var shapeUV = uv - vec2f(0.5); + shapeUV *= 0.67; + edge = pow(clamp(3.0 * length(shapeUV), 0.0, 1.0), 18.0); + } else if (u.u_shape < 3.0) { // daisy - vec2 shapeUV = uv - .5; + var shapeUV = uv - vec2f(0.5); shapeUV *= 1.68; - float r = length(shapeUV) * 2.; - float a = atan(shapeUV.y, shapeUV.x) + .2; - r *= (1. + .05 * sin(3. * a + 2. * t)); - float f = abs(cos(a * 3.)); - edge = smoothstep(f, f + .7, r); + var r = length(shapeUV) * 2.0; + let a = atan2(shapeUV.y, shapeUV.x) + 0.2; + r *= (1.0 + 0.05 * sin(3.0 * a + 2.0 * t)); + let f = abs(cos(a * 3.0)); + edge = smoothstep(f, f + 0.7, r); edge *= edge; - uv *= .8; + uv *= 0.8; cycleWidth *= 1.6; - } else if (u_shape < 4.) { + } else if (u.u_shape < 4.0) { // diamond - vec2 shapeUV = uv - .5; - shapeUV = rotate(shapeUV, .25 * PI); + var shapeUV = uv - vec2f(0.5); + shapeUV = rotate(shapeUV, 0.25 * PI); shapeUV *= 1.42; - shapeUV += .5; - vec2 mask = min(shapeUV, 1. - shapeUV); - vec2 pixel_thickness = vec2(.15); - float maskX = smoothstep(0.0, pixel_thickness.x, mask.x); - float maskY = smoothstep(0.0, pixel_thickness.y, mask.y); - maskX = pow(maskX, .25); - maskY = pow(maskY, .25); - edge = clamp(1. - maskX * maskY, 0., 1.); - } else if (u_shape < 5.) { + shapeUV += vec2f(0.5); + let mask_val = min(shapeUV, vec2f(1.0) - shapeUV); + let pixel_thickness = vec2f(0.15); + var maskX = smoothstep(0.0, pixel_thickness.x, mask_val.x); + var maskY = smoothstep(0.0, pixel_thickness.y, mask_val.y); + maskX = pow(maskX, 0.25); + maskY = pow(maskY, 0.25); + edge = clamp(1.0 - maskX * maskY, 0.0, 1.0); + } else if (u.u_shape < 5.0) { // metaballs - vec2 shapeUV = uv - .5; + var shapeUV = uv - vec2f(0.5); shapeUV *= 1.3; - edge = 0.; - for (int i = 0; i < 5; i++) { - float fi = float(i); - float speed = 1.5 + 2./3. * sin(fi * 12.345); - float angle = -fi * 1.5; - vec2 dir1 = vec2(cos(angle), sin(angle)); - vec2 dir2 = vec2(cos(angle + 1.57), sin(angle + 1.)); - vec2 traj = .4 * (dir1 * sin(t * speed + fi * 1.23) + dir2 * cos(t * (speed * 0.7) + fi * 2.17)); - float d = length(shapeUV + traj); + edge = 0.0; + for (var i: i32 = 0; i < 5; i++) { + let fi = f32(i); + let speed = 1.5 + 2.0 / 3.0 * sin(fi * 12.345); + let mb_angle = -fi * 1.5; + let dir1 = vec2f(cos(mb_angle), sin(mb_angle)); + let dir2 = vec2f(cos(mb_angle + 1.57), sin(mb_angle + 1.0)); + let traj = 0.4 * (dir1 * sin(t * speed + fi * 1.23) + dir2 * cos(t * (speed * 0.7) + fi * 2.17)); + let d = length(shapeUV + traj); edge += pow(1.0 - clamp(d, 0.0, 1.0), 4.0); } - edge = 1. - smoothstep(.65, .9, edge); - edge = pow(edge, 4.); + edge = 1.0 - smoothstep(0.65, 0.9, edge); + edge = pow(edge, 4.0); } - edge = mix(smoothstep(.9 - 2. * fwidth(edge), .9, edge), edge, smoothstep(0.0, 0.4, u_contour)); + let fw_edge = abs(dpdx(edge)) + abs(dpdy(edge)); + edge = mix(smoothstep(0.9 - 2.0 * fw_edge, 0.9, edge), edge, smoothstep(0.0, 0.4, u.u_contour)); } - float opacity = 0.; - if (u_isImage == true) { + var opacity: f32 = 0.0; + if (u.u_isImage > 0.5) { opacity = img.g; - float frame = getImgFrame(v_imageUV, 0.); + let frame = getImgFrame(input.v_imageUV, 0.0); opacity *= frame; } else { - opacity = 1. - smoothstep(.9 - 2. * fwidth(edge), .9, edge); - if (u_shape < 2.) { + let fw_edge2 = abs(dpdx(edge)) + abs(dpdy(edge)); + opacity = 1.0 - smoothstep(0.9 - 2.0 * fw_edge2, 0.9, edge); + if (u.u_shape < 2.0) { edge = 1.2 * edge; - } else if (u_shape < 5.) { + } else if (u.u_shape < 5.0) { edge = 1.8 * pow(edge, 1.5); } } - float diagBLtoTR = rotatedUV.x - rotatedUV.y; - float diagTLtoBR = rotatedUV.x + rotatedUV.y; + let diagBLtoTR = rotatedUV.x - rotatedUV.y; + let diagTLtoBR = rotatedUV.x + rotatedUV.y; - vec3 color = vec3(0.); - vec3 color1 = vec3(.98, 0.98, 1.); - vec3 color2 = vec3(.1, .1, .1 + .1 * smoothstep(.7, 1.3, diagTLtoBR)); + var color = vec3f(0.0); + let color1 = vec3f(0.98, 0.98, 1.0); + let color2 = vec3f(0.1, 0.1, 0.1 + 0.1 * smoothstep(0.7, 1.3, diagTLtoBR)); - vec2 grad_uv = uv - .5; + var grad_uv = uv - vec2f(0.5); - float dist = length(grad_uv + vec2(0., .2 * diagBLtoTR)); - grad_uv = rotate(grad_uv, (.25 - .2 * diagBLtoTR) * PI); - float direction = grad_uv.x; + let dist = length(grad_uv + vec2f(0.0, 0.2 * diagBLtoTR)); + grad_uv = rotate(grad_uv, (0.25 - 0.2 * diagBLtoTR) * PI); + var direction = grad_uv.x; - float bump = pow(1.8 * dist, 1.2); - bump = 1. - bump; - bump *= pow(uv.y, .3); + var bump = pow(1.8 * dist, 1.2); + bump = 1.0 - bump; + bump *= pow(uv.y, 0.3); - float thin_strip_1_ratio = .12 / cycleWidth * (1. - .4 * bump); - float thin_strip_2_ratio = .07 / cycleWidth * (1. + .4 * bump); - float wide_strip_ratio = (1. - thin_strip_1_ratio - thin_strip_2_ratio); + let thin_strip_1_ratio = 0.12 / cycleWidth * (1.0 - 0.4 * bump); + let thin_strip_2_ratio = 0.07 / cycleWidth * (1.0 + 0.4 * bump); + let wide_strip_ratio = (1.0 - thin_strip_1_ratio - thin_strip_2_ratio); - float thin_strip_1_width = cycleWidth * thin_strip_1_ratio; - float thin_strip_2_width = cycleWidth * thin_strip_2_ratio; + let thin_strip_1_width = cycleWidth * thin_strip_1_ratio; + let thin_strip_2_width = cycleWidth * thin_strip_2_ratio; - float noise = snoise(uv - t); + let noise = snoise(uv - vec2f(t)); - edge += (1. - edge) * u_distortion * noise; + edge += (1.0 - edge) * u.u_distortion * noise; direction += diagBLtoTR; - float contour = 0.; - direction -= 2. * noise * diagBLtoTR * (smoothstep(0., 1., edge) * (1.0 - smoothstep(0., 1., edge))); - direction *= mix(1., 1. - edge, smoothstep(.5, 1., u_contour)); - direction -= 1.7 * edge * smoothstep(.5, 1., u_contour); - direction += .2 * pow(u_contour, 4.) * (1.0 - smoothstep(0., 1., edge)); + var contour: f32 = 0.0; + direction -= 2.0 * noise * diagBLtoTR * (smoothstep(0.0, 1.0, edge) * (1.0 - smoothstep(0.0, 1.0, edge))); + direction *= mix(1.0, 1.0 - edge, smoothstep(0.5, 1.0, u.u_contour)); + direction -= 1.7 * edge * smoothstep(0.5, 1.0, u.u_contour); + direction += 0.2 * pow(u.u_contour, 4.0) * (1.0 - smoothstep(0.0, 1.0, edge)); - bump *= clamp(pow(uv.y, .1), .3, 1.); - direction *= (.1 + (1.1 - edge) * bump); + bump *= clamp(pow(uv.y, 0.1), 0.3, 1.0); + direction *= (0.1 + (1.1 - edge) * bump); - direction *= (.4 + .6 * (1.0 - smoothstep(.5, 1., edge))); - direction += .18 * (smoothstep(.1, .2, uv.y) * (1.0 - smoothstep(.2, .4, uv.y))); - direction += .03 * (smoothstep(.1, .2, 1. - uv.y) * (1.0 - smoothstep(.2, .4, 1. - uv.y))); + direction *= (0.4 + 0.6 * (1.0 - smoothstep(0.5, 1.0, edge))); + direction += 0.18 * (smoothstep(0.1, 0.2, uv.y) * (1.0 - smoothstep(0.2, 0.4, uv.y))); + direction += 0.03 * (smoothstep(0.1, 0.2, 1.0 - uv.y) * (1.0 - smoothstep(0.2, 0.4, 1.0 - uv.y))); - direction *= (.5 + .5 * pow(uv.y, 2.)); + direction *= (0.5 + 0.5 * pow(uv.y, 2.0)); direction *= cycleWidth; direction -= t; - float colorDispersion = (1. - bump); - colorDispersion = clamp(colorDispersion, 0., 1.); - float dispersionRed = colorDispersion; - dispersionRed += .03 * bump * noise; - dispersionRed += 5. * (smoothstep(-.1, .2, uv.y) * (1.0 - smoothstep(.1, .5, uv.y))) * (smoothstep(.4, .6, bump) * (1.0 - smoothstep(.4, 1., bump))); + var colorDispersion = (1.0 - bump); + colorDispersion = clamp(colorDispersion, 0.0, 1.0); + var dispersionRed = colorDispersion; + dispersionRed += 0.03 * bump * noise; + dispersionRed += 5.0 * (smoothstep(-0.1, 0.2, uv.y) * (1.0 - smoothstep(0.1, 0.5, uv.y))) * (smoothstep(0.4, 0.6, bump) * (1.0 - smoothstep(0.4, 1.0, bump))); dispersionRed -= diagBLtoTR; - float dispersionBlue = colorDispersion; + var dispersionBlue = colorDispersion; dispersionBlue *= 1.3; - dispersionBlue += (smoothstep(0., .4, uv.y) * (1.0 - smoothstep(.1, .8, uv.y))) * (smoothstep(.4, .6, bump) * (1.0 - smoothstep(.4, .8, bump))); - dispersionBlue -= .2 * edge; - - dispersionRed *= (u_shiftRed / 20.); - dispersionBlue *= (u_shiftBlue / 20.); - - float blur = 0.; - float rExtraBlur = 0.; - float gExtraBlur = 0.; - if (u_isImage == true) { - float softness = 0.05 * u_softness; - blur = softness + .5 * smoothstep(1., 10., u_repetition) * smoothstep(.0, 1., edge); - float smallCanvasT = 1.0 - smoothstep(100., 500., min(u_resolution.x, u_resolution.y)); - blur += smallCanvasT * smoothstep(.0, 1., edge); - rExtraBlur = softness * (0.05 + .1 * (u_shiftRed / 20.) * bump); - gExtraBlur = softness * 0.05 / max(0.001, abs(1. - diagBLtoTR)); + dispersionBlue += (smoothstep(0.0, 0.4, uv.y) * (1.0 - smoothstep(0.1, 0.8, uv.y))) * (smoothstep(0.4, 0.6, bump) * (1.0 - smoothstep(0.4, 0.8, bump))); + dispersionBlue -= 0.2 * edge; + + dispersionRed *= (u.u_shiftRed / 20.0); + dispersionBlue *= (u.u_shiftBlue / 20.0); + + var blur: f32 = 0.0; + var rExtraBlur: f32 = 0.0; + var gExtraBlur: f32 = 0.0; + if (u.u_isImage > 0.5) { + let softness = 0.05 * u.u_softness; + blur = softness + 0.5 * smoothstep(1.0, 10.0, u.u_repetition) * smoothstep(0.0, 1.0, edge); + let smallCanvasT = 1.0 - smoothstep(100.0, 500.0, min(u.u_resolution.x, u.u_resolution.y)); + blur += smallCanvasT * smoothstep(0.0, 1.0, edge); + rExtraBlur = softness * (0.05 + 0.1 * (u.u_shiftRed / 20.0) * bump); + gExtraBlur = softness * 0.05 / max(0.001, abs(1.0 - diagBLtoTR)); } else { - blur = u_softness / 15. + .3 * contour; + blur = u.u_softness / 15.0 + 0.3 * contour; } - vec3 w = vec3(thin_strip_1_width, thin_strip_2_width, wide_strip_ratio); - w[1] -= .02 * smoothstep(.0, 1., edge + bump); - float stripe_r = fract(direction + dispersionRed); - float r = getColorChanges(color1.r, color2.r, stripe_r, w, blur + fwidth(stripe_r) + rExtraBlur, bump, u_colorTint.r); - float stripe_g = fract(direction); - float g = getColorChanges(color1.g, color2.g, stripe_g, w, blur + fwidth(stripe_g) + gExtraBlur, bump, u_colorTint.g); - float stripe_b = fract(direction - dispersionBlue); - float b = getColorChanges(color1.b, color2.b, stripe_b, w, blur + fwidth(stripe_b), bump, u_colorTint.b); - - color = vec3(r, g, b); + var w = vec3f(thin_strip_1_width, thin_strip_2_width, wide_strip_ratio); + w = vec3f(w.x, w.y - 0.02 * smoothstep(0.0, 1.0, edge + bump), w.z); + let stripe_r = fract(direction + dispersionRed); + let fw_stripe_r = abs(dpdx(stripe_r)) + abs(dpdy(stripe_r)); + let r = getColorChanges(color1.r, color2.r, stripe_r, w, blur + fw_stripe_r + rExtraBlur, bump, u.u_colorTint.r); + let stripe_g = fract(direction); + let fw_stripe_g = abs(dpdx(stripe_g)) + abs(dpdy(stripe_g)); + let g = getColorChanges(color1.g, color2.g, stripe_g, w, blur + fw_stripe_g + gExtraBlur, bump, u.u_colorTint.g); + let stripe_b = fract(direction - dispersionBlue); + let fw_stripe_b = abs(dpdx(stripe_b)) + abs(dpdy(stripe_b)); + let b = getColorChanges(color1.b, color2.b, stripe_b, w, blur + fw_stripe_b, bump, u.u_colorTint.b); + + color = vec3f(r, g, b); color *= opacity; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - color = color + bgColor * (1. - opacity); - opacity = opacity + u_colorBack.a * (1. - opacity); + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + color = color + bgColor * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); ${ colorBandingFix } - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/mesh-gradient.ts b/packages/shaders/src/shaders/mesh-gradient.ts index 5f2f3c259..0d648b752 100644 --- a/packages/shaders/src/shaders/mesh-gradient.ts +++ b/packages/shaders/src/shaders/mesh-gradient.ts @@ -4,7 +4,7 @@ import { type ShaderSizingParams, type ShaderSizingUniforms, } from '../shader-sizing.js'; -import { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, proceduralHash21 } from '../shader-utils.js'; export const meshGradientMeta = { maxColorCount: 10, @@ -41,116 +41,113 @@ export const meshGradientMeta = { * */ -// language=GLSL -export const meshGradientFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform vec4 u_colors[${meshGradientMeta.maxColorCount}]; -uniform float u_colorsCount; - -uniform float u_distortion; -uniform float u_swirl; -uniform float u_grainMixer; -uniform float u_grainOverlay; +export const meshGradientFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_distortion: f32, + u_swirl: f32, + u_grainMixer: f32, + u_grainOverlay: f32, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; -in vec2 v_objectUV; -out vec4 fragColor; +${vertexOutputStruct} ${declarePI} ${rotation2} ${proceduralHash21} -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = hash21(i); - float b = hash21(i + vec2(1.0, 0.0)); - float c = hash21(i + vec2(0.0, 1.0)); - float d = hash21(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = hash21(i); + let b = hash21(i + vec2f(1.0, 0.0)); + let c = hash21(i + vec2f(0.0, 1.0)); + let d = hash21(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } -float noise(vec2 n, vec2 seedOffset) { +fn noise(n: vec2f, seedOffset: vec2f) -> f32 { return valueNoise(n + seedOffset); } -vec2 getPosition(int i, float t) { - float a = float(i) * .37; - float b = .6 + fract(float(i) / 3.) * .9; - float c = .8 + fract(float(i + 1) / 4.); +fn getPosition(i: i32, t: f32) -> vec2f { + let fi = f32(i); + let a = fi * 0.37; + let b = 0.6 + fract(fi / 3.0) * 0.9; + let c_val = 0.8 + fract(f32(i + 1) / 4.0); - float x = sin(t * b + a); - float y = cos(t * c + a * 1.5); + let x = sin(t * b + a); + let y = cos(t * c_val + a * 1.5); - return .5 + .5 * vec2(x, y); + return vec2f(0.5) + vec2f(0.5) * vec2f(x, y); } -void main() { - vec2 uv = v_objectUV; - uv += .5; - vec2 grainUV = uv * 1000.; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var uv = input.v_objectUV; + uv += vec2f(0.5); + let grainUV = uv * 1000.0; - float grain = noise(grainUV, vec2(0.)); - float mixerGrain = .4 * u_grainMixer * (grain - .5); + let grain = noise(grainUV, vec2f(0.0)); + let mixerGrain = 0.4 * u.u_grainMixer * (grain - 0.5); - const float firstFrameOffset = 41.5; - float t = .5 * (u_time + firstFrameOffset); + let firstFrameOffset: f32 = 41.5; + let t = 0.5 * (u.u_time + firstFrameOffset); - float radius = smoothstep(0., 1., length(uv - .5)); - float center = 1. - radius; - for (float i = 1.; i <= 2.; i++) { - uv.x += u_distortion * center / i * sin(t + i * .4 * smoothstep(.0, 1., uv.y)) * cos(.2 * t + i * 2.4 * smoothstep(.0, 1., uv.y)); - uv.y += u_distortion * center / i * cos(t + i * 2. * smoothstep(.0, 1., uv.x)); + let radius = smoothstep(0.0, 1.0, length(uv - vec2f(0.5))); + let center = 1.0 - radius; + for (var i: f32 = 1.0; i <= 2.0; i += 1.0) { + uv.x += u.u_distortion * center / i * sin(t + i * 0.4 * smoothstep(0.0, 1.0, uv.y)) * cos(0.2 * t + i * 2.4 * smoothstep(0.0, 1.0, uv.y)); + uv.y += u.u_distortion * center / i * cos(t + i * 2.0 * smoothstep(0.0, 1.0, uv.x)); } - vec2 uvRotated = uv; - uvRotated -= vec2(.5); - float angle = 3. * u_swirl * radius; + var uvRotated = uv; + uvRotated -= vec2f(0.5); + let angle = 3.0 * u.u_swirl * radius; uvRotated = rotate(uvRotated, -angle); - uvRotated += vec2(.5); - - vec3 color = vec3(0.); - float opacity = 0.; - float totalWeight = 0.; + uvRotated += vec2f(0.5); - for (int i = 0; i < ${meshGradientMeta.maxColorCount}; i++) { - if (i >= int(u_colorsCount)) break; + var color = vec3f(0.0); + var opacity: f32 = 0.0; + var totalWeight: f32 = 0.0; - vec2 pos = getPosition(i, t) + mixerGrain; - vec3 colorFraction = u_colors[i].rgb * u_colors[i].a; - float opacityFraction = u_colors[i].a; + for (var i: i32 = 0; i < ${meshGradientMeta.maxColorCount}; i++) { + if (i >= i32(u.u_colorsCount)) { break; } - float dist = length(uvRotated - pos); + let pos = getPosition(i, t) + vec2f(mixerGrain); + let colorFraction = u.u_colors[i].rgb * u.u_colors[i].a; + let opacityFraction = u.u_colors[i].a; + var dist = length(uvRotated - pos); dist = pow(dist, 3.5); - float weight = 1. / (dist + 1e-3); + let weight = 1.0 / (dist + 1e-3); color += colorFraction * weight; opacity += opacityFraction * weight; totalWeight += weight; } - color /= max(1e-4, totalWeight); - opacity /= max(1e-4, totalWeight); + color = color / max(1e-4, totalWeight); + opacity = opacity / max(1e-4, totalWeight); - float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.)); - grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5); + let grainOverlayBase = valueNoise(rotate(grainUV, 1.0) + vec2f(3.0)); + var grainOverlay = mix(grainOverlayBase, valueNoise(rotate(grainUV, 2.0) + vec2f(-1.0)), 0.5); grainOverlay = pow(grainOverlay, 1.3); - float grainOverlayV = grainOverlay * 2. - 1.; - vec3 grainOverlayColor = vec3(step(0., grainOverlayV)); - float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV); - grainOverlayStrength = pow(grainOverlayStrength, .8); - color = mix(color, grainOverlayColor, .35 * grainOverlayStrength); + let grainOverlayV = grainOverlay * 2.0 - 1.0; + let grainOverlayColor = vec3f(step(0.0, grainOverlayV)); + var grainOverlayStrength = u.u_grainOverlay * abs(grainOverlayV); + grainOverlayStrength = pow(grainOverlayStrength, 0.8); + color = mix(color, grainOverlayColor, 0.35 * grainOverlayStrength); - opacity += .5 * grainOverlayStrength; - opacity = clamp(opacity, 0., 1.); + opacity += 0.5 * grainOverlayStrength; + opacity = clamp(opacity, 0.0, 1.0); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/metaballs.ts b/packages/shaders/src/shaders/metaballs.ts index 9bc328172..bcce0e409 100644 --- a/packages/shaders/src/shaders/metaballs.ts +++ b/packages/shaders/src/shaders/metaballs.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, textureRandomizerR, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, textureRandomizerR, colorBandingFix } from '../shader-utils.js'; export const metaballsMeta = { maxColorCount: 8, @@ -38,79 +38,77 @@ export const metaballsMeta = { * */ -// language=GLSL -export const metaballsFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform sampler2D u_noiseTexture; - -uniform vec4 u_colorBack; -uniform vec4 u_colors[${ metaballsMeta.maxColorCount }]; -uniform float u_colorsCount; -uniform float u_size; -uniform float u_sizeRange; -uniform float u_count; - -in vec2 v_objectUV; +export const metaballsFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_size: f32, + u_sizeRange: f32, + u_count: f32, + u_colorBack: vec4f, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; +@group(1) @binding(0) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(1) var u_noiseTexture_samp: sampler; -out vec4 fragColor; +${vertexOutputStruct} ${ declarePI } ${ textureRandomizerR } -float noise(float x) { - float i = floor(x); - float f = fract(x); - float u = f * f * (3.0 - 2.0 * f); - vec2 p0 = vec2(i, 0.0); - vec2 p1 = vec2(i + 1.0, 0.0); - return mix(randomR(p0), randomR(p1), u); + +fn noise(x: f32) -> f32 { + let i = floor(x); + let f = fract(x); + let u_val = f * f * (3.0 - 2.0 * f); + let p0 = vec2f(i, 0.0); + let p1 = vec2f(i + 1.0, 0.0); + return mix(randomR(p0), randomR(p1), u_val); } -float getBallShape(vec2 uv, vec2 c, float p) { - float s = .5 * length(uv - c); - s = 1. - clamp(s, 0., 1.); +fn getBallShape(uv: vec2f, c: vec2f, p: f32) -> f32 { + var s = 0.5 * length(uv - c); + s = 1.0 - clamp(s, 0.0, 1.0); s = pow(s, p); return s; } -void main() { - vec2 shape_uv = v_objectUV; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var shape_uv = input.v_objectUV; - shape_uv += .5; + shape_uv += vec2f(0.5); - const float firstFrameOffset = 2503.4; - float t = .2 * (u_time + firstFrameOffset); + let firstFrameOffset: f32 = 2503.4; + let t = 0.2 * (u.u_time + firstFrameOffset); - vec3 totalColor = vec3(0.); - float totalShape = 0.; - float totalOpacity = 0.; + var totalColor = vec3f(0.0); + var totalShape: f32 = 0.0; + var totalOpacity: f32 = 0.0; - for (int i = 0; i < ${ metaballsMeta.maxBallsCount }; i++) { - if (i >= int(ceil(u_count))) break; + for (var i: i32 = 0; i < ${ metaballsMeta.maxBallsCount }; i++) { + if (i >= i32(ceil(u.u_count))) { break; } - float idxFract = float(i) / float(${ metaballsMeta.maxBallsCount }); - float angle = TWO_PI * idxFract; + let idxFract = f32(i) / f32(${ metaballsMeta.maxBallsCount }); + let angle = TWO_PI * idxFract; - float speed = 1. - .2 * idxFract; - float noiseX = noise(angle * 10. + float(i) + t * speed); - float noiseY = noise(angle * 20. + float(i) - t * speed); + let speed = 1.0 - 0.2 * idxFract; + let noiseX = noise(angle * 10.0 + f32(i) + t * speed); + let noiseY = noise(angle * 20.0 + f32(i) - t * speed); - vec2 pos = vec2(.5) + 1e-4 + .9 * (vec2(noiseX, noiseY) - .5); + let pos = vec2f(0.5) + vec2f(1e-4) + 0.9 * (vec2f(noiseX, noiseY) - vec2f(0.5)); - int safeIndex = i % int(u_colorsCount + 0.5); - vec4 ballColor = u_colors[safeIndex]; - ballColor.rgb *= ballColor.a; + let safeIndex = i % i32(u.u_colorsCount + 0.5); + var ballColor = u.u_colors[safeIndex]; + ballColor = vec4f(ballColor.rgb * ballColor.a, ballColor.a); - float sizeFrac = 1.; - if (float(i) > floor(u_count - 1.)) { - sizeFrac *= fract(u_count); + var sizeFrac: f32 = 1.0; + if (f32(i) > floor(u.u_count - 1.0)) { + sizeFrac *= fract(u.u_count); } - float shape = getBallShape(shape_uv, pos, 45. - 30. * u_size * sizeFrac); - shape *= pow(u_size, .2); - shape = smoothstep(0., 1., shape); + var shape = getBallShape(shape_uv, pos, 45.0 - 30.0 * u.u_size * sizeFrac); + shape *= pow(u.u_size, 0.2); + shape = smoothstep(0.0, 1.0, shape); totalColor += ballColor.rgb * shape; totalShape += shape; @@ -120,19 +118,19 @@ void main() { totalColor /= max(totalShape, 1e-4); totalOpacity /= max(totalShape, 1e-4); - float edge_width = fwidth(totalShape); - float finalShape = smoothstep(.4, .4 + edge_width, totalShape); + let edge_width = fwidth(totalShape); + let finalShape = smoothstep(0.4, 0.4 + edge_width, totalShape); - vec3 color = totalColor * finalShape; - float opacity = totalOpacity * finalShape; + var color = totalColor * finalShape; + var opacity = totalOpacity * finalShape; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - color = color + bgColor * (1. - opacity); - opacity = opacity + u_colorBack.a * (1. - opacity); + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + color = color + bgColor * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); ${ colorBandingFix } - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/neuro-noise.ts b/packages/shaders/src/shaders/neuro-noise.ts index 030234f06..134328e0e 100644 --- a/packages/shaders/src/shaders/neuro-noise.ts +++ b/packages/shaders/src/shaders/neuro-noise.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { rotation2, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, rotation2, colorBandingFix } from '../shader-utils.js'; /** * A glowing, web-like structure of fluid lines and soft intersections. @@ -35,73 +35,69 @@ import { rotation2, colorBandingFix } from '../shader-utils.js'; * Original algorithm: https://x.com/zozuar/status/1625182758745128981/ */ -// language=GLSL -export const neuroNoiseFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; -uniform vec2 u_resolution; -uniform float u_pixelRatio; - -uniform vec4 u_colorFront; -uniform vec4 u_colorMid; -uniform vec4 u_colorBack; -uniform float u_brightness; -uniform float u_contrast; - -in vec2 v_patternUV; +export const neuroNoiseFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_brightness: f32, + u_contrast: f32, + u_colorFront: vec4f, + u_colorMid: vec4f, + u_colorBack: vec4f, +} +@group(0) @binding(0) var u: Uniforms; -out vec4 fragColor; +${vertexOutputStruct} ${ rotation2 } -float neuroShape(vec2 uv, float t) { - vec2 sine_acc = vec2(0.); - vec2 res = vec2(0.); - float scale = 8.; +fn neuroShape(uv_in: vec2f, t: f32) -> f32 { + var uv = uv_in; + var sine_acc = vec2f(0.0); + var res = vec2f(0.0); + var scale: f32 = 8.0; - for (int j = 0; j < 15; j++) { - uv = rotate(uv, 1.); - sine_acc = rotate(sine_acc, 1.); - vec2 layer = uv * scale + float(j) + sine_acc - t; + for (var j: i32 = 0; j < 15; j++) { + uv = rotate(uv, 1.0); + sine_acc = rotate(sine_acc, 1.0); + let layer = uv * scale + vec2f(f32(j)) + sine_acc - vec2f(t); sine_acc += sin(layer); - res += (.5 + .5 * cos(layer)) / scale; - scale *= (1.2); + res += (vec2f(0.5) + 0.5 * cos(layer)) / scale; + scale *= 1.2; } return res.x + res.y; } -void main() { - vec2 shape_uv = v_patternUV; - shape_uv *= .13; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var shape_uv = input.v_patternUV; + shape_uv *= 0.13; - float t = .5 * u_time; + let t = 0.5 * u.u_time; - float noise = neuroShape(shape_uv, t); + var noise_val = neuroShape(shape_uv, t); - noise = (1. + u_brightness) * noise * noise; - noise = pow(noise, .7 + 6. * u_contrast); - noise = min(1.4, noise); + noise_val = (1.0 + u.u_brightness) * noise_val * noise_val; + noise_val = pow(noise_val, 0.7 + 6.0 * u.u_contrast); + noise_val = min(1.4, noise_val); - float blend = smoothstep(0.7, 1.4, noise); + let blend = smoothstep(0.7, 1.4, noise_val); - vec4 frontC = u_colorFront; - frontC.rgb *= frontC.a; - vec4 midC = u_colorMid; - midC.rgb *= midC.a; - vec4 blendFront = mix(midC, frontC, blend); + var frontC = u.u_colorFront; + frontC = vec4f(frontC.rgb * frontC.a, frontC.a); + var midC = u.u_colorMid; + midC = vec4f(midC.rgb * midC.a, midC.a); + let blendFront = mix(midC, frontC, blend); - float safeNoise = max(noise, 0.0); - vec3 color = blendFront.rgb * safeNoise; - float opacity = clamp(blendFront.a * safeNoise, 0., 1.); + let safeNoise = max(noise_val, 0.0); + var color = blendFront.rgb * safeNoise; + var opacity = clamp(blendFront.a * safeNoise, 0.0, 1.0); - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - color = color + bgColor * (1. - opacity); - opacity = opacity + u_colorBack.a * (1. - opacity); + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + color = color + bgColor * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); ${ colorBandingFix } - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/paper-texture.ts b/packages/shaders/src/shaders/paper-texture.ts index 83a6b252a..21486b700 100644 --- a/packages/shaders/src/shaders/paper-texture.ts +++ b/packages/shaders/src/shaders/paper-texture.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { rotation2, declarePI, fiberNoise, textureRandomizerR } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, rotation2, declarePI, fiberNoise, textureRandomizerR } from '../shader-utils.js'; /** * A static texture built from multiple noise layers, usable for realistic paper and cardboard surfaces. @@ -45,45 +45,44 @@ import { rotation2, declarePI, fiberNoise, textureRandomizerR } from '../shader- * */ -// language=GLSL -export const paperTextureFragmentShader: string = `#version 300 es -precision mediump float; - -uniform vec2 u_resolution; -uniform float u_pixelRatio; - -uniform vec4 u_colorFront; -uniform vec4 u_colorBack; - -uniform sampler2D u_image; -uniform float u_imageAspectRatio; - -uniform float u_contrast; -uniform float u_roughness; -uniform float u_fiber; -uniform float u_fiberSize; -uniform float u_crumples; -uniform float u_crumpleSize; -uniform float u_folds; -uniform float u_foldCount; -uniform float u_drops; -uniform float u_seed; -uniform float u_fade; +export const paperTextureFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorFront: vec4f, + u_colorBack: vec4f, + u_contrast: f32, + u_roughness: f32, + u_fiber: f32, + u_fiberSize: f32, + u_crumples: f32, + u_crumpleSize: f32, + u_folds: f32, + u_foldCount: f32, + u_drops: f32, + u_seed: f32, + u_fade: f32, +} +@group(0) @binding(0) var u: Uniforms; -uniform sampler2D u_noiseTexture; +@group(1) @binding(0) var u_image_tex: texture_2d; +@group(1) @binding(1) var u_image_samp: sampler; +@group(1) @binding(2) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(3) var u_noiseTexture_samp: sampler; -in vec2 v_imageUV; +${vertexOutputStruct} -out vec4 fragColor; +fn fwidth_f32(v: f32) -> f32 { + return abs(dpdx(v)) + abs(dpdy(v)); +} -float getUvFrame(vec2 uv) { - float aax = 2. * fwidth(uv.x); - float aay = 2. * fwidth(uv.y); +fn getUvFrame(uv: vec2f) -> f32 { + let aax = 2.0 * fwidth_f32(uv.x); + let aay = 2.0 * fwidth_f32(uv.y); - float left = smoothstep(0., aax, uv.x); - float right = 1. - smoothstep(1. - aax, 1., uv.x); - float bottom = smoothstep(0., aay, uv.y); - float top = 1. - smoothstep(1. - aay, 1., uv.y); + let left = smoothstep(0.0, aax, uv.x); + let right = 1.0 - smoothstep(1.0 - aax, 1.0, uv.x); + let bottom = smoothstep(0.0, aay, uv.y); + let top = 1.0 - smoothstep(1.0 - aay, 1.0, uv.y); return left * right * bottom * top; } @@ -91,21 +90,25 @@ float getUvFrame(vec2 uv) { ${ declarePI } ${ rotation2 } ${ textureRandomizerR } -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = randomR(i); - float b = randomR(i + vec2(1.0, 0.0)); - float c = randomR(i + vec2(0.0, 1.0)); - float d = randomR(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); + +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = randomR(i); + let b = randomR(i + vec2f(1.0, 0.0)); + let c = randomR(i + vec2f(0.0, 1.0)); + let d = randomR(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } -float fbm(vec2 n) { - float total = 0.0, amplitude = .4; - for (int i = 0; i < 3; i++) { + +fn fbm(n_in: vec2f) -> f32 { + var n = n_in; + var total: f32 = 0.0; + var amplitude: f32 = 0.4; + for (var i: i32 = 0; i < 3; i++) { total += valueNoise(n) * amplitude; n *= 1.99; amplitude *= 0.65; @@ -113,174 +116,176 @@ float fbm(vec2 n) { return total; } - -float randomG(vec2 p) { - vec2 uv = floor(p) / 50. + .5; - return texture(u_noiseTexture, fract(uv)).g; +fn randomG(p: vec2f) -> f32 { + let uv = floor(p) / 50.0 + vec2f(0.5); + return textureSampleLevel(u_noiseTexture_tex, u_noiseTexture_samp, fract(uv), 0.0).g; } -float roughness(vec2 p) { - p *= .1; - float o = 0.; - for (float i = 0.; ++i < 4.; p *= 2.1) { - vec4 w = vec4(floor(p), ceil(p)); - vec2 f = fract(p); + +fn roughnessFn(p_in: vec2f) -> f32 { + var p = p_in * 0.1; + var o: f32 = 0.0; + for (var i: f32 = 1.0; i < 4.0; i += 1.0) { + let w = vec4f(floor(p), ceil(p)); + let f = fract(p); o += mix( - mix(randomG(w.xy), randomG(w.xw), f.y), - mix(randomG(w.zy), randomG(w.zw), f.y), - f.x); - o += .2 / exp(2. * abs(sin(.2 * p.x + .5 * p.y))); + mix(randomG(w.xy), randomG(vec2f(w.x, w.w)), f.y), + mix(randomG(vec2f(w.z, w.y)), randomG(w.zw), f.y), + f.x); + o += 0.2 / exp(2.0 * abs(sin(0.2 * p.x + 0.5 * p.y))); + p *= 2.1; } - return o / 3.; + return o / 3.0; } ${ fiberNoise } -vec2 randomGB(vec2 p) { - vec2 uv = floor(p) / 50. + .5; - return texture(u_noiseTexture, fract(uv)).gb; +fn randomGB(p: vec2f) -> vec2f { + let uv = floor(p) / 50.0 + vec2f(0.5); + return textureSampleLevel(u_noiseTexture_tex, u_noiseTexture_samp, fract(uv), 0.0).gb; } -float crumpledNoise(vec2 t, float pw) { - vec2 p = floor(t); - float wsum = 0.; - float cl = 0.; - for (int y = -1; y < 2; y += 1) { - for (int x = -1; x < 2; x += 1) { - vec2 b = vec2(float(x), float(y)); - vec2 q = b + p; - vec2 q2 = q - floor(q / 8.) * 8.; - vec2 c = q + randomGB(q2); - vec2 r = c - t; - float w = pow(smoothstep(0., 1., 1. - abs(r.x)), pw) * pow(smoothstep(0., 1., 1. - abs(r.y)), pw); - cl += (.5 + .5 * sin((q2.x + q2.y * 5.) * 8.)) * w; + +fn crumpledNoise(t: vec2f, pw: f32) -> f32 { + let p = floor(t); + var wsum: f32 = 0.0; + var cl: f32 = 0.0; + for (var y: i32 = -1; y < 2; y += 1) { + for (var x: i32 = -1; x < 2; x += 1) { + let b = vec2f(f32(x), f32(y)); + let q = b + p; + let q2 = q - floor(q / 8.0) * 8.0; + let c = q + randomGB(q2); + let r = c - t; + let w = pow(smoothstep(0.0, 1.0, 1.0 - abs(r.x)), pw) * pow(smoothstep(0.0, 1.0, 1.0 - abs(r.y)), pw); + cl += (0.5 + 0.5 * sin((q2.x + q2.y * 5.0) * 8.0)) * w; wsum += w; } } - return pow(wsum != 0.0 ? cl / wsum : 0.0, .5) * 2.; -} -float crumplesShape(vec2 uv) { - return crumpledNoise(uv * .25, 16.) * crumpledNoise(uv * .5, 2.); + return pow(select(0.0, cl / wsum, wsum != 0.0), 0.5) * 2.0; } +fn crumplesShape(uv: vec2f) -> f32 { + return crumpledNoise(uv * 0.25, 16.0) * crumpledNoise(uv * 0.5, 2.0); +} -vec2 folds(vec2 uv) { - vec3 pp = vec3(0.); - float l = 9.; - for (float i = 0.; i < 15.; i++) { - if (i >= u_foldCount) break; - vec2 rand = randomGB(vec2(i, i * u_seed)); - float an = rand.x * TWO_PI; - vec2 p = vec2(cos(an), sin(an)) * rand.y; - float dist = distance(uv, p); - l = min(l, dist); - - if (l == dist) { - pp.xy = (uv - p.xy); - pp.z = dist; +fn foldsFn(uv: vec2f) -> vec2f { + var pp = vec3f(0.0); + var l: f32 = 9.0; + for (var i: f32 = 0.0; i < 15.0; i += 1.0) { + if (i < u.u_foldCount) { + let rand = randomGB(vec2f(i, i * u.u_seed)); + let an = rand.x * TWO_PI; + let p = vec2f(cos(an), sin(an)) * rand.y; + let dist = distance(uv, p); + l = min(l, dist); + + if (l == dist) { + pp = vec3f((uv - p.xy), dist); + } } } - return mix(pp.xy, vec2(0.), pow(pp.z, .25)); + return mix(pp.xy, vec2f(0.0), pow(pp.z, 0.25)); } -float drops(vec2 uv) { - vec2 iDropsUV = floor(uv); - vec2 fDropsUV = fract(uv); - float dropsMinDist = 1.; - for (int j = -1; j <= 1; j++) { - for (int i = -1; i <= 1; i++) { - vec2 neighbor = vec2(float(i), float(j)); - vec2 offset = randomGB(iDropsUV + neighbor); - offset = .5 + .5 * sin(10. * u_seed + TWO_PI * offset); - vec2 pos = neighbor + offset - fDropsUV; - float dist = length(pos); - dropsMinDist = min(dropsMinDist, dropsMinDist*dist); +fn dropsFn(uv: vec2f) -> f32 { + let iDropsUV = floor(uv); + let fDropsUV = fract(uv); + var dropsMinDist: f32 = 1.0; + for (var j: i32 = -1; j <= 1; j++) { + for (var i: i32 = -1; i <= 1; i++) { + let neighbor = vec2f(f32(i), f32(j)); + var offset = randomGB(iDropsUV + neighbor); + offset = vec2f(0.5) + 0.5 * sin(10.0 * u.u_seed + TWO_PI * offset); + let pos = neighbor + offset - fDropsUV; + let dist = length(pos); + dropsMinDist = min(dropsMinDist, dropsMinDist * dist); } } - return 1. - smoothstep(.05, .09, pow(dropsMinDist, .5)); + return 1.0 - smoothstep(0.05, 0.09, pow(dropsMinDist, 0.5)); } -float lst(float edge0, float edge1, float x) { +fn lst(edge0: f32, edge1: f32, x: f32) -> f32 { return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); } -void main() { +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { - vec2 imageUV = v_imageUV; - vec2 patternUV = v_imageUV - .5; - patternUV = 5. * (patternUV * vec2(u_imageAspectRatio, 1.)); + var imageUV = input.v_imageUV; + var patternUV = input.v_imageUV - vec2f(0.5); + patternUV = 5.0 * (patternUV * vec2f(u.u_imageAspectRatio, 1.0)); - vec2 roughnessUv = 1.5 * (gl_FragCoord.xy - .5 * u_resolution) / u_pixelRatio; - float roughness = roughness(roughnessUv + vec2(1., 0.)) - roughness(roughnessUv - vec2(1., 0.)); + let roughnessUv = 1.5 * (input.position.xy - 0.5 * u.u_resolution) / u.u_pixelRatio; + let roughness = roughnessFn(roughnessUv + vec2f(1.0, 0.0)) - roughnessFn(roughnessUv - vec2f(1.0, 0.0)); - vec2 crumplesUV = fract(patternUV * .02 / u_crumpleSize - u_seed) * 32.; - float crumples = u_crumples * (crumplesShape(crumplesUV + vec2(.05, 0.)) - crumplesShape(crumplesUV)); + let crumplesUV = fract(patternUV * 0.02 / u.u_crumpleSize - vec2f(u.u_seed)) * 32.0; + var crumples = u.u_crumples * (crumplesShape(crumplesUV + vec2f(0.05, 0.0)) - crumplesShape(crumplesUV)); - vec2 fiberUV = 2. / u_fiberSize * patternUV; - float fiber = fiberNoise(fiberUV, vec2(0.)); - fiber = .5 * u_fiber * (fiber - 1.); + let fiberUV = 2.0 / u.u_fiberSize * patternUV; + var fiber = fiberNoise(fiberUV, vec2f(0.0)); + fiber = 0.5 * u.u_fiber * (fiber - 1.0); - vec2 normal = vec2(0.); - vec2 normalImage = vec2(0.); + var normal = vec2f(0.0); + var normalImage = vec2f(0.0); - vec2 foldsUV = patternUV * .12; - foldsUV = rotate(foldsUV, 4. * u_seed); - vec2 w = folds(foldsUV); - foldsUV = rotate(foldsUV + .007 * cos(u_seed), .01 * sin(u_seed)); - vec2 w2 = folds(foldsUV); + var foldsUV = patternUV * 0.12; + foldsUV = rotate(foldsUV, 4.0 * u.u_seed); + var w = foldsFn(foldsUV); + foldsUV = rotate(foldsUV + vec2f(0.007 * cos(u.u_seed)), 0.01 * sin(u.u_seed)); + var w2 = foldsFn(foldsUV); - float drops = u_drops * drops(patternUV * 2.); + var drops = u.u_drops * dropsFn(patternUV * 2.0); - float fade = u_fade * fbm(.17 * patternUV + 10. * u_seed); - fade = clamp(8. * fade * fade * fade, 0., 1.); + var fade = u.u_fade * fbm(0.17 * patternUV + vec2f(10.0 * u.u_seed)); + fade = clamp(8.0 * fade * fade * fade, 0.0, 1.0); - w = mix(w, vec2(0.), fade); - w2 = mix(w2, vec2(0.), fade); - crumples = mix(crumples, 0., fade); - drops = mix(drops, 0., fade); - fiber *= mix(1., .5, fade); - roughness *= mix(1., .5, fade); + w = mix(w, vec2f(0.0), fade); + w2 = mix(w2, vec2f(0.0), fade); + crumples = mix(crumples, 0.0, fade); + drops = mix(drops, 0.0, fade); + fiber *= mix(1.0, 0.5, fade); + var roughnessMut = roughness * mix(1.0, 0.5, fade); - normal.xy += u_folds * min(5. * u_contrast, 1.) * 4. * max(vec2(0.), w + w2); - normalImage.xy += u_folds * 2. * w; + normal += u.u_folds * min(5.0 * u.u_contrast, 1.0) * 4.0 * max(vec2f(0.0), w + w2); + normalImage += u.u_folds * 2.0 * w; - normal.xy += crumples; - normalImage.xy += 1.5 * crumples; + normal += vec2f(crumples); + normalImage += 1.5 * vec2f(crumples); - normal.xy += 3. * drops; - normalImage.xy += .2 * drops; + normal += 3.0 * vec2f(drops); + normalImage += 0.2 * vec2f(drops); - normal.xy += u_roughness * 1.5 * roughness; - normal.xy += fiber; + normal += u.u_roughness * 1.5 * vec2f(roughnessMut); + normal += vec2f(fiber); - normalImage += u_roughness * .75 * roughness; - normalImage += .2 * fiber; + normalImage += u.u_roughness * 0.75 * vec2f(roughnessMut); + normalImage += 0.2 * vec2f(fiber); - vec3 lightPos = vec3(1., 2., 1.); - float res = dot(normalize(vec3(normal, 9.5 - 9. * pow(u_contrast, .1))), normalize(lightPos)); + let lightPos = vec3f(1.0, 2.0, 1.0); + let res = dot(normalize(vec3f(normal, 9.5 - 9.0 * pow(u.u_contrast, 0.1))), normalize(lightPos)); - vec3 fgColor = u_colorFront.rgb * u_colorFront.a; - float fgOpacity = u_colorFront.a; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - float bgOpacity = u_colorBack.a; + let fgColor = u.u_colorFront.rgb * u.u_colorFront.a; + let fgOpacity = u.u_colorFront.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + let bgOpacity = u.u_colorBack.a; - imageUV += .02 * normalImage; - float frame = getUvFrame(imageUV); - vec4 image = texture(u_image, imageUV); - image.rgb += .6 * pow(u_contrast, .4) * (res - .7); + imageUV += 0.02 * normalImage; + let frame = getUvFrame(imageUV); + var image = textureSampleLevel(u_image_tex, u_image_samp, imageUV, 0.0); + image = vec4f(image.rgb + 0.6 * pow(u.u_contrast, 0.4) * (res - 0.7), image.a); - frame *= image.a; + let frameMasked = frame * image.a; - vec3 color = fgColor * res; - float opacity = fgOpacity * res; + var color = fgColor * res; + var opacity = fgOpacity * res; - color += bgColor * (1. - opacity); - opacity += bgOpacity * (1. - opacity); - opacity = mix(opacity, 1., frame); + color += bgColor * (1.0 - opacity); + opacity += bgOpacity * (1.0 - opacity); + opacity = mix(opacity, 1.0, frameMasked); - color -= .007 * drops; + color -= 0.007 * vec3f(drops); - color.rgb = mix(color, image.rgb, frame); + color = mix(color, image.rgb, frameMasked); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/perlin-noise.ts b/packages/shaders/src/shaders/perlin-noise.ts index 56b8ab9c9..c00f76df6 100644 --- a/packages/shaders/src/shaders/perlin-noise.ts +++ b/packages/shaders/src/shaders/perlin-noise.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, colorBandingFix, proceduralHash11, proceduralHash21 } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, colorBandingFix, proceduralHash11, proceduralHash21 } from '../shader-utils.js'; /** * Classic animated 3D Perlin noise with exposed controls. @@ -34,113 +34,110 @@ import { declarePI, colorBandingFix, proceduralHash11, proceduralHash21 } from ' * */ -// language=GLSL -export const perlinNoiseFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform vec4 u_colorFront; -uniform vec4 u_colorBack; -uniform float u_proportion; -uniform float u_softness; -uniform float u_octaveCount; -uniform float u_persistence; -uniform float u_lacunarity; - -in vec2 v_patternUV; +export const perlinNoiseFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorFront: vec4f, + u_colorBack: vec4f, + u_proportion: f32, + u_softness: f32, + u_octaveCount: f32, + u_persistence: f32, + u_lacunarity: f32, +} +@group(0) @binding(0) var u: Uniforms; -out vec4 fragColor; +${vertexOutputStruct} -${ declarePI } -${ proceduralHash11 } -${ proceduralHash21 } +${declarePI} +${proceduralHash11} +${proceduralHash21} -float hash31(vec3 p) { - p = fract(p * 0.3183099) + 0.1; - p += dot(p, p.yzx + 19.19); +fn hash31(p_in: vec3f) -> f32 { + var p = fract(p_in * 0.3183099) + vec3f(0.1); + p += vec3f(dot(p, p.yzx + vec3f(19.19))); return fract(p.x * (p.y + p.z)); } -vec3 gradientPredefined(float hash) { - int idx = int(hash * 12.0) % 12; - - if (idx == 0) return vec3(1, 1, 0); - if (idx == 1) return vec3(-1, 1, 0); - if (idx == 2) return vec3(1, -1, 0); - if (idx == 3) return vec3(-1, -1, 0); - if (idx == 4) return vec3(1, 0, 1); - if (idx == 5) return vec3(-1, 0, 1); - if (idx == 6) return vec3(1, 0, -1); - if (idx == 7) return vec3(-1, 0, -1); - if (idx == 8) return vec3(0, 1, 1); - if (idx == 9) return vec3(0, -1, 1); - if (idx == 10) return vec3(0, 1, -1); - return vec3(0, -1, -1);// idx == 11 +fn gradientPredefined(hash: f32) -> vec3f { + let idx = i32(hash * 12.0) % 12; + + if (idx == 0) { return vec3f(1.0, 1.0, 0.0); } + if (idx == 1) { return vec3f(-1.0, 1.0, 0.0); } + if (idx == 2) { return vec3f(1.0, -1.0, 0.0); } + if (idx == 3) { return vec3f(-1.0, -1.0, 0.0); } + if (idx == 4) { return vec3f(1.0, 0.0, 1.0); } + if (idx == 5) { return vec3f(-1.0, 0.0, 1.0); } + if (idx == 6) { return vec3f(1.0, 0.0, -1.0); } + if (idx == 7) { return vec3f(-1.0, 0.0, -1.0); } + if (idx == 8) { return vec3f(0.0, 1.0, 1.0); } + if (idx == 9) { return vec3f(0.0, -1.0, 1.0); } + if (idx == 10) { return vec3f(0.0, 1.0, -1.0); } + return vec3f(0.0, -1.0, -1.0);// idx == 11 } -float interpolateSafe(float v000, float v001, float v010, float v011, -float v100, float v101, float v110, float v111, vec3 t) { - t = clamp(t, 0.0, 1.0); +fn interpolateSafe(v000: f32, v001: f32, v010: f32, v011: f32, + v100: f32, v101: f32, v110: f32, v111: f32, t_in: vec3f) -> f32 { + let t = clamp(t_in, vec3f(0.0), vec3f(1.0)); - float v00 = mix(v000, v100, t.x); - float v01 = mix(v001, v101, t.x); - float v10 = mix(v010, v110, t.x); - float v11 = mix(v011, v111, t.x); + let v00 = mix(v000, v100, t.x); + let v01 = mix(v001, v101, t.x); + let v10 = mix(v010, v110, t.x); + let v11 = mix(v011, v111, t.x); - float v0 = mix(v00, v10, t.y); - float v1 = mix(v01, v11, t.y); + let v0 = mix(v00, v10, t.y); + let v1 = mix(v01, v11, t.y); return mix(v0, v1, t.z); } -vec3 fade(vec3 t) { - return t * t * t * (t * (t * 6.0 - 15.0) + 10.0); +fn fade(t: vec3f) -> vec3f { + return t * t * t * (t * (t * 6.0 - vec3f(15.0)) + vec3f(10.0)); } -float perlinNoise(vec3 position, float seed) { - position += vec3(seed * 127.1, seed * 311.7, seed * 74.7); - - vec3 i = floor(position); - vec3 f = fract(position); - float h000 = hash31(i); - float h001 = hash31(i + vec3(0, 0, 1)); - float h010 = hash31(i + vec3(0, 1, 0)); - float h011 = hash31(i + vec3(0, 1, 1)); - float h100 = hash31(i + vec3(1, 0, 0)); - float h101 = hash31(i + vec3(1, 0, 1)); - float h110 = hash31(i + vec3(1, 1, 0)); - float h111 = hash31(i + vec3(1, 1, 1)); - vec3 g000 = gradientPredefined(h000); - vec3 g001 = gradientPredefined(h001); - vec3 g010 = gradientPredefined(h010); - vec3 g011 = gradientPredefined(h011); - vec3 g100 = gradientPredefined(h100); - vec3 g101 = gradientPredefined(h101); - vec3 g110 = gradientPredefined(h110); - vec3 g111 = gradientPredefined(h111); - float v000 = dot(g000, f - vec3(0, 0, 0)); - float v001 = dot(g001, f - vec3(0, 0, 1)); - float v010 = dot(g010, f - vec3(0, 1, 0)); - float v011 = dot(g011, f - vec3(0, 1, 1)); - float v100 = dot(g100, f - vec3(1, 0, 0)); - float v101 = dot(g101, f - vec3(1, 0, 1)); - float v110 = dot(g110, f - vec3(1, 1, 0)); - float v111 = dot(g111, f - vec3(1, 1, 1)); - - vec3 u = fade(f); - return interpolateSafe(v000, v001, v010, v011, v100, v101, v110, v111, u); +fn perlinNoise(position_in: vec3f, seed: f32) -> f32 { + let position = position_in + vec3f(seed * 127.1, seed * 311.7, seed * 74.7); + + let i = floor(position); + let f = fract(position); + let h000 = hash31(i); + let h001 = hash31(i + vec3f(0.0, 0.0, 1.0)); + let h010 = hash31(i + vec3f(0.0, 1.0, 0.0)); + let h011 = hash31(i + vec3f(0.0, 1.0, 1.0)); + let h100 = hash31(i + vec3f(1.0, 0.0, 0.0)); + let h101 = hash31(i + vec3f(1.0, 0.0, 1.0)); + let h110 = hash31(i + vec3f(1.0, 1.0, 0.0)); + let h111 = hash31(i + vec3f(1.0, 1.0, 1.0)); + let g000 = gradientPredefined(h000); + let g001 = gradientPredefined(h001); + let g010 = gradientPredefined(h010); + let g011 = gradientPredefined(h011); + let g100 = gradientPredefined(h100); + let g101 = gradientPredefined(h101); + let g110 = gradientPredefined(h110); + let g111 = gradientPredefined(h111); + let val000 = dot(g000, f - vec3f(0.0, 0.0, 0.0)); + let val001 = dot(g001, f - vec3f(0.0, 0.0, 1.0)); + let val010 = dot(g010, f - vec3f(0.0, 1.0, 0.0)); + let val011 = dot(g011, f - vec3f(0.0, 1.0, 1.0)); + let val100 = dot(g100, f - vec3f(1.0, 0.0, 0.0)); + let val101 = dot(g101, f - vec3f(1.0, 0.0, 1.0)); + let val110 = dot(g110, f - vec3f(1.0, 1.0, 0.0)); + let val111 = dot(g111, f - vec3f(1.0, 1.0, 1.0)); + + let u_fade = fade(f); + return interpolateSafe(val000, val001, val010, val011, val100, val101, val110, val111, u_fade); } -float p_noise(vec3 position, int octaveCount, float persistence, float lacunarity) { - float value = 0.0; - float amplitude = 1.0; - float frequency = 10.0; - float maxValue = 0.0; - octaveCount = clamp(octaveCount, 1, 8); +fn p_noise(position: vec3f, octaveCount_in: i32, persistence: f32, lacunarity: f32) -> f32 { + var value: f32 = 0.0; + var amplitude: f32 = 1.0; + var frequency: f32 = 10.0; + var maxValue: f32 = 0.0; + let octaveCount = clamp(octaveCount_in, 1, 8); - for (int i = 0; i < octaveCount; i++) { - float seed = float(i) * 0.7319; + for (var i: i32 = 0; i < octaveCount; i++) { + let seed = f32(i) * 0.7319; value += perlinNoise(position * frequency, seed) * amplitude; maxValue += amplitude; amplitude *= persistence; @@ -149,9 +146,9 @@ float p_noise(vec3 position, int octaveCount, float persistence, float lacunarit return value; } -float get_max_amp(float persistence, float octaveCount) { - persistence = clamp(persistence * 0.999, 0.0, 0.999); - octaveCount = clamp(octaveCount, 1.0, 8.0); +fn get_max_amp(persistence_in: f32, octaveCount_in: f32) -> f32 { + let persistence = clamp(persistence_in * 0.999, 0.0, 0.999); + let octaveCount = clamp(octaveCount_in, 1.0, 8.0); if (abs(persistence - 1.0) < 0.001) { return octaveCount; @@ -160,41 +157,41 @@ float get_max_amp(float persistence, float octaveCount) { return (1.0 - pow(persistence, octaveCount)) / max(1e-4, (1.0 - persistence)); } -void main() { - vec2 uv = v_patternUV; - uv *= .5; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var uv = input.v_patternUV; + uv *= 0.5; - float t = .2 * u_time; + let t = 0.2 * u.u_time; - vec3 p = vec3(uv, t); + let p = vec3f(uv, t); - float octCount = floor(u_octaveCount); - float noise = p_noise(p, int(octCount), u_persistence, u_lacunarity); + let octCount = floor(u.u_octaveCount); + let noise = p_noise(p, i32(octCount), u.u_persistence, u.u_lacunarity); - float max_amp = get_max_amp(u_persistence, octCount); - float noise_normalized = clamp((noise + max_amp) / max(1e-4, (2. * max_amp)) + (u_proportion - .5), 0.0, 1.0); - float sharpness = clamp(u_softness, 0., 1.); - float smooth_w = 0.5 * max(fwidth(noise_normalized), 0.001); - float res = smoothstep( - .5 - .5 * sharpness - smooth_w, - .5 + .5 * sharpness + smooth_w, - noise_normalized + let max_amp = get_max_amp(u.u_persistence, octCount); + let noise_normalized = clamp((noise + max_amp) / max(1e-4, (2.0 * max_amp)) + (u.u_proportion - 0.5), 0.0, 1.0); + let sharpness = clamp(u.u_softness, 0.0, 1.0); + let smooth_w = 0.5 * max(fwidth(noise_normalized), 0.001); + let res = smoothstep( + 0.5 - 0.5 * sharpness - smooth_w, + 0.5 + 0.5 * sharpness + smooth_w, + noise_normalized ); - vec3 fgColor = u_colorFront.rgb * u_colorFront.a; - float fgOpacity = u_colorFront.a; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - float bgOpacity = u_colorBack.a; + let fgColor = u.u_colorFront.rgb * u.u_colorFront.a; + let fgOpacity = u.u_colorFront.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + let bgOpacity = u.u_colorBack.a; - vec3 color = fgColor * res; - float opacity = fgOpacity * res; + var color = fgColor * res; + var opacity = fgOpacity * res; - color += bgColor * (1. - opacity); - opacity += bgOpacity * (1. - opacity); + color += bgColor * (1.0 - opacity); + opacity += bgOpacity * (1.0 - opacity); - ${ colorBandingFix } + ${colorBandingFix} - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/pulsing-border.ts b/packages/shaders/src/shaders/pulsing-border.ts index 6367dffbb..5c03ab51c 100644 --- a/packages/shaders/src/shaders/pulsing-border.ts +++ b/packages/shaders/src/shaders/pulsing-border.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, textureRandomizerGB, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, glslMod, textureRandomizerGB, colorBandingFix } from '../shader-utils.js'; export const pulsingBorderMeta = { maxColorCount: 5, @@ -53,63 +53,59 @@ export const pulsingBorderMeta = { * */ -// language=GLSL -export const pulsingBorderFragmentShader: string = `#version 300 es -precision lowp float; - -uniform float u_time; - -uniform vec4 u_colorBack; -uniform vec4 u_colors[${ pulsingBorderMeta.maxColorCount }]; -uniform float u_colorsCount; -uniform float u_roundness; -uniform float u_thickness; -uniform float u_marginLeft; -uniform float u_marginRight; -uniform float u_marginTop; -uniform float u_marginBottom; -uniform float u_aspectRatio; -uniform float u_softness; -uniform float u_intensity; -uniform float u_bloom; -uniform float u_spotSize; -uniform float u_spots; -uniform float u_pulse; -uniform float u_smoke; -uniform float u_smokeSize; - -uniform sampler2D u_noiseTexture; - -in vec2 v_responsiveUV; -in vec2 v_responsiveBoxGivenSize; -in vec2 v_patternUV; - -out vec4 fragColor; +export const pulsingBorderFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_roundness: f32, + u_thickness: f32, + u_marginLeft: f32, + u_marginRight: f32, + u_marginTop: f32, + u_marginBottom: f32, + u_aspectRatio: f32, + u_softness: f32, + u_intensity: f32, + u_bloom: f32, + u_spotSize: f32, + u_spots: f32, + u_pulse: f32, + u_smoke: f32, + u_smokeSize: f32, + u_colorBack: vec4f, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; +@group(1) @binding(0) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(1) var u_noiseTexture_samp: sampler; + +${vertexOutputStruct} ${ declarePI } +${ glslMod } -float beat(float time) { - float first = pow(abs(sin(time * TWO_PI)), 10.); - float second = pow(abs(sin((time - .15) * TWO_PI)), 10.); +fn beat(time: f32) -> f32 { + let first = pow(abs(sin(time * TWO_PI)), 10.0); + let second = pow(abs(sin((time - 0.15) * TWO_PI)), 10.0); return clamp(first + 0.6 * second, 0.0, 1.0); } -float sst(float edge0, float edge1, float x) { +fn sst(edge0: f32, edge1: f32, x: f32) -> f32 { return smoothstep(edge0, edge1, x); } -float roundedBox(vec2 uv, vec2 halfSize, float distance, float cornerDistance, float thickness, float softness) { - float borderDistance = abs(distance); - float aa = 2. * fwidth(distance); - float border = 1. - sst(min(mix(thickness, -thickness, softness), thickness + aa), max(mix(thickness, -thickness, softness), thickness + aa), borderDistance); - float cornerFadeCircles = 0.; - cornerFadeCircles = mix(1., cornerFadeCircles, sst(0., 1., length((uv + halfSize) / thickness))); - cornerFadeCircles = mix(1., cornerFadeCircles, sst(0., 1., length((uv - vec2(-halfSize.x, halfSize.y)) / thickness))); - cornerFadeCircles = mix(1., cornerFadeCircles, sst(0., 1., length((uv - vec2(halfSize.x, -halfSize.y)) / thickness))); - cornerFadeCircles = mix(1., cornerFadeCircles, sst(0., 1., length((uv - halfSize) / thickness))); +fn roundedBox(uv: vec2f, halfSize: vec2f, distance_val: f32, cornerDistance: f32, thickness: f32, softness: f32) -> f32 { + let borderDistance = abs(distance_val); + var aa = 2.0 * fwidth(distance_val); + var border = 1.0 - sst(min(mix(thickness, -thickness, softness), thickness + aa), max(mix(thickness, -thickness, softness), thickness + aa), borderDistance); + var cornerFadeCircles: f32 = 0.0; + cornerFadeCircles = mix(1.0, cornerFadeCircles, sst(0.0, 1.0, length((uv + halfSize) / thickness))); + cornerFadeCircles = mix(1.0, cornerFadeCircles, sst(0.0, 1.0, length((uv - vec2f(-halfSize.x, halfSize.y)) / thickness))); + cornerFadeCircles = mix(1.0, cornerFadeCircles, sst(0.0, 1.0, length((uv - vec2f(halfSize.x, -halfSize.y)) / thickness))); + cornerFadeCircles = mix(1.0, cornerFadeCircles, sst(0.0, 1.0, length((uv - halfSize) / thickness))); aa = fwidth(cornerDistance); - float cornerFade = sst(0., mix(aa, thickness, softness), cornerDistance); + var cornerFade = sst(0.0, mix(aa, thickness, softness), cornerDistance); cornerFade *= cornerFadeCircles; border += cornerFade; return border; @@ -117,48 +113,49 @@ float roundedBox(vec2 uv, vec2 halfSize, float distance, float cornerDistance, f ${ textureRandomizerGB } -float randomG(vec2 p) { - vec2 uv = floor(p) / 100. + .5; - return texture(u_noiseTexture, fract(uv)).g; +fn randomG(p: vec2f) -> f32 { + let uv = floor(p) / 100.0 + vec2f(0.5); + return textureSampleLevel(u_noiseTexture_tex, u_noiseTexture_samp, fract(uv), 0.0).g; } -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = randomG(i); - float b = randomG(i + vec2(1.0, 0.0)); - float c = randomG(i + vec2(0.0, 1.0)); - float d = randomG(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); + +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = randomG(i); + let b = randomG(i + vec2f(1.0, 0.0)); + let c = randomG(i + vec2f(0.0, 1.0)); + let d = randomG(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } -void main() { - const float firstFrameOffset = 109.; - float t = 1.2 * (u_time + firstFrameOffset); - - vec2 borderUV = v_responsiveUV; - float pulse = u_pulse * beat(.18 * u_time); - - float canvasRatio = v_responsiveBoxGivenSize.x / v_responsiveBoxGivenSize.y; - vec2 halfSize = vec2(.5); - borderUV.x *= max(canvasRatio, 1.); - borderUV.y /= min(canvasRatio, 1.); - halfSize.x *= max(canvasRatio, 1.); - halfSize.y /= min(canvasRatio, 1.); - - float mL = u_marginLeft; - float mR = u_marginRight; - float mT = u_marginTop; - float mB = u_marginBottom; - float mX = mL + mR; - float mY = mT + mB; - - if (u_aspectRatio > 0.) { - float shapeRatio = canvasRatio * (1. - mX) / max(1. - mY, 1e-6); - float freeX = shapeRatio > 1. ? (1. - mX) * (1. - 1. / max(abs(shapeRatio), 1e-6)) : 0.; - float freeY = shapeRatio < 1. ? (1. - mY) * (1. - shapeRatio) : 0.; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + let firstFrameOffset: f32 = 109.0; + let t = 1.2 * (u.u_time + firstFrameOffset); + + var borderUV = input.v_responsiveUV; + let pulse = u.u_pulse * beat(0.18 * u.u_time); + + let canvasRatio = input.v_responsiveBoxGivenSize.x / input.v_responsiveBoxGivenSize.y; + var halfSize = vec2f(0.5); + borderUV.x *= max(canvasRatio, 1.0); + borderUV.y /= min(canvasRatio, 1.0); + halfSize.x *= max(canvasRatio, 1.0); + halfSize.y /= min(canvasRatio, 1.0); + + var mL = u.u_marginLeft; + var mR = u.u_marginRight; + var mT = u.u_marginTop; + var mB = u.u_marginBottom; + var mX = mL + mR; + var mY = mT + mB; + + if (u.u_aspectRatio > 0.0) { + let shapeRatio = canvasRatio * (1.0 - mX) / max(1.0 - mY, 1e-6); + let freeX = select(0.0, (1.0 - mX) * (1.0 - 1.0 / max(abs(shapeRatio), 1e-6)), shapeRatio > 1.0); + let freeY = select(0.0, (1.0 - mY) * (1.0 - shapeRatio), shapeRatio < 1.0); mL += freeX * 0.5; mR += freeX * 0.5; mT += freeY * 0.5; @@ -167,110 +164,112 @@ void main() { mY = mT + mB; } - float thickness = .5 * u_thickness * min(halfSize.x, halfSize.y); + let thickness = 0.5 * u.u_thickness * min(halfSize.x, halfSize.y); - halfSize.x *= (1. - mX); - halfSize.y *= (1. - mY); + halfSize.x *= (1.0 - mX); + halfSize.y *= (1.0 - mY); - vec2 centerShift = vec2( - (mL - mR) * max(canvasRatio, 1.) * 0.5, - (mB - mT) / min(canvasRatio, 1.) * 0.5 + let centerShift = vec2f( + (mL - mR) * max(canvasRatio, 1.0) * 0.5, + (mB - mT) / min(canvasRatio, 1.0) * 0.5 ); borderUV -= centerShift; - halfSize -= mix(thickness, 0., u_softness); - - float radius = mix(0., min(halfSize.x, halfSize.y), u_roundness); - vec2 d = abs(borderUV) - halfSize + radius; - float outsideDistance = length(max(d, .0001)) - radius; - float insideDistance = min(max(d.x, d.y), .0001); - float cornerDistance = abs(min(max(d.x, d.y) - .45 * radius, .0)); - float distance = outsideDistance + insideDistance; - - float borderThickness = mix(thickness, 3. * thickness, u_softness); - float border = roundedBox(borderUV, halfSize, distance, cornerDistance, borderThickness, u_softness); - border = pow(border, 1. + u_softness); - - vec2 smokeUV = .3 * u_smokeSize * v_patternUV; - float smoke = clamp(3. * valueNoise(2.7 * smokeUV + .5 * t), 0., 1.); - smoke -= valueNoise(3.4 * smokeUV - .5 * t); - float smokeThickness = thickness + .2; - smokeThickness = min(.4, max(smokeThickness, .1)); - smoke *= roundedBox(borderUV, halfSize, distance, cornerDistance, smokeThickness, 1.); - smoke = 30. * smoke * smoke; - smoke *= mix(0., .5, pow(u_smoke, 2.)); - smoke *= mix(1., pulse, u_pulse); - smoke = clamp(smoke, 0., 1.); + halfSize -= vec2f(mix(thickness, 0.0, u.u_softness)); + + let radius = mix(0.0, min(halfSize.x, halfSize.y), u.u_roundness); + let d = abs(borderUV) - halfSize + vec2f(radius); + let outsideDistance = length(max(d, vec2f(0.0001))) - radius; + let insideDistance = min(max(d.x, d.y), 0.0001); + let cornerDistance = abs(min(max(d.x, d.y) - 0.45 * radius, 0.0)); + let distance_val = outsideDistance + insideDistance; + + let borderThickness = mix(thickness, 3.0 * thickness, u.u_softness); + var border = roundedBox(borderUV, halfSize, distance_val, cornerDistance, borderThickness, u.u_softness); + border = pow(border, 1.0 + u.u_softness); + + let smokeUV = 0.3 * u.u_smokeSize * input.v_patternUV; + var smoke = clamp(3.0 * valueNoise(2.7 * smokeUV + vec2f(0.5 * t)), 0.0, 1.0); + smoke -= valueNoise(3.4 * smokeUV - vec2f(0.5 * t)); + var smokeThickness = thickness + 0.2; + smokeThickness = min(0.4, max(smokeThickness, 0.1)); + smoke *= roundedBox(borderUV, halfSize, distance_val, cornerDistance, smokeThickness, 1.0); + smoke = 30.0 * smoke * smoke; + smoke *= mix(0.0, 0.5, pow(u.u_smoke, 2.0)); + smoke *= mix(1.0, pulse, u.u_pulse); + smoke = clamp(smoke, 0.0, 1.0); border += smoke; - border = clamp(border, 0., 1.); + border = clamp(border, 0.0, 1.0); - vec3 blendColor = vec3(0.); - float blendAlpha = 0.; - vec3 addColor = vec3(0.); - float addAlpha = 0.; + var blendColor = vec3f(0.0); + var blendAlpha: f32 = 0.0; + var addColor = vec3f(0.0); + var addAlpha: f32 = 0.0; - float bloom = 4. * u_bloom; - float intensity = 1. + (1. + 4. * u_softness) * u_intensity; + let bloom = 4.0 * u.u_bloom; + let intensity = 1.0 + (1.0 + 4.0 * u.u_softness) * u.u_intensity; - float angle = atan(borderUV.y, borderUV.x) / TWO_PI; + let angle = atan2(borderUV.y, borderUV.x) / TWO_PI; - for (int colorIdx = 0; colorIdx < ${ pulsingBorderMeta.maxColorCount }; colorIdx++) { - if (colorIdx >= int(u_colorsCount)) break; - float colorIdxF = float(colorIdx); + for (var colorIdx: i32 = 0; colorIdx < ${ pulsingBorderMeta.maxColorCount }; colorIdx++) { + if (colorIdx < i32(u.u_colorsCount)) { + let colorIdxF = f32(colorIdx); - vec3 c = u_colors[colorIdx].rgb * u_colors[colorIdx].a; - float a = u_colors[colorIdx].a; + let c = u.u_colors[colorIdx].rgb * u.u_colors[colorIdx].a; + let a = u.u_colors[colorIdx].a; - for (int spotIdx = 0; spotIdx < ${ pulsingBorderMeta.maxSpots }; spotIdx++) { - if (spotIdx >= int(u_spots)) break; - float spotIdxF = float(spotIdx); + for (var spotIdx: i32 = 0; spotIdx < ${ pulsingBorderMeta.maxSpots }; spotIdx++) { + if (spotIdx < i32(u.u_spots)) { + let spotIdxF = f32(spotIdx); - vec2 randVal = randomGB(vec2(spotIdxF * 10. + 2., 40. + colorIdxF)); + let randVal = randomGB(vec2f(spotIdxF * 10.0 + 2.0, 40.0 + colorIdxF)); - float time = (.1 + .15 * abs(sin(spotIdxF * (2. + colorIdxF)) * cos(spotIdxF * (2. + 2.5 * colorIdxF)))) * t + randVal.x * 3.; - time *= mix(1., -1., step(.5, randVal.y)); + var time = (0.1 + 0.15 * abs(sin(spotIdxF * (2.0 + colorIdxF)) * cos(spotIdxF * (2.0 + 2.5 * colorIdxF)))) * t + randVal.x * 3.0; + time *= mix(1.0, -1.0, step(0.5, randVal.y)); - float mask = .5 + .5 * mix( - sin(t + spotIdxF * (5. - 1.5 * colorIdxF)), - cos(t + spotIdxF * (3. + 1.3 * colorIdxF)), - step(mod(colorIdxF, 2.), .5) + var mask = 0.5 + 0.5 * mix( + sin(t + spotIdxF * (5.0 - 1.5 * colorIdxF)), + cos(t + spotIdxF * (3.0 + 1.3 * colorIdxF)), + step(glsl_mod_f32(colorIdxF, 2.0), 0.5) ); - float p = clamp(2. * u_pulse - randVal.x, 0., 1.); + let p = clamp(2.0 * u.u_pulse - randVal.x, 0.0, 1.0); mask = mix(mask, pulse, p); - float atg1 = fract(angle + time); - float spotSize = .05 + .6 * pow(u_spotSize, 2.) + .05 * randVal.x; - spotSize = mix(spotSize, .1, p); - float sector = sst(.5 - spotSize, .5, atg1) * (1. - sst(.5, .5 + spotSize, atg1)); + let atg1 = fract(angle + time); + var spotSize = 0.05 + 0.6 * pow(u.u_spotSize, 2.0) + 0.05 * randVal.x; + spotSize = mix(spotSize, 0.1, p); + var sector = sst(0.5 - spotSize, 0.5, atg1) * (1.0 - sst(0.5, 0.5 + spotSize, atg1)); sector *= mask; sector *= border; sector *= intensity; - sector = clamp(sector, 0., 1.); + sector = clamp(sector, 0.0, 1.0); - vec3 srcColor = c * sector; - float srcAlpha = a * sector; + let srcColor = c * sector; + let srcAlpha = a * sector; - blendColor += ((1. - blendAlpha) * srcColor); - blendAlpha = blendAlpha + (1. - blendAlpha) * srcAlpha; + blendColor += ((1.0 - blendAlpha) * srcColor); + blendAlpha = blendAlpha + (1.0 - blendAlpha) * srcAlpha; addColor += srcColor; addAlpha += srcAlpha; + } + } } } - vec3 accumColor = mix(blendColor, addColor, bloom); - float accumAlpha = mix(blendAlpha, addAlpha, bloom); - accumAlpha = clamp(accumAlpha, 0., 1.); + let accumColor = mix(blendColor, addColor, bloom); + var accumAlpha = mix(blendAlpha, addAlpha, bloom); + accumAlpha = clamp(accumAlpha, 0.0, 1.0); - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - vec3 color = accumColor + (1. - accumAlpha) * bgColor; - float opacity = accumAlpha + (1. - accumAlpha) * u_colorBack.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + var color = accumColor + (1.0 - accumAlpha) * bgColor; + var opacity = accumAlpha + (1.0 - accumAlpha) * u.u_colorBack.a; ${ colorBandingFix } - fragColor = vec4(color, opacity); + return vec4f(color, opacity); }`; export interface PulsingBorderUniforms extends ShaderSizingUniforms { diff --git a/packages/shaders/src/shaders/simplex-noise.ts b/packages/shaders/src/shaders/simplex-noise.ts index ebd593c1f..8ff4b023b 100644 --- a/packages/shaders/src/shaders/simplex-noise.ts +++ b/packages/shaders/src/shaders/simplex-noise.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { simplexNoise, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, simplexNoise, glslMod, colorBandingFix } from '../shader-utils.js'; export const simplexNoiseMeta = { maxColorCount: 10, @@ -36,90 +36,87 @@ export const simplexNoiseMeta = { * */ -// language=GLSL -export const simplexNoiseFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; -uniform float u_scale; - -uniform vec4 u_colors[${ simplexNoiseMeta.maxColorCount }]; -uniform float u_colorsCount; -uniform float u_stepsPerColor; -uniform float u_softness; - -in vec2 v_patternUV; - -out vec4 fragColor; +export const simplexNoiseFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_stepsPerColor: f32, + u_softness: f32, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; -${ simplexNoise } +${vertexOutputStruct} -float getNoise(vec2 uv, float t) { - float noise = .5 * snoise(uv - vec2(0., .3 * t)); - noise += .5 * snoise(2. * uv + vec2(0., .32 * t)); +${glslMod} +${simplexNoise} +fn getNoise(uv: vec2f, t: f32) -> f32 { + var noise = 0.5 * snoise(uv - vec2f(0.0, 0.3 * t)); + noise += 0.5 * snoise(2.0 * uv + vec2f(0.0, 0.32 * t)); return noise; } -float steppedSmooth(float m, float steps, float softness) { - float stepT = floor(m * steps) / steps; - float f = m * steps - floor(m * steps); - float fw = steps * fwidth(m); - float smoothed = smoothstep(.5 - softness, min(1., .5 + softness + fw), f); +fn steppedSmooth(m: f32, steps: f32, softness: f32, fw_m: f32) -> f32 { + let stepT = floor(m * steps) / steps; + let f = m * steps - floor(m * steps); + let fw = steps * fw_m; + let smoothed = smoothstep(0.5 - softness, min(1.0, 0.5 + softness + fw), f); return stepT + smoothed / steps; } -void main() { - vec2 shape_uv = v_patternUV; - shape_uv *= .1; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var shape_uv = input.v_patternUV; + shape_uv *= 0.1; - float t = .2 * u_time; + let t = 0.2 * u.u_time; - float shape = .5 + .5 * getNoise(shape_uv, t); + let shape = 0.5 + 0.5 * getNoise(shape_uv, t); - bool u_extraSides = true; + let u_extraSides = true; - float mixer = shape * (u_colorsCount - 1.); + var mixer = shape * (u.u_colorsCount - 1.0); if (u_extraSides == true) { - mixer = (shape - .5 / u_colorsCount) * u_colorsCount; + mixer = (shape - 0.5 / u.u_colorsCount) * u.u_colorsCount; } - float steps = max(1., u_stepsPerColor); - - vec4 gradient = u_colors[0]; - gradient.rgb *= gradient.a; - for (int i = 1; i < ${ simplexNoiseMeta.maxColorCount }; i++) { - if (i >= int(u_colorsCount)) break; + let steps = max(1.0, u.u_stepsPerColor); + let mixerFw = fwidth(mixer); - float localM = clamp(mixer - float(i - 1), 0., 1.); - localM = steppedSmooth(localM, steps, .5 * u_softness); + var gradient = u.u_colors[0]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); + for (var i: i32 = 1; i < ${simplexNoiseMeta.maxColorCount}; i++) { + if (i < i32(u.u_colorsCount)) { + var localM = clamp(mixer - f32(i - 1), 0.0, 1.0); + localM = steppedSmooth(localM, steps, 0.5 * u.u_softness, mixerFw); - vec4 c = u_colors[i]; - c.rgb *= c.a; - gradient = mix(gradient, c, localM); + var c = u.u_colors[i]; + c = vec4f(c.rgb * c.a, c.a); + gradient = mix(gradient, c, localM); + } } if (u_extraSides == true) { - if ((mixer < 0.) || (mixer > (u_colorsCount - 1.))) { - float localM = mixer + 1.; - if (mixer > (u_colorsCount - 1.)) { - localM = mixer - (u_colorsCount - 1.); + if ((mixer < 0.0) || (mixer > (u.u_colorsCount - 1.0))) { + var localM2 = mixer + 1.0; + if (mixer > (u.u_colorsCount - 1.0)) { + localM2 = mixer - (u.u_colorsCount - 1.0); } - localM = steppedSmooth(localM, steps, .5 * u_softness); - vec4 cFst = u_colors[0]; - cFst.rgb *= cFst.a; - vec4 cLast = u_colors[int(u_colorsCount - 1.)]; - cLast.rgb *= cLast.a; - gradient = mix(cLast, cFst, localM); + localM2 = steppedSmooth(localM2, steps, 0.5 * u.u_softness, mixerFw); + var cFst = u.u_colors[0]; + cFst = vec4f(cFst.rgb * cFst.a, cFst.a); + var cLast = u.u_colors[i32(u.u_colorsCount - 1.0)]; + cLast = vec4f(cLast.rgb * cLast.a, cLast.a); + gradient = mix(cLast, cFst, localM2); } } - vec3 color = gradient.rgb; - float opacity = gradient.a; + var color = gradient.rgb; + let opacity = gradient.a; - ${ colorBandingFix } + ${colorBandingFix} - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/smoke-ring.ts b/packages/shaders/src/shaders/smoke-ring.ts index aa032b058..a5d5aba12 100644 --- a/packages/shaders/src/shaders/smoke-ring.ts +++ b/packages/shaders/src/shaders/smoke-ring.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, textureRandomizerR, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, textureRandomizerR, colorBandingFix } from '../shader-utils.js'; export const smokeRingMeta = { maxColorCount: 10, @@ -42,47 +42,48 @@ export const smokeRingMeta = { * */ -// language=GLSL -export const smokeRingFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform sampler2D u_noiseTexture; - -uniform vec4 u_colorBack; -uniform vec4 u_colors[${ smokeRingMeta.maxColorCount }]; -uniform float u_colorsCount; - -uniform float u_thickness; -uniform float u_radius; -uniform float u_innerShape; -uniform float u_noiseScale; -uniform float u_noiseIterations; - -in vec2 v_objectUV; - -out vec4 fragColor; - -${ declarePI } -${ textureRandomizerR } -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = randomR(i); - float b = randomR(i + vec2(1.0, 0.0)); - float c = randomR(i + vec2(0.0, 1.0)); - float d = randomR(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); +export const smokeRingFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorBack: vec4f, + u_colorsCount: f32, + u_thickness: f32, + u_radius: f32, + u_innerShape: f32, + u_noiseScale: f32, + u_noiseIterations: f32, + u_colors: array, } -vec2 fbm(vec2 n0, vec2 n1) { - vec2 total = vec2(0.0); - float amplitude = .4; - for (int i = 0; i < ${ smokeRingMeta.maxNoiseIterations }; i++) { - if (i >= int(u_noiseIterations)) break; +@group(0) @binding(0) var u: Uniforms; + +@group(1) @binding(0) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(1) var u_noiseTexture_samp: sampler; + +${vertexOutputStruct} + +${declarePI} +${textureRandomizerR} + +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = randomR(i); + let b = randomR(i + vec2f(1.0, 0.0)); + let c = randomR(i + vec2f(0.0, 1.0)); + let d = randomR(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); +} + +fn fbm(n0_in: vec2f, n1_in: vec2f) -> vec2f { + var n0 = n0_in; + var n1 = n1_in; + var total = vec2f(0.0); + var amplitude: f32 = 0.4; + for (var i: i32 = 0; i < ${smokeRingMeta.maxNoiseIterations}; i++) { + if (i >= i32(u.u_noiseIterations)) { break; } total.x += valueNoise(n0) * amplitude; total.y += valueNoise(n1) * amplitude; n0 *= 1.99; @@ -92,72 +93,72 @@ vec2 fbm(vec2 n0, vec2 n1) { return total; } -float getNoise(vec2 uv, vec2 pUv, float t) { - vec2 pUvLeft = pUv + .03 * t; - float period = max(abs(u_noiseScale * TWO_PI), 1e-6); - vec2 pUvRight = vec2(fract(pUv.x / period) * period, pUv.y) + .03 * t; - vec2 noise = fbm(pUvLeft, pUvRight); - return mix(noise.y, noise.x, smoothstep(-.25, .25, uv.x)); +fn getNoise(uv: vec2f, pUv: vec2f, t: f32) -> f32 { + let pUvLeft = pUv + 0.03 * t; + let period = max(abs(u.u_noiseScale * TWO_PI), 1e-6); + let pUvRight = vec2f(fract(pUv.x / period) * period, pUv.y) + 0.03 * t; + let noiseVal = fbm(pUvLeft, pUvRight); + return mix(noiseVal.y, noiseVal.x, smoothstep(-0.25, 0.25, uv.x)); } -float getRingShape(vec2 uv) { - float radius = u_radius; - float thickness = u_thickness; +fn getRingShape(uv: vec2f) -> f32 { + let radius = u.u_radius; + let thickness = u.u_thickness; - float distance = length(uv); - float ringValue = 1. - smoothstep(radius, radius + thickness, distance); - ringValue *= smoothstep(radius - pow(u_innerShape, 3.) * thickness, radius, distance); + let distance_val = length(uv); + var ringValue = 1.0 - smoothstep(radius, radius + thickness, distance_val); + ringValue *= smoothstep(radius - pow(u.u_innerShape, 3.0) * thickness, radius, distance_val); return ringValue; } -void main() { - vec2 shape_uv = v_objectUV; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var shape_uv = input.v_objectUV; + + let t = u.u_time; - float t = u_time; + let cycleDuration: f32 = 3.0; + let period2 = 2.0 * cycleDuration; + let localTime1 = fract((0.1 * t + cycleDuration) / period2) * period2; + let localTime2 = fract((0.1 * t) / period2) * period2; + let timeBlend = 0.5 + 0.5 * sin(0.1 * t * PI / cycleDuration - 0.5 * PI); - float cycleDuration = 3.; - float period2 = 2.0 * cycleDuration; - float localTime1 = fract((0.1 * t + cycleDuration) / period2) * period2; - float localTime2 = fract((0.1 * t) / period2) * period2; - float timeBlend = .5 + .5 * sin(.1 * t * PI / cycleDuration - .5 * PI); + let atg = atan2(shape_uv.y, shape_uv.x) + 0.001; + let l = length(shape_uv); + let radialOffset = 0.5 * l - inverseSqrt(max(1e-4, l)); + let polar_uv1 = vec2f(atg, localTime1 - radialOffset) * u.u_noiseScale; + let polar_uv2 = vec2f(atg, localTime2 - radialOffset) * u.u_noiseScale; - float atg = atan(shape_uv.y, shape_uv.x) + .001; - float l = length(shape_uv); - float radialOffset = .5 * l - inversesqrt(max(1e-4, l)); - vec2 polar_uv1 = vec2(atg, localTime1 - radialOffset) * u_noiseScale; - vec2 polar_uv2 = vec2(atg, localTime2 - radialOffset) * u_noiseScale; - - float noise1 = getNoise(shape_uv, polar_uv1, t); - float noise2 = getNoise(shape_uv, polar_uv2, t); + let noise1 = getNoise(shape_uv, polar_uv1, t); + let noise2 = getNoise(shape_uv, polar_uv2, t); - float noise = mix(noise1, noise2, timeBlend); + let noiseVal = mix(noise1, noise2, timeBlend); - shape_uv *= (.8 + 1.2 * noise); + shape_uv *= (0.8 + 1.2 * noiseVal); - float ringShape = getRingShape(shape_uv); + let ringShape = getRingShape(shape_uv); - float mixer = ringShape * ringShape * (u_colorsCount - 1.); - int idxLast = int(u_colorsCount) - 1; - vec4 gradient = u_colors[idxLast]; - gradient.rgb *= gradient.a; - for (int i = ${ smokeRingMeta.maxColorCount } - 2; i >= 0; i--) { - float localT = clamp(mixer - float(idxLast - i - 1), 0., 1.); - vec4 c = u_colors[i]; - c.rgb *= c.a; + let mixer = ringShape * ringShape * (u.u_colorsCount - 1.0); + let idxLast = i32(u.u_colorsCount) - 1; + var gradient = u.u_colors[idxLast]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); + for (var i: i32 = ${smokeRingMeta.maxColorCount} - 2; i >= 0; i--) { + let localT = clamp(mixer - f32(idxLast - i - 1), 0.0, 1.0); + var c = u.u_colors[i]; + c = vec4f(c.rgb * c.a, c.a); gradient = mix(gradient, c, localT); } - vec3 color = gradient.rgb * ringShape; - float opacity = gradient.a * ringShape; + var color = gradient.rgb * ringShape; + var opacity = gradient.a * ringShape; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - color = color + bgColor * (1. - opacity); - opacity = opacity + u_colorBack.a * (1. - opacity); + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + color = color + bgColor * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); - ${ colorBandingFix } + ${colorBandingFix} - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/spiral.ts b/packages/shaders/src/shaders/spiral.ts index e212210da..e982d4e64 100644 --- a/packages/shaders/src/shaders/spiral.ts +++ b/packages/shaders/src/shaders/spiral.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { simplexNoise, declarePI, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, simplexNoise, glslMod, declarePI, colorBandingFix } from '../shader-utils.js'; /** * A single-colored animated spiral that morphs across a wide range of shapes - @@ -37,75 +37,72 @@ import { simplexNoise, declarePI, colorBandingFix } from '../shader-utils.js'; * */ -// language=GLSL -export const spiralFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform vec4 u_colorBack; -uniform vec4 u_colorFront; -uniform float u_density; -uniform float u_distortion; -uniform float u_strokeWidth; -uniform float u_strokeCap; -uniform float u_strokeTaper; -uniform float u_noise; -uniform float u_noiseFrequency; -uniform float u_softness; - -in vec2 v_patternUV; +export const spiralFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorBack: vec4f, + u_colorFront: vec4f, + u_density: f32, + u_distortion: f32, + u_strokeWidth: f32, + u_strokeCap: f32, + u_strokeTaper: f32, + u_noise: f32, + u_noiseFrequency: f32, + u_softness: f32, +} +@group(0) @binding(0) var u: Uniforms; -out vec4 fragColor; +${vertexOutputStruct} -${ declarePI } -${ simplexNoise } +${declarePI} +${glslMod} +${simplexNoise} -void main() { - vec2 uv = 2. * v_patternUV; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + let uv = 2.0 * input.v_patternUV; - float t = u_time; - float l = length(uv); - float density = clamp(u_density, 0., 1.); + let t = u.u_time; + var l = length(uv); + let density = clamp(u.u_density, 0.0, 1.0); l = pow(max(l, 1e-6), density); - float angle = atan(uv.y, uv.x) - t; - float angleNormalised = angle / TWO_PI; - - angleNormalised += .125 * u_noise * snoise(16. * pow(u_noiseFrequency, 3.) * uv); + let angle = atan2(uv.y, uv.x) - t; + var angleNormalised = angle / TWO_PI; - float offset = l + angleNormalised; - offset -= u_distortion * (sin(4. * l - .5 * t) * cos(PI + l + .5 * t)); - float stripe = fract(offset); + angleNormalised += 0.125 * u.u_noise * snoise(16.0 * pow(u.u_noiseFrequency, 3.0) * uv); - float shape = 2. * abs(stripe - .5); - float width = 1. - clamp(u_strokeWidth, .005 * u_strokeTaper, 1.); + var offset = l + angleNormalised; + offset -= u.u_distortion * (sin(4.0 * l - 0.5 * t) * cos(PI + l + 0.5 * t)); + let stripe = fract(offset); + let shape = 2.0 * abs(stripe - 0.5); + var width = 1.0 - clamp(u.u_strokeWidth, 0.005 * u.u_strokeTaper, 1.0); - float wCap = mix(width, (1. - stripe) * (1. - step(.5, stripe)), (1. - clamp(l, 0., 1.))); - width = mix(width, wCap, u_strokeCap); - width *= (1. - clamp(u_strokeTaper, 0., 1.) * l); + let wCap = mix(width, (1.0 - stripe) * (1.0 - step(0.5, stripe)), (1.0 - clamp(l, 0.0, 1.0))); + width = mix(width, wCap, u.u_strokeCap); + width *= (1.0 - clamp(u.u_strokeTaper, 0.0, 1.0) * l); - float fw = fwidth(offset); - float fwMult = 4. - 3. * (smoothstep(.05, .4, 2. * u_strokeWidth) * smoothstep(.05, .4, 2. * (1. - u_strokeWidth))); - float pixelSize = mix(fwMult * fw, fwidth(shape), clamp(fw, 0., 1.)); - pixelSize = mix(pixelSize, .002, u_strokeCap * (1. - clamp(l, 0., 1.))); + let fw = fwidth(offset); + let fwMult = 4.0 - 3.0 * (smoothstep(0.05, 0.4, 2.0 * u.u_strokeWidth) * smoothstep(0.05, 0.4, 2.0 * (1.0 - u.u_strokeWidth))); + var pixelSize = mix(fwMult * fw, fwidth(shape), clamp(fw, 0.0, 1.0)); + pixelSize = mix(pixelSize, 0.002, u.u_strokeCap * (1.0 - clamp(l, 0.0, 1.0))); - float res = smoothstep(width - pixelSize - u_softness, width + pixelSize + u_softness, shape); + let res = smoothstep(width - pixelSize - u.u_softness, width + pixelSize + u.u_softness, shape); - vec3 fgColor = u_colorFront.rgb * u_colorFront.a; - float fgOpacity = u_colorFront.a; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - float bgOpacity = u_colorBack.a; + let fgColor = u.u_colorFront.rgb * u.u_colorFront.a; + let fgOpacity = u.u_colorFront.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + let bgOpacity = u.u_colorBack.a; - vec3 color = fgColor * res; - float opacity = fgOpacity * res; + var color = fgColor * res; + var opacity = fgOpacity * res; - color += bgColor * (1. - opacity); - opacity += bgOpacity * (1. - opacity); + color += bgColor * (1.0 - opacity); + opacity += bgOpacity * (1.0 - opacity); - ${ colorBandingFix } + ${colorBandingFix} - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/static-mesh-gradient.ts b/packages/shaders/src/shaders/static-mesh-gradient.ts index adec223e7..e9c3a525c 100644 --- a/packages/shaders/src/shaders/static-mesh-gradient.ts +++ b/packages/shaders/src/shaders/static-mesh-gradient.ts @@ -4,7 +4,7 @@ import { type ShaderSizingParams, type ShaderSizingUniforms, } from '../shader-sizing.js'; -import { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, proceduralHash21, glslMod } from '../shader-utils.js'; export const staticMeshGradientMeta = { maxColorCount: 10, @@ -44,94 +44,95 @@ export const staticMeshGradientMeta = { * */ -// language=GLSL -export const staticMeshGradientFragmentShader: string = `#version 300 es -precision mediump float; - -uniform vec4 u_colors[${ staticMeshGradientMeta.maxColorCount }]; -uniform float u_colorsCount; - -uniform float u_positions; -uniform float u_waveX; -uniform float u_waveXShift; -uniform float u_waveY; -uniform float u_waveYShift; -uniform float u_mixing; -uniform float u_grainMixer; -uniform float u_grainOverlay; - -in vec2 v_objectUV; -out vec4 fragColor; - -${ declarePI } -${ rotation2 } -${ proceduralHash21 } - -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = hash21(i); - float b = hash21(i + vec2(1.0, 0.0)); - float c = hash21(i + vec2(0.0, 1.0)); - float d = hash21(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); +export const staticMeshGradientFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_positions: f32, + u_waveX: f32, + u_waveXShift: f32, + u_waveY: f32, + u_waveYShift: f32, + u_mixing: f32, + u_grainMixer: f32, + u_grainOverlay: f32, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; + +${vertexOutputStruct} + +${declarePI} +${rotation2} +${proceduralHash21} +${glslMod} + +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = hash21(i); + let b = hash21(i + vec2f(1.0, 0.0)); + let c = hash21(i + vec2f(0.0, 1.0)); + let d = hash21(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } -float noise(vec2 n, vec2 seedOffset) { +fn noise(n: vec2f, seedOffset: vec2f) -> f32 { return valueNoise(n + seedOffset); } -vec2 getPosition(int i, float t) { - float a = float(i) * .37; - float b = .6 + mod(float(i), 3.) * .3; - float c = .8 + mod(float(i + 1), 4.) * 0.25; +fn getPosition(idx: i32, t: f32) -> vec2f { + let fi = f32(idx); + let a = fi * 0.37; + let b = 0.6 + glsl_mod_f32(fi, 3.0) * 0.3; + let c_val = 0.8 + glsl_mod_f32(f32(idx + 1), 4.0) * 0.25; - float x = sin(t * b + a); - float y = cos(t * c + a * 1.5); + let x = sin(t * b + a); + let y = cos(t * c_val + a * 1.5); - return .5 + .5 * vec2(x, y); + return vec2f(0.5) + 0.5 * vec2f(x, y); } -void main() { - vec2 uv = v_objectUV; - uv += .5; - vec2 grainUV = uv * 1000.; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var uv = input.v_objectUV; + uv += vec2f(0.5); + let grainUV = uv * 1000.0; - float grain = noise(grainUV, vec2(0.)); - float mixerGrain = .4 * u_grainMixer * (grain - .5); + let grain = noise(grainUV, vec2f(0.0)); + let mixerGrain = 0.4 * u.u_grainMixer * (grain - 0.5); - float radius = smoothstep(0., 1., length(uv - .5)); - float center = 1. - radius; - for (float i = 1.; i <= 2.; i++) { - uv.x += u_waveX * center / i * cos(TWO_PI * u_waveXShift + i * 2. * smoothstep(.0, 1., uv.y)); - uv.y += u_waveY * center / i * cos(TWO_PI * u_waveYShift + i * 2. * smoothstep(.0, 1., uv.x)); + let radius = smoothstep(0.0, 1.0, length(uv - vec2f(0.5))); + let center = 1.0 - radius; + for (var i: f32 = 1.0; i <= 2.0; i += 1.0) { + uv.x += u.u_waveX * center / i * cos(TWO_PI * u.u_waveXShift + i * 2.0 * smoothstep(0.0, 1.0, uv.y)); + uv.y += u.u_waveY * center / i * cos(TWO_PI * u.u_waveYShift + i * 2.0 * smoothstep(0.0, 1.0, uv.x)); } - vec3 color = vec3(0.); - float opacity = 0.; - float totalWeight = 0.; - float positionSeed = 25. + .33 * u_positions; + var color = vec3f(0.0); + var opacity: f32 = 0.0; + var totalWeight: f32 = 0.0; + let positionSeed = 25.0 + 0.33 * u.u_positions; - for (int i = 0; i < ${ staticMeshGradientMeta.maxColorCount }; i++) { - if (i >= int(u_colorsCount)) break; + for (var i: i32 = 0; i < ${staticMeshGradientMeta.maxColorCount}; i++) { + if (i >= i32(u.u_colorsCount)) { break; } - vec2 pos = getPosition(i, positionSeed) + mixerGrain; - float dist = length(uv - pos); + let pos = getPosition(i, positionSeed) + vec2f(mixerGrain); + var dist = length(uv - pos); dist = length(uv - pos); - vec3 colorFraction = u_colors[i].rgb * u_colors[i].a; - float opacityFraction = u_colors[i].a; + let colorFraction = u.u_colors[i].rgb * u.u_colors[i].a; + let opacityFraction = u.u_colors[i].a; - float mixing = pow(u_mixing, .7); - float power = mix(2., 1., mixing); + let mixing = pow(u.u_mixing, 0.7); + let power = mix(2.0, 1.0, mixing); dist = pow(dist, power); - float w = 1. / (dist + 1e-3); - float baseSharpness = mix(.0, 8., clamp(w, 0., 1.)); - float sharpness = mix(baseSharpness, 1., mixing); + var w = 1.0 / (dist + 1e-3); + let baseSharpness = mix(0.0, 8.0, clamp(w, 0.0, 1.0)); + let sharpness = mix(baseSharpness, 1.0, mixing); w = pow(w, sharpness); color += colorFraction * w; opacity += opacityFraction * w; @@ -141,20 +142,20 @@ void main() { color /= max(1e-4, totalWeight); opacity /= max(1e-4, totalWeight); - float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.)); - grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5); + var grainOverlay = valueNoise(rotate(grainUV, 1.0) + vec2f(3.0)); + grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.0) + vec2f(-1.0)), 0.5); grainOverlay = pow(grainOverlay, 1.3); - float grainOverlayV = grainOverlay * 2. - 1.; - vec3 grainOverlayColor = vec3(step(0., grainOverlayV)); - float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV); - grainOverlayStrength = pow(grainOverlayStrength, .8); - color = mix(color, grainOverlayColor, .35 * grainOverlayStrength); + let grainOverlayV = grainOverlay * 2.0 - 1.0; + let grainOverlayColor = vec3f(step(0.0, grainOverlayV)); + var grainOverlayStrength = u.u_grainOverlay * abs(grainOverlayV); + grainOverlayStrength = pow(grainOverlayStrength, 0.8); + color = mix(color, grainOverlayColor, 0.35 * grainOverlayStrength); - opacity += .5 * grainOverlayStrength; - opacity = clamp(opacity, 0., 1.); + opacity += 0.5 * grainOverlayStrength; + opacity = clamp(opacity, 0.0, 1.0); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/static-radial-gradient.ts b/packages/shaders/src/shaders/static-radial-gradient.ts index 434d6562b..248b72ab4 100644 --- a/packages/shaders/src/shaders/static-radial-gradient.ts +++ b/packages/shaders/src/shaders/static-radial-gradient.ts @@ -4,7 +4,7 @@ import { type ShaderSizingParams, type ShaderSizingUniforms, } from '../shader-sizing.js'; -import { declarePI, rotation2, proceduralHash21 } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, proceduralHash21, glslMod } from '../shader-utils.js'; export const staticRadialGradientMeta = { maxColorCount: 10, @@ -47,173 +47,178 @@ export const staticRadialGradientMeta = { * */ -// language=GLSL -export const staticRadialGradientFragmentShader: string = `#version 300 es -precision mediump float; - -uniform vec4 u_colorBack; -uniform vec4 u_colors[${ staticRadialGradientMeta.maxColorCount }]; -uniform float u_colorsCount; - -uniform float u_radius; -uniform float u_focalDistance; -uniform float u_focalAngle; -uniform float u_falloff; -uniform float u_mixing; -uniform float u_distortion; -uniform float u_distortionShift; -uniform float u_distortionFreq; -uniform float u_grainMixer; -uniform float u_grainOverlay; - -in vec2 v_objectUV; -out vec4 fragColor; - -${ declarePI } -${ rotation2 } -${ proceduralHash21 } - -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = hash21(i); - float b = hash21(i + vec2(1.0, 0.0)); - float c = hash21(i + vec2(0.0, 1.0)); - float d = hash21(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); +export const staticRadialGradientFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorBack: vec4f, + u_colorsCount: f32, + u_radius: f32, + u_focalDistance: f32, + u_focalAngle: f32, + u_falloff: f32, + u_mixing: f32, + u_distortion: f32, + u_distortionShift: f32, + u_distortionFreq: f32, + u_grainMixer: f32, + u_grainOverlay: f32, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; + +${vertexOutputStruct} + +${declarePI} +${rotation2} +${proceduralHash21} +${glslMod} + +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = hash21(i); + let b = hash21(i + vec2f(1.0, 0.0)); + let c = hash21(i + vec2f(0.0, 1.0)); + let d = hash21(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); } -float noise(vec2 n, vec2 seedOffset) { +fn noise(n: vec2f, seedOffset: vec2f) -> f32 { return valueNoise(n + seedOffset); } -vec2 getPosition(int i, float t) { - float a = float(i) * .37; - float b = .6 + mod(float(i), 3.) * .3; - float c = .8 + mod(float(i + 1), 4.) * 0.25; +fn getPosition(idx: i32, t: f32) -> vec2f { + let fi = f32(idx); + let a = fi * 0.37; + let b = 0.6 + glsl_mod_f32(fi, 3.0) * 0.3; + let c_val = 0.8 + glsl_mod_f32(f32(idx + 1), 4.0) * 0.25; - float x = sin(t * b + a); - float y = cos(t * c + a * 1.5); + let x = sin(t * b + a); + let y = cos(t * c_val + a * 1.5); - return .5 + .5 * vec2(x, y); + return vec2f(0.5) + 0.5 * vec2f(x, y); } -void main() { - vec2 uv = 2. * v_objectUV; - vec2 grainUV = uv * 1000.; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + let uv = 2.0 * input.v_objectUV; + let grainUV = uv * 1000.0; - vec2 center = vec2(0.); - float angleRad = -radians(u_focalAngle + 90.); - vec2 focalPoint = vec2(cos(angleRad), sin(angleRad)) * u_focalDistance; - float radius = u_radius; + let center = vec2f(0.0); + let angleRad = -radians(u.u_focalAngle + 90.0); + let focalPoint = vec2f(cos(angleRad), sin(angleRad)) * u.u_focalDistance; + let radius = u.u_radius; - vec2 c_to_uv = uv - center; - vec2 f_to_uv = uv - focalPoint; - vec2 f_to_c = center - focalPoint; - float r = length(c_to_uv); + let c_to_uv = uv - center; + let f_to_uv = uv - focalPoint; + let f_to_c = center - focalPoint; + let r = length(c_to_uv); - float fragAngle = atan(c_to_uv.y, c_to_uv.x); - float angleDiff = fract((fragAngle - angleRad + PI) / TWO_PI) * TWO_PI - PI; + let fragAngle = atan2(c_to_uv.y, c_to_uv.x); + let angleDiff = fract((fragAngle - angleRad + PI) / TWO_PI) * TWO_PI - PI; - float halfAngle = acos(clamp(radius / max(u_focalDistance, 1e-4), 0.0, 1.0)); - float e0 = 0.6 * PI, e1 = halfAngle; - float lo = min(e0, e1), hi = max(e0, e1); - float s = smoothstep(lo, hi, abs(angleDiff)); - float isInSector = (e1 >= e0) ? (1.0 - s) : s; + let halfAngle = acos(clamp(radius / max(u.u_focalDistance, 1e-4), 0.0, 1.0)); + let e0 = 0.6 * PI; + let e1 = halfAngle; + let lo = min(e0, e1); + let hi = max(e0, e1); + let s = smoothstep(lo, hi, abs(angleDiff)); + let isInSector = select(s, 1.0 - s, e1 >= e0); - float a = dot(f_to_uv, f_to_uv); - float b = -2.0 * dot(f_to_uv, f_to_c); - float c = dot(f_to_c, f_to_c) - radius * radius; + let qa = dot(f_to_uv, f_to_uv); + let qb = -2.0 * dot(f_to_uv, f_to_c); + let qc = dot(f_to_c, f_to_c) - radius * radius; - float discriminant = b * b - 4.0 * a * c; - float t = 1.0; + let discriminant = qb * qb - 4.0 * qa * qc; + var t: f32 = 1.0; if (discriminant >= 0.0) { - float sqrtD = sqrt(discriminant); - float div = max(1e-4, 2.0 * a); - float t0 = (-b - sqrtD) / div; - float t1 = (-b + sqrtD) / div; + let sqrtD = sqrt(discriminant); + let div = max(1e-4, 2.0 * qa); + let t0 = (-qb - sqrtD) / div; + let t1 = (-qb + sqrtD) / div; t = max(t0, t1); - if (t < 0.0) t = 0.0; + if (t < 0.0) { t = 0.0; } } - float dist = length(f_to_uv); - float normalized = dist / max(1e-4, length(f_to_uv * t)); - float shape = clamp(normalized, 0.0, 1.0); + let dist = length(f_to_uv); + let normalized = dist / max(1e-4, length(f_to_uv * t)); + var shape = clamp(normalized, 0.0, 1.0); - float falloffMapped = mix(.2 + .8 * max(0., u_falloff + 1.), mix(1., 15., u_falloff * u_falloff), step(.0, u_falloff)); + let falloffMapped = mix(0.2 + 0.8 * max(0.0, u.u_falloff + 1.0), mix(1.0, 15.0, u.u_falloff * u.u_falloff), step(0.0, u.u_falloff)); - float falloffExp = mix(falloffMapped, 1., shape); + let falloffExp = mix(falloffMapped, 1.0, shape); shape = pow(shape, falloffExp); - shape = 1. - clamp(shape, 0., 1.); - - - float outerMask = .002; - float outer = 1.0 - smoothstep(radius - outerMask, radius + outerMask, r); - outer = mix(outer, 1., isInSector); - - shape = mix(0., shape, outer); - shape *= 1. - smoothstep(radius - .01, radius, r); - - float angle = atan(f_to_uv.y, f_to_uv.x); - shape -= pow(u_distortion, 2.) * shape * pow(abs(sin(PI * clamp(length(f_to_uv) - 0.2 + u_distortionShift, 0.0, 1.0))), 4.0) * (sin(u_distortionFreq * angle) + cos(floor(0.65 * u_distortionFreq) * angle)); - - float grain = noise(grainUV, vec2(0.)); - float mixerGrain = .4 * u_grainMixer * (grain - .5); - - float mixer = shape * u_colorsCount + mixerGrain; - vec4 gradient = u_colors[0]; - gradient.rgb *= gradient.a; - - float outerShape = 0.; - for (int i = 1; i < ${ staticRadialGradientMeta.maxColorCount + 1 }; i++) { - if (i > int(u_colorsCount)) break; - float mLinear = clamp(mixer - float(i - 1), 0.0, 1.0); - - float aa = fwidth(mLinear); - float width = min(u_mixing, 0.5); - float t = clamp((mLinear - (0.5 - width - aa)) / (2. * width + 2. * aa), 0., 1.); - float p = mix(2., 1., clamp((u_mixing - 0.5) * 2., 0., 1.)); - float m = t < 0.5 - ? 0.5 * pow(2. * t, p) - : 1. - 0.5 * pow(2. * (1. - t), p); - - float quadBlend = clamp((u_mixing - 0.5) * 2., 0., 1.); - m = mix(m, m * m, 0.5 * quadBlend); - - if (i == 1) { - outerShape = m; + shape = 1.0 - clamp(shape, 0.0, 1.0); + + let outerMask: f32 = 0.002; + var outer = 1.0 - smoothstep(radius - outerMask, radius + outerMask, r); + outer = mix(outer, 1.0, isInSector); + + shape = mix(0.0, shape, outer); + shape *= 1.0 - smoothstep(radius - 0.01, radius, r); + + let angle = atan2(f_to_uv.y, f_to_uv.x); + shape -= pow(u.u_distortion, 2.0) * shape * pow(abs(sin(PI * clamp(length(f_to_uv) - 0.2 + u.u_distortionShift, 0.0, 1.0))), 4.0) * (sin(u.u_distortionFreq * angle) + cos(floor(0.65 * u.u_distortionFreq) * angle)); + + let grain = noise(grainUV, vec2f(0.0)); + let mixerGrain = 0.4 * u.u_grainMixer * (grain - 0.5); + + let mixer = shape * u.u_colorsCount + mixerGrain; + var gradient = u.u_colors[0]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); + + var outerShape: f32 = 0.0; + for (var i: i32 = 1; i < ${staticRadialGradientMeta.maxColorCount + 1}; i++) { + if (i <= i32(u.u_colorsCount)) { + let mLinear = clamp(mixer - f32(i - 1), 0.0, 1.0); + + let aa = fwidth(mLinear); + let width = min(u.u_mixing, 0.5); + let tVal = clamp((mLinear - (0.5 - width - aa)) / (2.0 * width + 2.0 * aa), 0.0, 1.0); + let p = mix(2.0, 1.0, clamp((u.u_mixing - 0.5) * 2.0, 0.0, 1.0)); + var m = select( + 1.0 - 0.5 * pow(2.0 * (1.0 - tVal), p), + 0.5 * pow(2.0 * tVal, p), + tVal < 0.5 + ); + + let quadBlend = clamp((u.u_mixing - 0.5) * 2.0, 0.0, 1.0); + m = mix(m, m * m, 0.5 * quadBlend); + + if (i == 1) { + outerShape = m; + } + + var c = u.u_colors[i - 1]; + c = vec4f(c.rgb * c.a, c.a); + gradient = mix(gradient, c, m); } - - vec4 c = u_colors[i - 1]; - c.rgb *= c.a; - gradient = mix(gradient, c, m); } - vec3 color = gradient.rgb * outerShape; - float opacity = gradient.a * outerShape; + var color = gradient.rgb * outerShape; + var opacity = gradient.a * outerShape; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; color = color + bgColor * (1.0 - opacity); - opacity = opacity + u_colorBack.a * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); - float grainOverlay = valueNoise(rotate(grainUV, 1.) + vec2(3.)); - grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.) + vec2(-1.)), .5); + var grainOverlay = valueNoise(rotate(grainUV, 1.0) + vec2f(3.0)); + grainOverlay = mix(grainOverlay, valueNoise(rotate(grainUV, 2.0) + vec2f(-1.0)), 0.5); grainOverlay = pow(grainOverlay, 1.3); - float grainOverlayV = grainOverlay * 2. - 1.; - vec3 grainOverlayColor = vec3(step(0., grainOverlayV)); - float grainOverlayStrength = u_grainOverlay * abs(grainOverlayV); - grainOverlayStrength = pow(grainOverlayStrength, .8); - color = mix(color, grainOverlayColor, .35 * grainOverlayStrength); + let grainOverlayV = grainOverlay * 2.0 - 1.0; + let grainOverlayColor = vec3f(step(0.0, grainOverlayV)); + var grainOverlayStrength = u.u_grainOverlay * abs(grainOverlayV); + grainOverlayStrength = pow(grainOverlayStrength, 0.8); + color = mix(color, grainOverlayColor, 0.35 * grainOverlayStrength); - opacity += .5 * grainOverlayStrength; - opacity = clamp(opacity, 0., 1.); + opacity += 0.5 * grainOverlayStrength; + opacity = clamp(opacity, 0.0, 1.0); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/swirl.ts b/packages/shaders/src/shaders/swirl.ts index a3d31f91f..4b8d88923 100644 --- a/packages/shaders/src/shaders/swirl.ts +++ b/packages/shaders/src/shaders/swirl.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { simplexNoise, declarePI, rotation2, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, simplexNoise, glslMod, declarePI, rotation2, colorBandingFix } from '../shader-utils.js'; export const swirlMeta = { maxColorCount: 10, @@ -41,92 +41,90 @@ export const swirlMeta = { * */ -// language=GLSL -export const swirlFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform vec4 u_colorBack; -uniform vec4 u_colors[${ swirlMeta.maxColorCount }]; -uniform float u_colorsCount; -uniform float u_bandCount; -uniform float u_twist; -uniform float u_center; -uniform float u_proportion; -uniform float u_softness; -uniform float u_noise; -uniform float u_noiseFrequency; - -in vec2 v_objectUV; +export const swirlFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorBack: vec4f, + u_colorsCount: f32, + u_bandCount: f32, + u_twist: f32, + u_center: f32, + u_proportion: f32, + u_softness: f32, + u_noise: f32, + u_noiseFrequency: f32, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; -out vec4 fragColor; +${vertexOutputStruct} -${ declarePI } -${ simplexNoise } -${ rotation2 } +${declarePI} +${glslMod} +${simplexNoise} +${rotation2} -void main() { - vec2 shape_uv = v_objectUV; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + let shape_uv = input.v_objectUV; - float l = length(shape_uv); + var l = length(shape_uv); l = max(1e-4, l); - float t = u_time; + let t = u.u_time; - float angle = ceil(u_bandCount) * atan(shape_uv.y, shape_uv.x) + t; - float angle_norm = angle / TWO_PI; + let angle = ceil(u.u_bandCount) * atan2(shape_uv.y, shape_uv.x) + t; + let angle_norm = angle / TWO_PI; - float twist = 3. * clamp(u_twist, 0., 1.); - float offset = pow(l, -twist) + angle_norm; + let twist = 3.0 * clamp(u.u_twist, 0.0, 1.0); + let offset = pow(l, -twist) + angle_norm; - float shape = fract(offset); - shape = 1. - abs(2. * shape - 1.); - shape += u_noise * snoise(15. * pow(u_noiseFrequency, 2.) * shape_uv); + var shape = fract(offset); + shape = 1.0 - abs(2.0 * shape - 1.0); + shape += u.u_noise * snoise(15.0 * pow(u.u_noiseFrequency, 2.0) * shape_uv); - float mid = smoothstep(.2, .2 + .8 * u_center, pow(l, twist)); - shape = mix(0., shape, mid); + let mid = smoothstep(0.2, 0.2 + 0.8 * u.u_center, pow(l, twist)); + shape = mix(0.0, shape, mid); - float proportion = clamp(u_proportion, 0., 1.); - float exponent = mix(.25, 1., proportion * 2.); - exponent = mix(exponent, 10., max(0., proportion * 2. - 1.)); + let proportion = clamp(u.u_proportion, 0.0, 1.0); + var exponent = mix(0.25, 1.0, proportion * 2.0); + exponent = mix(exponent, 10.0, max(0.0, proportion * 2.0 - 1.0)); shape = pow(shape, exponent); - float mixer = shape * u_colorsCount; - vec4 gradient = u_colors[0]; - gradient.rgb *= gradient.a; + let mixer = shape * u.u_colorsCount; + var gradient = u.u_colors[0]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); - float outerShape = 0.; - for (int i = 1; i < ${ swirlMeta.maxColorCount + 1 }; i++) { - if (i > int(u_colorsCount)) break; + var outerShape: f32 = 0.0; + for (var i: i32 = 1; i < ${swirlMeta.maxColorCount + 1}; i++) { + if (i <= i32(u.u_colorsCount)) { + var m = clamp(mixer - f32(i - 1), 0.0, 1.0); + let aa = fwidth(m); + m = smoothstep(0.5 - 0.5 * u.u_softness - aa, 0.5 + 0.5 * u.u_softness + aa, m); - float m = clamp(mixer - float(i - 1), 0., 1.); - float aa = fwidth(m); - m = smoothstep(.5 - .5 * u_softness - aa, .5 + .5 * u_softness + aa, m); + if (i == 1) { + outerShape = m; + } - if (i == 1) { - outerShape = m; + var c = u.u_colors[i - 1]; + c = vec4f(c.rgb * c.a, c.a); + gradient = mix(gradient, c, m); } - - vec4 c = u_colors[i - 1]; - c.rgb *= c.a; - gradient = mix(gradient, c, m); } - float midAA = .1 * fwidth(pow(l, -twist)); - float outerMid = smoothstep(.2, .2 + midAA, pow(l, twist)); - outerShape = mix(0., outerShape, outerMid); + let midAA = 0.1 * fwidth(pow(l, -twist)); + let outerMid = smoothstep(0.2, 0.2 + midAA, pow(l, twist)); + outerShape = mix(0.0, outerShape, outerMid); - vec3 color = gradient.rgb * outerShape; - float opacity = gradient.a * outerShape; + var color = gradient.rgb * outerShape; + var opacity = gradient.a * outerShape; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; color = color + bgColor * (1.0 - opacity); - opacity = opacity + u_colorBack.a * (1.0 - opacity); + opacity = opacity + u.u_colorBack.a * (1.0 - opacity); - ${ colorBandingFix } + ${colorBandingFix} - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/voronoi.ts b/packages/shaders/src/shaders/voronoi.ts index b159c53ab..f951ca927 100644 --- a/packages/shaders/src/shaders/voronoi.ts +++ b/packages/shaders/src/shaders/voronoi.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, textureRandomizerGB } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, textureRandomizerGB } from '../shader-utils.js'; export const voronoiMeta = { maxColorCount: 5, @@ -46,49 +46,45 @@ export const voronoiMeta = { * */ -// language=GLSL -export const voronoiFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform float u_scale; - -uniform sampler2D u_noiseTexture; - -uniform vec4 u_colors[${ voronoiMeta.maxColorCount }]; -uniform float u_colorsCount; - -uniform float u_stepsPerColor; -uniform vec4 u_colorGlow; -uniform vec4 u_colorGap; -uniform float u_distortion; -uniform float u_gap; -uniform float u_glow; +export const voronoiFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_stepsPerColor: f32, + u_colorGlow: vec4f, + u_colorGap: vec4f, + u_distortion: f32, + u_gap: f32, + u_glow: f32, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; -in vec2 v_patternUV; +@group(1) @binding(0) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(1) var u_noiseTexture_samp: sampler; -out vec4 fragColor; +${vertexOutputStruct} -${ declarePI } -${ textureRandomizerGB } +${declarePI} +${textureRandomizerGB} -vec4 voronoi(vec2 x, float t) { - vec2 ip = floor(x); - vec2 fp = fract(x); +fn voronoi(x: vec2f, t: f32) -> vec4f { + let ip = floor(x); + let fp = fract(x); - vec2 mg, mr; - float md = 8.; - float rand = 0.; + var mg: vec2f; + var mr: vec2f; + var md: f32 = 8.0; + var rand: f32 = 0.0; - for (int j = -1; j <= 1; j++) { - for (int i = -1; i <= 1; i++) { - vec2 g = vec2(float(i), float(j)); - vec2 o = randomGB(ip + g); - float raw_hash = o.x; - o = .5 + u_distortion * sin(t + TWO_PI * o); - vec2 r = g + o - fp; - float d = dot(r, r); + for (var j: i32 = -1; j <= 1; j++) { + for (var i: i32 = -1; i <= 1; i++) { + let g = vec2f(f32(i), f32(j)); + let o_raw = randomGB(ip + g); + let raw_hash = o_raw.x; + let o = vec2f(0.5) + u.u_distortion * sin(vec2f(t) + TWO_PI * o_raw); + let r = g + o - fp; + let d = dot(r, r); if (d < md) { md = d; @@ -99,76 +95,76 @@ vec4 voronoi(vec2 x, float t) { } } - md = 8.; - for (int j = -2; j <= 2; j++) { - for (int i = -2; i <= 2; i++) { - vec2 g = mg + vec2(float(i), float(j)); - vec2 o = randomGB(ip + g); - o = .5 + u_distortion * sin(t + TWO_PI * o); - vec2 r = g + o - fp; - if (dot(mr - r, mr - r) > .00001) { - md = min(md, dot(.5 * (mr + r), normalize(r - mr))); + md = 8.0; + for (var j2: i32 = -2; j2 <= 2; j2++) { + for (var i2: i32 = -2; i2 <= 2; i2++) { + let g = mg + vec2f(f32(i2), f32(j2)); + let o_raw2 = randomGB(ip + g); + let o = vec2f(0.5) + u.u_distortion * sin(vec2f(t) + TWO_PI * o_raw2); + let r = g + o - fp; + if (dot(mr - r, mr - r) > 0.00001) { + md = min(md, dot(0.5 * (mr + r), normalize(r - mr))); } } } - return vec4(md, mr, rand); + return vec4f(md, mr, rand); } -void main() { - vec2 shape_uv = v_patternUV; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var shape_uv = input.v_patternUV; shape_uv *= 1.25; - float t = u_time; + let t = u.u_time; - vec4 voronoiRes = voronoi(shape_uv, t); + let voronoiRes = voronoi(shape_uv, t); - float shape = clamp(voronoiRes.w, 0., 1.); - float mixer = shape * (u_colorsCount - 1.); - mixer = (shape - .5 / u_colorsCount) * u_colorsCount; - float steps = max(1., u_stepsPerColor); + let shape = clamp(voronoiRes.w, 0.0, 1.0); + var mixer = shape * (u.u_colorsCount - 1.0); + mixer = (shape - 0.5 / u.u_colorsCount) * u.u_colorsCount; + let steps = max(1.0, u.u_stepsPerColor); - vec4 gradient = u_colors[0]; - gradient.rgb *= gradient.a; - for (int i = 1; i < ${ voronoiMeta.maxColorCount }; i++) { - if (i >= int(u_colorsCount)) break; - float localT = clamp(mixer - float(i - 1), 0.0, 1.0); + var gradient = u.u_colors[0]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); + for (var i: i32 = 1; i < ${voronoiMeta.maxColorCount}; i++) { + if (i >= i32(u.u_colorsCount)) { break; } + var localT = clamp(mixer - f32(i - 1), 0.0, 1.0); localT = round(localT * steps) / steps; - vec4 c = u_colors[i]; - c.rgb *= c.a; + var c = u.u_colors[i]; + c = vec4f(c.rgb * c.a, c.a); gradient = mix(gradient, c, localT); } - if ((mixer < 0.) || (mixer > (u_colorsCount - 1.))) { - float localT = mixer + 1.; - if (mixer > (u_colorsCount - 1.)) { - localT = mixer - (u_colorsCount - 1.); + if ((mixer < 0.0) || (mixer > (u.u_colorsCount - 1.0))) { + var localT2 = mixer + 1.0; + if (mixer > (u.u_colorsCount - 1.0)) { + localT2 = mixer - (u.u_colorsCount - 1.0); } - localT = round(localT * steps) / steps; - vec4 cFst = u_colors[0]; - cFst.rgb *= cFst.a; - vec4 cLast = u_colors[int(u_colorsCount - 1.)]; - cLast.rgb *= cLast.a; - gradient = mix(cLast, cFst, localT); + localT2 = round(localT2 * steps) / steps; + var cFst = u.u_colors[0]; + cFst = vec4f(cFst.rgb * cFst.a, cFst.a); + var cLast = u.u_colors[i32(u.u_colorsCount - 1.0)]; + cLast = vec4f(cLast.rgb * cLast.a, cLast.a); + gradient = mix(cLast, cFst, localT2); } - vec3 cellColor = gradient.rgb; - float cellOpacity = gradient.a; + let cellColor = gradient.rgb; + let cellOpacity = gradient.a; - float glows = length(voronoiRes.yz * u_glow); + var glows = length(voronoiRes.yz * u.u_glow); glows = pow(glows, 1.5); - vec3 color = mix(cellColor, u_colorGlow.rgb * u_colorGlow.a, u_colorGlow.a * glows); - float opacity = cellOpacity + u_colorGlow.a * glows; + var color = mix(cellColor, u.u_colorGlow.rgb * u.u_colorGlow.a, u.u_colorGlow.a * glows); + var opacity = cellOpacity + u.u_colorGlow.a * glows; - float edge = voronoiRes.x; - float smoothEdge = .02 / (2. * u_scale) * (1. + .5 * u_gap); - edge = smoothstep(u_gap - smoothEdge, u_gap + smoothEdge, edge); + let edge_raw = voronoiRes.x; + let smoothEdge = 0.02 / (2.0 * u.u_scale) * (1.0 + 0.5 * u.u_gap); + let edge = smoothstep(u.u_gap - smoothEdge, u.u_gap + smoothEdge, edge_raw); - color = mix(u_colorGap.rgb * u_colorGap.a, color, edge); - opacity = mix(u_colorGap.a, opacity, edge); + color = mix(u.u_colorGap.rgb * u.u_colorGap.a, color, edge); + opacity = mix(u.u_colorGap.a, opacity, edge); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/warp.ts b/packages/shaders/src/shaders/warp.ts index 95a0bfc94..3dcc05ca8 100644 --- a/packages/shaders/src/shaders/warp.ts +++ b/packages/shaders/src/shaders/warp.ts @@ -1,7 +1,7 @@ import type { vec4 } from '../types.js'; import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, rotation2, colorBandingFix } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, colorBandingFix } from '../shader-utils.js'; export const warpMeta = { maxColorCount: 10, @@ -44,116 +44,114 @@ export const warpMeta = { * */ -// language=GLSL -export const warpFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; -uniform float u_scale; - -uniform sampler2D u_noiseTexture; - -uniform vec4 u_colors[${ warpMeta.maxColorCount }]; -uniform float u_colorsCount; -uniform float u_proportion; -uniform float u_softness; -uniform float u_shape; -uniform float u_shapeScale; -uniform float u_distortion; -uniform float u_swirl; -uniform float u_swirlIterations; +export const warpFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorsCount: f32, + u_proportion: f32, + u_softness: f32, + u_shape: f32, + u_shapeScale: f32, + u_distortion: f32, + u_swirl: f32, + u_swirlIterations: f32, + u_colors: array, +} +@group(0) @binding(0) var u: Uniforms; -in vec2 v_patternUV; +@group(1) @binding(0) var u_noiseTexture_tex: texture_2d; +@group(1) @binding(1) var u_noiseTexture_samp: sampler; -out vec4 fragColor; +${vertexOutputStruct} ${ declarePI } ${ rotation2 } -float randomG(vec2 p) { - vec2 uv = floor(p) / 100. + .5; - return texture(u_noiseTexture, fract(uv)).g; -} -float valueNoise(vec2 st) { - vec2 i = floor(st); - vec2 f = fract(st); - float a = randomG(i); - float b = randomG(i + vec2(1.0, 0.0)); - float c = randomG(i + vec2(0.0, 1.0)); - float d = randomG(i + vec2(1.0, 1.0)); - vec2 u = f * f * (3.0 - 2.0 * f); - float x1 = mix(a, b, u.x); - float x2 = mix(c, d, u.x); - return mix(x1, x2, u.y); -} - -void main() { - vec2 uv = v_patternUV; - uv *= .5; - - const float firstFrameOffset = 118.; - float t = 0.0625 * (u_time + firstFrameOffset); +fn randomG(p: vec2f) -> f32 { + let uv = floor(p) / 100.0 + vec2f(0.5); + return textureSampleLevel(u_noiseTexture_tex, u_noiseTexture_samp, fract(uv), 0.0).g; +} - float n1 = valueNoise(uv * 1. + t); - float n2 = valueNoise(uv * 2. - t); - float angle = n1 * TWO_PI; - uv.x += 4. * u_distortion * n2 * cos(angle); - uv.y += 4. * u_distortion * n2 * sin(angle); +fn valueNoise(st: vec2f) -> f32 { + let i = floor(st); + let f = fract(st); + let a = randomG(i); + let b = randomG(i + vec2f(1.0, 0.0)); + let c = randomG(i + vec2f(0.0, 1.0)); + let d = randomG(i + vec2f(1.0, 1.0)); + let u_val = f * f * (vec2f(3.0) - 2.0 * f); + let x1 = mix(a, b, u_val.x); + let x2 = mix(c, d, u_val.x); + return mix(x1, x2, u_val.y); +} - float swirl = u_swirl; - for (int i = 1; i <= 20; i++) { - if (i >= int(u_swirlIterations)) break; - float iFloat = float(i); - // swirl *= (1. - smoothstep(.0, .25, length(fwidth(uv)))); - uv.x += swirl / iFloat * cos(t + iFloat * 1.5 * uv.y); - uv.y += swirl / iFloat * cos(t + iFloat * 1. * uv.x); +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var uv = input.v_patternUV; + uv *= 0.5; + + const firstFrameOffset: f32 = 118.0; + let t = 0.0625 * (u.u_time + firstFrameOffset); + + let n1 = valueNoise(uv * 1.0 + vec2f(t)); + let n2 = valueNoise(uv * 2.0 - vec2f(t)); + let angle = n1 * TWO_PI; + uv = vec2f(uv.x + 4.0 * u.u_distortion * n2 * cos(angle), uv.y); + uv = vec2f(uv.x, uv.y + 4.0 * u.u_distortion * n2 * sin(angle)); + + let swirl = u.u_swirl; + for (var i: i32 = 1; i <= 20; i++) { + if (i >= i32(u.u_swirlIterations)) { break; } + let iFloat = f32(i); + uv = vec2f(uv.x + swirl / iFloat * cos(t + iFloat * 1.5 * uv.y), uv.y); + uv = vec2f(uv.x, uv.y + swirl / iFloat * cos(t + iFloat * 1.0 * uv.x)); } - float proportion = clamp(u_proportion, 0., 1.); - - float shape = 0.; - if (u_shape < .5) { - vec2 checksShape_uv = uv * (.5 + 3.5 * u_shapeScale); - shape = .5 + .5 * sin(checksShape_uv.x) * cos(checksShape_uv.y); - shape += .48 * sign(proportion - .5) * pow(abs(proportion - .5), .5); - } else if (u_shape < 1.5) { - vec2 stripesShape_uv = uv * (2. * u_shapeScale); - float f = fract(stripesShape_uv.y); - shape = smoothstep(.0, .55, f) * (1.0 - smoothstep(.45, 1., f)); - shape += .48 * sign(proportion - .5) * pow(abs(proportion - .5), .5); + let proportion = clamp(u.u_proportion, 0.0, 1.0); + + var shape: f32 = 0.0; + if (u.u_shape < 0.5) { + let checksShape_uv = uv * (0.5 + 3.5 * u.u_shapeScale); + shape = 0.5 + 0.5 * sin(checksShape_uv.x) * cos(checksShape_uv.y); + shape += 0.48 * sign(proportion - 0.5) * pow(abs(proportion - 0.5), 0.5); + } else if (u.u_shape < 1.5) { + let stripesShape_uv = uv * (2.0 * u.u_shapeScale); + let f = fract(stripesShape_uv.y); + shape = smoothstep(0.0, 0.55, f) * (1.0 - smoothstep(0.45, 1.0, f)); + shape += 0.48 * sign(proportion - 0.5) * pow(abs(proportion - 0.5), 0.5); } else { - float shapeScaling = 5. * (1. - u_shapeScale); - float e0 = 0.45 - shapeScaling; - float e1 = 0.55 + shapeScaling; + let shapeScaling = 5.0 * (1.0 - u.u_shapeScale); + let e0 = 0.45 - shapeScaling; + let e1 = 0.55 + shapeScaling; shape = smoothstep(min(e0, e1), max(e0, e1), 1.0 - uv.y + 0.3 * (proportion - 0.5)); } - float mixer = shape * (u_colorsCount - 1.); - vec4 gradient = u_colors[0]; - gradient.rgb *= gradient.a; - float aa = fwidth(shape); - for (int i = 1; i < ${ warpMeta.maxColorCount }; i++) { - if (i >= int(u_colorsCount)) break; - float m = clamp(mixer - float(i - 1), 0.0, 1.0); - - float localMixerStart = floor(m); - float softness = .5 * u_softness + fwidth(m); - float smoothed = smoothstep(max(0., .5 - softness - aa), min(1., .5 + softness + aa), m - localMixerStart); - float stepped = localMixerStart + smoothed; - - m = mix(stepped, m, u_softness); - - vec4 c = u_colors[i]; - c.rgb *= c.a; - gradient = mix(gradient, c, m); + let mixer = shape * (u.u_colorsCount - 1.0); + var gradient = u.u_colors[0]; + gradient = vec4f(gradient.rgb * gradient.a, gradient.a); + let aa = fwidth(shape); + for (var i: i32 = 1; i < ${ warpMeta.maxColorCount }; i++) { + if (i < i32(u.u_colorsCount)) { + var m = clamp(mixer - f32(i - 1), 0.0, 1.0); + + let localMixerStart = floor(m); + let softness = 0.5 * u.u_softness + fwidth(m); + let smoothed = smoothstep(max(0.0, 0.5 - softness - aa), min(1.0, 0.5 + softness + aa), m - localMixerStart); + let stepped = localMixerStart + smoothed; + + m = mix(stepped, m, u.u_softness); + + var c = u.u_colors[i]; + c = vec4f(c.rgb * c.a, c.a); + gradient = mix(gradient, c, m); + } } - vec3 color = gradient.rgb; - float opacity = gradient.a; + var color = gradient.rgb; + let opacity = gradient.a; ${ colorBandingFix } - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/water.ts b/packages/shaders/src/shaders/water.ts index 822075db8..12aefd147 100644 --- a/packages/shaders/src/shaders/water.ts +++ b/packages/shaders/src/shaders/water.ts @@ -1,6 +1,6 @@ import type { ShaderMotionParams } from '../shader-mount.js'; import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI, rotation2, simplexNoise } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, glslMod, simplexNoise } from '../shader-utils.js'; /** * Water-like surface distortion with natural caustic realism. Works as an image filter or standalone animated texture. @@ -37,114 +37,117 @@ import { declarePI, rotation2, simplexNoise } from '../shader-utils.js'; * */ -// language=GLSL -export const waterFragmentShader: string = `#version 300 es -precision mediump float; - -uniform float u_time; - -uniform vec4 u_colorBack; -uniform vec4 u_colorHighlight; - -uniform sampler2D u_image; -uniform float u_imageAspectRatio; +export const waterFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorBack: vec4f, + u_colorHighlight: vec4f, + u_size: f32, + u_highlights: f32, + u_layering: f32, + u_edges: f32, + u_caustic: f32, + u_waves: f32, +} +@group(0) @binding(0) var u: Uniforms; -uniform float u_size; -uniform float u_highlights; -uniform float u_layering; -uniform float u_edges; -uniform float u_caustic; -uniform float u_waves; +${vertexOutputStruct} -in vec2 v_imageUV; +@group(1) @binding(0) var u_image_tex: texture_2d; +@group(1) @binding(1) var u_image_samp: sampler; -out vec4 fragColor; +${declarePI} +${rotation2} +${glslMod} +${simplexNoise} -${ declarePI } -${ rotation2 } -${ simplexNoise } +fn fwidth_f32(x: f32) -> f32 { + return abs(dpdx(x)) + abs(dpdy(x)); +} -float getUvFrame(vec2 uv) { - float aax = 2. * fwidth(uv.x); - float aay = 2. * fwidth(uv.y); +fn getUvFrame(uv: vec2f) -> f32 { + let aax = 2.0 * fwidth_f32(uv.x); + let aay = 2.0 * fwidth_f32(uv.y); - float left = smoothstep(0., aax, uv.x); - float right = 1.0 - smoothstep(1. - aax, 1., uv.x); - float bottom = smoothstep(0., aay, uv.y); - float top = 1.0 - smoothstep(1. - aay, 1., uv.y); + let left = smoothstep(0.0, aax, uv.x); + let right = 1.0 - smoothstep(1.0 - aax, 1.0, uv.x); + let bottom = smoothstep(0.0, aay, uv.y); + let top = 1.0 - smoothstep(1.0 - aay, 1.0, uv.y); return left * right * bottom * top; } -mat2 rotate2D(float r) { - return mat2(cos(r), sin(r), -sin(r), cos(r)); +fn rotate2D(r: f32) -> mat2x2f { + return mat2x2f(cos(r), sin(r), -sin(r), cos(r)); } -float getCausticNoise(vec2 uv, float t, float scale) { - vec2 n = vec2(.1); - vec2 N = vec2(.1); - mat2 m = rotate2D(.5); - for (int j = 0; j < 6; j++) { - uv *= m; - n *= m; - vec2 q = uv * scale + float(j) + n + (.5 + .5 * float(j)) * (mod(float(j), 2.) - 1.) * t; +fn getCausticNoise(uv_in: vec2f, t: f32, scale_in: f32) -> f32 { + var uv = uv_in; + var scale = scale_in; + var n = vec2f(0.1); + var N_val = vec2f(0.1); + let m = rotate2D(0.5); + for (var j: i32 = 0; j < 6; j++) { + uv = m * uv; + n = m * n; + let q = uv * scale + vec2f(f32(j)) + n + (0.5 + 0.5 * f32(j)) * (glsl_mod_f32(f32(j), 2.0) - 1.0) * t; n += sin(q); - N += cos(q) / scale; + N_val += cos(q) / scale; scale *= 1.1; } - return (N.x + N.y + 1.); + return (N_val.x + N_val.y + 1.0); } -void main() { - vec2 imageUV = v_imageUV; - vec2 patternUV = v_imageUV - .5; - patternUV = (patternUV * vec2(u_imageAspectRatio, 1.)); - patternUV /= (.01 + .09 * u_size); +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var imageUV = input.v_imageUV; + var patternUV = input.v_imageUV - vec2f(0.5); + patternUV = (patternUV * vec2f(u.u_imageAspectRatio, 1.0)); + patternUV /= (0.01 + 0.09 * u.u_size); - float t = u_time; + let t = u.u_time; - float wavesNoise = snoise((.3 + .1 * sin(t)) * .1 * patternUV + vec2(0., .4 * t)); + let wavesNoise = snoise((0.3 + 0.1 * sin(t)) * 0.1 * patternUV + vec2f(0.0, 0.4 * t)); - float causticNoise = getCausticNoise(patternUV + u_waves * vec2(1., -1.) * wavesNoise, 2. * t, 1.5); + var causticNoise = getCausticNoise(patternUV + u.u_waves * vec2f(1.0, -1.0) * wavesNoise, 2.0 * t, 1.5); - causticNoise += u_layering * getCausticNoise(patternUV + 2. * u_waves * vec2(1., -1.) * wavesNoise, 1.5 * t, 2.); + causticNoise += u.u_layering * getCausticNoise(patternUV + 2.0 * u.u_waves * vec2f(1.0, -1.0) * wavesNoise, 1.5 * t, 2.0); causticNoise = causticNoise * causticNoise; - float edgesDistortion = smoothstep(0., .1, imageUV.x); - edgesDistortion *= smoothstep(0., .1, imageUV.y); - edgesDistortion *= (smoothstep(1., 1.1, imageUV.x) + (1.0 - smoothstep(.8, .95, imageUV.x))); - edgesDistortion *= (1.0 - smoothstep(.9, 1., imageUV.y)); - edgesDistortion = mix(edgesDistortion, 1., u_edges); + var edgesDistortion = smoothstep(0.0, 0.1, imageUV.x); + edgesDistortion *= smoothstep(0.0, 0.1, imageUV.y); + edgesDistortion *= (smoothstep(1.0, 1.1, imageUV.x) + (1.0 - smoothstep(0.8, 0.95, imageUV.x))); + edgesDistortion *= (1.0 - smoothstep(0.9, 1.0, imageUV.y)); + edgesDistortion = mix(edgesDistortion, 1.0, u.u_edges); - float causticNoiseDistortion = .02 * causticNoise * edgesDistortion; + let causticNoiseDistortion = 0.02 * causticNoise * edgesDistortion; - float wavesDistortion = .1 * u_waves * wavesNoise; + let wavesDistortion = 0.1 * u.u_waves * wavesNoise; - imageUV += vec2(wavesDistortion, -wavesDistortion); - imageUV += (u_caustic * causticNoiseDistortion); + imageUV += vec2f(wavesDistortion, -wavesDistortion); + imageUV += (u.u_caustic * causticNoiseDistortion); - float frame = getUvFrame(imageUV); + let frame = getUvFrame(imageUV); - vec4 image = texture(u_image, imageUV); - vec4 backColor = u_colorBack; - backColor.rgb *= backColor.a; + let image = textureSampleLevel(u_image_tex, u_image_samp, imageUV, 0.0); + var backColor = u.u_colorBack; + backColor = vec4f(backColor.rgb * backColor.a, backColor.a); - vec3 color = mix(backColor.rgb, image.rgb, image.a * frame); - float opacity = backColor.a + image.a * frame; + var color = mix(backColor.rgb, image.rgb, image.a * frame); + var opacity = backColor.a + image.a * frame; - causticNoise = max(-.2, causticNoise); + causticNoise = max(-0.2, causticNoise); - float hightlight = .025 * u_highlights * causticNoise; - hightlight *= u_colorHighlight.a; - color = mix(color, u_colorHighlight.rgb, .05 * u_highlights * causticNoise); + var hightlight = 0.025 * u.u_highlights * causticNoise; + hightlight *= u.u_colorHighlight.a; + color = mix(color, u.u_colorHighlight.rgb, 0.05 * u.u_highlights * causticNoise); opacity += hightlight; - color += hightlight * (.5 + .5 * wavesNoise); - opacity += hightlight * (.5 + .5 * wavesNoise); + color += vec3f(hightlight * (0.5 + 0.5 * wavesNoise)); + opacity += hightlight * (0.5 + 0.5 * wavesNoise); - opacity = clamp(opacity, 0., 1.); + opacity = clamp(opacity, 0.0, 1.0); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/shaders/waves.ts b/packages/shaders/src/shaders/waves.ts index 7ab1e4808..59a27bc33 100644 --- a/packages/shaders/src/shaders/waves.ts +++ b/packages/shaders/src/shaders/waves.ts @@ -1,5 +1,5 @@ import { type ShaderSizingParams, type ShaderSizingUniforms } from '../shader-sizing.js'; -import { declarePI } from '../shader-utils.js'; +import { systemUniformFields, vertexOutputStruct, declarePI } from '../shader-utils.js'; /** * Static line pattern configurable into textures ranging from sharp zigzags to smooth flowing waves. @@ -32,60 +32,59 @@ import { declarePI } from '../shader-utils.js'; * */ -// language=GLSL -export const wavesFragmentShader: string = `#version 300 es -precision mediump float; - -uniform vec4 u_colorFront; -uniform vec4 u_colorBack; -uniform float u_shape; -uniform float u_frequency; -uniform float u_amplitude; -uniform float u_spacing; -uniform float u_proportion; -uniform float u_softness; - -in vec2 v_patternUV; +export const wavesFragmentShader: string = ` +struct Uniforms { + ${systemUniformFields} + u_colorFront: vec4f, + u_colorBack: vec4f, + u_shape: f32, + u_frequency: f32, + u_amplitude: f32, + u_spacing: f32, + u_proportion: f32, + u_softness: f32, +} +@group(0) @binding(0) var u: Uniforms; -out vec4 fragColor; +${vertexOutputStruct} -${ declarePI } +${declarePI} -void main() { - vec2 shape_uv = v_patternUV; - shape_uv *= 4.; +@fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { + var shape_uv = input.v_patternUV; + shape_uv *= 4.0; - float wave = .5 * cos(shape_uv.x * u_frequency * TWO_PI); - float zigzag = 2. * abs(fract(shape_uv.x * u_frequency) - .5); - float irregular = sin(shape_uv.x * .25 * u_frequency * TWO_PI) * cos(shape_uv.x * u_frequency * TWO_PI); - float irregular2 = .75 * (sin(shape_uv.x * u_frequency * TWO_PI) + .5 * cos(shape_uv.x * .5 * u_frequency * TWO_PI)); + let wave = 0.5 * cos(shape_uv.x * u.u_frequency * TWO_PI); + let zigzag = 2.0 * abs(fract(shape_uv.x * u.u_frequency) - 0.5); + let irregular = sin(shape_uv.x * 0.25 * u.u_frequency * TWO_PI) * cos(shape_uv.x * u.u_frequency * TWO_PI); + let irregular2 = 0.75 * (sin(shape_uv.x * u.u_frequency * TWO_PI) + 0.5 * cos(shape_uv.x * 0.5 * u.u_frequency * TWO_PI)); - float offset = mix(zigzag, wave, smoothstep(0., 1., u_shape)); - offset = mix(offset, irregular, smoothstep(1., 2., u_shape)); - offset = mix(offset, irregular2, smoothstep(2., 3., u_shape)); - offset *= 2. * u_amplitude; + var offset = mix(zigzag, wave, smoothstep(0.0, 1.0, u.u_shape)); + offset = mix(offset, irregular, smoothstep(1.0, 2.0, u.u_shape)); + offset = mix(offset, irregular2, smoothstep(2.0, 3.0, u.u_shape)); + offset *= 2.0 * u.u_amplitude; - float spacing = (.001 + u_spacing); - float shape = .5 + .5 * sin((shape_uv.y + offset) * PI / spacing); + let spacing = (0.001 + u.u_spacing); + let shape = 0.5 + 0.5 * sin((shape_uv.y + offset) * PI / spacing); - float aa = .0001 + fwidth(shape); - float dc = 1. - clamp(u_proportion, 0., 1.); - float e0 = dc - u_softness - aa; - float e1 = dc + u_softness + aa; - float res = smoothstep(min(e0, e1), max(e0, e1), shape); + let aa = 0.0001 + fwidth(shape); + let dc = 1.0 - clamp(u.u_proportion, 0.0, 1.0); + let e0 = dc - u.u_softness - aa; + let e1 = dc + u.u_softness + aa; + let res = smoothstep(min(e0, e1), max(e0, e1), shape); - vec3 fgColor = u_colorFront.rgb * u_colorFront.a; - float fgOpacity = u_colorFront.a; - vec3 bgColor = u_colorBack.rgb * u_colorBack.a; - float bgOpacity = u_colorBack.a; + let fgColor = u.u_colorFront.rgb * u.u_colorFront.a; + let fgOpacity = u.u_colorFront.a; + let bgColor = u.u_colorBack.rgb * u.u_colorBack.a; + let bgOpacity = u.u_colorBack.a; - vec3 color = fgColor * res; - float opacity = fgOpacity * res; + var color = fgColor * res; + var opacity = fgOpacity * res; - color += bgColor * (1. - opacity); - opacity += bgOpacity * (1. - opacity); + color += bgColor * (1.0 - opacity); + opacity += bgOpacity * (1.0 - opacity); - fragColor = vec4(color, opacity); + return vec4f(color, opacity); } `; diff --git a/packages/shaders/src/vertex-shader.ts b/packages/shaders/src/vertex-shader.ts index 584a919af..f6ce058f7 100644 --- a/packages/shaders/src/vertex-shader.ts +++ b/packages/shaders/src/vertex-shader.ts @@ -1,153 +1,121 @@ -/** Vertex shader for the shader mount */ -// language=GLSL -export const vertexShaderSource = `#version 300 es -precision mediump float; - -layout(location = 0) in vec4 a_position; - -uniform vec2 u_resolution; -uniform float u_pixelRatio; -uniform float u_imageAspectRatio; -uniform float u_originX; -uniform float u_originY; -uniform float u_worldWidth; -uniform float u_worldHeight; -uniform float u_fit; -uniform float u_scale; -uniform float u_rotation; -uniform float u_offsetX; -uniform float u_offsetY; - -out vec2 v_objectUV; -out vec2 v_objectBoxSize; -out vec2 v_responsiveUV; -out vec2 v_responsiveBoxGivenSize; -out vec2 v_patternUV; -out vec2 v_patternBoxSize; -out vec2 v_imageUV; - -vec3 getBoxSize(float boxRatio, vec2 givenBoxSize) { - vec2 box = vec2(0.); - // fit = none +/** Vertex shader for the shader mount (WGSL) — appended after the fragment shader into one module */ +export const vertexShaderSource = ` +fn vs_getBoxSize(boxRatio: f32, givenBoxSize: vec2f) -> vec3f { + var box = vec2f(0.0); box.x = boxRatio * min(givenBoxSize.x / boxRatio, givenBoxSize.y); - float noFitBoxWidth = box.x; - if (u_fit == 1.) { // fit = contain - box.x = boxRatio * min(u_resolution.x / boxRatio, u_resolution.y); - } else if (u_fit == 2.) { // fit = cover - box.x = boxRatio * max(u_resolution.x / boxRatio, u_resolution.y); + let noFitBoxWidth = box.x; + if (u.u_fit == 1.0) { + box.x = boxRatio * min(u.u_resolution.x / boxRatio, u.u_resolution.y); + } else if (u.u_fit == 2.0) { + box.x = boxRatio * max(u.u_resolution.x / boxRatio, u.u_resolution.y); } box.y = box.x / boxRatio; - return vec3(box, noFitBoxWidth); + return vec3f(box, noFitBoxWidth); } -void main() { - gl_Position = a_position; - - vec2 uv = gl_Position.xy * .5; - vec2 boxOrigin = vec2(.5 - u_originX, u_originY - .5); - vec2 givenBoxSize = vec2(u_worldWidth, u_worldHeight); - givenBoxSize = max(givenBoxSize, vec2(1.)) * u_pixelRatio; - float r = u_rotation * 3.14159265358979323846 / 180.; - mat2 graphicRotation = mat2(cos(r), sin(r), -sin(r), cos(r)); - vec2 graphicOffset = vec2(-u_offsetX, u_offsetY); - - - // =================================================== - - float fixedRatio = 1.; - vec2 fixedRatioBoxGivenSize = vec2( - (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x, - (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y +@vertex fn vs_main(@location(0) a_position: vec2f) -> VertexOutput { + var output: VertexOutput; + output.position = vec4f(a_position, 0.0, 1.0); + + let uv = a_position * 0.5; + let boxOrigin = vec2f(0.5 - u.u_originX, u.u_originY - 0.5); + var givenBoxSize = vec2f(u.u_worldWidth, u.u_worldHeight); + givenBoxSize = max(givenBoxSize, vec2f(1.0)) * u.u_pixelRatio; + let r = u.u_rotation * 3.14159265358979323846 / 180.0; + let graphicRotation = mat2x2f(cos(r), sin(r), -sin(r), cos(r)); + let graphicOffset = vec2f(-u.u_offsetX, u.u_offsetY); + + // Object UV + let fixedRatio: f32 = 1.0; + let fixedRatioBoxGivenSize = vec2f( + select(givenBoxSize.x, u.u_resolution.x, u.u_worldWidth == 0.0), + select(givenBoxSize.y, u.u_resolution.y, u.u_worldHeight == 0.0) ); - v_objectBoxSize = getBoxSize(fixedRatio, fixedRatioBoxGivenSize).xy; - vec2 objectWorldScale = u_resolution.xy / v_objectBoxSize; - - v_objectUV = uv; - v_objectUV *= objectWorldScale; - v_objectUV += boxOrigin * (objectWorldScale - 1.); - v_objectUV += graphicOffset; - v_objectUV /= u_scale; - v_objectUV = graphicRotation * v_objectUV; - - // =================================================== - - v_responsiveBoxGivenSize = vec2( - (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x, - (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y + output.v_objectBoxSize = vs_getBoxSize(fixedRatio, fixedRatioBoxGivenSize).xy; + let objectWorldScale = u.u_resolution.xy / output.v_objectBoxSize; + + var objectUV = uv; + objectUV *= objectWorldScale; + objectUV += boxOrigin * (objectWorldScale - vec2f(1.0)); + objectUV += graphicOffset; + objectUV /= u.u_scale; + objectUV = graphicRotation * objectUV; + output.v_objectUV = objectUV; + + // Responsive UV + let responsiveBoxGivenSize = vec2f( + select(givenBoxSize.x, u.u_resolution.x, u.u_worldWidth == 0.0), + select(givenBoxSize.y, u.u_resolution.y, u.u_worldHeight == 0.0) ); - float responsiveRatio = v_responsiveBoxGivenSize.x / v_responsiveBoxGivenSize.y; - vec2 responsiveBoxSize = getBoxSize(responsiveRatio, v_responsiveBoxGivenSize).xy; - vec2 responsiveBoxScale = u_resolution.xy / responsiveBoxSize; - - #ifdef ADD_HELPERS - v_responsiveHelperBox = uv; - v_responsiveHelperBox *= responsiveBoxScale; - v_responsiveHelperBox += boxOrigin * (responsiveBoxScale - 1.); - #endif - - v_responsiveUV = uv; - v_responsiveUV *= responsiveBoxScale; - v_responsiveUV += boxOrigin * (responsiveBoxScale - 1.); - v_responsiveUV += graphicOffset; - v_responsiveUV /= u_scale; - v_responsiveUV.x *= responsiveRatio; - v_responsiveUV = graphicRotation * v_responsiveUV; - v_responsiveUV.x /= responsiveRatio; - - // =================================================== - - float patternBoxRatio = givenBoxSize.x / givenBoxSize.y; - vec2 patternBoxGivenSize = vec2( - (u_worldWidth == 0.) ? u_resolution.x : givenBoxSize.x, - (u_worldHeight == 0.) ? u_resolution.y : givenBoxSize.y + output.v_responsiveBoxGivenSize = responsiveBoxGivenSize; + let responsiveRatio = responsiveBoxGivenSize.x / responsiveBoxGivenSize.y; + let responsiveBoxSize = vs_getBoxSize(responsiveRatio, responsiveBoxGivenSize).xy; + let responsiveBoxScale = u.u_resolution.xy / responsiveBoxSize; + + var responsiveUV = uv; + responsiveUV *= responsiveBoxScale; + responsiveUV += boxOrigin * (responsiveBoxScale - vec2f(1.0)); + responsiveUV += graphicOffset; + responsiveUV /= u.u_scale; + responsiveUV.x *= responsiveRatio; + responsiveUV = graphicRotation * responsiveUV; + responsiveUV.x /= responsiveRatio; + output.v_responsiveUV = responsiveUV; + + // Pattern UV + let patternBoxGivenSize = vec2f( + select(givenBoxSize.x, u.u_resolution.x, u.u_worldWidth == 0.0), + select(givenBoxSize.y, u.u_resolution.y, u.u_worldHeight == 0.0) ); - patternBoxRatio = patternBoxGivenSize.x / patternBoxGivenSize.y; - - vec3 boxSizeData = getBoxSize(patternBoxRatio, patternBoxGivenSize); - v_patternBoxSize = boxSizeData.xy; - float patternBoxNoFitBoxWidth = boxSizeData.z; - vec2 patternBoxScale = u_resolution.xy / v_patternBoxSize; - - v_patternUV = uv; - v_patternUV += graphicOffset / patternBoxScale; - v_patternUV += boxOrigin; - v_patternUV -= boxOrigin / patternBoxScale; - v_patternUV *= u_resolution.xy; - v_patternUV /= u_pixelRatio; - if (u_fit > 0.) { - v_patternUV *= (patternBoxNoFitBoxWidth / v_patternBoxSize.x); + let patternBoxRatio = patternBoxGivenSize.x / patternBoxGivenSize.y; + + let boxSizeData = vs_getBoxSize(patternBoxRatio, patternBoxGivenSize); + output.v_patternBoxSize = boxSizeData.xy; + let patternBoxNoFitBoxWidth = boxSizeData.z; + let patternBoxScale = u.u_resolution.xy / output.v_patternBoxSize; + + var patternUV = uv; + patternUV += graphicOffset / patternBoxScale; + patternUV += boxOrigin; + patternUV -= boxOrigin / patternBoxScale; + patternUV *= u.u_resolution.xy; + patternUV /= u.u_pixelRatio; + if (u.u_fit > 0.0) { + patternUV *= (patternBoxNoFitBoxWidth / output.v_patternBoxSize.x); } - v_patternUV /= u_scale; - v_patternUV = graphicRotation * v_patternUV; - v_patternUV += boxOrigin / patternBoxScale; - v_patternUV -= boxOrigin; - // x100 is a default multiplier between vertex and fragmant shaders - // we use it to avoid UV presision issues - v_patternUV *= .01; - - // =================================================== - - vec2 imageBoxSize; - if (u_fit == 1.) { // contain - imageBoxSize.x = min(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio; - } else if (u_fit == 2.) { // cover - imageBoxSize.x = max(u_resolution.x / u_imageAspectRatio, u_resolution.y) * u_imageAspectRatio; + patternUV /= u.u_scale; + patternUV = graphicRotation * patternUV; + patternUV += boxOrigin / patternBoxScale; + patternUV -= boxOrigin; + patternUV *= 0.01; + output.v_patternUV = patternUV; + + // Image UV + var imageBoxSize: vec2f; + if (u.u_fit == 1.0) { + imageBoxSize.x = min(u.u_resolution.x / u.u_imageAspectRatio, u.u_resolution.y) * u.u_imageAspectRatio; + } else if (u.u_fit == 2.0) { + imageBoxSize.x = max(u.u_resolution.x / u.u_imageAspectRatio, u.u_resolution.y) * u.u_imageAspectRatio; } else { - imageBoxSize.x = min(10.0, 10.0 / u_imageAspectRatio * u_imageAspectRatio); + imageBoxSize.x = min(10.0, 10.0 / u.u_imageAspectRatio * u.u_imageAspectRatio); } - imageBoxSize.y = imageBoxSize.x / u_imageAspectRatio; - vec2 imageBoxScale = u_resolution.xy / imageBoxSize; - - v_imageUV = uv; - v_imageUV *= imageBoxScale; - v_imageUV += boxOrigin * (imageBoxScale - 1.); - v_imageUV += graphicOffset; - v_imageUV /= u_scale; - v_imageUV.x *= u_imageAspectRatio; - v_imageUV = graphicRotation * v_imageUV; - v_imageUV.x /= u_imageAspectRatio; - - v_imageUV += .5; - v_imageUV.y = 1. - v_imageUV.y; -}`; + imageBoxSize.y = imageBoxSize.x / u.u_imageAspectRatio; + let imageBoxScale = u.u_resolution.xy / imageBoxSize; + + var imageUV = uv; + imageUV *= imageBoxScale; + imageUV += boxOrigin * (imageBoxScale - vec2f(1.0)); + imageUV += graphicOffset; + imageUV /= u.u_scale; + imageUV.x *= u.u_imageAspectRatio; + imageUV = graphicRotation * imageUV; + imageUV.x /= u.u_imageAspectRatio; + + imageUV += vec2f(0.5); + imageUV.y = 1.0 - imageUV.y; + output.v_imageUV = imageUV; + + return output; +} +`; diff --git a/packages/shaders/tsconfig.json b/packages/shaders/tsconfig.json index 34976bce6..b728efc21 100644 --- a/packages/shaders/tsconfig.json +++ b/packages/shaders/tsconfig.json @@ -2,6 +2,7 @@ "compilerOptions": { // Enable latest features "lib": ["ESNext", "DOM", "DOM.Iterable"], + "types": ["@webgpu/types"], "target": "ESNext", "declaration": true, "outDir": "./dist", From 64850b0980addfbb483697abd56eb209e0353468 Mon Sep 17 00:00:00 2001 From: Ksenia Kondrashova Date: Tue, 21 Apr 2026 19:32:42 +0400 Subject: [PATCH 2/5] revert temporary changes --- docs/src/app/home-thumbnails.ts | 31 +----- docs/src/app/page.tsx | 2 +- docs/src/app/test/page.tsx | 143 ++----------------------- package.json | 2 +- packages/shaders/src/shader-mount.ts | 149 ++++++++++++++++++++++++++- 5 files changed, 160 insertions(+), 167 deletions(-) diff --git a/docs/src/app/home-thumbnails.ts b/docs/src/app/home-thumbnails.ts index 62eb49cc6..e8c94e9ce 100644 --- a/docs/src/app/home-thumbnails.ts +++ b/docs/src/app/home-thumbnails.ts @@ -119,7 +119,6 @@ export const homeThumbnails = [ ShaderComponent: PaperTexture, image: paperTextureImg, shaderConfig: { ...paperTexturePresets[0].params, scale: 1.05 }, - alwaysLivePreview: false, }, { name: 'fluted glass', @@ -127,7 +126,6 @@ export const homeThumbnails = [ ShaderComponent: FlutedGlass, image: flutedGlassImg, shaderConfig: { ...flutedGlassPresets[0].params, scale: 1.05, distortion: 0.2 }, - alwaysLivePreview: false, }, { name: 'water', @@ -140,7 +138,6 @@ export const homeThumbnails = [ colorBack: '#e0f2ff', image: '/images/image-filters/0018.webp', }, - alwaysLivePreview: false, }, { name: 'image dithering', @@ -148,7 +145,6 @@ export const homeThumbnails = [ ShaderComponent: ImageDithering, image: imageDitheringImg, shaderConfig: { ...imageDitheringPresets[0].params, scale: 1.05 }, - alwaysLivePreview: false, }, { name: 'halftone dots', @@ -160,7 +156,6 @@ export const homeThumbnails = [ image: '/images/image-filters/0018.webp', speed: 0, }, - alwaysLivePreview: false, }, { name: 'halftone cmyk', @@ -174,7 +169,6 @@ export const homeThumbnails = [ softness: 0.5, speed: 0, }, - alwaysLivePreview: false, }, ], }, @@ -193,7 +187,6 @@ export const homeThumbnails = [ suspendWhenProcessingImage: true, image: '/images/logos/diamond.svg', }, - alwaysLivePreview: false, }, { name: 'liquid metal', @@ -206,7 +199,6 @@ export const homeThumbnails = [ suspendWhenProcessingImage: true, image: '/images/logos/diamond.svg', }, - alwaysLivePreview: false, }, { name: 'gem smoke', @@ -222,7 +214,6 @@ export const homeThumbnails = [ suspendWhenProcessingImage: true, image: '/images/logos/diamond.svg', }, - alwaysLivePreview: false, }, ], }, @@ -235,7 +226,6 @@ export const homeThumbnails = [ url: '/mesh-gradient', ShaderComponent: MeshGradient, shaderConfig: { ...meshGradientPresets[0].params }, - alwaysLivePreview: false, }, { name: 'static mesh gradient', @@ -243,7 +233,6 @@ export const homeThumbnails = [ ShaderComponent: StaticMeshGradient, image: staticMeshGradientImg, shaderConfig: { ...staticMeshGradientPresets[0].params }, - alwaysLivePreview: false, }, { name: 'static radial gradient', @@ -251,7 +240,6 @@ export const homeThumbnails = [ ShaderComponent: StaticRadialGradient, image: staticRadialGradientImg, shaderConfig: { ...staticRadialGradientPresets[0].params, radius: 0.65, offsetY: -0.03 }, - alwaysLivePreview: false, }, { name: 'dithering', @@ -262,7 +250,7 @@ export const homeThumbnails = [ shaderConfig: { ...ditheringPresets[0].params }, // This shader doesn't render consistently when // thumbnails are resized, so we live render it - alwaysLivePreview: false, + alwaysLivePreview: true, }, { name: 'grain gradient', @@ -272,7 +260,7 @@ export const homeThumbnails = [ shaderConfig: { ...grainGradientPresets[0].params, speed: 1.5 }, // This shader thumbnails look nowhere near as crisp // when resized, so we live render it to maintain the quality - alwaysLivePreview: false, + alwaysLivePreview: true, }, { name: 'dot orbit', @@ -280,7 +268,6 @@ export const homeThumbnails = [ url: '/dot-orbit', ShaderComponent: DotOrbit, shaderConfig: { ...dotOrbitPresets[0].params, scale: 0.45 }, - alwaysLivePreview: false, }, { name: 'dot grid', @@ -288,7 +275,6 @@ export const homeThumbnails = [ ShaderComponent: DotGrid, image: dotGridImg, shaderConfig: { ...dotGridPresets[0].params, gapX: 24, gapY: 24, size: 1.5, speed: 0 }, - alwaysLivePreview: false, }, { name: 'warp', @@ -296,7 +282,6 @@ export const homeThumbnails = [ ShaderComponent: Warp, image: warpImg, shaderConfig: { ...warpPresets[0].params, scale: 0.6 }, - alwaysLivePreview: false, }, { name: 'spiral', @@ -304,7 +289,6 @@ export const homeThumbnails = [ ShaderComponent: Spiral, image: spiralImg, shaderConfig: { ...spiralPresets[0].params, scale: 0.5, speed: 2 }, - alwaysLivePreview: false, }, { name: 'swirl', @@ -312,7 +296,6 @@ export const homeThumbnails = [ ShaderComponent: Swirl, image: swirlImg, shaderConfig: { ...swirlPresets[0].params }, - alwaysLivePreview: false, }, { name: 'waves', @@ -320,7 +303,6 @@ export const homeThumbnails = [ ShaderComponent: Waves, image: wavesImg, shaderConfig: { ...wavesPresets[0].params, speed: 0, scale: 0.55, spacing: 1.2 }, - alwaysLivePreview: false, }, { name: 'neuro noise', @@ -328,7 +310,6 @@ export const homeThumbnails = [ url: '/neuro-noise', ShaderComponent: NeuroNoise, shaderConfig: { ...neuroNoisePresets[0].params, scale: 0.8 }, - alwaysLivePreview: false, }, { name: 'perlin', @@ -336,7 +317,6 @@ export const homeThumbnails = [ ShaderComponent: PerlinNoise, image: perlinNoiseImg, shaderConfig: { ...perlinNoisePresets[0].params, scale: 0.8, speed: 0.2 }, - alwaysLivePreview: false, }, { name: 'simplex noise', @@ -347,7 +327,6 @@ export const homeThumbnails = [ ...simplexNoisePresets[0].params, scale: 0.4, }, - alwaysLivePreview: false, }, { name: 'voronoi', @@ -355,7 +334,6 @@ export const homeThumbnails = [ ShaderComponent: Voronoi, image: voronoiImg, shaderConfig: { ...voronoiPresets[0].params, scale: 0.35 }, - alwaysLivePreview: false, }, { name: 'pulsing border', @@ -363,7 +341,6 @@ export const homeThumbnails = [ ShaderComponent: PulsingBorder, image: pulsingBorderImg, shaderConfig: { ...pulsingBorderPresets[0].params }, - alwaysLivePreview: false, }, { name: 'metaballs', @@ -371,7 +348,6 @@ export const homeThumbnails = [ url: '/metaballs', ShaderComponent: Metaballs, shaderConfig: { ...metaballsPresets[0].params, scale: 1, count: 8, speed: 1.5 }, - alwaysLivePreview: false, }, { name: 'color panels', @@ -379,7 +355,6 @@ export const homeThumbnails = [ ShaderComponent: ColorPanels, image: colorPanelsImg, shaderConfig: { ...colorPanelsPresets[0].params, scale: 0.75, speed: 2 }, - alwaysLivePreview: false, }, { name: 'smoke ring', @@ -387,7 +362,6 @@ export const homeThumbnails = [ url: '/smoke-ring', ShaderComponent: SmokeRing, shaderConfig: { ...smokeRingPresets[0].params, scale: 0.8, speed: 1 }, - alwaysLivePreview: false, }, { name: 'god rays', @@ -395,7 +369,6 @@ export const homeThumbnails = [ ShaderComponent: GodRays, image: godRaysImg, shaderConfig: { ...godRaysPresets[0].params, offsetY: -0.7, speed: 1.25 }, - alwaysLivePreview: false, }, ], }, diff --git a/docs/src/app/page.tsx b/docs/src/app/page.tsx index 482dd8048..b950df4fc 100644 --- a/docs/src/app/page.tsx +++ b/docs/src/app/page.tsx @@ -117,7 +117,7 @@ function ShaderItem({ /> )} - {shaderVisibility !== 'hidden' && ( + {shaderVisibility !== 'hidden' && shaderConfig.speed !== 0 && ( -
{children}
-
- {name} -
-
- ); -} +import { PaperTexture } from '@paper-design/shaders-react'; export default function TestPage() { - return ( -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- ); + return ( +
+ + + +
+ ); } diff --git a/package.json b/package.json index 2d3e43818..74dc8c364 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,9 @@ "@types/bun": "^1.1.11", "@webgpu/types": "^0.1.69", "esbuild": "^0.24.0", - "glob": "^11.0.1", "nodemon": "^3.1.7", "npm-run-all": "^4.1.5", + "glob": "^11.0.1", "prettier": "^3.4.2", "prettier-plugin-tailwindcss": "^0.6.14", "rimraf": "^6.0.1", diff --git a/packages/shaders/src/shader-mount.ts b/packages/shaders/src/shader-mount.ts index 23b3ad72f..092aeeb84 100644 --- a/packages/shaders/src/shader-mount.ts +++ b/packages/shaders/src/shader-mount.ts @@ -27,15 +27,25 @@ export class ShaderMount { private gpuSamplers: Map = new Map(); private textureUnitMap: Map = new Map(); + /** The fragment shader that we are using */ private fragmentShader: string; + /** Stores the RAF for the render loop */ private rafId: number | null = null; + /** Time of the last rendered frame */ private lastRenderTime = 0; + /** Total time that we have played any animation, passed as a uniform to the shader for time-based VFX */ private currentFrame = 0; + /** The speed that we progress through animation time (multiplies by delta time every update). Allows negatives to play in reverse. If set to 0, rAF will stop entirely so static shaders have no recurring performance costs */ private speed = 0; + /** Actual speed used that accounts for document visibility (we pause the shader if the tab is hidden) */ private currentSpeed = 0; + /** Uniforms that are provided by the user for the specific shader being mounted (not including uniforms that this Mount adds, like time and resolution) */ private providedUniforms: ShaderMountUniforms; + /** Names of the uniforms that should have mipmaps generated for them */ private mipmaps: string[] = []; + /** Just a sanity check to make sure frames don't run after we're disposed */ private hasBeenDisposed = false; + /** If the resolution of the canvas has changed since the last render */ private resolutionChanged = true; private minPixelRatio; private maxPixelCount; @@ -45,17 +55,33 @@ export class ShaderMount { private isReady = false; constructor( + /** The div you'd like to mount the shader to. The shader will match its size. */ parentElement: HTMLElement, fragmentShader: string, uniforms: ShaderMountUniforms, /** @deprecated WebGL context attributes param is ignored in the WebGPU renderer. Kept for API compatibility. */ _contextAttributes?: WebGLContextAttributes, + /** The speed of the animation, or 0 to stop it. Supports negative values to play in reverse. */ speed = 0, + /** Pass a frame to offset the starting u_time value and give deterministic results*/ frame = 0, + /** + * The minimum pixel ratio to render at, defaults to 2. + * May be reduced to improve performance or increased together with `maxPixelCount` to improve antialiasing. + */ minPixelRatio = 2, + /** + * The maximum amount of physical device pixels to render for the shader, + * by default it's 1920 * 1080 * 2x dpi (per each side) = 8,294,400 pixels of a 4K screen. + * Actual DOM size of the canvas can be larger, it will just lose quality after this. + * + * May be reduced to improve performance or increased to improve quality on high-resolution screens. + */ maxPixelCount: number = DEFAULT_MAX_PIXEL_COUNT, + /** Names of the uniforms that should have mipmaps generated for them */ mipmaps: string[] = [] ) { + // nodeType check instead of `instanceof` to work across document boundaries (iframes, PiP windows) if (parentElement?.nodeType === 1) { this.parentElement = parentElement as PaperShaderElement; } else { @@ -71,12 +97,14 @@ export class ShaderMount { this.ownerDocument.head.prepend(styleElement); } + // Create the canvas element and mount it into the provided element const canvasElement = this.ownerDocument.createElement('canvas'); this.canvasElement = canvasElement; this.parentElement.prepend(canvasElement); this.fragmentShader = fragmentShader; this.providedUniforms = uniforms; this.mipmaps = mipmaps; + // Base our starting animation time on the provided frame value this.currentFrame = frame; this.minPixelRatio = minPixelRatio; this.maxPixelCount = maxPixelCount; @@ -142,17 +170,24 @@ export class ShaderMount { this.createBindGroups(); // Clear cache so all values are re-applied to the GPU buffer this.uniformCache = {}; + // Put the user provided values into the uniforms this.setUniformValues(this.providedUniforms); this.isReady = true; + // Set up the resize observer to handle window resizing and set u_resolution this.setupResizeObserver(); + // Set up the visual viewport change listener to handle zoom changes (pinch zoom and classic browser zoom) visualViewport?.addEventListener('resize', this.handleVisualViewportChange); + // Listen for document visibility changes to pause the shader when the tab is hidden this.ownerDocument.addEventListener('visibilitychange', this.handleDocumentVisibilityChange); + // Set the animation speed after everything is ready to go this.setSpeed(this.speed); + // Mark parent element as paper shader mount this.parentElement.setAttribute('data-paper-shader', ''); + // Add the shaderMount instance to the div mount element to make it easily accessible this.parentElement.paperShaderMount = this; } @@ -275,6 +310,12 @@ export class ShaderMount { } } + /** + * The scale that we should render at. + * - Used to target 2x rendering even on 1x screens for better antialiasing + * - Prevents the virtual resolution from going beyond the maximum resolution + * - Accounts for the page zoom level so we render in physical device pixels rather than CSS pixels + */ private renderScale = 1; private parentWidth = 0; private parentHeight = 0; @@ -304,34 +345,62 @@ export class ShaderMount { this.resizeObserver.observe(this.parentElement); }; + // Visual viewport resize handler, mainly used to react to browser zoom changes. + // Resize observer by itself does not react to pinch zoom, and although it usually + // reacts to classic browser zoom, it's not guaranteed in edge cases. + // Since timing between visual viewport changes and resize observer is complex + // and because we'd like to know the device pixel sizes of elements, we just restart + // the observer to get a guaranteed fresh callback regardless if it would have triggered or not. private handleVisualViewportChange = () => { this.resizeObserver?.disconnect(); this.setupResizeObserver(); + + // In case of debugging timing, from here on: + // - animation frame 1: a paint after the visual viewport resize + // - animation frame 2: a paint after the resize observer has been handled, if it was ever triggered }; + /** Resize handler for when the container div changes size or the max pixel count changes and we want to resize our canvas to match */ private handleResize = () => { + // Aim to render at least as many pixels as physically displayed + // This will overshoot when the user zooms out, but that's acceptable + let targetPixelWidth = 0; let targetPixelHeight = 0; + // If window.devicePixelRatio is below 1, it's safe to say the browser is just zoomed out + // We can use 1 as the minimum value not to upscale it needlessly to meet the min pixel ratio param const dpr = Math.max(1, window.devicePixelRatio); const pinchZoom = visualViewport?.scale ?? 1; if (this.devicePixelsSupported) { + // Use the real pixel size if we know it, plus meet the min pixel ratio requirement and add in pinch zoom const scaleToMeetMinPixelRatio = Math.max(1, this.minPixelRatio / dpr); targetPixelWidth = this.parentDevicePixelWidth * scaleToMeetMinPixelRatio * pinchZoom; targetPixelHeight = this.parentDevicePixelHeight * scaleToMeetMinPixelRatio * pinchZoom; } else { + // Otherwise try to approximate the element size in device pixels using devicePixelRatio. + // (devicePixelRatio is imprecise and element's width/height may be fractional CSS sizes, not real pixels). let targetRenderScale = Math.max(dpr, this.minPixelRatio) * pinchZoom; if (this.isSafari) { + // As of 2025, Safari reports physical devicePixelRatio, but other browsers add the current zoom level: + // https://bugs.webkit.org/show_bug.cgi?id=124862 + // + // In Safari we need to factor in the zoom level manually in order to set the target resolution. + // To avoid sidebars upscaling the target resolution, set a minimum zoom level of 1. + // This will render at higher resolution when zoomed out, but that's fine. + // (We mostly care about maintaining good quality when zoomed in). const zoomLevel = bestGuessBrowserZoom(this.ownerDocument); targetRenderScale *= Math.max(1, zoomLevel); } + // Rounding the client width/height since they may be fractional in CSS layout values targetPixelWidth = Math.round(this.parentWidth) * targetRenderScale; targetPixelHeight = Math.round(this.parentHeight) * targetRenderScale; } + // Prevent the total rendered pixel count from exceeding maxPixelCount const maxPixelCountHeadroom = Math.sqrt(this.maxPixelCount) / Math.sqrt(targetPixelWidth * targetPixelHeight); const scaleToMeetMaxPixelCount = Math.min(1, maxPixelCountHeadroom); const newWidth = Math.round(targetPixelWidth * scaleToMeetMaxPixelCount); @@ -341,13 +410,14 @@ export class ShaderMount { if ( this.canvasElement.width !== newWidth || this.canvasElement.height !== newHeight || - this.renderScale !== newRenderScale + this.renderScale !== newRenderScale // Usually, only render scale changes when the user zooms in/out ) { this.renderScale = newRenderScale; this.canvasElement.width = newWidth; this.canvasElement.height = newHeight; this.resolutionChanged = true; + // this is necessary to avoid flashes while resizing (the next scheduled render will set uniforms) this.render(performance.now()); } }; @@ -362,14 +432,17 @@ export class ShaderMount { if (this.canvasElement.width === 0 || this.canvasElement.height === 0) return; + // Calculate the delta time const dt = currentTime - this.lastRenderTime; this.lastRenderTime = currentTime; + // Increase the total animation time by dt * animationSpeed if (this.currentSpeed !== 0) { this.currentFrame += dt * this.currentSpeed; } this.writeUniform('u_time', this.currentFrame * 0.001); + // If the resolution has changed, we need to update the uniform if (this.resolutionChanged) { this.writeUniform('u_resolution', [this.canvasElement.width, this.canvasElement.height]); this.writeUniform('u_pixelRatio', this.renderScale); @@ -408,6 +481,7 @@ export class ShaderMount { this.device.queue.submit([commandEncoder.finish()]); + // Loop if we're animating if (this.currentSpeed !== 0) { this.requestRender(); } else { @@ -443,6 +517,7 @@ export class ShaderMount { } } + /** Creates a texture from an image and sets it into a uniform value */ private setTextureUniform = (uniformName: string, image: HTMLImageElement): void => { if (!image.complete || image.naturalWidth === 0) { throw new Error(`Paper Shaders: image for uniform ${uniformName} must be fully loaded`); @@ -470,6 +545,7 @@ export class ShaderMount { image.naturalHeight, ]); + // Store the texture this.textures.set(uniformName, texture); if (!this.gpuSamplers.has(uniformName)) { @@ -495,6 +571,7 @@ export class ShaderMount { this.rebuildTextureBindGroup(); }; + /** Utility: recursive equality test for all the uniforms */ private areUniformValuesEqual = (a: any, b: any): boolean => { if (a === b) return true; if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) { @@ -503,30 +580,40 @@ export class ShaderMount { return false; }; + /** Sets the provided uniform values into the GPU uniform buffer, can be a partial list of uniforms that have changed */ private setUniformValues = (updatedUniforms: ShaderMountUniforms) => { Object.entries(updatedUniforms).forEach(([key, value]) => { + // Grab the value to use in the uniform cache let cacheValue: ShaderMountUniforms[keyof ShaderMountUniforms] | string = value; if (value instanceof HTMLImageElement) { + // Images use their src for the cache value to save memory cacheValue = `${value.src.slice(0, 200)}|${value.naturalWidth}x${value.naturalHeight}`; } + // Check if the uniform value has changed and, if not, bail early to avoid extra work if (this.areUniformValuesEqual(this.uniformCache[key], cacheValue)) return; + // Update the uniform cache if we are still here this.uniformCache[key] = cacheValue; if (value instanceof HTMLImageElement) { + // Texture case, requires a good amount of code so it gets its own function: this.setTextureUniform(key, value); } else if (Array.isArray(value)) { + // Array case let flatArray: number[] | null = null; + // If it's an array of same-sized arrays, flatten it down so we can set the uniform if (value[0] !== undefined && Array.isArray(value[0])) { const firstChildLength = value[0].length; if (value.every((arr) => (arr as number[]).length === firstChildLength)) { + // Array of same-sized arrays case, flattens the array and sets it flatArray = value.flat(); } else { console.warn(`Paper Shaders: All child arrays must be the same length for ${key}`); return; } } else { + // Array of primitive values case flatArray = value as number[]; } @@ -534,26 +621,33 @@ export class ShaderMount { this.writeUniform(key, flatArray); } } else if (typeof value === 'number') { + // Number case this.writeUniform(key, value); } else if (typeof value === 'boolean') { + // Boolean case this.writeUniform(key, value ? 1 : 0); } else if (value !== undefined) { + // May happen on the server for SSR when undefined images are passed in console.warn(`Paper Shaders: Unsupported uniform type for ${key}: ${typeof value}`); } }); }; + /** Gets the current total animation time from 0ms */ public getCurrentFrame = (): number => { return this.currentFrame; }; + /** Set a frame to get a deterministic result, frames are literally just milliseconds from zero since the animation started */ public setFrame = (newFrame: number): void => { this.currentFrame = newFrame; this.lastRenderTime = performance.now(); this.render(performance.now()); }; + /** Set an animation speed (or 0 to stop animation) */ public setSpeed = (newSpeed = 1): void => { + // Set the new animation speed this.speed = newSpeed; this.setCurrentSpeed(this.ownerDocument.hidden ? 0 : newSpeed); }; @@ -562,26 +656,31 @@ export class ShaderMount { this.currentSpeed = newSpeed; if (this.rafId === null && newSpeed !== 0) { + // Moving from 0 to animating, kick off a new rAF loop this.lastRenderTime = performance.now(); this.rafId = requestAnimationFrame(this.render); } if (this.rafId !== null && newSpeed === 0) { + // Moving from animating to not animating, cancel the rAF loop cancelAnimationFrame(this.rafId); this.rafId = null; } }; + /** Set the maximum pixel count for the shader, this will limit the number of pixels that will be rendered */ public setMaxPixelCount = (newMaxPixelCount: number = DEFAULT_MAX_PIXEL_COUNT): void => { this.maxPixelCount = newMaxPixelCount; this.handleResize(); }; + /** Set the minimum pixel ratio for the shader */ public setMinPixelRatio = (newMinPixelRatio: number = 2): void => { this.minPixelRatio = newMinPixelRatio; this.handleResize(); }; + /** Update the uniforms that are provided by the outside shader, can be a partial set with only the uniforms that have changed */ public setUniforms = (newUniforms: ShaderMountUniforms): void => { this.setUniformValues(newUniforms); this.providedUniforms = { ...this.providedUniforms, ...newUniforms }; @@ -592,14 +691,18 @@ export class ShaderMount { this.setCurrentSpeed(this.ownerDocument.hidden ? 0 : this.speed); }; + /** Dispose of the shader mount, cleaning up all of the GPU resources */ public dispose = (): void => { + // Immediately mark as disposed to prevent future renders from leaking in this.hasBeenDisposed = true; + // Cancel the rAF loop if (this.rafId !== null) { cancelAnimationFrame(this.rafId); this.rafId = null; } + // Clean up all textures this.textures.forEach((texture) => { texture.destroy(); }); @@ -620,7 +723,9 @@ export class ShaderMount { this.uniformLayout.clear(); + // Remove the shader from the div wrapper element this.canvasElement.remove(); + // Free up the reference to self to enable garbage collection delete this.parentElement.paperShaderMount; }; } @@ -725,14 +830,22 @@ const defaultStyle = `@layer paper-shaders { } }`; +/** The parent `
` element that has a ShaderMount available on it */ export interface PaperShaderElement extends HTMLElement { paperShaderMount: ShaderMount | undefined; } +/** Check if an element is a Paper shader element */ export function isPaperShaderElement(element: HTMLElement): element is PaperShaderElement { return 'paperShaderMount' in element; } +/** + * Uniform types that we support to be auto-mapped into the fragment shader + * + * We accept undefined as a convenience for server rendering, when some things may be undefined + * We just skip setting the uniform if it's undefined. This allows the shader mount to still take up space during server rendering + */ export interface ShaderMountUniforms { [key: string]: boolean | number | number[] | number[][] | HTMLImageElement | undefined; } @@ -749,6 +862,13 @@ export type ShaderPreset = { export type ImageShaderPreset = { name: string; + /** + * Params for the shader excluding the image. + * Image is excluded as it isn't considered a preset, + * e.g. when switching between presets it shouldn't switch the image. + * + * While we exclude images from presets they should still be set with a default prop value so the code-first usage of shaders remains great. + */ params: Required>; }; @@ -757,24 +877,51 @@ function isSafari() { return ua.includes('safari') && !ua.includes('chrome') && !ua.includes('android'); } +// Zoom level can be estimated comparing the browser's outerWidth and the viewport width. +// It's nowhere near perfect because it's affected by the presence of browser sidebars, +// like a vertical web inspector or Arc's sidebar. Also, both outerWidth and innerWidth +// are integers, which would almost never give us a perfect ratio at face values. +// +// Still, this is pretty accurate in the vast majority of cases. +// +// Note 1: +// Avoid innerWidth, use visualViewport.width instead. +// - innerWidth is affected by pinch zoom in Safari, but not other browsers. +// visualViewport.width works consistently in all browsers. +// - innerWidth is rounded to integer, but not visualViewport.width. +// - visualViewport.width is affected by hard scrollbars, so they need to be added manually +// +// Note 2: +// Opening a sidebar in Safari like web inspector or bookmarks will throw off the zoom +// level detection and result in a larger target resolution. Not a concern in real-world usage +// with Safari, but we'd rather not try to detect zoom levels with other browsers +// (e.g. Arc always has a sidebar, which affects outerWidth vs visualViewport.width). function bestGuessBrowserZoom(doc: Document) { const viewportScale = visualViewport?.scale ?? 1; const viewportWidth = visualViewport?.width ?? window.innerWidth; const scrollbarWidth = window.innerWidth - doc.documentElement.clientWidth; const innerWidth = viewportScale * viewportWidth + scrollbarWidth; + // outerWidth and innerWidth are always integers so we won't often get the original zoom ratio + // E.g. given a 125% zoom, outerWidth = 1657, innerWidth = 1325, 1657 / 1325 = 1.2505660377 + // We check for common zoom levels and return the closest one if found. + const ratio = outerWidth / innerWidth; const zoomPercentageRounded = Math.round(100 * ratio); + // All zoom levels divisible by 5% if (zoomPercentageRounded % 5 === 0) { return zoomPercentageRounded / 100; } + // 33% zoom if (zoomPercentageRounded === 33) { return 1 / 3; } + // 67% zoom if (zoomPercentageRounded === 67) { return 2 / 3; } + // 133% zoom if (zoomPercentageRounded === 133) { return 4 / 3; } From 7469242ccde90468ebafadb24be98c6a608eb02e Mon Sep 17 00:00:00 2001 From: Ksenia Kondrashova Date: Tue, 21 Apr 2026 19:33:03 +0400 Subject: [PATCH 3/5] flipY for ImageDithering --- packages/shaders/src/shaders/image-dithering.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/shaders/src/shaders/image-dithering.ts b/packages/shaders/src/shaders/image-dithering.ts index ad6719e31..d64ef181f 100644 --- a/packages/shaders/src/shaders/image-dithering.ts +++ b/packages/shaders/src/shaders/image-dithering.ts @@ -97,7 +97,6 @@ fn getImageUV(uv_in: vec2f) -> vec2f { imageUV = vec2f(imageUV.x / u.u_imageAspectRatio, imageUV.y); imageUV += vec2f(0.5); - imageUV = vec2f(imageUV.x, 1.0 - imageUV.y); return imageUV; } From 5956fe07d00ad12838aa23f4c41f35754412aa4b Mon Sep 17 00:00:00 2001 From: Ksenia Kondrashova Date: Tue, 21 Apr 2026 19:45:39 +0400 Subject: [PATCH 4/5] coordinate flips for randomizers consistency --- packages/shaders/src/shaders/dithering.ts | 3 ++- packages/shaders/src/shaders/fluted-glass.ts | 3 ++- packages/shaders/src/shaders/image-dithering.ts | 4 +++- packages/shaders/src/shaders/paper-texture.ts | 3 ++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/shaders/src/shaders/dithering.ts b/packages/shaders/src/shaders/dithering.ts index b7f9ba6a8..d35b869c0 100644 --- a/packages/shaders/src/shaders/dithering.ts +++ b/packages/shaders/src/shaders/dithering.ts @@ -96,7 +96,8 @@ fn getBayerValue(uv: vec2f, size: i32) -> f32 { let t = 0.5 * u.u_time; let pxSize = u.u_pxSize * u.u_pixelRatio; - var pxSizeUV = input.position.xy - 0.5 * u.u_resolution; + let fragCoord = vec2f(input.position.x, u.u_resolution.y - input.position.y); + var pxSizeUV = fragCoord - 0.5 * u.u_resolution; pxSizeUV /= pxSize; let canvasPixelizedUV = (floor(pxSizeUV) + vec2f(0.5)) * pxSize; let normalizedUV = canvasPixelizedUV / u.u_resolution; diff --git a/packages/shaders/src/shaders/fluted-glass.ts b/packages/shaders/src/shaders/fluted-glass.ts index f71dd4fe9..4fdc76781 100644 --- a/packages/shaders/src/shaders/fluted-glass.ts +++ b/packages/shaders/src/shaders/fluted-glass.ts @@ -177,7 +177,8 @@ fn smoothFract(x: f32) -> f32 { var uv = input.v_imageUV; - let uvMask = input.position.xy / u.u_resolution.xy; + let fragCoord = vec2f(input.position.x, u.u_resolution.y - input.position.y); + let uvMask = fragCoord / u.u_resolution.xy; let sw = vec2f(0.005); let margins = vec4f(u.u_marginLeft, u.u_marginTop, u.u_marginRight, u.u_marginBottom); let mask = diff --git a/packages/shaders/src/shaders/image-dithering.ts b/packages/shaders/src/shaders/image-dithering.ts index d64ef181f..03db077e5 100644 --- a/packages/shaders/src/shaders/image-dithering.ts +++ b/packages/shaders/src/shaders/image-dithering.ts @@ -97,6 +97,7 @@ fn getImageUV(uv_in: vec2f) -> vec2f { imageUV = vec2f(imageUV.x / u.u_imageAspectRatio, imageUV.y); imageUV += vec2f(0.5); + imageUV = vec2f(imageUV.x, 1.0 - imageUV.y); return imageUV; } @@ -138,7 +139,8 @@ fn getBayerValue(uv: vec2f, size: i32) -> f32 { @fragment fn fs_main(input: VertexOutput) -> @location(0) vec4f { let pxSize = u.u_pxSize * u.u_pixelRatio; - var pxSizeUV = input.position.xy - 0.5 * u.u_resolution; + let fragCoord = vec2f(input.position.x, u.u_resolution.y - input.position.y); + var pxSizeUV = fragCoord - 0.5 * u.u_resolution; pxSizeUV /= pxSize; let canvasPixelizedUV = (floor(pxSizeUV) + vec2f(0.5)) * pxSize; let normalizedUV = canvasPixelizedUV / u.u_resolution; diff --git a/packages/shaders/src/shaders/paper-texture.ts b/packages/shaders/src/shaders/paper-texture.ts index 21486b700..8dc14520d 100644 --- a/packages/shaders/src/shaders/paper-texture.ts +++ b/packages/shaders/src/shaders/paper-texture.ts @@ -213,7 +213,8 @@ fn lst(edge0: f32, edge1: f32, x: f32) -> f32 { var patternUV = input.v_imageUV - vec2f(0.5); patternUV = 5.0 * (patternUV * vec2f(u.u_imageAspectRatio, 1.0)); - let roughnessUv = 1.5 * (input.position.xy - 0.5 * u.u_resolution) / u.u_pixelRatio; + let fragCoord = vec2f(input.position.x, u.u_resolution.y - input.position.y); + let roughnessUv = 1.5 * (fragCoord - 0.5 * u.u_resolution) / u.u_pixelRatio; let roughness = roughnessFn(roughnessUv + vec2f(1.0, 0.0)) - roughnessFn(roughnessUv - vec2f(1.0, 0.0)); let crumplesUV = fract(patternUV * 0.02 / u.u_crumpleSize - vec2f(u.u_seed)) * 32.0; From 7ed7285609812707e6537c48423cb97e3bac9f66 Mon Sep 17 00:00:00 2001 From: Ksenia Kondrashova Date: Tue, 21 Apr 2026 21:43:23 +0400 Subject: [PATCH 5/5] syntax highlights --- packages/shaders/src/shader-color-spaces.ts | 1 + packages/shaders/src/shader-utils.ts | 13 +++++++++++++ packages/shaders/src/shaders/color-panels.ts | 1 + packages/shaders/src/shaders/dithering.ts | 1 + packages/shaders/src/shaders/dot-grid.ts | 1 + packages/shaders/src/shaders/dot-orbit.ts | 1 + packages/shaders/src/shaders/fluted-glass.ts | 1 + packages/shaders/src/shaders/gem-smoke.ts | 1 + packages/shaders/src/shaders/god-rays.ts | 1 + packages/shaders/src/shaders/grain-gradient.ts | 1 + packages/shaders/src/shaders/halftone-cmyk.ts | 1 + packages/shaders/src/shaders/halftone-dots.ts | 1 + packages/shaders/src/shaders/heatmap.ts | 1 + packages/shaders/src/shaders/image-dithering.ts | 1 + packages/shaders/src/shaders/liquid-metal.ts | 1 + packages/shaders/src/shaders/mesh-gradient.ts | 1 + packages/shaders/src/shaders/metaballs.ts | 1 + packages/shaders/src/shaders/neuro-noise.ts | 1 + packages/shaders/src/shaders/paper-texture.ts | 1 + packages/shaders/src/shaders/perlin-noise.ts | 1 + packages/shaders/src/shaders/pulsing-border.ts | 1 + packages/shaders/src/shaders/simplex-noise.ts | 1 + packages/shaders/src/shaders/smoke-ring.ts | 1 + packages/shaders/src/shaders/spiral.ts | 1 + .../shaders/src/shaders/static-mesh-gradient.ts | 1 + .../shaders/src/shaders/static-radial-gradient.ts | 1 + packages/shaders/src/shaders/swirl.ts | 1 + packages/shaders/src/shaders/voronoi.ts | 1 + packages/shaders/src/shaders/warp.ts | 1 + packages/shaders/src/shaders/water.ts | 1 + packages/shaders/src/shaders/waves.ts | 1 + packages/shaders/src/vertex-shader.ts | 1 + 32 files changed, 44 insertions(+) diff --git a/packages/shaders/src/shader-color-spaces.ts b/packages/shaders/src/shader-color-spaces.ts index 557a37523..f1b79bf82 100644 --- a/packages/shaders/src/shader-color-spaces.ts +++ b/packages/shaders/src/shader-color-spaces.ts @@ -5,6 +5,7 @@ export const ShaderColorSpaces = { export type ShaderColorSpace = keyof typeof ShaderColorSpaces; +// language=WGSL export const declareOklchTransforms = ` // magic numbers (and magic could be better tbh) diff --git a/packages/shaders/src/shader-utils.ts b/packages/shaders/src/shader-utils.ts index 481eebd51..d9cc96f61 100644 --- a/packages/shaders/src/shader-utils.ts +++ b/packages/shaders/src/shader-utils.ts @@ -1,5 +1,6 @@ // Shared WGSL definitions used by all shaders +// language=WGSL export const systemUniformFields = ` u_resolution: vec2f, u_pixelRatio: f32, @@ -16,6 +17,7 @@ export const systemUniformFields = ` u_offsetY: f32, `; +// language=WGSL export const vertexOutputStruct = ` struct VertexOutput { @builtin(position) position: vec4f, @@ -31,17 +33,20 @@ struct VertexOutput { // WGSL utility functions +// language=WGSL export const declarePI = ` const TWO_PI: f32 = 6.28318530718; const PI: f32 = 3.14159265358979323846; `; +// language=WGSL export const rotation2 = ` fn rotate(uv: vec2f, th: f32) -> vec2f { return mat2x2f(cos(th), sin(th), -sin(th), cos(th)) * uv; } `; +// language=WGSL export const proceduralHash11 = ` fn hash11(p_in: f32) -> f32 { var p = fract(p_in * 0.3183099) + 0.1; @@ -50,6 +55,7 @@ fn hash11(p_in: f32) -> f32 { } `; +// language=WGSL export const proceduralHash21 = ` fn hash21(p_in: vec2f) -> f32 { var p = fract(p_in * vec2f(0.3183099, 0.3678794)) + vec2f(0.1); @@ -58,6 +64,7 @@ fn hash21(p_in: vec2f) -> f32 { } `; +// language=WGSL export const proceduralHash22 = ` fn hash22(p_in: vec2f) -> vec2f { var p = fract(p_in * vec2f(0.3183099, 0.3678794)) + vec2f(0.1); @@ -66,6 +73,7 @@ fn hash22(p_in: vec2f) -> vec2f { } `; +// language=WGSL export const textureRandomizerR = ` fn randomR(p: vec2f) -> f32 { let uv = floor(p) / 100.0 + vec2f(0.5); @@ -73,6 +81,7 @@ fn randomR(p: vec2f) -> f32 { } `; +// language=WGSL export const textureRandomizerGB = ` fn randomGB(p: vec2f) -> vec2f { let uv = floor(p) / 100.0 + vec2f(0.5); @@ -80,10 +89,12 @@ fn randomGB(p: vec2f) -> vec2f { } `; +// language=WGSL export const colorBandingFix = ` color += vec3f(1.0 / 256.0 * (fract(sin(dot(0.014 * input.position.xy, vec2f(12.9898, 78.233))) * 43758.5453123) - 0.5)); `; +// language=WGSL export const glslMod = ` fn glsl_mod_f32(x: f32, y: f32) -> f32 { return x - y * floor(x / y); @@ -96,6 +107,7 @@ fn glsl_mod_vec3(x: vec3f, y: f32) -> vec3f { } `; +// language=WGSL export const simplexNoise = ` fn permute3(x: vec3f) -> vec3f { return glsl_mod_vec3(((x * 34.0) + vec3f(1.0)) * x, 289.0); } fn snoise(v: vec2f) -> f32 { @@ -128,6 +140,7 @@ fn snoise(v: vec2f) -> f32 { } `; +// language=WGSL export const fiberNoise = ` fn fiberRandom(p: vec2f) -> f32 { let uv = floor(p) / 100.0; diff --git a/packages/shaders/src/shaders/color-panels.ts b/packages/shaders/src/shaders/color-panels.ts index 5b1be2a5c..5b6d16d4a 100644 --- a/packages/shaders/src/shaders/color-panels.ts +++ b/packages/shaders/src/shaders/color-panels.ts @@ -44,6 +44,7 @@ export const colorPanelsMeta = { * */ +// language=WGSL export const colorPanelsFragmentShader: string = ` struct Uniforms { ${ systemUniformFields } diff --git a/packages/shaders/src/shaders/dithering.ts b/packages/shaders/src/shaders/dithering.ts index d35b869c0..eaeb5c2cd 100644 --- a/packages/shaders/src/shaders/dithering.ts +++ b/packages/shaders/src/shaders/dithering.ts @@ -34,6 +34,7 @@ import { systemUniformFields, vertexOutputStruct, simplexNoise, declarePI, proce * * */ +// language=WGSL export const ditheringFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/dot-grid.ts b/packages/shaders/src/shaders/dot-grid.ts index c6ec41216..c010c41f9 100644 --- a/packages/shaders/src/shaders/dot-grid.ts +++ b/packages/shaders/src/shaders/dot-grid.ts @@ -34,6 +34,7 @@ import { systemUniformFields, vertexOutputStruct, declarePI, glslMod, simplexNoi * */ +// language=WGSL export const dotGridFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/dot-orbit.ts b/packages/shaders/src/shaders/dot-orbit.ts index d9f57af8b..8a2c2361b 100644 --- a/packages/shaders/src/shaders/dot-orbit.ts +++ b/packages/shaders/src/shaders/dot-orbit.ts @@ -40,6 +40,7 @@ export const dotOrbitMeta = { * */ +// language=WGSL export const dotOrbitFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/fluted-glass.ts b/packages/shaders/src/shaders/fluted-glass.ts index 4fdc76781..77e783898 100644 --- a/packages/shaders/src/shaders/fluted-glass.ts +++ b/packages/shaders/src/shaders/fluted-glass.ts @@ -50,6 +50,7 @@ import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, procedur * */ +// language=WGSL export const flutedGlassFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/gem-smoke.ts b/packages/shaders/src/shaders/gem-smoke.ts index a5c29b035..44afb120d 100644 --- a/packages/shaders/src/shaders/gem-smoke.ts +++ b/packages/shaders/src/shaders/gem-smoke.ts @@ -48,6 +48,7 @@ export const gemSmokeMeta = { * */ +// language=WGSL export const gemSmokeFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/god-rays.ts b/packages/shaders/src/shaders/god-rays.ts index 72ada91b3..197634d58 100644 --- a/packages/shaders/src/shaders/god-rays.ts +++ b/packages/shaders/src/shaders/god-rays.ts @@ -44,6 +44,7 @@ export const godRaysMeta = { * */ +// language=WGSL export const godRaysFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/grain-gradient.ts b/packages/shaders/src/shaders/grain-gradient.ts index 84a41aaec..bd28aed99 100644 --- a/packages/shaders/src/shaders/grain-gradient.ts +++ b/packages/shaders/src/shaders/grain-gradient.ts @@ -67,6 +67,7 @@ export const grainGradientMeta = { * */ +// language=WGSL export const grainGradientFragmentShader: string = ` struct Uniforms { ${ systemUniformFields } diff --git a/packages/shaders/src/shaders/halftone-cmyk.ts b/packages/shaders/src/shaders/halftone-cmyk.ts index 7e905466a..b3dec4851 100644 --- a/packages/shaders/src/shaders/halftone-cmyk.ts +++ b/packages/shaders/src/shaders/halftone-cmyk.ts @@ -50,6 +50,7 @@ import { systemUniformFields, vertexOutputStruct, declarePI } from '../shader-ut * */ +// language=WGSL export const halftoneCmykFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/halftone-dots.ts b/packages/shaders/src/shaders/halftone-dots.ts index 950b56fbc..cbf6a292e 100644 --- a/packages/shaders/src/shaders/halftone-dots.ts +++ b/packages/shaders/src/shaders/halftone-dots.ts @@ -40,6 +40,7 @@ import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, procedur * */ +// language=WGSL export const halftoneDotsFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/heatmap.ts b/packages/shaders/src/shaders/heatmap.ts index 5aa837045..651b6d13f 100644 --- a/packages/shaders/src/shaders/heatmap.ts +++ b/packages/shaders/src/shaders/heatmap.ts @@ -44,6 +44,7 @@ export const heatmapMeta = { * */ +// language=WGSL export const heatmapFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/image-dithering.ts b/packages/shaders/src/shaders/image-dithering.ts index 03db077e5..7bec1f5a2 100644 --- a/packages/shaders/src/shaders/image-dithering.ts +++ b/packages/shaders/src/shaders/image-dithering.ts @@ -37,6 +37,7 @@ import { systemUniformFields, vertexOutputStruct, proceduralHash21, declarePI } * */ +// language=WGSL export const imageDitheringFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/liquid-metal.ts b/packages/shaders/src/shaders/liquid-metal.ts index 757383ac8..037fd3d97 100644 --- a/packages/shaders/src/shaders/liquid-metal.ts +++ b/packages/shaders/src/shaders/liquid-metal.ts @@ -46,6 +46,7 @@ import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, glslMod, * */ +// language=WGSL export const liquidMetalFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/mesh-gradient.ts b/packages/shaders/src/shaders/mesh-gradient.ts index 0d648b752..3f1bd9fd3 100644 --- a/packages/shaders/src/shaders/mesh-gradient.ts +++ b/packages/shaders/src/shaders/mesh-gradient.ts @@ -41,6 +41,7 @@ export const meshGradientMeta = { * */ +// language=WGSL export const meshGradientFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/metaballs.ts b/packages/shaders/src/shaders/metaballs.ts index bcce0e409..3711c6e4f 100644 --- a/packages/shaders/src/shaders/metaballs.ts +++ b/packages/shaders/src/shaders/metaballs.ts @@ -38,6 +38,7 @@ export const metaballsMeta = { * */ +// language=WGSL export const metaballsFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/neuro-noise.ts b/packages/shaders/src/shaders/neuro-noise.ts index 134328e0e..213b26429 100644 --- a/packages/shaders/src/shaders/neuro-noise.ts +++ b/packages/shaders/src/shaders/neuro-noise.ts @@ -35,6 +35,7 @@ import { systemUniformFields, vertexOutputStruct, rotation2, colorBandingFix } f * Original algorithm: https://x.com/zozuar/status/1625182758745128981/ */ +// language=WGSL export const neuroNoiseFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/paper-texture.ts b/packages/shaders/src/shaders/paper-texture.ts index 8dc14520d..039ed3b68 100644 --- a/packages/shaders/src/shaders/paper-texture.ts +++ b/packages/shaders/src/shaders/paper-texture.ts @@ -45,6 +45,7 @@ import { systemUniformFields, vertexOutputStruct, rotation2, declarePI, fiberNoi * */ +// language=WGSL export const paperTextureFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/perlin-noise.ts b/packages/shaders/src/shaders/perlin-noise.ts index c00f76df6..fb7597bb1 100644 --- a/packages/shaders/src/shaders/perlin-noise.ts +++ b/packages/shaders/src/shaders/perlin-noise.ts @@ -34,6 +34,7 @@ import { systemUniformFields, vertexOutputStruct, declarePI, colorBandingFix, pr * */ +// language=WGSL export const perlinNoiseFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/pulsing-border.ts b/packages/shaders/src/shaders/pulsing-border.ts index 5c03ab51c..d13fa8f31 100644 --- a/packages/shaders/src/shaders/pulsing-border.ts +++ b/packages/shaders/src/shaders/pulsing-border.ts @@ -53,6 +53,7 @@ export const pulsingBorderMeta = { * */ +// language=WGSL export const pulsingBorderFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/simplex-noise.ts b/packages/shaders/src/shaders/simplex-noise.ts index 8ff4b023b..94af59d49 100644 --- a/packages/shaders/src/shaders/simplex-noise.ts +++ b/packages/shaders/src/shaders/simplex-noise.ts @@ -36,6 +36,7 @@ export const simplexNoiseMeta = { * */ +// language=WGSL export const simplexNoiseFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/smoke-ring.ts b/packages/shaders/src/shaders/smoke-ring.ts index a5d5aba12..ce2fc92e1 100644 --- a/packages/shaders/src/shaders/smoke-ring.ts +++ b/packages/shaders/src/shaders/smoke-ring.ts @@ -42,6 +42,7 @@ export const smokeRingMeta = { * */ +// language=WGSL export const smokeRingFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/spiral.ts b/packages/shaders/src/shaders/spiral.ts index e982d4e64..cec73bfd3 100644 --- a/packages/shaders/src/shaders/spiral.ts +++ b/packages/shaders/src/shaders/spiral.ts @@ -37,6 +37,7 @@ import { systemUniformFields, vertexOutputStruct, simplexNoise, glslMod, declare * */ +// language=WGSL export const spiralFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/static-mesh-gradient.ts b/packages/shaders/src/shaders/static-mesh-gradient.ts index e9c3a525c..2ecb0946e 100644 --- a/packages/shaders/src/shaders/static-mesh-gradient.ts +++ b/packages/shaders/src/shaders/static-mesh-gradient.ts @@ -44,6 +44,7 @@ export const staticMeshGradientMeta = { * */ +// language=WGSL export const staticMeshGradientFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/static-radial-gradient.ts b/packages/shaders/src/shaders/static-radial-gradient.ts index 248b72ab4..8d543e582 100644 --- a/packages/shaders/src/shaders/static-radial-gradient.ts +++ b/packages/shaders/src/shaders/static-radial-gradient.ts @@ -47,6 +47,7 @@ export const staticRadialGradientMeta = { * */ +// language=WGSL export const staticRadialGradientFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/swirl.ts b/packages/shaders/src/shaders/swirl.ts index 4b8d88923..0dda45e1c 100644 --- a/packages/shaders/src/shaders/swirl.ts +++ b/packages/shaders/src/shaders/swirl.ts @@ -41,6 +41,7 @@ export const swirlMeta = { * */ +// language=WGSL export const swirlFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/voronoi.ts b/packages/shaders/src/shaders/voronoi.ts index f951ca927..aece917be 100644 --- a/packages/shaders/src/shaders/voronoi.ts +++ b/packages/shaders/src/shaders/voronoi.ts @@ -46,6 +46,7 @@ export const voronoiMeta = { * */ +// language=WGSL export const voronoiFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/warp.ts b/packages/shaders/src/shaders/warp.ts index 3dcc05ca8..66cefae0d 100644 --- a/packages/shaders/src/shaders/warp.ts +++ b/packages/shaders/src/shaders/warp.ts @@ -44,6 +44,7 @@ export const warpMeta = { * */ +// language=WGSL export const warpFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/water.ts b/packages/shaders/src/shaders/water.ts index 12aefd147..878ddf3e9 100644 --- a/packages/shaders/src/shaders/water.ts +++ b/packages/shaders/src/shaders/water.ts @@ -37,6 +37,7 @@ import { systemUniformFields, vertexOutputStruct, declarePI, rotation2, glslMod, * */ +// language=WGSL export const waterFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/shaders/waves.ts b/packages/shaders/src/shaders/waves.ts index 59a27bc33..b4f8d98f2 100644 --- a/packages/shaders/src/shaders/waves.ts +++ b/packages/shaders/src/shaders/waves.ts @@ -32,6 +32,7 @@ import { systemUniformFields, vertexOutputStruct, declarePI } from '../shader-ut * */ +// language=WGSL export const wavesFragmentShader: string = ` struct Uniforms { ${systemUniformFields} diff --git a/packages/shaders/src/vertex-shader.ts b/packages/shaders/src/vertex-shader.ts index f6ce058f7..d613becb0 100644 --- a/packages/shaders/src/vertex-shader.ts +++ b/packages/shaders/src/vertex-shader.ts @@ -1,4 +1,5 @@ /** Vertex shader for the shader mount (WGSL) — appended after the fragment shader into one module */ +// language=WGSL export const vertexShaderSource = ` fn vs_getBoxSize(boxRatio: f32, givenBoxSize: vec2f) -> vec3f { var box = vec2f(0.0);