Sunday, January 09, 2011

XOR Demoeffect in WebGL

I wanted to do a series on a set of demoeffects, inspired by Shader Toy (and Denthor!). The majority of the shader toy effects are either raytraced, or polar/circle-based. So I thought I would begin with polar/circles and work my way from there. Most of the effects are based on a number of "oldschool" democoder tricks that are easy to understand once you have the background knowledge.

I won't be covering the basics behind writing shaders, but I might do so at a later point.

All of iq's shaders are rendered using a pixel shader (or fragment shader in OpenGL talk). The first step is to calculate the position of the pixel on the screen (or canvas in case of WebGL) normalised from -1 to 1. In a picture this is:

To visualise this in the shader we could write:

void main(void)
vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
gl_FragColor = vec4(p.x,p.y,0.0,1.0);

Now each horizontal value will get an increasing value of red, and each vertical value will get an increasing value of green (we are using the RGBA color space)

We can use the cartesian equation of a circle to generate a gradient of circles.

Again, to visualise this in the shader we could write:

void main(void)
vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
float radius = sqrt(p.x*p.x + p.y*p.y);
gl_FragColor = vec4(radius,0.0,0.0,1.0);

If we want to generate concentric circles we could set a boolean value on or off depending on a modulo operation. If we take the modulo of a value and then test whether it is above half-way then we can generate an on-off pulse. For example, if we get a value ranging from 0 to 1, module 0.1, we can generate a on/off pulse by testing if it is greater than 0.05.

In shader code this would be:

void main(void)
vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;

float radius = sqrt(p.x*p.x + p.y*p.y);
float modulo = mod(radius,0.1);
bool toggle = false;
if (modulo > 0.05) toggle = true;
if (toggle)
gl_FragColor = vec4(1.0,0.0,0.0,1.0);
gl_FragColor = vec4(0.0,0.0,0.0,1.0);

This is a bit long-handed, we can shorten this by directly setting the toggle value, and using the dot product to perform the square operations.

float radius = sqrt(dot(p,p));
bool toggle = mod(radius,0.1)>0.05;

Great! Now we have all the background knowledge we need to make our first interesting effect. This will be based on the XOR operation:
XOR Truth Table
Input Output
0 0 0
0 1 1
1 0 1
1 1 0

First, lets generate two different circles.

void main(void)
vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;

vec2 offset2 = vec2(0.3,0.3);

float radius1 = sqrt(dot(p,p));
float radius2 = sqrt(dot(p-offset2,p-offset2));

bool toggle1 = mod(radius1,0.1)>0.05;
bool toggle2 = mod(radius2,0.1)>0.05;

gl_FragColor = vec4(toggle1,toggle2,0.0,1.0);

Wonderfull! Now if we add in the XOR truth table:

//xor via if statements
float col = 0.0;
if (toggle1) col = 1.0;
if (toggle2) col = 1.0;
if ((toggle1) && (toggle2)) col = 0.0;

gl_FragColor = vec4(col,0.0,0.0,1.0);

And we get a wonderful overlapping pattern.

Add in a bit of animation and we have our first demoeffect! Click the button below in a WebGL enabled browser (Firefox, Chrome, Safari). (view source for full code)

Your browser doesn't appear to support the HTML5 <canvas> element.


Giles said...

Great article! I've been wondering how these demoscene effects work.

One question -- because the vertex positions X, Y coords already range from -1 to 1, you can get the same effect and skip the resolution calculations by just stuffing the aVertexLocation.xy into a varying vec variable in the vertex shader and pulling it out again in the fragment shader. Is there any negative to doing it that way?


Adrian said...

Sure you could certainly do that, the only negative is that sometimes you do want to know your resolution (for example when doing raytracing based effects)

Giles said...

Thanks, makes sense. So it's not strictly necessary for this example, but it's useful boilerplate code for demos anyway.

Fan Hongjian said...

Nice. I used it in my WebGL introduction in Chinese. Hope it is OK. Thanks.

Chris said...

BTW, works nicely in IE11 too

Abhishek Bansal said...

very nicely explained !!
i did some tweaking and made this

Thank you :)