RayMarching Shader Pt.1 (GLSL)

Image of a ray marched scene with a sphere, a plane and lighting and shadows.

On this page, the coding steps to produce a basic Ray Marching fragment shader, that creates a ray marched scene of a sphere and a plane, with basic diffuse directional lighting and shadows.

If you quickly want to try out the examples I recommend the web based GLSL editor from the book of shaders over here:
http://editor.thebookofshaders.com

If you prefer working with an offline editor I recommend Visual Studio Code with the glsl-canvas extension: https://code.visualstudio.com
Or if you prefer Sublime Text then you can install this package : https://packagecontrol.io/packages/glslViewer

If you are reading this on an iOS device I recommend the app ‘Shaders’ which can be used to program and run GLSL shaders and also has a public timeline that you can use to share your work with others:
https://apps.apple.com/app/shaders-shader-editor/id1221602862?l=en

1. RayMarching Shader: Distance Field

Ray marching shader distance field example image.
precision highp float; 
uniform vec2 u_resolution;  // Width and height of the shader
uniform float u_time;  // Time elapsed

// Constants
#define PI 3.1415925359
#define TWO_PI 6.2831852
#define MAX_STEPS 100
#define MAX_DIST 100.
#define SURFACE_DIST .01

float GetDist(vec3 p)
{
    vec4 s = vec4(0,1,6. + sin(u_time)*3.,1); //Sphere. xyz is position w is radius
    float sphereDist = length(p-s.xyz) - s.w;
    float planeDist = p.y;
    float d = min(sphereDist,planeDist);

    return d;
}

float RayMarch(vec3 ro, vec3 rd) 
{
    float dO = 0.; //Distane Origin
    for(int i=0;i<MAX_STEPS;i++)
    {
        vec3 p = ro + rd * dO;
        float ds = GetDist(p); // ds is Distance Scene
        dO += ds;
        if(dO > MAX_DIST || ds < SURFACE_DIST) break;
    }
    return dO;
}

void main()
{
    vec2 uv = (gl_FragCoord.xy-.5*u_resolution.xy)/u_resolution.y;
    vec3 ro = vec3(0,1,0); // Ray Origin/ Camera
    vec3 rd = normalize(vec3(uv.x,uv.y,1));
    float d = RayMarch(ro,rd); // Distance
    d/= 10.;
    vec3 color = vec3(d);
    
    // Set the output color
    gl_FragColor = vec4(color,1.0);
}

Distance field
The ray march loop marches from the origin/camera into the direction of the scene. After each iteration it checks if the distance to the scene is smaller than the minimum distance(SURACE_DIST). If so, it returns the distance to the object. If the distance is greater than the max distance, meaning the ray hasn’t hit an object it also breaks out of the loop.

2. RayMarching Shader: Distance Field + Normals

Ray marching shader distance field + normals example image
precision highp float; 
uniform vec2 u_resolution;  // Width and height of the shader
uniform float u_time;  // Time elapsed

// Constants
#define PI 3.1415925359
#define TWO_PI 6.2831852
#define MAX_STEPS 100
#define MAX_DIST 100.
#define SURFACE_DIST .01

float GetDist(vec3 p) 
{
    vec4 s = vec4(0,1,6,1); //Sphere xyz is position w is radius
    float sphereDist = length(p-s.xyz) - s.w;
    float planeDist  = p.y;
    float d = min(sphereDist,planeDist);

    return d;
}

float RayMarch(vec3 ro, vec3 rd) 
{
    float dO = 0.; //Distane Origin
    for(int i=0;i<MAX_STEPS;i++)
    {
        vec3 p = ro + rd * dO;
        float ds = GetDist(p); // ds is Distance Scene
        dO += ds;
        if(dO > MAX_DIST || ds < SURFACE_DIST) break;
    }
    return dO;
}

vec3 GetNormal(vec3 p)
{ 
    float d = GetDist(p); // Distance
    vec2 e = vec2(.01,0); // Epsilon
    vec3 n = d - vec3(
    GetDist(p-e.xyy),  
    GetDist(p-e.yxy),
    GetDist(p-e.yyx));
  
    return normalize(n);
}

void main()
{
    vec2 uv = vec2(gl_FragCoord.xy-.5*u_resolution.xy)/u_resolution.y;
    vec3 ro = vec3(0,1,0); // Ray Origin/Camera
    vec3 rd = normalize(vec3(uv.x,uv.y,1)); // Ray Direction
  
    float d = RayMarch(ro,rd); // Distance
  
    vec3 p = ro + rd * d;
    //float dif = GetLight(p); // Diffuse lighting
    //d/= 10.;
    vec3 color = vec3(0);
    //color = vec3(dif);
    color = GetNormal(p);

    // Set the output color
    gl_FragColor = vec4(color,1.0);
}

Normals
To get the normals the point where the raymarch hits is used and a small amount (Epsilon) is added to the point in the right, up and forward direction. This new offset point is then normalized to turn it into a unit vector/direction.

3. RayMarching Shader: Distance Field + Normals + Diffuse Lighting

Ray marching shader distance field + normals + diffuse lighting example image.
precision highp float;
uniform vec2 u_resolution; // Width & height of the shader
uniform float u_time; // Time elapsed
// Constants
#define PI 3.1415925359
#define TWO_PI 6.2831852
#define MAX_STEPS 100
#define MAX_DIST 100.
#define SURFACE_DIST .01

float GetDist(vec3 p) 
{
    vec4 s = vec4(0,1,6,1); //Sphere xyz is position w is radius
    float sphereDist = length(p-s.xyz) - s.w;
    float planeDist  = p.y;
    float d = min(sphereDist,planeDist);
    return d;
}

