November 25, 2014

GLSL Eye ball

Just like the palm tree from earlier, I just made a step by step of iq's tutorial here of how he made an eye ball using shader code.

Here is his final result:


Let's do this!
First, we need a noise function.  Noise is used to add a randomized look, and if altered correctly, can give the image a natural, organic effect.  This is the noise function we will be using:


Let's make some noise!

    
Here we are using our noise function.

    
We made an fbm function (stands for fractal brownian motion) which is better explained elsewhere on the web.  But hey our noise looks better.
So we’ve replaced noise with fbm.  This is just to show what the fbm function looks like.

Now let's start making the eye.

    
Put a circle in the middle of our screen.  That will be the base of the eyeball.



    
Now we’re adding our fbm function.  Makes it look more interesting.

    
Add the pupil using smoothstep.

    
Add a yellow center

    
Let’s add a white pattern around the eye.


    
Alter the pattern by changing the parameters in our fbm function.


    
Back off on the white a bit using smoothstep.



    
Add some black flecks.


    
Actually, tweak the black flecks a bit.


Add a black border.


    
Edges are a bit aliased let’s fix that.


    
Apply some domain distortion to the atan to make the image a lil more interesting.  Here, we are changing the angle with some fbm.
(I’m moving the a declaration up a bit).


    
We can tweak the frequency to change the pattern!  Weird looking eye but the pattern could be used in cool ways for other things.

    
But let’s go for a more realistic eyeball.

Almost done!  Wouldn't a fake reflection make it look even better?

    
Whoa that's bright.


    
Better.


    
Some tweaking - make the reflection a bit bigger in smoothstep, move it a little in length, and when assigning to color give a slight yellowish tint and make it softer.



    
Add some animation to make it look creepy.  (Sorry, not animated here).


    
This just makes the animation non-uniform for different parts of the circle to make it even creepier.

//////////////////

And we're done!  Isn't that cool?
So here's the entire source code:
float hash( float n )
{
  return fract(sin(n)*43758.5453);
}

float noise( in vec2 x )
{
  vec2 p = floor(x);
    vec2 f = fract(x);
    f = f*f*(3.0-2.0*f);
    float n = p.x + p.y*57.0;
    float res = mix(mix( hash(n+  0.0), hash(n+  1.0),f.x),
    mix( hash(n+ 57.0), hash(n+ 58.0),f.x),f.y);
  return res;
}

float fbm(vec2 p) {
  float f = 0.0;
    f += 0.5000*noise(p); p *= 2.02;
    f += 0.2500*noise(p); p *= 2.03;
    f += 0.1250*noise(p); p *= 2.04;
    f += 0.0625*noise(p); p *= 2.01;
    f /= 0.9375;
    return f;
}

void main(void)
{
  vec2 uv = gl_FragCoord.xy / iResolution.xy;
    vec2 p = -1.0 + 2.0*uv;
    p.x *= iResolution.x/iResolution.y;
     
    float r = sqrt(dot(p,p));
    vec3 col = vec3(1.0);
     
    float ss = 0.5 + 0.5*sin(4.0*iGlobalTime);
  float anim = 1.0 + 0.1*ss*clamp(1.0 - r, 0.0, 1.0);
  r *= anim;
     
    if (r < 0.8) {
      col = vec3(0.0, 0.3, 0.4);  
         
     float a = atan(p.y, p.x);
        a += 0.05*fbm(20.0*p);
         
        // color variation
        float f = fbm(5.0*p);
   col = mix(col, vec3(0.2, 0.5, 0.4), f);
         
        // yellow center
     f = smoothstep(0.5, 0.2, r);
        col = mix(col, vec3(0.9, 0.6, 0.2), f);
         
        // white pattern
        f = fbm(vec2(5.0*r, 20.0*a));
     f = smoothstep(0.3, 1.0, f);
        col = mix(col, vec3(1.0), f);
         
        // black flecks
        f = fbm(vec2(10.0*r,16.0*a));
        f = smoothstep(0.4, 1.0, f);
        col *= (1.0 - f*0.7);
         
        // black border
        f = smoothstep(0.9, 0.55, r);
        col *= f;
         
        // reflection
        f = 1.0 - smoothstep(0.0, 0.3,
     length(p - vec2(0.25, 0.25)));
     col += vec3(1.0, 0.9, 0.8)*vec3(f)*0.7;
         
        // anti-aliasing
        f = smoothstep(0.75, 0.8, r);
        col = mix(col, vec3(1.0), f);
         
     // pupil
     f = smoothstep(0.2, 0.25, r);
        col *= f;
  }
     
    gl_FragColor = vec4(col, 1.0);
}

But I actually prefer green eyes so I made this one:

(link to shadertoy)

November 24, 2014

Sphere with Occlusion

Improved on my previous sphere examples where now the little sphere can go behind the bigger sphere.

I'm not sure if I'm doing it completely right.. my shadow is kinda hacky when the little sphere goes behind the bigger sphere.  I basically check to see if the z coordinate is negative and if it is, I add shadow to it based on its z position.  If I move the spheres to a different location then it won't work anymore, so I'll need to fix that.

Again gif recording software sucks so here are two images:


To see it in action: (link to shadertoy)

And source code:
struct sphere {
    vec3 pos;
    float radius;
    vec3 col;
    int id;
};
 
vec3 light = vec3(9.0, 3.0, 8.0);
vec3 eye = vec3(0.0, 0.0, 8.0);
vec3 up = vec3(0.0, 1.0, 0.0);
sphere s1;
sphere s2;

struct eye_coord_system  {
    vec3 n;
    vec3 u;
    vec3 v;
};

float iSphere(vec3 ray_origin, vec3 ray_direction, sphere sph) {
    vec3 d = ray_direction;
    float a = dot(d, d);
    float b = 2.0 * dot(d, ray_origin - sph.pos);
    float c = dot(ray_origin - sph.pos, ray_origin - sph.pos) - (sph.radius * sph.radius);
    float delta = b*b - 4.0*a*c;
    float t = delta < 0.0 ? -1.0 : (-b - sqrt(delta)) / (2.0*a);
    return t;
}

vec3 get_sphere_normal(vec3 pos, sphere sph ) {
    return (pos - sph.pos)/sph.radius;
}

vec3 get_color_of_point(eye_coord_system eye_coord, vec3 ray_origin, vec3 ray_direction, vec3 col) {
    vec3 pos1 = vec3(-1.0), pos2 = vec3(-1.0);
    float intersection1 = iSphere(ray_origin, ray_direction, s1);
 
  if(intersection1 > 0.0) {
        pos1 = ray_origin + intersection1*ray_direction;
  vec3 nor = get_sphere_normal(pos1, s1);
        vec3 reflection = normalize(reflect(ray_direction, nor));
  float diffuse = max(0.0, dot(nor, normalize(light)));
        float specular = pow(dot(reflection, eye_coord.u), 30.0);
        specular = max(specular, 0.000001); // for some reason 0.0 returns crappy results

        float reflect_intersection = iSphere(pos1, reflection, s2);
        reflection = vec3(0.0);
     
        if (reflect_intersection > 0.0) {
            reflection = vec3(s2.col * diffuse);
        }
         
  col = vec3(diffuse + reflection + specular);
  }
 
  float intersection2 = iSphere(ray_origin, ray_direction, s2);
  if(intersection2 > 0.0) {
     pos2 = ray_origin + intersection2*ray_direction;
     vec3 nor = get_sphere_normal(pos2, s2);
        vec3 reflection = normalize(reflect(ray_direction, nor));
      float diffuse = max(0.0, dot(nor, normalize(light)));
        float specular = pow(dot(reflection, eye_coord.u), 30.0);
     specular = max(specular, 0.000001); // for some reason 0.0 returns crappy results
        if (pos2.z > 0.0 && pos2.z > pos1.z || pos2.z < 0.0 && pos1.z < 0.0) {
         col = s2.col * diffuse + specular * 0.5;
            col = pos2.z < 0.0 && pos1.z < 0.0 ? col - abs(pos2.z) : col;
     }
  }
  return col;
}

vec3 orbit(sphere s) {
    float orbit_radius = 1.8;
    return vec3 (orbit_radius*cos(iGlobalTime), 0.0, sin(iGlobalTime)*orbit_radius);
}

eye_coord_system create_coord_system(vec3 eye, vec3 center, vec3 up) {
    vec3 n = vec3(eye - center) / length(eye - center);
    vec3 u = cross(up, n) / length(cross(up, n));
    vec3 v = cross(n, u);
    eye_coord_system coord_system = eye_coord_system(n, u, v);
    return coord_system;
}

vec3 calculate_pixel_loc(sphere sph, vec3 eye, eye_coord_system eye_coord) {
    float i = gl_FragCoord.x; float j = gl_FragCoord.y;
    float X = iResolution.x; float Y = iResolution.y;
    float aspectRatio = iResolution.x / iResolution.y;
    float d = distance(sph.pos, eye)/2.0;
    float H = 1.5; float W = H * aspectRatio;
 
    vec3 C = eye - eye_coord.n * d;
    vec3 L = C - eye_coord.u * (W/2.0) - eye_coord.v * (H/2.0);
    vec3 s = L + eye_coord.u * i * (W/X) + eye_coord.v * j * (H/Y);
 
    return s;
}

void main(void) {
    vec3 s1_pos = vec3(0.0), s2_pos = vec3(orbit(s2));
    float s1_radius = 1.0, s2_radius = 0.3;
    vec3 s1_col = vec3(0.3), s2_col = vec3(1.0, 0.0, 1.0);
    int s1_id = 1, s2_id = 2;
    s1 = sphere(s1_pos, s1_radius, s1_col, s1_id);
    s2 = sphere(s2_pos, s2_radius, s2_col, s2_id);
 
    eye_coord_system eye_coord = create_coord_system(eye, s1.pos, up);
    vec3 s = calculate_pixel_loc(s1, eye, eye_coord);
    vec3 ray_direction = s - eye;
 
    vec3 col = vec3(0.3);
    col = get_color_of_point(eye_coord, eye, ray_direction, col);
 
    gl_FragColor = vec4(col,1.0);
}

