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/package.json b/package.json index c038123ba..74dc8c364 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@types/bun": "^1.1.11", + "@webgpu/types": "^0.1.69", "esbuild": "^0.24.0", "nodemon": "^3.1.7", "npm-run-all": "^4.1.5", 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-mount.ts b/packages/shaders/src/shader-mount.ts index b7c1183df..092aeeb84 100644 --- a/packages/shaders/src/shader-mount.ts +++ b/packages/shaders/src/shader-mount.ts @@ -5,9 +5,28 @@ 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 = {}; + + // 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(); + /** The fragment shader that we are using */ private fragmentShader: string; /** Stores the RAF for the render loop */ @@ -28,21 +47,20 @@ export class ShaderMount { 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, + /** @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*/ @@ -91,73 +109,206 @@ export class ShaderMount { 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(); + 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 = {}; // 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(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. @@ -265,7 +416,6 @@ export class ShaderMount { 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()); @@ -273,13 +423,15 @@ export class ShaderMount { }; 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; } + if (this.canvasElement.width === 0 || this.canvasElement.height === 0) return; + // Calculate the delta time const dt = currentTime - this.lastRenderTime; this.lastRenderTime = currentTime; @@ -288,23 +440,46 @@ export class ShaderMount { this.currentFrame += dt * this.currentSpeed; } - // Clear the canvas - this.gl.clear(this.gl.COLOR_BUFFER_BIT); - - // Update uniforms - this.gl.useProgram(this.program); - - // Update the time uniform - this.gl.uniform1f(this.uniformLocations.u_time!, this.currentFrame * 0.001); + this.writeUniform('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) { @@ -321,81 +496,92 @@ export class ShaderMount { this.rafId = requestAnimationFrame(this.render); }; + 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); + } + } + } + } + /** 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`); } + 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 */ + /** Sets the provided uniform values into the GPU uniform buffer, 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; @@ -409,67 +595,40 @@ export class ShaderMount { // 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 + // Array of same-sized arrays case, flattens the array and 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 + // Array of primitive values case 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); + // Number case + this.writeUniform(key, value); } else if (typeof value === 'boolean') { - // Boolean case, supports true and false - this.gl.uniform1i(location, value ? 1 : 0); - } else { + // 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(`Unsupported uniform type for ${key}: ${typeof value}`); + console.warn(`Paper Shaders: Unsupported uniform type for ${key}: ${typeof value}`); } }); }; @@ -512,14 +671,12 @@ export class ShaderMount { /** 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(); }; @@ -527,7 +684,6 @@ export class ShaderMount { public setUniforms = (newUniforms: ShaderMountUniforms): void => { this.setUniformValues(newUniforms); this.providedUniforms = { ...this.providedUniforms, ...newUniforms }; - this.render(performance.now()); }; @@ -535,7 +691,7 @@ export class ShaderMount { this.setCurrentSpeed(this.ownerDocument.hidden ? 0 : this.speed); }; - /** Dispose of the shader mount, cleaning up all of the WebGL resources */ + /** 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; @@ -546,25 +702,16 @@ export class ShaderMount { 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); + // Clean up all textures + 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,7 +721,7 @@ 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(); @@ -583,67 +730,87 @@ export class ShaderMount { }; } -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; @@ -746,17 +913,14 @@ function bestGuessBrowserZoom(doc: Document) { 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..d9cc96f61 100644 --- a/packages/shaders/src/shader-utils.ts +++ b/packages/shaders/src/shader-utils.ts @@ -1,132 +1,184 @@ -// language=GLSL +// Shared WGSL definitions used by all shaders + +// language=WGSL +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, +`; + +// language=WGSL +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 + +// language=WGSL 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 +// language=WGSL 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 +// language=WGSL 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 +// language=WGSL 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 +// language=WGSL 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 +// language=WGSL 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 +// language=WGSL 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 +// language=WGSL 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)); +`; + +// language=WGSL +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 +// language=WGSL 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 +// language=WGSL 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..5b6d16d4a 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,108 @@ 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; +// language=WGSL +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 +157,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..eaeb5c2cd 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,228 @@ 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; +// language=WGSL +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; + let fragCoord = vec2f(input.position.x, u.u_resolution.y - input.position.y); + var pxSizeUV = fragCoord - 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..c010c41f9 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,98 @@ 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; +// language=WGSL +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..8a2c2361b 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,47 @@ 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; +// language=WGSL +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 +88,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..77e783898 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,339 @@ 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); +// language=WGSL +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 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 = 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..44afb120d 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,229 @@ 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; +// language=WGSL +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..197634d58 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,121 @@ 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; +// language=WGSL +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..bd28aed99 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,57 @@ 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; +// language=WGSL +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 +129,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..b3dec4851 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,293 @@ 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; +// language=WGSL +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..cbf6a292e 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,314 @@ 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); +// language=WGSL +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..651b6d13f 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,254 @@ 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); +// language=WGSL +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..7bec1f5a2 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,81 @@ 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; +// language=WGSL +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 +122,86 @@ 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; + let fragCoord = vec2f(input.position.x, u.u_resolution.y - input.position.y); + var pxSizeUV = fragCoord - 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..037fd3d97 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,328 @@ 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; +// language=WGSL +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..3f1bd9fd3 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,114 @@ 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; +// language=WGSL +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..3711c6e4f 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,78 @@ 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; +// language=WGSL +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 +119,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..213b26429 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,70 @@ 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; +// language=WGSL +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..039ed3b68 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,45 @@ 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; +// language=WGSL +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 +91,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 +117,177 @@ 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 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)); - 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..fb7597bb1 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,111 @@ 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; +// language=WGSL +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 +147,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 +158,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..d13fa8f31 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,60 @@ 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; +// language=WGSL +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 +114,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 +165,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..94af59d49 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,88 @@ 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; +// language=WGSL +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..ce2fc92e1 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,49 @@ 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); +// language=WGSL +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 +94,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..cec73bfd3 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,73 @@ 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; +// language=WGSL +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..2ecb0946e 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,96 @@ 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); +// language=WGSL +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 +143,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..8d543e582 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,179 @@ 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); +// language=WGSL +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..0dda45e1c 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,91 @@ 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; +// language=WGSL +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..aece917be 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,46 @@ 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; +// language=WGSL +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 +96,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..66cefae0d 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,115 @@ 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; +// language=WGSL +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..878ddf3e9 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,118 @@ 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; +// language=WGSL +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..b4f8d98f2 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,60 @@ 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; +// language=WGSL +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..d613becb0 100644 --- a/packages/shaders/src/vertex-shader.ts +++ b/packages/shaders/src/vertex-shader.ts @@ -1,153 +1,122 @@ -/** 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 */ +// language=WGSL +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",