home dev games gallery work
Lewpen.com»Research & Development»3D Graphics»WebGL»GLSL Fractals»Outlines & Colours

Outlines & Colours

Full colour fractal with lighting and toon shading

/ index.html

<html>
<title>WebGL Fragment Shader Canvas</title>

<script type="text/javascript">
//<![CDATA[
/*
  WebGL Fragment Framework
  (c) Lewpen Kinross-Skeels 2011
  www.lewpen.com

  This provides a simple way to play with fragment shaders.
  You provide an HTML canvas element and a fragment shader, and this shader is executed for each pixel in
  the canvas.  This is implemented by rendering one quad which takes up the whole area of the canvas.

  Your fragment shader has 3 inputs:

    varying vec3 vPosition;  //  Position of this pixel in the canvas - ranging from -1.0 to 1.0 in X and Y
    uniform float uTime;     //  Time in seconds since the animaton started
    uniform vec2 uMousePos;  //  Position of the mouse cursor over the canvas, from -1.0 to 1.0 in X and Y
*/

    var TEXTURE_RES0 = 8;
    var TEXTURE_RES1 = 16;
    var TEXTURE_RES2 = 32;
    var TEXTURE_RES3 = 64;
    var TEXTURE_RES4 = 128;
    var TEXTURE_RES5 = 256;
    var TEXTURE_RES6 = 512;

var gl;
    var fractal_shaderProgram;
    var final_shaderProgram;
    var aVertexPosition;


    function initGL(canvas)
    {
        console.logFunction( 'initGL', [ canvas ] )

        try
        {
            gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        }
        catch(e)
        {
            throw [ e, "Could not initialise WebGL, sorry :-(" ]
        }

        if (!gl)
        {
            throw "Could not initialise WebGL, sorry :-("
        }
    }


  function createShaderProgram( gl, vs, fs )
  {
    console.logFunction( 'createShaderProgram', [ gl, vs, fs ] )

    var shaderProgram = gl.createProgram()

    //  Create vertex shader

    console.log( '* CREATING VSHADER' )

    var vshader = gl.createShader( gl.VERTEX_SHADER )
    gl.shaderSource( vshader, vs )
    gl.compileShader( vshader )

    if( !gl.getShaderParameter( vshader, gl.COMPILE_STATUS ) )
    {
      alert("Vertex shader compilation error\n"+gl.getShaderInfoLog(vshader)+"\n\n--\n"+vs);
      return null
    }

    //  Create fragment shader

    console.log( '* CREATING FSHADER' )

    var fshader = gl.createShader( gl.FRAGMENT_SHADER )
    gl.shaderSource( fshader, fs )
    gl.compileShader( fshader )

    if( !gl.getShaderParameter( fshader, gl.COMPILE_STATUS ) )
    {
      alert("Fragment shader compilation error\n"+gl.getShaderInfoLog(fshader)+"\n\n--\n"+fs);
      return null
    }

    //  Create and link program

    console.log( '* ATTACHING VSHADER' )

    gl.attachShader( shaderProgram, vshader )

    console.log( '* ATTACHING FSHADER' )

    gl.attachShader( shaderProgram, fshader )

    console.log( '* LINKING PROGRAM' )

    gl.linkProgram( shaderProgram )

    if( !gl.getProgramParameter(shaderProgram, gl.LINK_STATUS) )
    {
      console.log( '! LINKING FAILED: '+gl.getProgramInfoLog( shaderProgram ) )

      return null
    }

    console.log( '* WORKED: ' + shaderProgram )

    return shaderProgram
  }


  function getShaderFromElement( gl, id )
  {
    console.logFunction( 'getShaderFromElement', [ gl, id ] )

    var shaderScript = document.getElementById( id )

    if( !shaderScript ) return null

    var str = "";
    var k = shaderScript.firstChild;
    while (k)
    {
        if (k.nodeType == 3)
            str += k.textContent;
        k = k.nextSibling;
    }

    var shader;
    if (shaderScript.type == "x-shader/x-fragment")
    {
        shader = gl.createShader(gl.FRAGMENT_SHADER);
    }
    else if (shaderScript.type == "x-shader/x-vertex")
    {
        shader = gl.createShader(gl.VERTEX_SHADER);
    }
    else
    {
        return null;
    }

    gl.shaderSource(shader, str);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
    {
        alert(gl.getShaderInfoLog(shader));
        return null;
    }

    return shader;
  }




    function CreateFragmentShader( shader_element_id )
    {
        console.logFunction( 'CreateFragmentShader', [] )

        vs =
            '#ifdef GL_ES\nprecision mediump float;\n#endif\n' +
            'attribute vec3 aVertexPosition; uniform float uTime; varying vec3 vPosition;' +
            'void main(void) { gl_Position = vec4(aVertexPosition, 1.0); vPosition = aVertexPosition; }'

        fs =
            //      '#ifdef GL_ES\nprecision mediump float;\n#endif\n' +
            document.getElementById( shader_element_id ).textContent

        var shader_program = createShaderProgram( gl, vs, fs )

        //  Create the vertex array buffers

        gl.useProgram( shader_program );

        var aVertexPosition = gl.getAttribLocation( shader_program, "aVertexPosition" )
        gl.enableVertexAttribArray( aVertexPosition )
        return shader_program
    }




  var vertexPositionBuffer

  function initBuffers()
  {
    console.logFunction( 'initBuffers', [] )

    vertexPositionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, vertexPositionBuffer);
    var vertices = [
         1.0, -1.0, 1.0,
        -1.0, -1.0, 1.0,
         1.0,  1.0, 1.0,
        -1.0,  1.0, 1.0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
    vertexPositionBuffer.itemSize = 3;
    vertexPositionBuffer.numItems = 4;
  }


    //  Resolution 0 is always a tiny texture containing zeroes (TEXTURE_RES0)

    var fractal_res0_framebuffer
    var fractal_res0_texture

    //  Resolution 1 is TEXTURE_RES1

    var fractal_res1_framebuffer
    var fractal_res1_texture

    //  Resolution 2 is TEXTURE_RES2

    var fractal_res2_framebuffer
    var fractal_res2_texture

    //  Resolution 3 is TEXTURE_RES3

    var fractal_res3_framebuffer
    var fractal_res3_texture

    //  Resolution 4 is TEXTURE_RES3

    var fractal_res4_framebuffer
    var fractal_res4_texture

    //  Resolution 5 is TEXTURE_RES3

    var fractal_res5_framebuffer
    var fractal_res5_texture

    //  Resolution 6 is TEXTURE_RES3

    var fractal_res6_framebuffer
    var fractal_res6_texture

    function initFramebuffer( w, h )
    {
        var f
        var t

        console.log( 'initFrameBuffer '+w+' x '+h );
        f = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, f);
        f.width = w;
        f.height = h;

        t = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, t);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, f.width, f.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);

        var renderbuffer = gl.createRenderbuffer();
        gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
        gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, f.width, f.height);

        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t, 0);
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);

        gl.bindTexture(gl.TEXTURE_2D, null);
        gl.bindRenderbuffer(gl.RENDERBUFFER, null);
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        return [f,t]
  }


  
  //---- webgl_timer

  var webgl_statusElementName = false
  var lastTime = 0
  var normalisedTime = 0
  var timer_count = 0

  function webgl_timer()
  {
    //  Play as fast as possible
    if( window['mozRequestAnimationFrame'] ) mozRequestAnimationFrame( webgl_timer )
    else if( window['webkitRequestAnimationFrame'] ) webkitRequestAnimationFrame( webgl_timer )
    
    timer_count ++

    if( timer_count == 1 )
    {
      console.log( 'START OF FIRST TIMER' )
    }

    var timeNow = new Date().getTime();

    if (lastTime != 0 )
    {
      var elapsed = timeNow - lastTime;

      normalisedTime += elapsed;

      if( webgl_statusElementName && ((timer_count%10)===0) )
      {
        var status = document.getElementById( webgl_statusElementName )
        var fps = (1000.0 / Math.max( elapsed, 0.0001 ));
        fps = Math.floor( fps*1000 ) / 1000;
        status.innerHTML = 'FPS: ' + fps;
      }
    }

    lastTime = timeNow;

//    var depth_texture = gl.getUniformLocation( fractal_shaderProgram, "depth_texture" )

    //- Draw

    //- LOW RES DEPTH TEXTURE

    gl.useProgram( fractal_shaderProgram );

      var timeUniform = gl.getUniformLocation( fractal_shaderProgram, "uTime" )
      gl.uniform1f( timeUniform, normalisedTime / 1000.0 )

      var render_fractal = function( current_framebuffer, previous_layer_texture, previous_layer_res )
    {
        gl.bindFramebuffer(gl.FRAMEBUFFER, current_framebuffer )

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, previous_layer_texture);
        gl.uniform1i(gl.getUniformLocation( fractal_shaderProgram, "uPreviousLayerSampler" ), 0);

        gl.viewport(0, 0, current_framebuffer.width, current_framebuffer.height);
        gl.uniform1f(gl.getUniformLocation( fractal_shaderProgram, "uPreviousLayerResolution" ), previous_layer_res );
        gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT )
        gl.bindBuffer( gl.ARRAY_BUFFER, vertexPositionBuffer )
        gl.vertexAttribPointer( aVertexPosition, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0 )
        gl.drawArrays( gl.TRIANGLE_STRIP, 0, vertexPositionBuffer.numItems )
    }

    //  draw scene

    render_fractal( fractal_res1_framebuffer, fractal_res0_texture, TEXTURE_RES0 )
    render_fractal( fractal_res2_framebuffer, fractal_res1_texture, TEXTURE_RES1 )
    render_fractal( fractal_res3_framebuffer, fractal_res2_texture, TEXTURE_RES2 )
    render_fractal( fractal_res4_framebuffer, fractal_res3_texture, TEXTURE_RES3 )
    render_fractal( fractal_res5_framebuffer, fractal_res4_texture, TEXTURE_RES4 )
    render_fractal( fractal_res6_framebuffer, fractal_res5_texture, TEXTURE_RES5 )

      //- NORMAL RESOLUTION DEPTH TEXTUEE
