AI Liquid Chrome Mirror - Mouse Velocity Metallic Distortion - xelsed.ai
52 views ยท Dec 16, 2025
Touch liquid mercury with your cursor. Move slowly for a calm reflective surface, move fast to create violent ripples across the chrome. A mesmerizing WEBGL shader that responds to mouse velocity, creating a living metallic blob with blue-purple reflections. Created with xelsed-alpha6-flash.
๐ Embed Code
Copy this code to embed this sketch on your website:
<iframe src="https://p5js.ai/embed/184f99cc-7b02-4884-be32-db8a23cbbf9e" width="400" height="400" frameborder="0" allow="autoplay" title="AI Liquid Chrome Mirror - Mouse Velocity Metallic Distortion - xelsed.ai"></iframe>
Adjust width and height as needed. Preview embed โ
โ About This Sketch 3 questions
What visual effects does the AI Liquid Chrome Mirror sketch produce?
This sketch creates a mesmerizing liquid mercury effect that reflects blue-purple hues, responding dynamically to mouse movements to create smooth surfaces or violent ripples.
How can users interact with the AI Liquid Chrome Mirror sketch?
Users can interact by moving their mouse across the canvas; slower movements create calm reflections while faster movements generate dramatic ripples in the metallic surface.
What creative coding concepts are demonstrated in the AI Liquid Chrome Mirror sketch?
The sketch showcases the use of WebGL shaders to create real-time visual effects, as well as the concept of mouse velocity influencing graphic output for dynamic interactivity.
๐ฌ AI Chat History 2 messages
**Important Note:** For convenience and ease of sharing in the p5.js editor, the vertex and fragment shader codes are embedded directly within `sketch.js` as string constants. If you were working in a local environment with separate `.glsl` files, you would use `myShader = loadShader('vert.glsl', 'frag.glsl');` in `preload()`.
---
### `index.html`
This file sets up your HTML page, loads p5.js, and links to your stylesheet and sketch.
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Liquid Chrome Shader</title>
<link rel="stylesheet" href="style.css">
<!-- Load p5.js v1.11.3 from CDN -->
<script src="https://cdn.jsdelivr.net/npm/p5@1.11.3/lib/p5.min.js"></script>
<!-- Load your sketch -->
<script src="sketch.js"></script>
</head>
<body>
</body>
</html>
```
---
### `style.css`
This CSS ensures the canvas fills the entire window and removes default margins.
```css
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #0d0d1a; /* Matches the shader's dark background */
}
canvas {
display: block;
}
```
---
### `sketch.js`
This is your main p5.js code, including the embedded shader strings.
```js
// sketch.js
let myShader;
let prevMouseX = 0;
let prevMouseY = 0;
let velocityX = 0;
let velocityY = 0;
function preload() {
// Create the shader from the embedded vertex and fragment shader strings
// The 'this.renderer' argument is important for p5.js 1.x shaders
myShader = new p5.Shader(this.renderer, vert, frag);
}
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
noStroke(); // No outlines for the rectangle that draws the shader
// Apply the shader to the canvas
shader(myShader);
// Initialize previous mouse position to current mouse position
prevMouseX = mouseX;
prevMouseY = mouseY;
}
function draw() {
// 1. Calculate mouse velocity
let dx = mouseX - prevMouseX;
let dy = mouseY - prevMouseY;
// Lerp (linear interpolate) velocity for smoothing and decay
// This makes the ripples settle slowly when the mouse stops moving
velocityX = lerp(velocityX, dx, 0.1);
velocityY = lerp(velocityY, dy, 0.1);
// Update previous mouse position for the next frame's calculation
prevMouseX = mouseX;
prevMouseY = mouseY;
// 2. Pass uniforms to the shader
// Uniforms are variables that you pass from p5.js to your shader
myShader.setUniform('u_resolution', [width, height]);
// Invert Y for mouse position and velocity to match WebGL coordinate system
myShader.setUniform('u_mouse', [mouseX, height - mouseY]);
myShader.setUniform('u_velocity', [velocityX, -velocityY]);
// Pass time for animations within the shader (e.g., color shifts, ripple speed)
myShader.setUniform('u_time', frameCount * 0.01); // frameCount is a p5.js built-in, 0.01 controls time speed
// 3. Draw a rectangle that covers the entire canvas
// This triggers the fragment shader for every pixel on the screen
rect(0, 0, width, height);
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
// Re-apply the shader after canvas resize to ensure it's still active
shader(myShader);
}
// === Shader Code (Embedded) ===
// Vertex Shader: Basic pass-through shader
// It takes the vertex position and texture coordinates and passes them to the fragment shader
const vert = `
// vertex.glsl
attribute vec3 aPosition; // Vertex position
attribute vec2 aTexCoord; // Texture coordinates
varying vec2 vTexCoord; // Pass texture coordinates to fragment shader
void main() {
vTexCoord = aTexCoord; // Assign texture coordinates
vec4 positionVec4 = vec4(aPosition, 1.0); // Convert position to 4D vector
gl_Position = positionVec4; // Set the final vertex position
}
`;
// Fragment Shader: The core logic for the liquid chrome surface
const frag = `
// fragment.glsl
precision highp float; // Use high precision for floats
varying vec2 vTexCoord; // Receive texture coordinates from vertex shader
// Uniforms passed from p5.js
uniform vec2 u_resolution; // Canvas width and height
uniform vec2 u_mouse; // Current mouse position (inverted Y)
uniform vec2 u_velocity; // Current mouse velocity (inverted Y)
uniform float u_time; // Elapsed time since sketch started
// Constants for raymarching
const int MAX_STEPS = 100; // Maximum raymarching steps
const float MAX_DIST = 10.0; // Maximum ray distance
const float EPSILON = 0.005; // Small value for collision detection and normal calculation
const float PI = 3.14159265359;
const float TWO_PI = PI * 2.0;
// Sphere Signed Distance Function (SDF)
// Returns the shortest distance from a point 'p' to a sphere of radius 'r' centered at the origin
float sdSphere(vec3 p, float r) {
return length(p) - r;
}
// Deformation SDF (ripple)
// Creates a wave-like deformation on the surface
float rippleSDF(vec3 p, float strength, float speed) {
float d = length(p.xy); // Calculate distance from the sphere's vertical axis
// Use a sine wave to create the ripple pattern, influenced by time and speed
float ripple = sin(d * 15.0 - u_time * speed) * strength;
return ripple;
}
// Combined Distance Function ( SDF )
// Defines the shape of our 3D object by combining a sphere and the ripple deformation
float df(vec3 p) {
float sphereRadius = 1.0;
float baseDist = sdSphere(p, sphereRadius); // Base sphere shape
// Mouse velocity determines the strength of the ripples
float velLen = length(u_velocity) * 0.01; // Scale velocity for appropriate ripple strength
velLen = clamp(velLen, 0.0, 0.5); // Cap the ripple strength to a maximum value
// Apply the ripple deformation
float rippleDist = rippleSDF(p, velLen, 2.0); // strength, speed
return baseDist + rippleDist; // Combine sphere and ripple SDFs
}
// Calculate normals from the SDF (simple distance-based)
// Normals are vectors perpendicular to the surface, used for lighting and reflection
vec3 calcNormal(vec3 p) {
vec2 e = vec2(EPSILON, 0.0); // Small offset for finite difference calculation
return normalize(vec3(
df(p + e.xyy) - df(p - e.xyy), // Calculate x-component of normal
df(p + e.yxy) - df(p - e.yxy), // Calculate y-component of normal
df(p + e.yyx) - df(p - e.yyx) // Calculate z-component of normal
));
}
// Fake environment reflection (procedural chrome look with color shifts)
// Generates a reflective pattern using sin/cos based on the reflection direction
vec3 fakeEnvReflection(vec3 refDir) {
refDir = normalize(refDir); // Ensure reflection direction is a unit vector
// Convert reflection direction to spherical coordinates for pattern generation
float u = atan(refDir.z, refDir.x) / TWO_PI + 0.5;
float v = asin(refDir.y) / PI + 0.5;
// Create various patterns using sin/cos functions
float pattern1 = sin(u * 10.0 + u_time * 0.1) * 0.5 + 0.5;
float pattern2 = cos(v * 8.0 + u_time * 0.05) * 0.5 + 0.5;
// Define base colors for the chrome reflection (blues and purples)
vec3 color1 = vec3(0.1, 0.2, 0.5); // Deep blue
vec3 color2 = vec3(0.4, 0.2, 0.6); // Purple
vec3 color3 = vec3(0.7, 0.8, 0.9); // Light metallic sheen
// Mix colors based on patterns to create the reflective effect
vec3 reflectionColor = mix(color1, color2, pattern1);
reflectionColor = mix(reflectionColor, color3, pattern2 * 0.5);
// Add subtle, time-based color shifts for more dynamism
reflectionColor += sin(u_time * 0.2) * vec3(0.05, 0.0, 0.05); // Subtle purple shift
reflectionColor += cos(u_time * 0.15) * vec3(0.0, 0.05, 0.05); // Subtle cyan shift
return reflectionColor;
}
void main() {
// Convert vTexCoord (0-1) to fragment coordinates (0-resolution)
vec2 fragCoord = vTexCoord * u_resolution;
vec2 uv = fragCoord / u_resolution;
uv = uv * 2.0 - 1.0; // Map to -1 to 1 range
// Adjust for aspect ratio
uv.x *= u_resolution.x / u_resolution.y;
// Raymarching setup
vec3 camPos = vec3(0.0, 0.0, -2.0); // Camera position slightly back along Z-axis
vec3 rayDir = normalize(vec3(uv, 1.0)); // Direction of the ray from camera through current pixel
vec3 currentPos = camPos; // Start ray at camera position
float totalDist = 0.0; // Total distance traveled by the ray
bool hit = false; // Flag to check if the ray hit the object
// Raymarching loop
for (int i = 0; i < MAX_STEPS; i++) {
float dist = df(currentPos); // Get distance to the object from current ray position
totalDist += dist; // Accumulate total distance
currentPos += rayDir * dist; // Move the ray forward by the distance
if (dist < EPSILON) { // If distance is very small, we've hit the object
hit = true;
break;
}
if (totalDist > MAX_DIST) { // If ray traveled too far, it missed the object
break;
}
}
vec3 finalColor = vec3(0.05, 0.05, 0.1); // Dark background color
if (hit) {
vec3 N = calcNormal(currentPos); // Calculate normal at the hit point
vec3 R = reflect(rayDir, N); // Calculate reflection vector
vec3 reflectionColor = fakeEnvReflection(R); // Get the reflective color
// Fresnel effect: Makes surfaces more reflective at grazing angles
float fresnel = pow(1.0 + dot(rayDir, N), 5.0);
fresnel = clamp(fresnel, 0.0, 1.0); // Clamp fresnel to 0-1 range
// Mix reflection color with a brighter color based on fresnel for edge glow
finalColor = mix(reflectionColor, vec3(1.0), fresnel * 0.5);
}
gl_FragColor = vec4(finalColor, 1.0); // Set the final pixel color
}
`;
```