Skip to content

Instantly share code, notes, and snippets.

@wschutzer
Created May 4, 2025 13:36
Show Gist options
  • Save wschutzer/8a2233f2e4b19e6fff0bd40dc89538ff to your computer and use it in GitHub Desktop.
Save wschutzer/8a2233f2e4b19e6fff0bd40dc89538ff to your computer and use it in GitHub Desktop.
Black and white sliding disks
/* Sliding disks with offsets
* --------------------------
*
* This program draws two superimposed grids of black and white disks. One grid remains
* stationary and the other grid moves from right to left. As it does, optical effects
* are created which depends on small horizontal offsets applied to the disks.
*
* This code uses shaders to aproximate the motion blur effect by averaging the pixels along a sequence
* of frames.
*
* Copyright (C) 2025 Waldeck Schutzer (@infinitymathart)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
final boolean recording = false;
final int duration = 10; //Seconds
final int fps = 60; // Frames Per Second
final float num_frames = fps*duration;
// Motion blur:
final int samples_per_frame = 10; // How many frames to average?
final float shutter_angle = 2.4; // How distant the frames are in time?
final int frameWidth = recording ? 2160 : 500;
final float fac = 1.0*frameWidth/500;
final float aspect = 1350.0/1080; // 1920.0/1080;
final int frameHeight = int(aspect * frameWidth);
final float whiteSize = 0.99;
final float blackSize = 0.97;
float t = 0; // Normalized animation time in the interval 0 - 1.
PShader motionBlur;
PGraphics gtex0; // Each frame is drawn here
PFont courier;
// Vertex shader code
String[] motionBlurVertShader = {
"#version 100",
"#define PROCESSING_TEXTURE_SHADER",
"uniform mat4 transformMatrix;",
"uniform mat4 texMatrix;",
"attribute vec4 position;",
"attribute vec4 color;",
"attribute vec2 texCoord;",
"varying vec4 vertColor;",
"varying vec4 vertTexCoord;",
"void main() {",
"gl_Position = transformMatrix * position;",
"vertTexCoord = texMatrix * vec4(texCoord, 1.0, 1.0);",
"vertColor = color;",
"}"};
String[] motionBlurFragShader = {
"#version 100",
"#ifdef GL_ES",
"precision mediump float;",
"precision mediump int;",
"#endif",
"#define PROCESSING_TEXTURE_SHADER",
"uniform sampler2D texture; // Source and result frame, works as an accumulator",
"uniform sampler2D tex0; // Currently drawn frame to be added",
"uniform float alpha;",
"uniform float beta;",
"varying vec4 vertColor; // Seems to be white",
"varying vec4 vertTexCoord;",
"void main()",
"{",
" vec4 curColor = texture2D(tex0, vertTexCoord.xy); // Color of current pixel ",
" vec4 accColor = texture2D(texture, vertTexCoord.xy); // Color in accumulator",
" gl_FragColor = beta*(alpha*accColor + curColor); // Blending",
"}"};
class disk
{
float x = 0, y = 0 , r = 1;
color c;
float offset = 0;
disk(float _x, float _y, float _r, color _c)
{
x = _x; y = _y; r = _r; c = _c;
}
void draw(PGraphics g, float t)
{
g.fill(c);
g.noStroke();
g.circle(x + (t<0 ? 0 : offset + map(t, 0, 1, 0, -gridCellSize)), y, r);
}
};
int gridWidth=11;
int gridHeight=11;
int gridCellWidth = (frameWidth-100)/gridWidth;
int gridCellHeight = (frameHeight-100)/gridHeight;
int gridCellSize = (gridCellWidth < gridCellHeight) ? gridCellWidth : gridCellHeight;
int gridLeft = (frameWidth - gridCellSize * gridWidth + gridCellSize) / 2;
int gridTop = (frameHeight - gridCellSize * gridHeight + gridCellSize) / 2;
disk[] disks1;
disk[] disks2;
void settings()
{
size(frameWidth, frameHeight, P2D); // Use P2D renderer for shader support
}
void setup()
{
gtex0 = createGraphics(width, height, P2D);
gtex0.smooth(4);
motionBlur = new PShader(this, motionBlurVertShader, motionBlurFragShader);
int k = 0;
disks1 = new disk[gridWidth*gridHeight];
disks2 = new disk[(gridWidth+2)*gridHeight];
for(int i=0; i<gridWidth; i++)
{
float x = i*gridCellSize + gridLeft;
for(int j=0; j<gridHeight; j++)
{
float y = j*gridCellSize + gridTop;
disks1[k] = new disk(x, y, whiteSize*gridCellSize, color(255,255,255));
k++;
}
}
k = 0;
for(int i=0; i<gridWidth+2; i++)
{
float x = i*gridCellSize + gridLeft - gridCellSize;
for(int j=0; j<gridHeight; j++)
{
float y = j*gridCellSize + gridTop;
disks2[k] = new disk(x, y, blackSize*gridCellSize, color(0,0,0));
// Different offsets create different effects
// disks2[k].offset = 1.0*fac*(i+j); // top-left to bottom-right
// disks2[k].offset = 0.5*fac*(j); // top to bottom
// disks2[k].offset = 0.5*fac*(i-j); // bottom-left to top-right
disks2[k].offset = 0.5*fac*sqrt(sq(i-(gridWidth+1)/2.0)+sq(j-gridHeight/2.0)); // propagating from center
k++;
}
}
courier = createFont("Courier New",14*fac,true);
}
void draw()
{
background(0);
for(int i=0; i<samples_per_frame; i++)
{
t = map((recording ? 1.0*(frameCount-1): 1.0*mouseX/width*num_frames) + i*shutter_angle/samples_per_frame, 0, num_frames, 0, 1)%1;
draw_(gtex0,t); // Draw on gtex1
motionBlur.set("tex0", gtex0);
motionBlur.set("alpha", (float)(i));
motionBlur.set("beta", (float)(1.0/(i+1)));
filter(motionBlur); // Averages the frames with a moving average
}
if (recording)
{
println(frameCount + "/" + num_frames);
saveFrame("/tmp/r/frame_####.png");
if (frameCount >= num_frames)
exit();
}
}
void draw_(PGraphics g,float t)
{
g.beginDraw();
g.background(0); // Clear the canvas
g.noStroke();
for(int i=0; i<gridWidth*gridHeight; i++)
disks1[i].draw(g, -1);
for(int i=0; i<(gridWidth+2)*gridHeight; i++)
disks2[i].draw(g, t);
postDraw(g);
g.endDraw();
}
void postDraw(PGraphics g)
{
// texts
g.pushMatrix();
g.translate(width/2,height/2);
g.fill(255);stroke(255);
g.textAlign(CENTER, CENTER);
g.textFont(courier);
g.text("@infinitymathart • 2025",0.0*width,0.41*height);
g.popMatrix();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment