picture home | art | events | music | rack extensions | downloads | pixel blog | qt_tools

omino pixel blog

pixels, motion, and scripting
david van brink // Tue 2008.10.21 23:55 // {pixel bender}

Pixel Bender: Crazy Spots

I was trying to do something else. But, as you well know, sometimes bugs are really cool. Here’s my bug of the night. Click Go, once, and Scramble a few times.

crazyspots.swf

click for
http://omino.com/pixelblog/wp-content/uploads/2008/10/crazyspots.swf

Geometrical interpretation: a perfectly nonreflective sphere is in each cubical cell, getting cross-sected just like the cubes.

And here’s the source code. It’s based on “cubes” from a few posts back.

/src/pixel_bender/cubes/crazySpots.pbk in new window download /src/pixel_bender/cubes/crazySpots.pbk to file hide /src/pixel_bender/cubes/crazySpots.pbk

1 comments
kris // Sat 2009.01.17 06:226:22 am

haha real nice bug :D

oh, i dont know. what do you think?


david van brink // Mon 2008.10.20 23:28 // {after effects pixel bender}

Pixel Bender: A Gradient… and Some Math

Featuring mix(), clamp(), and dot().

Very often I need a simple linear-gradient in After Effects, and end up sing the four-color gradient because that’s all I can find. And so, here, for your perusal, is a Pixel Bender implementation of a Linear Gradient Generator.

After that, we’ll see the code, and after that, a quick review of the math.

Here is the .swf. (With some improvements on the Pixel Bender viewer — you can drag any float2 parameters as points, now.)

gradient.swf

click for
http://omino.com/pixelblog/wp-content/uploads/2008/10/gradient.swf

Isn’t that fun? Well, I’m easily amused.

The Code

Here’s the Pixel Bender code.



kernel Gradient 
{
    output pixel4 dst;
    parameter float2 p1<minValue:float2(0,0);maxValue:float2(400,400);defaultValue:float2(10,10);>;
    parameter float2 p2<minValue:float2(0,0);maxValue:float2(400,400);defaultValue:float2(130,130);>;

    parameter float3 color1<defaultValue:float3(0,0,0);>;
    parameter float3 color2<defaultValue:float3(1,1,1);>;

    void evaluatePixel() 
    {
        float2 co = outCoord();
        
        // shift everything relative to p1.
        float2 uv = co - p1;
        float2 xy = p2 - p1;

        // the math.
        float g = dot(uv,xy) / dot(xy,xy);
        g = clamp(g,0.0,1.0);

        // the color.
        dst.a = 1.0;
        dst.rgb = mix(color1,color2,g);
    }
}

The Math

I’m ok at math, but not always fluent in it. I can remember what a matrix is, but not necessarily exactly what a cross-product is. Maybe you’re like me. In which case this quick run through the math and logic behind the gradient fill will be quite useful in your own Pixel Bender bending!

And be assured, a few pages of high-school-algebra-style scribblings, and the occasional google (How do I invert a rotation again? What’s the formula for perspective?) really can lead to workable results. Good clean fun!

Let’s go.

Consider a trivial gradient, where our two control points are at (0,0) and (1,0). That would be easy! Just take the x-value of any pixel, and that’s your color. (That is, the position on the ramp between your two gradient colors.)

trivialGradient.png

By the way, this picture is from Ron Avitzur’s “Graphing Calculator”. The story of its development is legendary; less well known is that it’s actually a very powerful and useful tool.

I found the easiest way to think about a general two-point gradient was to ask, “How do we rotate and scale our control points back to this trivial gradient?” And let’s assume that the first control point is always at (0,0).

Here is matrix for a basic rotation, which moves any input point by a rotation around (0,0):

gradientEq1.jpg[1]

A rotate-and-scale matrix looks like that, times a constant on each element.

We want to think of our general gradient as a transformation on the trivial gradient. Let’s say our second control point is (X,Y), and our first control point is (0,0) as mentioned above. The rotate-and-scale matrix being applied, conceptually, is:

gradientEq2.jpg[2]

To figure out the 0-to-1 trivial position of any of our input points, we need to invert it. A matrix inversion looks like:

gradientEq3.jpg[3]

So, we invert the second equation [2] by plugging it in to [3] and get:

gradientEq4.jpg[4]

Now, let’s call each pixel position of our output (U,V). For our gradient, we actually don’t care about the resulting Y position; just X gives us our gradient value. So we get:

gradientEq5.jpg[5]

Now, the definition of the dot product of (A1,A2,A3,…) and (B1,B2,B3,…) is (A1B1 + A2B2 + A3B3 + …). So, from the above, UV+XY is, conveniently, (U,V) dot (X,Y). Similarly, X2 + Y2 is (X,Y) dot (X,Y).

And now, when you read this part of the code a second time, it should be much clearer what’s going on:

    void evaluatePixel() {
        float2 co = outCoord();
        
        // shift everything relative to p1.
        float2 uv = co - p1;
        float2 xy = p2 - p1;

        // the math.
        float g = dot(uv,xy) / dot(xy,xy);
        g = clamp(g,0.0,1.0);

        // the color.
        dst.a = 1.0;
        dst.rgb = mix(color1,color2,g);
    }

Simple and useful!

Next up, soon, more cubes.

5 comments
Ron Avitzur // Wed 2008.10.22 10:5710:57 am

You can express max(min(x,1),0) more concisely in GC as clamp(x,0,1).

less well known is that it’s actually a very powerful and useful tool.

Thank you! (I find it deeply ironic that after so many years working to improve math education, the story is way better known than the software.)

david van brink // Wed 2008.10.22 11:1111:11 am

Hi Ron — I remember meeting you briefly in the lobby of I think DA2, because Qarin knew you. I don’t remember which color badge you were using that day. Anyhow, yep, I use GC pretty often for thinking about stuff. Also, for fast cool graphics, like on the side of the “space house” in http://omino.com/gata/.

Thanks for a great product!

aron // Mon 2008.10.27 00:4012:40 am

“Very often I need a simple linear-gradient in After Effects, and end up sing the four-color gradient because that’s all I can find.”
hmm, haven’t you ever noticed Generate—> ramp? that’s what linear gradient is called in ae.
A bit simpler than this code :) but anyway, good job!

david van brink // Mon 2008.10.27 17:525:52 pm

Hahaha Thanks aron. I knew it had to be there somewhere. Oh well, I figured out some math along the way. And seriously — two lines for the math, really, taht’s pretty simple, isn’t it? :)

caseyc // Thu 2010.01.21 11:1311:13 am

Would this work as a “Gradient Overlay” style filter in Flash, so you could add gradient fills to text? I’m sick of hacking the effect with bevel filters etc.

oh, i dont know. what do you think?


david van brink // Sun 2008.10.19 20:40 // {pixel bender}

Pixel Bender: Debug Pips

Featuring distance().

One of the important games we play while writing a script or software — or other kinds of black boxes, too — is, “How can we tell what’s going on inside?”

Modern debuggers in modern programming languages make this a nearly-solved problem. Sometimes. Maybe it’s a new language and the tools aren’t there yet, or maybe you’re too cheap to purchase them. Or maybe you just like debugging with print statements. That’s cool.

In the case of Pixel Bender, I’ve been lately using several float2 variables to represent locations on the input or output. It turns out to be quite handy to drop “print” markers right in the output, to better see what’s going on.

The trick is extremely simple. If the distance to float2 value is within a few pixels, draw a color. Here’s the code to do it. It should be done last, after other pixel computations. That way it can be easily commented out or deleted before shipping.

        float p1Dist = distance(p1,outCoord());
        if(p1Dist < 3.0)
            dst.rgb = float3(1,0,0); // draw red
        else if (p1Dist < 5.0)
            dst.rgb = float3(0,0,0); // black outline

Handy!

Haven't figured out how to stamp little text figures into the output yet. You'd need to decompose your desired output on a per-pixel decision tree... would be an interesting logic-reduction problem, but perhaps impractical. It would be possible and useful to stamp 3 floating point values as "special debug pixels" at (0,0) and other easy to find locations.

At any rate, here's the whole kernel, ready for copy-and-pasting.

<languageVersion : 1.0;>
kernel DebugMarkers<namespace:"ns";vendor:"omino.com";version:1;> {
    input image4 src;
    output pixel4 dst;
    parameter float2 p1<minValue:float2(0,0);maxValue:float2(400,400);>;
    parameter float2 p2<minValue:float2(0,0);maxValue:float2(400,400);>;

    void evaluatePixel() {
        float2 co = outCoord();
        dst = sampleNearest(src,co);
        float p1Dist = distance(p1,co);
        if(p1Dist < 3.0)
            dst.rgb = float3(1,0,0);
        else if (p1Dist < 5.0)
            dst.rgb = float3(0,0,0);
        float p2Dist = distance(p2,co);
        if(p2Dist < 3.0)
            dst.rgb = float3(0,1,0);
        else if(p2Dist < 5.0)
            dst.rgb = float3(1,1,1);
    }
}

oh, i dont know. what do you think?


david van brink // Thu 2008.10.16 23:07 // {pixel bender}

Pixel Bender: Fun With Cubes 1

In which I present instances of a family of closed-cell geometric objects which are computationally trivial to render.

The Concept

Imagine moving a plane through a three dimensional mesh of cubes.

checkerSquare320.mp4

click for
http://omino.com/pixelblog/content/2008/pixelBender/checkerSquare320.mp4

You start by rotating the plane to your liking. That is, for each pixel on the plane (since that is our rendering reference point) we rotate and translate it as we like, from (x, y, 0) to (x’, y’, z’). To find out which “cell” we’re in, we just look at the integer parts of the prime-coordinates. In the animation above, the cells have been “checkered” by whether their integer coordinates’ sum is even or odd. (Actually, I just used LightWave’s 3d checker texture function. But you get the idea.)

Drawing the checkerboard is easy. Drawing some depth in the cubes is only a little bit trickier. We need a perpendicular vector from the plane, to cast a ray, so we also rotate the perpendicular vector (0,0,1) by the same amount. Then we project that ray from the fractional position within the cell until it hits one of the 6 walls of the cube. Lastly, we give a non-optical depth cue based on how far the intersected wall is.

The Implementation

Here’s the math part of the rotation. There’s probably an easier way to get the 3-axis rotation matrix… But this works.

// concatenate three simple rotations to spinM
float3x3 spinXM = float3x3(1,0,0,0,cos(spin.x),spin.x,0,-sin(spin.x),cos(spin.x));
float3x3 spinYM = float3x3(cos(spin.y),0,sin(spin.y),0,1,0,-sin(spin.y),0,cos(spin.y));
float3x3 spinZM = float3x3(cos(spin.z), sin(spin.z), 0,-sin(spin.z), cos(spin.z), 0, 0, 0, 1 );
float3x3 spinM = spinXM * spinYM * spinZM;

float2 center = float2(400,500) / 2.0; // YUCK! hardcoded for my blog entry.
float2 oc = (outCoord() - center) / zoom; // adjust "input coordinates"

// we rotate the requested coordinate by 3 axes, giving the
// R3 point on the plane of the screen (after the mesh is turnt)
float3 p = float3(oc.x,oc.y,0) * spinM; //oc.x * axis1 + oc.y * axis2;

// perp is our viewing line, straight down from the screen.
float3 perp = float3(0,0,1) * spinM;

Here is an interactive demo of the algorithm, in Flash and Pixel Bender. (I added a full screen button, whee!)

cubes01.swf

click for
http://omino.com/pixelblog/content/2008/pixelBender/cubes01.swf

And here is the Pixel Bender kernel:

/src/pixel_bender/cubes/cubes01.pbk in new window download /src/pixel_bender/cubes/cubes01.pbk to file hide /src/pixel_bender/cubes/cubes01.pbk

And for reference, here is the template for the Flash displayer, compilable by mxmlc.

/src/pixel_bender/cubes/pbTemplate.mxml in new window download /src/pixel_bender/cubes/pbTemplate.mxml to file hide /src/pixel_bender/cubes/pbTemplate.mxml

Flex trivia: stage.addEventListener(FullScreenEvent.FULL_SCREEN,screenChange);
works from mx:Application applicationComplete because the UI and display list is all instanced, but not from mx:Application creationComplete which happens a little bit earlier.

Next up: Suppose we didn’t intersect with a simple plane… !

oh, i dont know. what do you think?



0.0842s
(c) 2003-2013 omino.com / contact poly@omino.com