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. float4x4 worldViewProjection : WORLDVIEWPROJECTION; float theta; float num; // input parameters for our vertex shader struct VertexShaderInput { float4 position : POSITION; float2 texCoord : TEXCOORD0; }; // input parameters for our pixel shader // also the output parameters for our vertex shader struct PixelShaderInput { float4 position : POSITION; float2 texCoord : TEXCOORD0; float4 color : COLOR; }; /** * vertexShaderMain - our vertex shader for the juggling texture */ PixelShaderInput vertexShaderMain(VertexShaderInput input) { PixelShaderInput output; output.position = mul(input.position, worldViewProjection); output.texCoord = 4.0 * (input.texCoord - float2(0.5, 0.5)); output.color = float4(1, 1, 1, 1); return output; } float length_2(in float2 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(in float pi, in float4 offset, in float2 source_hand, in float2 dest_hand, in float height_factor, in float baseline, in float ball_radius_2, in float hand_throw_offset, in float2 Z, in 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. float4 time = fmod(theta / pi + offset, (num - 1)) / (num - 1); float dx = dest_hand.x - source_hand.x; float4 x = time * dx + source_hand.x - hand_throw_offset; float4 y = time * (1 - time); y = y * height_factor + baseline; float4 ZX = Z.x; float4 ZY = Z.y; float4 len_2 = (ZX - x) * (ZX - x) + (ZY - y) * (ZY - y); // This antialiasing fuzzes the balls just a bit larger than they would // otherwise be. float4 temp = clamp((len_2 - ball_radius_2) / threshold, 0, 1); // One minus the product of all entries in temp. temp.xy = temp.xy * temp.zw; return 1 - temp.x * temp.y; } float4 drawAirborneBalls(in float pi, in float4 offset, in float2 right_hand, in float2 left_hand, in float height_factor, in float baseline, in float ball_radius_2, in float hand_swing_radius, in float2 Z, in 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, left_hand, right_hand, height_factor, baseline, ball_radius_2, -hand_swing_radius, Z, threshold)); return float4(value, value, value, value); } /** * pixelShaderMain - pixel shader */ float4 pixelShaderMain(PixelShaderInput input) : COLOR { const float pi = 3.14159265; const float baseline = -1.4; const float2 right_hand = float2(0.8, baseline); const float2 left_hand = float2(-0.8, baseline); const float hand_swing_radius = 0.25; const float hand_radius = 0.15; const float hand_radius_2 = hand_radius * hand_radius; const float ball_radius = 0.08; const float ball_radius_2 = ball_radius * ball_radius; const float4 right_hand_color = float4(1, 0, 0, 1); const float4 left_hand_color = float4(0, 0, 1, 1); const float4 background_color = float4(0, 0, 0, 0); const float threshold = 0.002; // Used in clamp for antialiasing. float height_factor = num * 0.75; float2 Z = input.texCoord; // Coerce to the range [0, 2 * Pi * num]. float2 r_h = hand_swing_radius * float2(-cos(theta), sin(theta)) + right_hand; float2 l_h = hand_swing_radius * float2(-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. float4 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, 1) * (Z.y < r_h.y) * right_hand_color + clamp((hand_radius_2 - length_2(Z - l_h)) / threshold, 0, 1) * (Z.y < l_h.y) * left_hand_color; // Draw the ball in the hand. There is always a ball in exactly one hand, and // which hand that is alternates. float2 hand; if (fmod(floor(theta / pi), 2) > 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 - result.a) * (1 - clamp((length_2(Z - hand) - ball_radius_2) / threshold, 0, 1)); // Draw airborne balls. float4 offset = float4(0, 2, 4, 6); result += (1 - 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; result += (1 - result.a) * drawAirborneBalls(pi, offset, right_hand, left_hand, height_factor, baseline, ball_radius_2, hand_swing_radius, Z, threshold); return result; } // Here we tell our effect file *which* functions are // our vertex and pixel shaders. // #o3d VertexShaderEntryPoint vertexShaderMain // #o3d PixelShaderEntryPoint pixelShaderMain // #o3d MatrixLoadOrder RowMajor