/*
    gl.bindFramebuffer(gl.FRAMEBUFFER, null)

    //  draw scene
    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    gl.activeTexture(gl.TEXTURE0);
    gl.bindTexture(gl.TEXTURE_2D, fractal_res1_texture);

    gl.uniform1i(gl.getUniformLocation( fractal_shaderProgram, "uPreviousLayerSampler" ), 0);

    gl.uniform1f(gl.getUniformLocation( fractal_shaderProgram, "uPreviousLayerResolution" ), TEXTURE_RES0 );
        
    gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT )
    gl.bindBuffer( gl.ARRAY_BUFFER, vertexPositionBuffer )
    gl.vertexAttribPointer( aVertexPosition, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0 )
    gl.drawArrays( gl.TRIANGLE_STRIP, 0, vertexPositionBuffer.numItems )
  */


    //- RGB FINAL OUTPUT

      gl.useProgram( final_shaderProgram );

      var timeUniform2 = gl.getUniformLocation( final_shaderProgram, "uTime" )
      gl.uniform1f( timeUniform2, normalisedTime / 1000.0 )

      gl.bindFramebuffer(gl.FRAMEBUFFER, null)

      //  draw scene
      gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_2D, fractal_res6_texture);
      gl.uniform1i(gl.getUniformLocation( final_shaderProgram, "uPreviousLayerSampler" ), 0);

      gl.uniform1f(gl.getUniformLocation( final_shaderProgram, "uPreviousLayerResolution" ), TEXTURE_RES2 );

      gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT )
      gl.bindBuffer( gl.ARRAY_BUFFER, vertexPositionBuffer )
      gl.vertexAttribPointer( aVertexPosition, vertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0 )
      gl.drawArrays( gl.TRIANGLE_STRIP, 0, vertexPositionBuffer.numItems )



      //- DONE
    
    if( timer_count == 1 )
    {
      console.log( 'END OF FIRST TIMER' )
    }
  }


  //---- webgl_init
  
    function webgl_init( canvasElementName, statusElementName )
    {
        console.logFunction( 'webgl_init', [ canvasElementName, statusElementName ] )

        if( statusElementName ) webgl_statusElementName = statusElementName

        var canvas = document.getElementById( canvasElementName );
        initGL(canvas);

        fractal_shaderProgram = CreateFragmentShader( 'shader' );
        final_shaderProgram = CreateFragmentShader( 'finalshader' );

        initBuffers();

        var buf0 = initFramebuffer( TEXTURE_RES0, TEXTURE_RES0 );  //  for render to texture
        fractal_res0_framebuffer = buf0[0]
        fractal_res0_texture = buf0[1]

        var buf1 = initFramebuffer( TEXTURE_RES1, TEXTURE_RES1 );  //  for render to texture
        fractal_res1_framebuffer = buf1[0]
        fractal_res1_texture = buf1[1]

        var buf2 = initFramebuffer( TEXTURE_RES2, TEXTURE_RES2 );  //  for render to texture
        fractal_res2_framebuffer = buf2[0]
        fractal_res2_texture = buf2[1]

        var buf3 = initFramebuffer( TEXTURE_RES3, TEXTURE_RES3 );  //  for render to texture
        fractal_res3_framebuffer = buf3[0]
        fractal_res3_texture = buf3[1]

        var buf4 = initFramebuffer( TEXTURE_RES4, TEXTURE_RES4 );  //  for render to texture
        fractal_res4_framebuffer = buf4[0]
        fractal_res4_texture = buf4[1]

        var buf5 = initFramebuffer( TEXTURE_RES5, TEXTURE_RES5 );  //  for render to texture
        fractal_res5_framebuffer = buf5[0]
        fractal_res5_texture = buf5[1]

        var buf6 = initFramebuffer( TEXTURE_RES6, TEXTURE_RES6 );  //  for render to texture
        fractal_res6_framebuffer = buf6[0]
        fractal_res6_texture = buf6[1]

        gl.clearColor(0.0, 0.0, 0.0, 1.0);

        gl.clearDepth(1.0);

        gl.enable(gl.BLEND);
        gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

        gl.enable(gl.DEPTH_TEST);
        gl.depthFunc(gl.LEQUAL);

        console.log( 'STARTING TIMER' )

        webgl_timer()
    }


  console.logFunction = function( fn_name, fn_args )
  {
    var msg = fn_name + '( '

    for( var i=0; i<fn_args.length; i++ )
    {
      if( i > 0 ) msg = msg + ', '
      msg = msg + fn_args[i]
    }

    console.log( msg )
  }
  
  
//]]>
</script>



<script id="shader" type="x-shader/x-fragment">

#ifdef GL_ES
  precision mediump float;
  #endif

  varying vec3 vPosition;
  uniform float uTime;
  uniform vec2 uMousePos;

  //  this will be the output from the previous loop
   uniform sampler2D uPreviousLayerSampler;
   uniform float uPreviousLayerResolution;
   
//ifs
float fu; //fractal_spheresubstract
float fd; //fractal_distancemult
vec3 cs;  //fractal_csize
float fs; //fractal_size
vec3 fc;  //fractal_c

vec2 rs;  //resolution

//camera
vec3  cp; //camera position
mat3  cr; //camera rotation



float unpack(const in vec4 rgba_depth)
{
    const vec4 bitShifts = vec4(1.0,
    1.0 / 255.0,
    1.0 / (255.0 * 255.0),
    1.0 / (255.0 * 255.0 * 255.0));
    return dot(rgba_depth, bitShifts);
}

/// &lt;summary&gt;
/// Pack a floating point value into an RGBA (32bpp).
/// Used by SSM, PCF, and ESM.
///
/// Note that video cards apply some sort of bias (error?) to pixels,
/// so we must correct for that by subtracting the next component's
/// value from the previous component.
/// &lt;/summary&gt;
vec4 pack (float depth)
{
//return ( floor( (depth) * 255.0 ) / 255.0) * vec4( 1.0, 0.0, 0.0, 1.0 );
//return vec4( floor( fract( depth ) * 255.0 ) / 255.0, 0.0, 0.0, 0.0 );

const vec4 bias = vec4(1.0 / 255.0,
1.0 / 255.0,
1.0 / 255.0,
0.0);

float r = floor( depth * 255.0 );
float g = floor( fract( depth * 255.0 ) * 255.0);
float b = floor( fract( depth * 255.0 * 255.0) * 255.0);
float a = floor( fract( depth * 255.0 * 255.0 * 255.0) * 255.0);
vec4 colour = (1.0 / 255.0) * vec4(r, g, b, 255.0);

return colour;// - (colour.yzww * bias);


}






