Been a while since I wrote an article on lighting and ray-marching, so I decided to write one on how to set-up a basic Lambertian light algorithms. All the code and the final shader are available on my ShaderToy page, anyways, let’s dive in. We will use Ray-Marching using 2 triangles, and a distance field for the ease of use.

Let’s start all black

I started by only using a simple sky and black out every color on my scene. Sad, sad, sad…

Image for post

The goal of lambertian lighting is quite simple, let’s throw the equation first…

Image for post

I love to break every part of an equation, this way it makes it easier to understand and implement. What we want to know, or compute is “What is the color of the pixel”, and for now, we have a hit point in our scene. Well for now we do not see it, because I forced all the hitpoint to be in black. I will take a sphere as an example, because we love spheres 🙂

Image for post

I made the sphere in white, then we need to shade it. Let’s walk every terms of the equation:

L dot N

Is the first part we need to look at.

L: Is the light position in worldspace. Normally defined by a vec3 in GLSL or float3 in HLSL.

N: is the normal in worldspace from the point we want to shade.

The dot part:

Most shading language have a dot operator, if not, the idea is simply to mesure how two vectors are oriented towards each others. In 2D we could express the dot product between vector a and b as follow.

Image for post

The dot gives you the ability to track information on how the point N and the light pos L are oriented one vs the other.

Image for post

Therefore we will use the power of GLSL to deal with the dot product. Let’s code this simple idea before moving to color and intensity.

vec3 lightPos = vec3(1.0, 1.0, -12.);
vec3 L = normalize(lightPos — pos);
vec3 N = computeNormal(pos);
col = vec3(dot(N, L));

I plug the dot product result into a vec3 constructor, as the result is a float, and we want to get express as a rgb out color. Synthaxic sugar, but very usefull 🙂

Giving us:

Image for post

ShadertoyThis help only covers the parts of GLSL ES that are relevant for Shadertoy. For the complete specification please have…www.shadertoy.com

So at this point, we are almost done. The hardest part is done and everything that we need to warp our head around is simply: what is the color of the light, and what is it’s intensity?

C and I

C: the color of the light

I: the intensity

So we simply need to define a color and an intensity. Let’s say the light will be green.

vec3 ligthColor = vec3(0.01, 1., 0.01);

With an intensity of 1.5, intensity if usually define as a float, but to smash everything into GLSL, simply use the vec3(float A) to assign a float as every member of the vec3 class.

float intensity = 1.5; vec3 Li = vec3(intensity);

vec3 lightPos = vec3(1.0, 1.0, -12.);
vec3 L = normalize(lightPos — pos);
vec3 N = computeNormal(pos);
vec3 C = vec3(0.01, 1.0, 0.05);
col = vec3(dot(N, L)) * C;

Giving us:

Image for post

For intensity let’s add this way:

vec3 lightPos = vec3(1.0, 1.0, -12.);
vec3 L = normalize(lightPos — pos);
vec3 N = computeNormal(pos);
vec3 C = vec3(0.01, 1.0, 0.05);
float scalarI = 1.5;
vec3 I = vec3(scalarI);
col = vec3(dot(N, L)) * C * I;

Giving us:

Image for post

Here is the final Pixel shader code 🙂 Everything we implemented is at line

94!

#define RAYMARCH_STEPS = 265

float sdSphere(vec3 pos, float radius)
{
   return length(pos) - radius;
}

float sdBox( vec3 p, vec3 b )
{
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}

vec2 opu(vec2 d1, vec2 d2)
{
    return (d1.x < d2.x) ? d1 : d2;
}

float plane(vec3 pos)
{	
    
    vec3 q = pos;
    return q.y;
}

vec2 df(vec3 pos)
{
    
    float shape1 = sdSphere(pos + vec3(0., -2., 0.), 1.);
    float shape2 = sdBox(pos + vec3(3.0, 0.0, 0.0), vec3(1.0));
	float shape3 = 	plane(pos);    				
    vec2 res =     vec2(shape1,       1.0 /*Object ID*/);
    res = opu(res, vec2(shape2,       2.0/*Object ID*/));
    res = opu(res, vec2(shape3,       3.0/*Object ID*/));     
    return res;
}


float castRay(vec3 ro, vec3 rd)
{
    float t = 0.0;
    for(int i = 0; i < 100; i++)
    {
    	vec3 pos = ro + t * rd;
        
        float h = df(pos).x;
        if(h < 0.001)
        {
        	break;
        }
        t += h;
        
        if(t > 20.0) break;
        
    }
    
    if(t > 20.0) t = -1.0;
    
    return t;
}

vec3 computeNormal(vec3 pos)
{
    vec2 eps = vec2(0.1, 0.0);
    return normalize(vec3(
        df(pos + eps.xyy).x - df(pos - eps.xyy).x,
        df(pos + eps.yxy).x - df(pos - eps.yxy).x,
        df(pos + eps.yyx).x - df(pos - eps.yyx).x
    ));
}



void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
   // Normalized pixel coordinates (from 0 to 1)
   vec2 uv = (fragCoord -.5 * iResolution.xy) / iResolution.y;
		
   // the ray origin
   vec3 pos = vec3(0.0, 2.0, -5.0);
	    
   // the ray direction
   vec3 dir = normalize(vec3(uv.x, uv.y, 1));    
   // The default color, if there is no hitpoint
	vec3 col = vec3(0.4, 0.75, 1.0) - 0.7 * (sin(dir.x) *  dir.x + dir.y);
	    
   for(int i = 0; i < 120; i++)
   {	
       // Get the distance of the ray
       float d = df(pos).x;
       float id = df(pos).y;
       // we hit something
       if(d < 0.01)
       {	
		vec3 lightPos = vec3(1.0, 1.0, -12.);
		vec3 L = normalize(lightPos - pos);
           	vec3 N = computeNormal(pos);
           	vec3 C = vec3(0.01, 1.0, 0.05);
           	float scalarI = 1.5; 
           	vec3 I = vec3(scalarI);
           	col = vec3(dot(N, L)) * C * I;
		break;
	}
	   pos += d * dir;
    }
	    
    // Output pixel to screen    
    fragColor = vec4(col, 1.0);
}

Leave a Reply

Your email address will not be published. Required fields are marked *