Project_2025-12-29_14-46-59
GLSL shader by scry · created 2025-12-29 · updated 2025-12-30 · 10s loop · 5 passes
This page opens the shader in the ShaderKit browser GLSL editor: edit it live, fork it, or render it to video, GIF or images up to 8K. Also available as a full-screen view and an embeddable player.
Shader source (GLSL)
Common
// ============================================
// UTILITY MACROS & FUNCTIONS
// ============================================
#define pi acos(-1.)
#define deg (pi/180.) // 1 degree in radians
#define time (iTime*2.*pi/10.) // sin(time) loops every 10 seconds
// Shorthand accessors
#define R iResolution.xy
#define ar (R.x/R.y) // aspect ratio
#define M iMouse
#define xm (M.xy/R) // normalized mouse [0,1]
#define nm ((xm.xy-0.5)*vec2(ar,1.)+0.5) // aspect-corrected mouse
// 2D rotation matrix
mat2 r2d(float a) {
return mat2(cos(a),sin(a),-sin(a),cos(a));
}
// Hash function for pseudo-randomness
float hash(float n) {
return fract(sin(n) * 43758.5453123);
}
// Color seed for barrier coloring
vec3 cs = vec3(1.,2.,3.);
// ============================================
// DATA BUFFER LAYOUT (iChannel0)
// ============================================
#define BALL_POS ivec2(0,0)
#define SCORE_POS ivec2(1,0)
#define BALL_RADIUS_POS ivec2(2,0)
#define BARRIER_START 2 // First pixel x-index for barrier data
#define NUM_PARTICLES 1 // Particles per row for explosions
#define PARTICLE_ROWS 1 // Number of rows for particle data
// ============================================
// BARRIER CONFIGURATION
// ============================================
#define BARRIER_SPACING 0.4
#define NUM_BARRIERS 200 // Number of concentric ring barriers
#define BARRIER_INITIAL_HEALTH 30.
#define BARRIER_ROTATION_SPEED -0.2 // radians per second
// Gap configuration
#define GAP_WIDTH_MODE 1.5 // 0.0=angular, 1.0=constant width
#define BASE_GAP_SIZE 0.08 // base gap size
#define GAP_DESTROYS_BARRIER 1. // >0.5 means passing through gap destroys barrier
#define GAP_VELOCITY_MULTIPLIER 1.5 // velocity multiplier when passing through gap
// Returns the radius of barrier i (innermost = 0)
float barrierRadius(int i) {
return 0.15 + iTime*-0.09 + float(i) * BARRIER_SPACING;
}
// ============================================
// BALL PHYSICS
// ============================================
#define BALL_ELASTICITY .88
#define GRAVITY 1.8 // Gravity acceleration (units per second^2)
#define BOUNCE_RANDOMNESS 0.5 // Amount of random angle deviation on bounce (0.0=none, 0.1=~6 degrees)
// ============================================
// WORLD BOUNDARIES & CAMERA
// ============================================
#define OUTER_BOUNDARY_RADIUS 2.85
#define CAMERA_FOLLOW_AMOUNT -0.7 // 0.0=static, 1.0=fully centered on ball
// ============================================
// DEBUG
// ============================================
#define DEBUG_VISUALIZATION 0.0
Buffer A (iChannel0)
// iChannel0: Data storage buffer
// Pixel (0,0): ball position (xy), velocity (zw)
// Pixel (1,0): score (x), unused (yzw)
// Pixel (2,0): ball radius (x), unused (yzw)
// Pixels (2-9,0): barrier health (x), gap angle (y), destroyed flag (z)
// gap passage flag (w) - set to 1.0 when ball passes through
// Pixels (0-63,1-4): particle data (pos.xy, vel.xy) per particle
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
ivec2 px = ivec2(fragCoord);
vec4 data = texelFetch(iChannel0, px, 0);
float dt = 1.0 / 60.0;
// Initialize on first frame
if (iFrame < 2) {
if (px == BALL_POS) {
fragColor = vec4(0.0, 0.0, 0.3, 0.4); // pos, vel
} else if (px == SCORE_POS) {
fragColor = vec4(0.0, 0.0, 0.0, 0.0); // score starts at 0
} else if (px == BALL_RADIUS_POS) {
fragColor = vec4(0.02, 0.0, 0.0, 0.0); // ball radius
} else if (px.y == 0 && px.x >= BARRIER_START && px.x < BARRIER_START + NUM_BARRIERS) {
int idx = px.x - BARRIER_START;
float gapAngle = float(idx)*time*0.1 + 0.; // all gaps same angle for now
fragColor = vec4(BARRIER_INITIAL_HEALTH, gapAngle, 0.0, 0.0); // health, gap angle, destroyed, gap_passed
} else if (px.y >= 1 && px.y <= PARTICLE_ROWS && px.x < NUM_PARTICLES) {
fragColor = vec4(0.0); // inactive particles
} else {
fragColor = vec4(0.0);
}
return;
}
// Ball physics (pixel 0,0)
if (px == BALL_POS) {
vec2 pos = data.xy;
vec2 vel = data.zw;
// Check if any barrier flagged a gap passage last frame, apply velocity reduction
for (int i = 0; i < NUM_BARRIERS; i++) {
vec4 barrier = texelFetch(iChannel0, ivec2(BARRIER_START + i, 0), 0);
// w component stores gap passage flag from last frame
if (barrier.w > 0.5) {
vel *= GAP_VELOCITY_MULTIPLIER;
break; // Only apply once per frame even if multiple gaps passed
}
}
float originalSpeed = length(vel);
float ballRadius = texelFetch(iChannel0, ivec2(2, 0), 0).x;
if (ballRadius < 0.001) ballRadius = 0.02; // Fallback
vel.y -= GRAVITY * dt; // gravity
// Iterative collision - handle multiple bounces per frame
float remainingDt = dt;
for (int bounce = 0; bounce < 10 && remainingDt > 0.0001; bounce++) {
vec2 rayOrigin = pos;
vec2 rayDir = vel * remainingDt;
bool collided = false;
float closestT = 2.0;
int closestBarrier = -1;
vec2 closestNormal = vec2(0.0);
bool hitGapEdge = false;
for (int i = 0; i < NUM_BARRIERS; i++) {
vec4 barrier = texelFetch(iChannel0, ivec2(BARRIER_START + i, 0), 0);
if (barrier.z > 0.5) continue;
float r = barrierRadius(i);
float gapAngle = barrier.y + iTime * BARRIER_ROTATION_SPEED;
float gapSize = mix(BASE_GAP_SIZE, BASE_GAP_SIZE / r, GAP_WIDTH_MODE);
// Check collision with expanded radius (barrier +/- ball radius)
// First check gap edge collisions (radial walls at gap boundaries)
// Gap edges are at angles: gapAngle - gapSize and gapAngle + gapSize
for (int edgeIdx = 0; edgeIdx < 2; edgeIdx++) {
float edgeAngle = gapAngle + (edgeIdx == 0 ? -gapSize : gapSize);
vec2 edgeDir = vec2(cos(edgeAngle), sin(edgeAngle));
vec2 edgeNormal = vec2(-sin(edgeAngle), cos(edgeAngle)) * (edgeIdx == 0 ? 1.0 : -1.0);
// Ray-line intersection: check if ray crosses the radial edge line
// Edge line goes from origin outward along edgeDir
// We only care about the segment from inner to outer radius of the ring
// Parametric line: P = rayOrigin + t * rayDir
// Edge constraint: dot(P - origin, edgeNormal) = 0 (on the edge plane)
// Radial constraint: length(P) between (r - ballRadius) and (r + ballRadius)
float denom = dot(rayDir, edgeNormal);
if (abs(denom) > 0.0001) {
float t = -dot(rayOrigin, edgeNormal) / denom;
if (t > 0.00001 && t < 1.0 && t < closestT) {
vec2 hitPos = rayOrigin + rayDir * t;
float hitDist = length(hitPos);
// Check if hit point is within the ring thickness
if (hitDist > r - ballRadius * 0.5 && hitDist < r + ballRadius * 0.5) {
// Check if we're hitting from the correct side (inside the gap)
float hitAngle = atan(hitPos.y, hitPos.x);
float angleFromGapCenter = mod(hitAngle - gapAngle + 3.0*pi, 2.0*pi) - pi;
// Only register hit if coming from inside the gap
bool comingFromGap = abs(angleFromGapCenter) < gapSize * 1.2;
if (comingFromGap) {
closestT = t;
closestBarrier = i;
closestNormal = edgeNormal;
collided = true;
hitGapEdge = true;
}
}
}
}
}
// We need to check both inner and outer edges
float rOuter = r + ballRadius * 0.5;
float rInner = max(r - ballRadius * 0.5, 0.001);
// Ray-circle intersection: |rayOrigin + t*rayDir|^2 = r^2
float a = dot(rayDir, rayDir);
if (a < 0.000001) continue; // No movement
float b = 2.0 * dot(rayOrigin, rayDir);
// Check both the outer and inner collision surfaces
for (int surface = 0; surface < 2; surface++) {
float checkR = (surface == 0) ? rOuter : rInner;
float c = dot(rayOrigin, rayOrigin) - checkR * checkR;
float discriminant = b * b - 4.0 * a * c;
if (discriminant >= 0.0) {
float sqrtDisc = sqrt(discriminant);
float t1 = (-b - sqrtDisc) / (2.0 * a);
float t2 = (-b + sqrtDisc) / (2.0 * a);
// Check both intersections
for (int j = 0; j < 2; j++) {
float t = (j == 0) ? t1 : t2;
if (t > 0.00001 && t < 1.0 && t < closestT) {
vec2 hitPos = rayOrigin + rayDir * t;
float hitDist = length(hitPos);
// Get the actual barrier contact point (on the barrier circle, not expanded)
vec2 contactPoint = normalize(hitPos) * r;
float hitAngle = atan(hitPos.y, hitPos.x);
// Calculate gap angle at the exact collision time
float collisionTime = iTime - remainingDt + t * remainingDt;
float adjustedGap = barrier.y + collisionTime * BARRIER_ROTATION_SPEED;
// Check if in gap with some tolerance
float angleDiff = mod(hitAngle - adjustedGap + 3.0*pi, 2.0*pi) - pi;
bool inGap = abs(angleDiff) < gapSize * 1.1; // Slight tolerance
if (!inGap) {
closestT = t;
closestBarrier = i;
closestNormal = normalize(contactPoint);
collided = true;
}
}
}
}
}
}
if (collided) {
// Store collision info for barrier damage
vec2 hitPos = rayOrigin + rayDir * closestT;
// Gap edges use the stored normal directly
if (hitGapEdge) {
vec2 n = closestNormal;
// Reflect velocity off gap edge
vel = reflect(vel, n) * BALL_ELASTICITY;
// Add random perturbation
float randAngle = (hash(iTime * 1000.0 + float(closestBarrier) * 13.7) - 0.5) * BOUNCE_RANDOMNESS;
vel = r2d(randAngle) * vel;
// Push ball away from edge
pos = hitPos + n * 0.005;
remainingDt *= (1.0 - closestT);
} else {
// Original ring collision code
float r = barrierRadius(closestBarrier);
bool fromInside = length(rayOrigin) < r;
vec2 n = fromInside ? -closestNormal : closestNormal;
// Account for barrier motion (moving inward)
vec2 barrierVelocity = normalize(closestNormal) * (-0.1);
vec2 relativeVel = vel - barrierVelocity;
vec2 reflectedRelativeVel = reflect(relativeVel, n);
vel = reflectedRelativeVel + barrierVelocity;
vel *= BALL_ELASTICITY;
// Add random perturbation
float randAngle = (hash(iTime * 1000.0 + float(closestBarrier) * 13.7) - 0.5) * BOUNCE_RANDOMNESS;
vel = r2d(randAngle) * vel;
// Push ball away from barrier surface
if (fromInside) {
pos = normalize(hitPos) * (r - ballRadius - 0.002);
} else {
pos = normalize(hitPos) * (r + ballRadius + 0.002);
}
remainingDt *= (1.0 - closestT);
}
} else {
pos = rayOrigin + rayDir;
break;
}
}
// Final safety check: ensure ball isn't stuck inside any barrier
for (int i = 0; i < NUM_BARRIERS; i++) {
vec4 barrier = texelFetch(iChannel0, ivec2(BARRIER_START + i, 0), 0);
if (barrier.z > 0.5) continue;
float br = barrierRadius(i);
float distFromCenter = length(pos);
float gapAngle = barrier.y + iTime * BARRIER_ROTATION_SPEED;
float gapSize = mix(BASE_GAP_SIZE, BASE_GAP_SIZE / br, GAP_WIDTH_MODE);
float posAngle = atan(pos.y, pos.x);
float angleDiff = mod(posAngle - gapAngle + 3.0*pi, 2.0*pi) - pi;
bool inGap = abs(angleDiff) < gapSize;
// If ball center is very close to the barrier ring and not in gap, push out
// Only push outward relative to THIS barrier, not to the outermost one
if (abs(distFromCenter - br) < ballRadius * 0.8 && !inGap) {
// Push to whichever side (inside or outside) is closer
float pushDist = (distFromCenter > br) ? (br + ballRadius + 0.005) : (br - ballRadius - 0.005);
pos = normalize(pos) * pushDist;
vel = reflect(vel, normalize(pos)) * BALL_ELASTICITY;
}
}
// Outer boundary check
float dist = length(pos);
if (dist > OUTER_BOUNDARY_RADIUS - ballRadius) {
pos = normalize(pos) * (OUTER_BOUNDARY_RADIUS - ballRadius - 0.005);
vel = reflect(vel, -normalize(pos)) * BALL_ELASTICITY;
}
fragColor = vec4(pos, vel);
}
// Score (pixel 1,0)
else if (px == SCORE_POS) {
// Count destroyed barriers as score
float score = 0.0;
for (int i = 0; i < NUM_BARRIERS; i++) {
vec4 barrier = texelFetch(iChannel0, ivec2(BARRIER_START + i, 0), 0);
if (barrier.z > 0.5) score += 1.0; // Count destroyed barriers
}
fragColor = vec4(score, 0.0, 0.0, 0.0);
}
// Barrier data
else if (px.y == 0 && px.x >= BARRIER_START && px.x < BARRIER_START + NUM_BARRIERS) {
int idx = px.x - BARRIER_START;
float r = barrierRadius(idx);
vec4 prevBallData = texelFetch(iChannel0, BALL_POS, 0);
vec4 ballData = texelFetch(iChannel0, BALL_POS, 0);
vec2 ballPos = ballData.xy;
vec2 ballVel = ballData.zw;
float ballDist = length(ballPos);
float gapAngle = data.y;
float ballRadius = texelFetch(iChannel0, ivec2(2, 0), 0).x;
if (ballRadius < 0.001) ballRadius = 0.02;
float angularGap = BASE_GAP_SIZE;
float constantWidthGap = BASE_GAP_SIZE / r;
float gapSize = mix(angularGap, constantWidthGap, GAP_WIDTH_MODE);
float health = data.x;
float destroyed = data.z;
float gapPassed = 0.0; // Reset gap passage flag each frame
// Check if gap is larger than the entire ring circumference
float ringCircumference = 2.0 * pi * r;
float gapArcLength = gapSize * r; // Convert angular gap to arc length
if (gapArcLength >= ringCircumference) {
health = 0.0;
destroyed = 1.0;
}
// Only process if not already destroyed
if (data.z < 0.5) {
// Detect if ball passed through this barrier's gap OR hit the solid part
// Get previous frame's ball data to detect crossing
vec4 prevBallDataRaw = texelFetch(iChannel0, BALL_POS, 0);
// We need to check the ball's actual trajectory
// Since ball physics already ran, we reconstruct previous position
// But we also check current position relative to barrier
float currDist = length(ballPos);
bool hitBarrier = false;
bool passedThroughGap = false;
// More robust gap passage detection:
// Check if ball is very close to the barrier radius AND in the gap region
// This catches cases where the ball just passed through
float distToBarrier = abs(currDist - r);
// Calculate current gap angle
float currentGapAngle = data.y + iTime * BARRIER_ROTATION_SPEED;
float ballAngle = atan(ballPos.y, ballPos.x);
float angleDiffCurr = mod(ballAngle - currentGapAngle + 3.0*pi, 2.0*pi) - pi;
bool ballInGapRegion = abs(angleDiffCurr) < gapSize * 1.3;
// Check if ball recently crossed this barrier by looking at velocity direction
// If ball is moving outward and is just outside, or moving inward and just inside
float radialVel = dot(normalize(ballPos), ballVel);
bool movingOutward = radialVel > 0.0;
bool justOutside = currDist > r && currDist < r + ballRadius * 2.0 + length(ballVel) * dt;
bool justInside = currDist < r && currDist > r - ballRadius * 2.0 - length(ballVel) * dt;
// Detect gap passage: ball crossed through gap region recently
if (ballInGapRegion) {
if ((movingOutward && justOutside) || (!movingOutward && justInside)) {
passedThroughGap = true;
gapPassed = 1.0;
}
}
// Also check with ray-based method for redundancy
vec2 prevBallPos = ballPos - ballVel * dt;
float prevDist = length(prevBallPos - ballVel * dt * 0.5); // Look further back to catch slow collisions
bool crossedOutward = (prevDist < r - ballRadius * 0.5 && currDist >= r + ballRadius * 0.5);
bool crossedInward = (prevDist > r + ballRadius * 0.5 && currDist <= r - ballRadius * 0.5);
if ((crossedOutward || crossedInward) && ballInGapRegion) {
// Verify the crossing happened through the gap
float crossAngle = atan(ballPos.y, ballPos.x);
float angleDiff = mod(crossAngle - currentGapAngle + 3.0*pi, 2.0*pi) - pi;
bool inGap = abs(angleDiff) < gapSize * 1.5; // More generous for gap passage
if (inGap) {
passedThroughGap = true;
gapPassed = 1.0;
}
}
// Check both inner and outer collision surfaces
vec2 rayOrigin = prevBallPos;
vec2 rayDir = ballVel * dt;
for (int surface = 0; surface < 2; surface++) {
float checkR = (surface == 0) ? (r + ballRadius) : max(r - ballRadius, 0.001);
float a = dot(rayDir, rayDir);
if (a < 0.000001) continue;
float b = 2.0 * dot(rayOrigin, rayDir);
float c = dot(rayOrigin, rayOrigin) - checkR * checkR;
float discriminant = b * b - 4.0 * a * c;
if (discriminant >= 0.0) {
float sqrtDisc = sqrt(discriminant);
float t1 = (-b - sqrtDisc) / (2.0 * a);
float t2 = (-b + sqrtDisc) / (2.0 * a);
for (int j = 0; j < 2; j++) {
float t = (j == 0) ? t1 : t2;
if (t >= 0.0 && t <= 1.0) {
vec2 hitPos = rayOrigin + rayDir * t;
float hitAngle = atan(hitPos.y, hitPos.x);
// Calculate gap angle at collision time
float collisionTime = iTime - dt + t * dt;
float adjustedGap = data.y + collisionTime * BARRIER_ROTATION_SPEED;
float angleDiff = mod(hitAngle - adjustedGap + 3.0*pi, 2.0*pi) - pi;
bool inGap = abs(angleDiff) < gapSize * 1.1;
// Only register hit if NOT in gap (ball bounced off solid part)
if (!inGap && !passedThroughGap) {
hitBarrier = true;
break;
}
}
}
}
if (hitBarrier) break;
}
// If ball passed through the gap and GAP_DESTROYS_BARRIER is enabled, destroy the barrier
if (passedThroughGap && GAP_DESTROYS_BARRIER > 0.5) {
health = 0.0;
destroyed = 1.0;
}
// If ball hit the solid part, reduce health
if (hitBarrier) {
health -= 1.0;
if (health <= 0.0) {
health = 0.0;
destroyed = 1.0;
}
}
}
fragColor = vec4(health, data.y, destroyed, gapPassed);
}
// Particle data
else if (px.y >= 1 && px.y <= PARTICLE_ROWS && px.x < NUM_PARTICLES) {
vec2 ppos = data.xy;
vec2 pvel = data.zw;
if (length(pvel) > 0.01) {
pvel.y -= 0.3 * dt;
pvel *= 0.98;
ppos += pvel * dt;
}
fragColor = vec4(ppos, pvel);
}
else {
fragColor = data;
}
}
Buffer B (iChannel1)
// iChannel1: Collision detection buffer (currently handled in iChannel0)
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
fragColor = texelFetch(iChannel0, ivec2(fragCoord), 0);
}
Buffer C (iChannel2)
// iChannel2: Visualization buffer
// Simple digit rendering
float digit(vec2 p, int d) {
p = p * 2.0 - 1.0;
if (abs(p.x) > 0.4 || abs(p.y) > 0.5) return 0.0;
// 7-segment display patterns for digits 0-9
// Segments: top=1, top-right=2, bottom-right=4, bottom=8, bottom-left=16, top-left=32, middle=64
int segs = 0;
if (d == 0) segs = 63; // 0: all but middle
else if (d == 1) segs = 6; // 1: right side only
else if (d == 2) segs = 91; // 2: top, top-right, middle, bottom-left, bottom
else if (d == 3) segs = 79; // 3: top, top-right, middle, bottom-right, bottom
else if (d == 4) segs = 102; // 4: top-left, middle, top-right, bottom-right
else if (d == 5) segs = 109; // 5: top, top-left, middle, bottom-right, bottom
else if (d == 6) segs = 125; // 6: top, top-left, middle, bottom-left, bottom-right, bottom
else if (d == 7) segs = 7; // 7: top, top-right, bottom-right
else if (d == 8) segs = 127; // 8: all segments
else if (d == 9) segs = 111; // 9: top, top-left, top-right, middle, bottom-right, bottom
float s = 0.0;
if ((segs & 1) > 0) s += step(abs(p.y - 0.4), 0.08) * step(abs(p.x), 0.3); // top
if ((segs & 2) > 0) s += step(abs(p.x - 0.3), 0.08) * step(abs(p.y - 0.2), 0.2); // top-right
if ((segs & 4) > 0) s += step(abs(p.x - 0.3), 0.08) * step(abs(p.y + 0.2), 0.2); // bottom-right
if ((segs & 8) > 0) s += step(abs(p.y + 0.4), 0.08) * step(abs(p.x), 0.3); // bottom
if ((segs & 16) > 0) s += step(abs(p.x + 0.3), 0.08) * step(abs(p.y + 0.2), 0.2); // bottom-left
if ((segs & 32) > 0) s += step(abs(p.x + 0.3), 0.08) * step(abs(p.y - 0.2), 0.2); // top-left
if ((segs & 64) > 0) s += step(abs(p.y), 0.08) * step(abs(p.x), 0.3); // middle
return clamp(s, 0.0, 1.0);
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord.xy / iResolution.xy;
// Camera offset
vec4 ballDataCam = texelFetch(iChannel0, BALL_POS, 0);
vec2 cameraOffset = ballDataCam.xy * CAMERA_FOLLOW_AMOUNT;
// World position: subtract camera offset to center ball
vec2 p = (uv - 0.5) * vec2(ar, 1.0) * 1.8 - cameraOffset;
vec3 col = texture(iChannel2,uv).rgb*0.87;
vec2 ballPos = ballDataCam.xy;
vec2 ballVel = ballDataCam.zw;
// Draw particles
for (int row = 1; row <= PARTICLE_ROWS; row++) {
for (int i = 0; i < NUM_PARTICLES; i++) {
vec4 part = texelFetch(iChannel0, ivec2(i, row), 0);
if (length(part.zw) > 0.01) {
float d = length(p - part.xy);
float brightness = length(part.zw) * 2.0;
col += vec3(1.0, 0.6, 0.2) * smoothstep(0.015, 0.005, d) * brightness;
}
}
}
// Draw ball
float ballD = length(p - ballPos);
col += vec3(0.9, 0.9, 1.0) * smoothstep(0.025, 0.015, ballD);
col += vec3(0.3, 0.5, 1.0) * smoothstep(0.04, 0.02, ballD);
// DEBUG: Draw a line showing the ball's velocity ray
if (DEBUG_VISUALIZATION > 0.5) {
vec2 rayEnd = ballPos + ballVel * (1.0/60.0);
float rayDist = length(p - ballPos);
vec2 toPoint = p - ballPos;
vec2 rayNorm = normalize(ballVel);
float alongRay = dot(toPoint, rayNorm);
float perpDist = length(toPoint - rayNorm * alongRay);
if (alongRay > 0.0 && alongRay < length(ballVel) * (9./60.0) && perpDist < 0.005) {
col += vec3(1.0, 1.0, 0.0); // Yellow ray
}
}
// Draw outer boundary
float boundary = smoothstep(0.015, 0.005, abs(length(p) - OUTER_BOUNDARY_RADIUS));
col += vec3(0.3, 0.3, 0.4) * boundary;
// Get score
float score = texelFetch(iChannel0, SCORE_POS, 0).x;
// Draw score
vec2 scoreUV = uv - vec2(0.05, 0.9);
if (scoreUV.x > 0.0 && scoreUV.x < 0.15 && scoreUV.y > 0.0 && scoreUV.y < 0.08) {
int sc = int(score);
vec2 d1uv = (scoreUV - vec2(0.0, 0.0)) / vec2(0.05, 0.08);
vec2 d2uv = (scoreUV - vec2(0.05, 0.0)) / vec2(0.05, 0.08);
vec2 d3uv = (scoreUV - vec2(0.1, 0.0)) / vec2(0.05, 0.08);
col += vec3(1.0) * digit(d1uv, (sc / 100) % 10);
col += vec3(1.0) * digit(d2uv, (sc / 10) % 10);
col += vec3(1.0) * digit(d3uv, sc % 10);
}
fragColor = vec4(col, 1.0);
}
Buffer D (iChannel3)
// iChannel3: Barrier visualization buffer
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord.xy / iResolution.xy;
// Camera offset
vec4 ballDataCam = texelFetch(iChannel0, BALL_POS, 0);
vec2 cameraOffset = ballDataCam.xy * CAMERA_FOLLOW_AMOUNT;
// World position: subtract camera offset to center ball
vec2 p = (uv - 0.5) * vec2(ar, 1.0) * 1.8 - cameraOffset;
// Background color
vec3 col = vec3(0.02, 0.02, 0.05);
// Draw barriers
for (int i = 0; i < NUM_BARRIERS; i++) {
vec4 barrier = texelFetch(iChannel0, ivec2(BARRIER_START + i, 0), 0);
if (barrier.z > 0.5) continue; // destroyed
float r = barrierRadius(i);
float dist = length(p);
float angle = atan(p.y, p.x);
float gapAngle = barrier.y + iTime * BARRIER_ROTATION_SPEED;
float angularGap = BASE_GAP_SIZE;
float constantWidthGap = BASE_GAP_SIZE / r;
float gapSize = mix(angularGap, constantWidthGap, GAP_WIDTH_MODE);
bool inGap = abs(mod(angle - gapAngle + pi, 2.0*pi) - pi) < gapSize;
float ring = smoothstep(0.015, 0.01, abs(dist - r));
float health = barrier.x / BARRIER_INITIAL_HEALTH;
//vec3 barrierCol = mix(vec3(1.0, 0.2, 0.1), vec3(0.2, 1.0, 0.3), health);
vec3 scol = (sin(float(i)/1.+cs*1.7)*0.5+0.6);
vec3 barrierCol = mix(vec3(1.,0.,0.),scol,health);
if (!inGap) {
col += ring * barrierCol;
col += smoothstep(0.08, 0.01, abs(dist - r))*vec3(1.,0.,0.)*(1.-health);
} else {
col += ring * barrierCol*0.25;
}
}
fragColor = vec4(col, 1.0);
}
Image
Not used
More shaders by scry
- Spiral Nebula Nocturne
- Iridescent Ground Ritual
- Project_2026-08-15_18-22-06
- Project_2026-08-14_16-45-00
- Project_2026-07-24_15-10-23
- Project_2026-07-15_03-18-26
- Glass of Orange Juice (raymarched)
- Project_2026-07-13_00-53-50
- Project_2026-07-10_16-39-54
- Popcorn ceiling
- Project_2026-06-20_15-32-19
- Project_2026-06-25_12-22-43
Browse all public shaders · All shaders by scry · ShaderKit home
Unread comments
×Projects with new comments. Click to open and read.