float dE(vec3 p) {
//  peterb
//   p.x += 0.00015*sin(p.y*8000.0+uTime*30.0)*(0.5+0.5*sin(uTime*30.0/8.0));
float dEfactor=1.0;
//int fractal_iterations = 12;
for(int i=0;i<16;i++){
//box folding
p=2.0*clamp(p, -cs, cs)-p;
//inversion
float k=max(fs/dot(p,p),1.0);
p*=k;
dEfactor*=k;
//julia seed
p+=fc;
}

//return 10.01*dot(p,p);
//call basic shape and scale its DE
//need to adjust fractal_distancemult with non zero julia seed

//return 0.1*dot(p,p);
float aa = 1.5;//length(p.xy);

float rxy=aa-fu;
//distance from pos to the pseudo kleinian basic shape ...
return
(fd*max(0.0,abs(length(p.xy)*p.z)/sqrt(dot(p,p)))
/
abs(dEfactor));
}



float rd(vec2 fragment_coordinates, float ray_length) {

   //calculate ray direction from fragment coordinates ...
   vec2 ray_position = fragment_coordinates;//(0.5*rs-fragment_coordinates)/vec2(rs.x,-rs.y);
   ray_position.x *= (rs.x/rs.y); //aspect_ratio
   vec3 ray_direction = normalize(cr * vec3(ray_position.x * vec3(1, 0, 0) + ray_position.y * vec3(0, 1, 0) - .9 * vec3(0, 0, 1)));
   //sphere tracing initialization ...
   //float ray_length = 0.0;  //  this is now provided in the function arguments
   vec3  ray = cp+ray_length*ray_direction;
   float epsilon = 0.0000006;//0.0000006;
   float  calculated_distance;
   int   tracingsteps = 0;
   bool  hit = false;
   //magic :) DO NOT TOUCH!

float uCurrentLayerResolution = uPreviousLayerResolution * 2.0;//== 0.0 ? 128.0 : 512.0;

    float eps_factor = 1.0*1.41423*(1.01/uCurrentLayerResolution);


   //  TODO: Use the pixel size as the cutoff point

   //actual sphere tracing ...
   //#define tracingsteps_max 200

float last_ray_length = ray_length;

   for (int i=0; i<50; i++) {

    //  epsilon is set based on the distance

    tracingsteps = i;
      calculated_distance = dE(ray);     
      //small stepback to remove 'fuzzel'-structures near inside shooting range
      //#define raylength_max 10000.0
/*      if (hit && calculated_distance<epsilon || ray_length>10000.) {
         tracingsteps--;
         break;
      }
      hit = false;
 */
last_ray_length = ray_length;

      ray_length += calculated_distance;
      epsilon = ray_length * eps_factor;
      if (calculated_distance<epsilon) {
   //      hit = true;
         break;
      }
ray = cp+ray_length*ray_direction;
}
   //---   
   //intersects ?
   //#define tracingsteps_max 200
//   return float(tracingsteps)/float(40);
/*
      float epsilon_normal = 1.5e-7;
      vec3 normal = normalize(vec3(
         dE(ray+vec3(epsilon_normal, 0, 0))-dE(ray-vec3(epsilon_normal, 0, 0)),
         dE(ray+vec3(0, epsilon_normal, 0))-dE(ray-vec3(0, epsilon_normal, 0)),
         dE(ray+vec3(0, 0, epsilon_normal))-dE(ray-vec3(0, 0, epsilon_normal)))
      );
*/
//    if( hit )
    {
      return last_ray_length;//1.0 - 1.0/( ray_length*30.0 + 1.0 );
// + 0.1 + 0.1 * dot( normal, vec3( 0.707, 0.0, 0.707 ) );
    }
//    else
//    {
//        return 0.5;
//    }
}

//return rotation matrix for rotating around vector v by angle
mat3 mr(vec3 v, float angle) {
    float c = cos(radians(angle));
    float s = sin(radians(angle));
    return mat3(c+(1.0-c)*v.x*v.x   , (1.0-c)*v.x*v.y-s*v.z  , (1.0-c)*v.x*v.z+s*v.y,
               (1.0-c)*v.x*v.y+s*v.z, c+(1.0-c)*v.y*v.y      , (1.0-c)*v.y*v.z-s*v.x,
               (1.0-c)*v.x*v.z-s*v.y, (1.0 - c)*v.y*v.z+s*v.x, c+(1.0-c)*v.z*v.z);
}

void main(void)
{
  float ray_length = 0.0;

  if( uPreviousLayerResolution > 0.0 )
  {
    float uu1 = floor( (0.5+vPosition.x*0.5) * uPreviousLayerResolution ) / uPreviousLayerResolution;
    float vv1 = floor( (0.5+vPosition.y*0.5) * uPreviousLayerResolution ) / uPreviousLayerResolution;
    //float uu2 = floor( (0.5+vPosition.x*0.5) * uPreviousLayerResolution + 1.0 ) / uPreviousLayerResolution;
    //float vv2 = floor( (0.5+vPosition.y*0.5) * uPreviousLayerResolution ) / uPreviousLayerResolution;
    //float uu3 = floor( (0.5+vPosition.x*0.5) * uPreviousLayerResolution + 1.0 ) / uPreviousLayerResolution;
    //float vv3 = floor( (0.5+vPosition.y*0.5) * uPreviousLayerResolution + 1.0 ) / uPreviousLayerResolution;
    //float uu4 = floor( (0.5+vPosition.x*0.5) * uPreviousLayerResolution ) / uPreviousLayerResolution;
    //float vv4 = floor( (0.5+vPosition.y*0.5) * uPreviousLayerResolution + 1.0 ) / uPreviousLayerResolution;

    float l1 = unpack( texture2D( uPreviousLayerSampler, vec2(uu1, vv1) ) );
    //float l2 = unpack( texture2D( uPreviousLayerSampler, vec2(uu2, vv2) ) );
    //float l3 = unpack( texture2D( uPreviousLayerSampler, vec2(uu3, vv3) ) );
    //float l4 = unpack( texture2D( uPreviousLayerSampler, vec2(uu4, vv4) ) );

    ray_length = l1;
    //if( l2 > ray_length ) ray_length = l2;
    //if( l3 > ray_length ) ray_length = l3;
    //if( l4 > ray_length ) ray_length = l4;

    //  TODO: Unpack and decode ray_length

    //if(   fract(floor(fract(uTime)*5.0)*0.5) > 0.25     )
    {
    //        gl_FragColor =  texture2D( uPreviousLayerSampler, vec2(uu, vv) );
           //gl_FragColor = vec4( 1.0 ) * ray_length;//(1.0 - 1.0/( ray_length*30.0 + 1.0 ) );
    //       return;
    }
  }

    float camera_roll = 0.;
    float camera_pitch = 90.;
    float camera_yaw = 0.;

    vec2 custom_glFragCoord = gl_FragCoord.xy;    
    float temp_scenetimer = 200.0 - uTime * 0.10;

    fu = 10.0;
    fd = 0.763;
    cs = vec3(0.80800,0.80800,1.16700);
    fs = 1.0;
    fc = vec3(0.0);
    rs = vec2( 200.0, 200.0 );

    camera_pitch = 23.9;
    cp = vec3(0.8,-1.16339+16.0-temp_scenetimer*1.395,-1.80153);
    cs.y = 0.58000;

       fu = 1.21000;
       cp = vec3(0.0007,0.02000,0.10000-temp_scenetimer*0.00028);
       camera_pitch = 0.0;
       camera_yaw = temp_scenetimer*0.009*180.0;
       cs.x = 0.90000;

    cr = mat3( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 );
    cr = mr(vec3(0, 1, 0), 180.0 - camera_yaw) * mr(vec3(1, 0, 0), -camera_pitch) * mr(vec3(0, 0, 1), camera_roll);    

    float a = rd( vec2( vPosition.x, vPosition.y ), ray_length );

    gl_FragColor = pack(a); //vec4(1.0) * a * 10.0;//pack( a );
  }


</script>