float RayMarch(vec3 ro, vec3 rd) 
{
    float dO = 0.; //Distane Origin
    for(int i=0;i<MAX_STEPS;i++)
    {
        vec3 p = ro + rd * dO;
        float ds = GetDist(p); // ds is Distance Scene
        dO += ds;
        if(dO > MAX_DIST || ds < SURFACE_DIST) break;
    }
    return dO;
}

vec3 GetNormal(vec3 p)
{
    float d = GetDist(p);
    vec2 e = vec2(.01,0);
    vec3 n = d - vec3(
    GetDist(p-e.xyy),
    GetDist(p-e.yxy),
    GetDist(p-e.yyx));

    return normalize(n);
}

float GetLight(vec3 p)
{ 
    // Light (directional diffuse)
    vec3 lightPos = vec3(5.*sin(u_time),5.,5.0*cos(u_time)); // Light Position
    vec3 l = normalize(lightPos-p); // Light Vector
    vec3 n = GetNormal(p); // Normal Vector
  
    float dif = dot(n,l); // Diffuse light
    dif = clamp(dif,0.,1.); // Clamp so it doesnt go below 0

    return dif;
}

void main()
{
    vec2 uv = (gl_FragCoord.xy-.5*u_resolution.xy)/u_resolution.y;
    vec3 ro = vec3(0,1,0); // Ray Origin/Camera
    vec3 rd = normalize(vec3(uv.x,uv.y,1)); // Ray Direction

    float d = RayMarch(ro,rd); // Distance
  
    vec3 p = ro + rd * d;
    float dif = GetLight(p); // Diffuse lighting
    d*= .2;
    vec3 color = vec3(dif);
    //color += GetNormal(p);
    //float color = GetLight(p);

    // Set the output color
    gl_FragColor = vec4(color,1.0);
}

Diffuse light (Directional)
To calculate diffuse light, the angle between the surface normal and the light vector from a raymarched point is used. To get the angle, the Dot product is used which returns the angle in a -1 <> 1 range. So when the light vector is perpendicular to the normal vector the Dot product is 1 and if its parralel to the normal vector it is 0.

4. RayMarching Shader: Distance Field + Normals + Diffuse + Shadow

Ray marching shader distance field + normals + diffuse + shadow example image
precision highp float;
uniform vec2 u_resolution; // Width & height of the shader
uniform float u_time; // Time elapsed

// Constants
#define PI 3.1415925359
#define TWO_PI 6.2831852
#define MAX_STEPS 100 // Mar Raymarching steps
#define MAX_DIST 100. // Max Raymarching distance
#define SURF_DIST .01 // Surface Distance

float GetDist(vec3 p) 
{
  vec4 s = vec4(0,1,6,1); //Sphere xyz is position w is radius
  float sphereDist = length(p-s.xyz) - s.w;
  float planeDist  = p.y;
  float d = min(sphereDist,planeDist);
  return d;
}

float RayMarch(vec3 ro, vec3 rd) 
{
  float dO = 0.; //Distane Origin
  for(int i=0;i<MAX_STEPS;i++)
  {
    vec3 p = ro + rd * dO;
    float ds = GetDist(p); // ds is Distance Scene
    dO += ds;
    if(dO > MAX_DIST || ds < SURF_DIST) 
      break;
  }
  return dO;
}

vec3 GetNormal(vec3 p)
{ 
    float d = GetDist(p); // Distance
    vec2 e = vec2(.01,0); // Epsilon
    vec3 n = d - vec3(
    GetDist(p-e.xyy),
    GetDist(p-e.yxy),
    GetDist(p-e.yyx));

    return normalize(n);
}
float GetLight(vec3 p)
{ 
    // Directional light
    vec3 lightPos = vec3(5.*sin(u_time),5.,5.0*cos(u_time)); // Light Position
    vec3 l = normalize(lightPos-p); // Light Vector
    vec3 n = GetNormal(p); // Normal Vector
  
    float dif = dot(n,l); // Diffuse light
    dif = clamp(dif,0.,1.); // Clamp so it doesnt go below 0
  
    // Shadows
    float d = RayMarch(p+n*SURF_DIST*2., l); 
    
    if(d<length(lightPos-p)) dif *= .1;

    return dif;
}

void main()
{
    vec2 uv = (gl_FragCoord.xy-.5*u_resolution.xy)/u_resolution.y;
    
    vec3 ro = vec3(0,1,0); // Ray Origin/Camera
    vec3 rd = normalize(vec3(uv.x,uv.y,1)); // Ray Direction
  
    float d = RayMarch(ro,rd); // Distance
  
    vec3 p = ro + rd * d;
    float dif = GetLight(p); // Diffuse lighting
    d*= .2;
    vec3 color = vec3(dif);
    //color += GetNormal(p);
    //float color = GetLight(p);

    // Set the output color
    gl_FragColor = vec4(color,1.0);
}

Shadows
To check if a point is in the shade, we raymarch from that point into the direction of the light and if the dist we get out of it is smaller than the distance to the light than we know we hit something in between the light and that point. The line below almost works but because the point were raymarching from is already closer to the surface than what we allow it to be in the ray march loop, it kicks out of the marching loop immediately…:
float d = RayMarch(p,l);
By moving the point away from the surface a little bit (in the direction of the normal) we can make sure they raymarch loop starts normally!:
float d = RayMarch(p+nSURF_DIST2., l);

Sources
The Art of Code: The Art Of Code Youtube
Inigo Quilez: http://www.iquilezles.org

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Powered by WordPress.com.

Up ↑

%d bloggers like this: