A Pen by Ksenia Kondrashova on CodePen.
Created
September 20, 2024 23:13
-
-
Save Kvnbbg/6d7e29ff73673d6557caee522470883a to your computer and use it in GitHub Desktop.
Lightweight Pixel Distortion Animation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<input id="image-selector-input" style="visibility:hidden;" type="file"> | |
<div class="container"> | |
<canvas></canvas> | |
</div> | |
<script type="x-shader/x-fragment" id="vertShader"> | |
precision highp float; | |
varying vec2 vUv; | |
attribute vec2 a_position; | |
void main () { | |
vUv = .5 * (a_position + 1.); | |
gl_Position = vec4(a_position, 0., 1.); | |
} | |
</script> | |
<script type="x-shader/x-fragment" id="fragShader"> | |
precision highp float; | |
precision highp sampler2D; | |
varying vec2 vUv; | |
uniform sampler2D u_input_txr; | |
uniform sampler2D u_image_texture; | |
uniform vec2 u_pointer; | |
uniform vec3 u_dot_color; | |
uniform float u_time; | |
uniform float u_tile_scale; | |
uniform float u_offset; | |
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } | |
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; } | |
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); } | |
float snoise(vec2 v) { | |
const vec4 C = vec4(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 = mod289(i); | |
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); | |
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; | |
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; | |
return 130.0 * dot(m, g); | |
} | |
void main () { | |
vec2 sampling_uv = vUv; | |
sampling_uv.y = 1. - sampling_uv.y; | |
sampling_uv -= .5; | |
sampling_uv /= u_tile_scale; | |
vec2 fract_uv = fract(sampling_uv); | |
vec2 floor_uv = floor(sampling_uv); | |
fract_uv.x += u_offset * snoise(floor_uv + .001 * u_time); | |
sampling_uv = (floor_uv + fract_uv); | |
sampling_uv *= u_tile_scale; | |
sampling_uv += .5; | |
vec4 img_shifted = texture2D(u_image_texture, sampling_uv); | |
img_shifted.a = step(.1, img_shifted.a); | |
img_shifted.a *= step(0., sampling_uv.x); | |
img_shifted.a *= (1. - step(1., sampling_uv.x)); | |
gl_FragColor = img_shifted; | |
} | |
</script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import GUI from "https://cdn.jsdelivr.net/npm/[email protected]/+esm" | |
const containerEl = document.querySelector(".container")[0]; | |
const canvasEl = document.querySelector("canvas"); | |
const imgInput = document.querySelector("#image-selector-input"); | |
canvasEl.width = 0; | |
const devicePixelRatio = Math.min(window.devicePixelRatio, 2); | |
const params = { | |
tileSize: 33, | |
offset: .5, | |
loadMyImage: () => { | |
imgInput.click(); | |
} | |
}; | |
imgInput.onchange = () => { | |
const [file] = imgInput.files; | |
if (file) { | |
const reader = new FileReader(); | |
reader.onload = e => { | |
loadImage(e.target.result); | |
}; | |
reader.readAsDataURL(file); | |
} | |
}; | |
let image, uniforms; | |
const gl = initShader(); | |
updateUniforms(); | |
loadImage("https://ksenia-k.com/img/codepen/for-px-distortion-demo-2.jpg"); | |
createControls(); | |
render(); | |
window.addEventListener("resize", resizeCanvas); | |
function initShader() { | |
const vsSource = document.getElementById("vertShader").innerHTML; | |
const fsSource = document.getElementById("fragShader").innerHTML; | |
const gl = canvasEl.getContext("webgl") || canvasEl.getContext("experimental-webgl"); | |
if (!gl) { | |
alert("WebGL is not supported by your browser."); | |
} | |
function createShader(gl, sourceCode, type) { | |
const shader = gl.createShader(type); | |
gl.shaderSource(shader, sourceCode); | |
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; | |
} | |
return shader; | |
} | |
const vertexShader = createShader(gl, vsSource, gl.VERTEX_SHADER); | |
const fragmentShader = createShader(gl, fsSource, gl.FRAGMENT_SHADER); | |
function createShaderProgram(gl, vertexShader, fragmentShader) { | |
const program = gl.createProgram(); | |
gl.attachShader(program, vertexShader); | |
gl.attachShader(program, fragmentShader); | |
gl.linkProgram(program); | |
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { | |
console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(program)); | |
return null; | |
} | |
return program; | |
} | |
const shaderProgram = createShaderProgram(gl, vertexShader, fragmentShader); | |
uniforms = getUniforms(shaderProgram); | |
function getUniforms(program) { | |
let uniforms = []; | |
let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); | |
for (let i = 0; i < uniformCount; i++) { | |
let uniformName = gl.getActiveUniform(program, i).name; | |
uniforms[uniformName] = gl.getUniformLocation(program, uniformName); | |
} | |
return uniforms; | |
} | |
const vertices = new Float32Array([-1., -1., 1., -1., -1., 1., 1., 1.]); | |
const vertexBuffer = gl.createBuffer(); | |
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); | |
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); | |
gl.useProgram(shaderProgram); | |
const positionLocation = gl.getAttribLocation(shaderProgram, "a_position"); | |
gl.enableVertexAttribArray(positionLocation); | |
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); | |
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); | |
return gl; | |
} | |
function updateUniforms() { | |
gl.uniform1f(uniforms.u_tile_scale, params.tileSize / canvasEl.clientHeight); | |
gl.uniform1f(uniforms.u_offset, params.offset); | |
} | |
function loadImage(src) { | |
image = new Image(); | |
image.crossOrigin = "anonymous"; | |
image.src = src; | |
image.onload = () => { | |
const imageTexture = gl.createTexture(); | |
gl.bindTexture(gl.TEXTURE_2D, imageTexture); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); | |
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); | |
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); | |
gl.uniform1i(uniforms.u_image_texture, 0); | |
resizeCanvas(); | |
}; | |
} | |
function render() { | |
const currentTime = performance.now(); | |
gl.uniform1f(uniforms.u_time, currentTime); | |
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); | |
requestAnimationFrame(render); | |
} | |
function resizeCanvas() { | |
const imgRatio = image.naturalWidth / image.naturalHeight; | |
canvasEl.height = canvasEl.clientHeight * devicePixelRatio; | |
canvasEl.width = canvasEl.height * imgRatio; | |
gl.viewport(0, 0, canvasEl.width, canvasEl.height); | |
} | |
function createControls() { | |
const gui = new GUI(); | |
gui.close(); | |
gui | |
.add(params, "tileSize", 10, 100, 1) | |
.onChange(updateUniforms) | |
.name("tile size") | |
gui | |
.add(params, "offset", 0, 1) | |
.onChange(updateUniforms) | |
gui | |
.add(params, "loadMyImage") | |
.name("load image") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
html, body { | |
margin: 0; | |
overflow: hidden; | |
} | |
.container { | |
width: 100%; | |
height: 100vh; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
canvas { | |
display: block; | |
height: 55%; | |
} | |
.lil-gui { | |
--width: 450px; | |
max-width: 90%; | |
--widget-height: 20px; | |
font-size: 15px; | |
--input-font-size: 15px; | |
--padding: 10px; | |
--spacing: 10px; | |
--slider-knob-width: 5px; | |
--background-color: rgba(5, 0, 15, .8); | |
--widget-color: rgba(255, 255, 255, .3); | |
--focus-color: rgba(255, 255, 255, .4); | |
--hover-color: rgba(255, 255, 255, .5); | |
--font-family: monospace; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment