#ifdef GL_ES
precision mediump float; // Set floating point precision to medium on mobile/WebGL ES
#endif
// Input uniforms provided by the rendering system
uniform vec2 u_resolution; // Canvas size in pixels
uniform vec2 u_mouse; // Mouse position in pixels
uniform float u_time; // Time in seconds since shader started
uniform sampler2D u_webcam; // Webcam texture sampler
uniform vec2 u_webcam_resolution; // Webcam resolution in pixels
uniform float u_dpi; // Controls the density of the halftone pattern
uniform int u_color_theme; // Color mode (1=color, 2=grayscale, other=white)
uniform float u_pattern_density;
uniform float u_radius_modulation; // Controls how much the halftone pattern is affected by brightness
uniform bool u_invert_pattern; // Whether to invert the halftone pattern (0=normal, 1=inverted)
/**
* Adjusts UV coordinates to maintain aspect ratio when mapping a texture to canvas
* This ensures the image is "covered" properly without distortion
*
* @param textureAR Aspect ratio of the texture (width/height)
* @param canvasAR Aspect ratio of the canvas (width/height)
* @param uv The original UV coordinates
* @return Adjusted UV coordinates that maintain proper aspect ratio
*/
vec2 adjustUV(float textureAR, float canvasAR, vec2 uv) {
bool isLandscape = canvasAR < textureAR; // Check if the canvas is wider than it is tall relative to texture
float scale = isLandscape ? canvasAR / textureAR : textureAR / canvasAR; // Calculate scaling factor
float x = !isLandscape ? uv.x : (uv.x - 0.5) * scale + 0.5; // Center and scale X if needed
float y = isLandscape ? uv.y : (uv.y - 0.5) * scale + 0.5; // Center and scale Y if needed
return vec2(x, y); // Return adjusted coordinates
}
void main() {
// Convert fragment coordinate to normalized [0,1] UV space
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
// Calculate aspect ratios for the webcam and canvas
float videoAR = u_webcam_resolution.x / u_webcam_resolution.y; // Webcam aspect ratio
float canvasAR = u_resolution.x / u_resolution.y; // Canvas aspect ratio
// Adjust UVs to maintain proper aspect ratio using the "cover" approach
vec2 adjustedUV = adjustUV(videoAR, canvasAR, uv);
// Flip X coordinate to correct webcam mirroring
adjustedUV.x = 1.0 - adjustedUV.x;
// Set up pixelation effect for the webcam image
vec2 pixelatedUv = adjustedUV;
pixelatedUv = pixelatedUv * 2.0 - 1.0; // Convert to [-1,1] range
// Determine if the canvas is being cropped horizontally
bool isCroppedHor = canvasAR < videoAR;
// Calculate pixelated coordinates based on aspect ratio
// This creates a blocky/pixelated look by quantizing the UV coordinates
float pixelX1 = floor(((pixelatedUv.x + 1.) / 2.) * u_dpi) / (u_dpi);
float pixelY1 = (floor(pixelatedUv.y * u_dpi / videoAR / 2.) / (u_dpi / videoAR / 2.) + 1.) / 2.;
float pixelX2 = (floor(pixelatedUv.x * u_dpi * videoAR / 2.) / (u_dpi * videoAR / 2.) + 1.) / 2.;
float pixelY2 = floor(((pixelatedUv.y + 1.) / 2.) * u_dpi) / (u_dpi);
// Choose the appropriate pixelation based on the cropping direction
float pixelX = isCroppedHor ? pixelX2 : pixelX1;
float pixelY = isCroppedHor ? pixelY2 : pixelY1;
// Sample the webcam texture with pixelated coordinates
vec4 texel = texture2D(u_webcam, vec2(pixelX, pixelY));
// Calculate brightness using standard luminance weights
vec3 luma = vec3(0.2126, 0.7152, 0.0722); // Standard RGB to luminance conversion weights
float brightness = dot(texel.rgb, luma); // Calculate perceived brightness
// Apply color theme based on user selection
vec3 color = u_color_theme == 1 ? vec3(texel.rgb) : // Original color
u_color_theme == 2 ? vec3(brightness) : // Grayscale
vec3(1.); // White (default)
// Prepare UV coordinates for halftone pattern
vec2 halftoneUv = uv;
halftoneUv = halftoneUv * 2.0 - 1.0; // Convert to [-1,1] range
// Scale halftone pattern based on aspect ratio
halftoneUv.x = isCroppedHor ? halftoneUv.x * u_resolution.x / u_resolution.y : halftoneUv.x;
halftoneUv.y = isCroppedHor ? halftoneUv.y : halftoneUv.y * u_resolution.y / u_resolution.x;
// Create a repeating grid for the halftone pattern
float posOffX = isCroppedHor ? 0. : mod(u_dpi, 2.) / 2.; // Offset X for even/odd DPI
float posOffY = isCroppedHor ? mod(u_dpi, 2.) / 2. : 0.; // Offset Y for even/odd DPI
// Divide space into grid cells
halftoneUv.x = fract(halftoneUv.x * u_dpi / 2. + posOffX);
halftoneUv.y = fract(halftoneUv.y * u_dpi / 2. + posOffY);
// Remap each grid cell to [-1,1] range
halftoneUv = halftoneUv * 2.0 - 1.0;
// Set parameters for the halftone effect
float blur = u_dpi * 0.0025; // Edge blur amount for the circles (scales with DPI)
// Calculate circle radius based on brightness and modulation
// Higher brightness = larger circles when modulation is less than 1
float rad = (1. - u_radius_modulation + (brightness * u_radius_modulation)) * u_pattern_density;
// Create a circle in each grid cell
float d = length(halftoneUv); // Distance from center of cell
// Apply smoothstep to create a soft-edged circle
d = smoothstep(rad - blur, rad + blur, d);
// Apply pattern inversion if selected
d = u_invert_pattern ? d : (1. - d);
// Apply halftone pattern to the color
color = color * d;
// Output final color with original alpha
gl_FragColor = vec4(color, texel.a);
}