Juggler
This sample displays a juggling pattern computed entirely in a shader.
3 Balls
5 Balls
7 Balls
9 Balls
11 Balls
13 Balls
15 Balls
17 Balls
Animate
// The 4x4 world view projection matrix. uniform mat4 worldViewProjection; // input parameters for our vertex shader attribute vec4 position; attribute vec2 texCoord0; // input parameters for our pixel shader varying vec2 v_texCoord; /** * vertexShaderMain - our vertex shader for the juggling texture */ void main() { gl_Position = worldViewProjection * position; v_texCoord = 4.0 * (texCoord0 - vec2(0.5, 0.5)); } // #o3d SplitMarker varying vec2 v_texCoord; uniform float theta; uniform float num; float length_2(vec2 v) { return dot(v, v); } // Draw the balls in a single arc. // Returns 1 if the pixel is within a ball, 0 if it isn't, and a value in // between for pixels right on the edge, for antialiasing. float drawBallsInArc(float pi, vec4 offset, vec2 source_hand, vec2 dest_hand, float height_factor, float baseline, float ball_radius_2, float hand_throw_offset, vec2 Z, float threshold) { // Map theta from its current range of [0, 2 * num * pi) onto [0, (num - 1)) // by scaling, adding offset, and modding, then map that to [0, 1) by scaling. // The first mapping tells us where in the repeating cycle we are, and the // second mapping simplifies the calculation of the parabola. // The vector offset is used to distinguish between balls in the same arc, but // out of phase. At the beginning of this function, all the operations are // vectorized to save instructions; we get to calculate 4 ball positions for // the price of 1. // The reason for the (num - 1) in the expression below is that with num // balls, each ball spends (num - 1) beats in the air, then one in the hand. // So (num - 1) is the length of time a parabola takes. vec4 time = mod(theta / pi + offset, (num - 1.0)) / (num - 1.0); float dx = dest_hand.x - source_hand.x; vec4 x = time * dx + source_hand.x - hand_throw_offset; vec4 y = time * (1.0 - time); y = y * height_factor + baseline; vec4 ZX = vec4(Z.x); vec4 ZY = vec4(Z.y); vec4 len_2 = (ZX - x) * (ZX - x) + (ZY - y) * (ZY - y); // This antialiasing fuzzes the balls just a bit larger than they would // otherwise be. vec4 temp = clamp((len_2 - ball_radius_2) / threshold, 0.0, 1.0); // One minus the product of all entries in temp. temp.xy = temp.xy * temp.zw; return 1.0 - temp.x * temp.y; } vec4 drawAirborneBalls(float pi, vec4 offset, vec2 right_hand, vec2 left_hand, float height_factor, float baseline, float ball_radius_2, float hand_swing_radius, vec2 Z, float threshold) { float value = // balls going right to left (drawBallsInArc(pi, offset, right_hand, left_hand, height_factor, baseline, ball_radius_2, hand_swing_radius, Z, threshold) + // balls going left to right drawBallsInArc(pi, offset + 1.0, left_hand, right_hand, height_factor, baseline, ball_radius_2, -hand_swing_radius, Z, threshold)); return vec4(value, value, value, value); } /** * pixelShaderMain - pixel shader */ void main() { float pi = 3.14159265; float baseline = -1.4; vec2 right_hand = vec2(0.8, baseline); vec2 left_hand = vec2(-0.8, baseline); float hand_swing_radius = 0.25; float hand_radius = 0.15; float hand_radius_2 = hand_radius * hand_radius; float ball_radius = 0.08; float ball_radius_2 = ball_radius * ball_radius; vec4 right_hand_color = vec4(1, 0, 0, 1); vec4 left_hand_color = vec4(0, 0, 1, 1); vec4 background_color = vec4(0, 0, 0, 0); float threshold = 0.002; // Used in clamp for antialiasing. float height_factor = num * 0.75; vec2 Z = v_texCoord; // Coerce to the range [0, 2 * Pi * num]. vec2 r_h = hand_swing_radius * vec2(-cos(theta), sin(theta)) + right_hand; vec2 l_h = hand_swing_radius * vec2(-cos(theta), -sin(theta)) + left_hand; // Initialize color of pixel to background_color. Background color has an // alpha of 0. Color of objects each have alpha 1, so multiplying by // (1-alpha) before adding the color ensures that nothing gets overdrawn. // It's kind of like a rudimentary z-buffer. vec4 result = background_color; // Draw the hands. The antialiasing here fuzzes the hands just a little bit // smaller than they would otherwise be. That's the opposite of what we do // for the balls, just because it happens to be cheaper here to do smaller and // cheaper in drawBallsInArc to do larger. result += clamp((hand_radius_2 - length_2(Z - r_h)) / threshold, 0.0, 1.0) * (Z.y < r_h.y ? 1.0 : 0.0) * right_hand_color + clamp((hand_radius_2 - length_2(Z - l_h)) / threshold, 0.0, 1.0) * (Z.y < l_h.y ? 1.0 : 0.0) * left_hand_color; // Draw the ball in the hand. There is always a ball in exactly one hand, and // which hand that is alternates. vec2 hand; if (mod(floor(theta / pi), 2.0) > 0.5) { hand = r_h; } else { hand = l_h; } // The antialiasing here fuzzes the balls just a bit bigger than they would // otherwise be. This is more work than making them smaller [the extra // subtraction in the "1 - clamp..." below], but inverting it in // drawBallsInArc would be more expensive, and they have to match so that the // balls are all the same size. result += (1.0 - result.a) * (1.0 - clamp((length_2(Z - hand) - ball_radius_2) / threshold, 0.0, 1.0)); // Draw airborne balls. vec4 offset = vec4(0, 2, 4, 6); result += (1.0 - result.a) * drawAirborneBalls(pi, offset, right_hand, left_hand, height_factor, baseline, ball_radius_2, hand_swing_radius, Z, threshold); // For each up-to-4 pairs of balls you want to add, increment offset by // (8, 8, 8, 8) and call drawAirborneBalls again. offset += 8.0; result += (1.0 - result.a) * drawAirborneBalls(pi, offset, right_hand, left_hand, height_factor, baseline, ball_radius_2, hand_swing_radius, Z, threshold); gl_FragColor = result; } // #o3d MatrixLoadOrder RowMajor