Skip to content

Instantly share code, notes, and snippets.

@Kvnbbg
Created September 20, 2024 23:13
Show Gist options
  • Save Kvnbbg/6d7e29ff73673d6557caee522470883a to your computer and use it in GitHub Desktop.
Save Kvnbbg/6d7e29ff73673d6557caee522470883a to your computer and use it in GitHub Desktop.
Lightweight Pixel Distortion Animation
<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>
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")
}
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