<script id="finalshader" type="x-shader/x-fragment">

    #ifdef GL_ES
    precision mediump float;
    #endif

    varying vec3 vPosition;
    uniform float uTime;
    uniform vec2 uMousePos;

    uniform sampler2D uPreviousLayerSampler;

    float unpack(const in vec4 rgba_depth)
    {
    const vec4 bitShifts = vec4(1.0,
    1.0 / 255.0,
    1.0 / (255.0 * 255.0),
    0.0 / (255.0 * 255.0 * 255.0));
    return dot(rgba_depth, bitShifts);
    }


    //ifs
    float fu; //fractal_spheresubstract
    float fd; //fractal_distancemult
    vec3 cs;  //fractal_csize
    float fs; //fractal_size
    vec3 fc;  //fractal_c

    vec2 rs;  //resolution

    //camera
    vec3  cp; //camera position
    mat3  cr; //camera rotation


    vec3 dE(vec3 p) {

    vec3 last_pos = p;
        //  peterb
        //   p.x += 0.00015*sin(p.y*8000.0+uTime*30.0)*(0.5+0.5*sin(uTime*30.0/8.0));
        float dEfactor=1.0;
        //int fractal_iterations = 12;
        for(int i=0;i<20;i++)
        {

        //box folding
        p=2.0*clamp(p, -cs, cs)-p;
        //inversion
        float k=max(fs/dot(p,p),1.0);
        p*=k;
        dEfactor*=k;
        //julia seed
        p+=fc;
    last_pos += p;
        }

    return 0.5 + 0.5 * last_pos;
    //float d = dot( last_pos, last_pos );

    //return vec3(0.5) + 0.75*vec3
    //(
    //    sin(d*0.154+2.317), sin(d*0.124+1.331), sin(d*0.172-1.22)
    //);
    return vec3( 0.5 ) + vec3( 0.5 ) * dot( last_pos, last_pos );
    /*
        //return 10.01*dot(p,p);
        //call basic shape and scale its DE
        //need to adjust fractal_distancemult with non zero julia seed

        //return 0.1*dot(p,p);
        float aa = 1.5;//length(p.xy);

        float rxy=aa-fu;
        //distance from pos to the pseudo kleinian basic shape ...
        return
        (fd*max(0.0,abs(length(p.xy)*p.z)/sqrt(dot(p,p)))
        /
        abs(dEfactor));
    */
    }



    vec3 raypos(vec2 fragment_coordinates, float ray_length )
    {
        //calculate ray direction from fragment coordinates ...
        vec2 ray_position = fragment_coordinates;//(0.5*rs-fragment_coordinates)/vec2(rs.x,-rs.y);
        ray_position.x *= (rs.x/rs.y); //aspect_ratio
        vec3 ray_direction = normalize(cr * vec3(ray_position.x * vec3(1, 0, 0) + ray_position.y * vec3(0, 1, 0) - .9 * vec3(0, 0, 1)));
        //sphere tracing initialization ...
        //float ray_length = 0.0;  //  this is now provided in the function arguments
        return cp+ray_length*ray_direction;
    }

    //return rotation matrix for rotating around vector v by angle
    mat3 mr(vec3 v, float angle)
    {
    float c = cos(radians(angle));
    float s = sin(radians(angle));
    return mat3(c+(1.0-c)*v.x*v.x   , (1.0-c)*v.x*v.y-s*v.z  , (1.0-c)*v.x*v.z+s*v.y,
    (1.0-c)*v.x*v.y+s*v.z, c+(1.0-c)*v.y*v.y      , (1.0-c)*v.y*v.z-s*v.x,
    (1.0-c)*v.x*v.z-s*v.y, (1.0 - c)*v.y*v.z+s*v.x, c+(1.0-c)*v.z*v.z);
    }

    vec4 colour( float ray_length )
    {
        float camera_roll = 0.;
        float camera_pitch = 90.;
        float camera_yaw = 0.;

        vec2 custom_glFragCoord = gl_FragCoord.xy;
        float temp_scenetimer = 200.0 - uTime * 0.10;

    fu = 10.0;
    fd = 0.763;
    cs = vec3(0.80800,0.80800,1.16700);
    fs = 1.0;
    fc = vec3(0.0);
    rs = vec2( 200.0, 200.0 );

    camera_pitch = 23.9;
        cp = vec3(0.8,-1.16339+16.0-temp_scenetimer*1.395,-1.80153);
        cs.y = 0.58000;

        cp = vec3(0.0007,0.02000,0.10000-temp_scenetimer*0.00028);
        camera_pitch = 0.0;
        camera_yaw = temp_scenetimer*0.009*180.0;
        cs.x = 0.90000;

        cr = mat3( 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0 );
        cr = mr(vec3(0, 1, 0), 180.0 - camera_yaw) * mr(vec3(1, 0, 0), -camera_pitch) * mr(vec3(0, 0, 1), camera_roll);

        vec3 ray = raypos( vec2( vPosition.x, vPosition.y ), ray_length );

        vec3 a = dE( ray );

        return vec4( 0.5 + 0.1 * a.xyz, 1.0 );
    }


    void main(void)
    {
        //  Texture lookup coordinates

        float uu = 0.5 + vPosition.x*0.5;
        float vv = 0.5 + vPosition.y*0.5;

        //  Unpack and decode ray_length

    float ray_length = unpack( texture2D( uPreviousLayerSampler, vec2(uu, vv) ) );

    float s = 1.0 / 512.0;
    float ray_length1 = unpack( texture2D( uPreviousLayerSampler, vec2(uu-s, vv) ) );
    float ray_length2 = unpack( texture2D( uPreviousLayerSampler, vec2(uu+s, vv) ) );
    float ray_length3 = unpack( texture2D( uPreviousLayerSampler, vec2(uu, vv-s) ) );
    float ray_length4 = unpack( texture2D( uPreviousLayerSampler, vec2(uu, vv+s) ) );

    float ss = 1.0;

    ss *= ray_length1 / ray_length;
    ss *= ray_length2 / ray_length;
    ss *= ray_length3 / ray_length;
    ss *= ray_length4 / ray_length;

    float ll = ray_length1 / ray_length;

    ll = ll*ll*ll*ll*ll;
    //ll = ll*ll*ll*ll*ll;

    ss *= ss*(ss)*ss*ss;
    //ss *= ss*(ss)*ss*ss;

    if( ss < 0.0 ) ss = 0.0;
    if( ss > 1.0 ) ss = 1.0;

    gl_FragColor = ll * ss * (1.0 - 100.0 * ray_length) * vec4( 1.0 ) * colour( ray_length );// * ((log( ray_length * 1000000.0 )-0.5)*0.5+0.5);

        //gl_FragColor = texture2D( uPreviousLayerSampler, vec2( uu, vv ) );

        //gl_FragColor = vec4( 1.0, 0.5, 0.2, 1.0 );
    }