I use 118 lines of code to do this while I bet iq could do it in less than 50 :( that's why he works at Pixar

Mandelbrot zoom

It was funny, after being really proud of my Mandelbrot and showing it to people they were like "What, that's it?  It just sits there?"

So I made it move.  It zooms in on the point (-1.56, 0) but what's interesting is you can really see the fractal nature of the Mandelbrot that you couldn't see from the original.  As it zooms in you can start seeing the same shape over and over again.

Zooming was hard at first, but it ended up being simple when I tried not to make it so complicated.  The bulk of the work is done here:

vec2 zoom_in_uv(vec2 uv, vec3 S, float zoom_in_point, float zoom) {
    uv = translate(uv, vec3(zoom_in_point, vec2(0.0)));
    uv = scale(uv, vec3(zoom));
    uv = translate(uv, vec3(-zoom_in_point, vec2(0.0)));
    return uv;
}

I translate the image to its original coordinates (I had shifted it a bit to center it on the Mandelbrot), scaled it to the appropriate zoom, and translated it back (explanation on page 25 of these slides).

I encountered an issue while zooming - I was zooming by the same amount every time, but when you zoom in more and more the image seems to zoom much slower (the zoom loses its effectiveness the smaller you get.)  So some additional tweaking was needed to speed up the zoom as it went into the image.  Taking advantage of the fact that if you take the square root of a positive number less than 1, it gets bigger, then I increased the current zoom by the square root of itself, which does serve to speed up the zoom as it zooms into the image.

My gif recording software sucks so I'll just post pictures.



And here's the source code:
struct complex {
  float r;
    float i;
};

complex add(complex c1, complex c2) {
  return complex(c1.r + c2.r, c1.i + c2.i);
}

complex multiply(complex c1, complex c2) {
  complex c_new = complex(0.0, 0.0);
    c_new.r = c1.r * c2.r - (c1.i * c2.i);
    c_new.i = c1.r * c2.i + (c1.i * c2.r);
    return c_new;
}

vec2 translate(vec2 uv, vec3 T) {
    return vec2(uv.x + T.x, uv.y + T.y);
}

vec2 scale(vec2 uv, vec3 S) {
    return vec2(uv.x * S.x, uv.y * S.y);
}

vec2 create_uv(vec3 S, vec3 T) {
  vec2 uv = (gl_FragCoord.xy / iResolution.xy);
    uv = scale(uv, S);
    uv = translate(uv, -T);
    return uv;
}

vec2 zoom_in_uv(vec2 uv, vec3 S, float zoom_in_point, float zoom) {
    uv = translate(uv, vec3(zoom_in_point, vec2(0.0)));
  uv = scale(uv, vec3(zoom));
  uv = translate(uv, vec3(-zoom_in_point, vec2(0.0)));
    return uv;
}

bool has_escaped(complex c) {
  return abs(c.r) + abs(c.i) >= 4.0;
}

complex mandybrot_eq(complex z, complex c) {
  complex z_new = multiply(z, z);
    return add(z_new, c);  
}

vec3 glow(vec3 col) {
  col.z += 0.05*cos(iGlobalTime);
  col += pow(cos(iGlobalTime * 0.7),8.0) * 0.1 * vec3(0.8, 0.8, 0.8);
    return col;
}

float speed_up_as_we_zoom_in(float zoom) {
    float this_gets_bigger_as_zoom_approaches_0 = sqrt(zoom);
    float tweak = zoom - this_gets_bigger_as_zoom_approaches_0;
    float dont_let_tweak_be_bigger_than_zoom = 2.17;
 
  return dont_let_tweak_be_bigger_than_zoom*zoom + tweak;
}

vec3 create_scaling_vector(float scale, float aspectRatio) {
  return vec3(scale*aspectRatio, scale, 0.0);
}

vec3 create_translate_vector(float x_offset, float y_offset) {
  return vec3(x_offset, y_offset, 0.0);  
}

void main(void)
{
  float aspectRatio = iResolution.x / iResolution.y, scale = 2.5;
  float x_offset = 0.3 + scale, y_offset = scale/2.0;
    vec3 S = create_scaling_vector(scale, aspectRatio);
    vec3 T = create_translate_vector(x_offset, y_offset);
    vec2 unzoomed_uv = create_uv(S, T);
 
    float zoom = 1.0/mod(iGlobalTime, 10.0);
    zoom = speed_up_as_we_zoom_in(zoom);
    vec2 uv = zoom_in_uv(unzoomed_uv, S, 1.56, zoom*0.1);

    complex z = complex(uv.x, uv.y);
    complex c = complex(uv.x, uv.y);
 
  vec3 col = vec3(0.0);
    const float iterations = 111.0;
    for (float i = 0.0; i < iterations; i++) {
  z = mandybrot_eq(z, c);
        if (has_escaped(z)) {
col += vec3( i/iterations, 0.0, i/iterations);
            col = glow(col);
            break;
        }
    }
 
   gl_FragColor = vec4(col, 1.0);
}

I know some of my variable names are pretty dumb (dont_let_tweak_be_bigger_than_zoom) but they help me keep track of what's happening thankyouverymuch.