home dev games gallery work
Lewpen.com»Research & Development»3D Graphics»WebGL»GLSL Raytracer»Raytracer 008 - Bumpy Textures

Raytracer 008 - Bumpy Textures

Creating bumpy surfaces by perturbing the surface normal at each point

/ index.html

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

<script src="webgl_fragment_framework.js?download"></script>

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

  /*
    WebGL Realtime Raytracer
    (c) Lewpen Kinross-Skeels 2011
    www.lewpen.com
  */

  #ifdef GL_ES
  precision mediump float;
  #endif

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

  vec3 ray;
  vec3 dir;

  vec4 sphere_pos[3];
  vec3 sphere_col[3];

  float intersectSphere( in vec3 ray, in vec3 dir, in vec3 sphere_pos, in float radius )
  {
    float x = sphere_pos.x;
    float y = sphere_pos.y;
    float z = sphere_pos.z;

    float a, b, c, d, t1, t2;

    a = dir.x * dir.x + dir.y * dir.y + dir.z * dir.z;
    b = 2.0 * (dir.x * (ray.x - x) + dir.y * (ray.y - y) + dir.z * (ray.z - z));
    c = (ray.x - x) * (ray.x - x) + (ray.y - y) * (ray.y - y) + (ray.z - z) * (ray.z - z) - radius * radius;
    d = b * b - 4.0 * a * c;

    if( d <= 0.0 ) return 0.0;

    d = sqrt(d);

    t2 = (-b + d) / (2.0 * a);
    t1 = (-b - d) / (2.0 * a);

    if( t1 > t2 ) return t2;
    return t1;
  }

  int intersect( in vec3 ray, in vec3 dir, out vec3 col, out vec3 pos, out vec3 normal, out vec3 reflection )
  {
    float max_t = 9999999.0;
    int found_i = -1;

    for( int i = 0; i<3; i++ )
    {
      float t = intersectSphere( ray, dir, sphere_pos[i].xyz, sphere_pos[i].w );

      if( t > 0.0001 && t < max_t )
      {
        max_t = t;
        found_i = i;
      }
    }

    if( found_i == -1 )
    {
      col = vec3( 0.0, 0.0, 0.0 );
      return -1;
    }

    for( int i = 0; i<3; i++ )
    {
      if( found_i == i )
      {
        pos = ray + max_t * dir;

        normal = (pos - sphere_pos[i].xyz) / sphere_pos[i].w;

        reflection = dir - 2.0*normal*dot( dir, normal );

        col = sphere_col[i];
      }
    }

    return found_i;
  }

  bool intersect_shadow( in vec3 ray, in vec3 dir )
  {
    for( int i = 0; i<3; i++ )
    {
      float t = intersectSphere( ray, dir, sphere_pos[i].xyz, sphere_pos[i].w );

      if( t > 0.0 )
      {
        return false;
      }
    }
    return true;
  }

  void main(void)
  {
    sphere_pos[0] = vec4( -1.0, 0.0, 4.0, 0.8 );
    sphere_col[0] = vec3( 0.5, 0.0, 1.0 );

    sphere_pos[1] = vec4( 1.0, 0.5, 3.0, 0.9 );
    sphere_col[1] = vec3( 1.0, 0.75, 0.0 );

    sphere_pos[2] = vec4( 0.2, -0.75, 3.5, 1.0 );
    sphere_col[2] = vec3( 0.75, 1.0, 0.0 );

    ray = vec3( 0.0, 0.0, 0.0 );
    dir = normalize( vPosition );

    vec3 final_col;

    vec3 col;
    vec3 pos;
    vec3 normal;
    vec3 reflection;

    vec3 light = vec3( -0.5, 3.0, 2.0 );

    for( int i=0; i<3; i++ )
    {
      float x = sphere_pos[i].x + 1.3*sin(sphere_pos[i].y*2.13 + uTime*(0.6762+sphere_pos[i].z*0.163));
      float y = sphere_pos[i].y + 1.3*sin(sphere_pos[i].z*1.13 + uTime*(0.8467+sphere_pos[i].x*0.193));
      float z = sphere_pos[i].z + 0.3*sin(sphere_pos[i].x*4.13 + uTime*(1.1112+sphere_pos[i].y*0.183));

      sphere_pos[i] = vec4( x, y, z, sphere_pos[i].w );
    }

    //  Intersect first ray

    int sphere_index = intersect( ray, dir, col, pos, normal, reflection );

    if( sphere_index == -1 )
    {
      final_col = vec3( 0.25 ) * dir.z;
    }
    else
    {
      //  Perturb normal and direction vector

      float f = sin(normal.x*5.918+13.841)*0.43 + sin(normal.y*11.8218+7.41)*0.21 + sin(normal.z*16.821+11.841)*0.34;
      float g = sin(normal.y*2.167+14.841)*0.13 + sin(normal.z*18.1298+5.98)*0.17 + sin(normal.x*15.231+12.321)*0.70;
      float h = f*g;//sin(normal.z*3.324+11.312)*0.22 + sin(normal.x*13.4241+5.12)*0.34 + sin(normal.y*19.212+17.424)*0.51;

      normal += 0.1 * vec3( f, g, h );

      reflection = dir - 2.0*normal*dot( dir, normal );

      //  Calculate colour of surface

      vec3 light_dir = light - pos;

      if( intersect_shadow( pos+light_dir*0.0000001, light_dir+normal ) )
      {
        light_dir = normalize( light_dir );
        col *= max( dot( normal, light_dir ), 0.0 )*0.8 + 0.2;
        float phong = pow( max( dot( reflection, light_dir ), 0.0 ), 17.0 );
        col = vec3( 1.0, 1.0, 1.0 ) * phong + col*(1.0-phong);
      }
      else
      {
        col *= 0.2;
      }

      final_col = col + 0.1 * vec3( sin( dot( pos, pos ) ) );

      sphere_index = intersect( pos+reflection*0.0000001, reflection, col, pos, normal, reflection );

      if( sphere_index >= 0 )
      {
        vec3 light_dir = light - pos;

        if( intersect_shadow( pos+light_dir*0.0001, light_dir ) )
        {
          light_dir = normalize( light_dir );
          col *= max( dot( normal, light_dir ), 0.0 )*0.8 + 0.2;
          float phong = pow( max( dot( reflection, light_dir ), 0.0 ), 17.0 );
          col = vec3( 1.0, 1.0, 1.0 ) * phong + col*(1.0-phong);
        }
        else
        {
          col *= 0.2;
        }

        final_col = final_col*0.75 + col*0.25;
      }
    }

    gl_FragColor = vec4( final_col, 1.0 );
  }

</script>

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

<table cellspacing="0" cellpadding="0" border="0" width="100%" height="100%"><tr><td align="center" valign="middle">
<table cellspacing="0" cellpadding="0" border="0" width="500">
  <tr>
    <td style="padding:8px;background:#555555;color:#FFFFFF;font-family:arial;font-size:11px;font-weight:bold">
      <div>WebGL Fragment Shader Raytracer - 3 Spheres - Bumpy</div>
    </td>
  </tr>
  <tr>
    <td><canvas id="canvas" width="500" height="500" 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

Using shadow rays to determine if the path between each pixel and the light source is blocked

First Test - Using a fragment shader to hit-test 3 spheres and colour each pixel based on the surface normal

Lets try it with 6 spheres this time

Adding a lighting model with specular (Phong) and diffuse lighting

Pushing it a bit with 16 spheres

Adding a pearlescent effect by adjusting the brightness of each point by the sine of the distance from the camera

Recursive ray tracing to add reflection to the spheres

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