Plane deformation

Notes on plane deformation effects


The effect can be performed in a fragment shader on any textured quad with uv coords 'normalized' to place (0,0) at the center before performing any calculations.

the brick texture is sourced from the Pixar One Twenty Eight texture library

drawing drawing
before deform after deform


A tunnel effect can be created by converting the the uv's to polar coordnate space and stretching things out a tad

void main() {
    vec2 uv = vUv * 2.0 - 1.0;
    gl_FragColor = deformationFunction(uv);
vec2 deformationFunction(vec2 uv) {
    vec2 p = uv;    
    float a = atan( p.y, p.x ) ;
    float r = sqrt( dot(p,p) );
    uv.x = 3.0/(r + .1);
    uv.y = 3.0*a/3.1416;        
    return uv;


Not quite what we want there's a large amount of distortion which makes the result looks nothing like a tunnel, the values for uv.x and uv.y are growing out of bounds (< 0.0 && > 1.0) one quick fix for this is clamping the result based on a threshold, grabbing the fractional component could also work

vec2 clampUv(vec2 uv, float m) {
    return vec2( mod(uv.x, m), mod(uv.y, m));


Now we have something that looks like a tunnel but not quite, what's missing is depth which can be added by applying a circular gradiant to the alpha channel 0.0 in the center expanding out to 1.0

Since uv coords were adjusted our coordnate space to have (0.0, 0.0) in the center we can just smoothstep the alpha component

float alpha = smoothstep(0.0, 0.5, length(uv.y));


Expanding on what we have

With a base to work off we can continue experimenting with different deformations

lut_6 lut_5 lut_7

Interpolation between scenes

Transitioning between deformations can be handled by mixing the computed uv values

    DistortOut a = doForScene(scene, p);
    DistortOut b = doForScene(scene+1, p);
    vec2 mixUV = mix(a.uv, b.uv, sceneDelta);

Scene management

All that's left to do is figuring out which scene is being displayed and it's progress based on the current time

int sceneIndex(float time, float sceneLength, float totalScenes) {
    float sceneIndex = time / sceneLength;
    float scene = mod(floor(sceneIndex), totalScenes);
    int sI = int(scene);
    return sI;
float sceneDelta(float time, float sceneLength) {
    float sceneIndex = floor(time / sceneLength);
    float sceneStartTime = (sceneIndex) * sceneLength;
    float progress = time - sceneStartTime;
    return progress / sceneLength;