</script>







<body onload="webgl_init( 'canvas', 'status' )" bgcolor="#000000">

<table cellspacing="0" cellpadding="0" border="0" width="100%" aheight="100%"><tr><td align="center" valign="middle">
<table cellspacing="0" cellpadding="0" border="0" width="200">
  <tr>
    <td style="padding:8px;background:#555555;color:#FFFFFF;font-family:arial;font-size:11px;font-weight:bold">
      <div>WebGL Fractal - Colour, Lighting &amp; Edge Detection</div>
    </td>
  </tr>
  <tr>
    <td><canvas id="canvas" width="512" height="512" style="border:5px solid #FFFFFF"></canvas></td>
  </tr><tr>
    <td style="padding:8px;background:#555555;color:#FFFFFF;font-family:arial;font-size:11px;font-weight:bold">
      <div id="status">&nbsp;</div>
    </td>
  </tr>
</table>
</td></tr></table>


</body>
</html>
Related Articles

Another variation on low raycasting steps

Created by using a small number of raycasting steps

Created by using a very small number of raycasting steps

Adding colour

Edge detection using the z-buffer

Games

The Dodge Game
Flatspace

2-Player Games:

Quake 2D
Meteora

Puzzle Games:

Mini Tetris
Sudoku Solver

Development

3D Graphics:

3D Graphics Articles
WebGL Examples
OpenGL Examples
Flash 3D Engine
Java 3D Engine

Development:

Programming Articles
Game Development Examples

Work

Portfolio
Clients
Startups & Projects
Expertise

Links

CubeLogix Studios
PHP Charts & Graphs
Local Legends Football