Raymarch Workshop

Taught by: Char Stiles (contact@charstiles.com)

 

In this class we will learn about ray marching! Ray marching uses a very contained and mathematical way to describe a 3D scene in one fragment shader. 

You will create your own 3D scene or music visualizer with ray marching. I will go over some examples of raymarching, how to set up the scene, and provide more material/functions/techniques so you can customize your scene or visualizer to fit your style.

Ray marching is seriously my favorite technique for fun shader programming. One of the reasons I love it is that it is physically based, i.e. you follow a ray the same way photons bounce around in the real world. It has a lot of use in the future with all this drive towards physically based realtime rendering techniques.

Schedule

6:30-6:45 Introduction to ray marching & some examples.

6:45-7:25 We will write a raymarcher together.

7:25-8 Workshopping time. I will being going around and every 20 min or so present new functions and techniques you can use in your music visualizer or scene.

8-8:30 Those who wish can show their ray marched music visualizer or scene. I will take questions and present steps going forward to learn more about ray marching.

Links

Next Steps

These are some specific suggestions from me if you want to continue learning but don’t know how to start.

1. Learn the maths from Physically Based Rendering book
** If you haven’t looked at this already, look through it! Its wonderfully dense and has a lot of versatile useful information. [http://www.pbr-book.org/]

2. Get into more tutorials!
** This tutorial here: https://github.com/ajweeks/RaymarchingWorkshop is super thorough and really useful!

3. Get involved with the community online

*** you can see some amazing raymarched scenes online at shadertoy.com

Code

Here is the code that we are going to walk through together:

// Author: Char Stiles
// Title: Raymarching template

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

const int steps = 128;
const float smallNumber = 0.01;
const float maxDist = 10.;

float scene(vec3 position){
// So this is different from the sphere equation above in that I am
// splitting the position into its three different positions
// and adding a 10th of a cos wave to the x position so it oscillates left
// to right and a (positive) sin wave to the z position
// so it will go back and forth.
float sphere = length(
vec3(
position.x + cos(u_time)/10.,
position.y,
position.z + sin(u_time) -1.)
)-0.5;

// This is different from the ground equation because the UV is only
// between -1 and 1 we want more than 1/2pi of a wave per length of the
// screen so we multiply the position by a factor of 10 inside the trig
// functions. Since sin and cos oscillate between -1 and 1, that would be
// the entire height of the screen so we divide by a factor of 10.
float ground = position.y + sin(position.x * 10.) / 10.
+ cos(position.z * 10.) / 10. + 1.;

// We want to return whichever one is closest to the ray, so we return the
// minimum distance.
return min(sphere,ground);
}

vec4 trace (vec3 origin, vec3 direction){

float dist = 0.;
float totalDistance = 0.;
vec3 positionOnRay = origin;

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

dist = scene(positionOnRay);

// Advance along the ray trajectory the amount that we know the ray
// can travel without going through an object.
positionOnRay += dist * direction;

// Total distance is keeping track of how much the ray has traveled
// thus far.
totalDistance += dist;

// If we hit an object or are close enough to an object,
if (dist < smallNumber){ // return the distance the ray had to travel normalized so be white // at the front and black in the back. return 1. - (vec4(totalDistance) / maxDist); } if (totalDistance > maxDist){

return vec4(0.); // Background color.
}
}

return vec4(0.);// Background color.
}

vec3 lookAt(vec2 uv, vec3 camOrigin, vec3 camTarget){
vec3 zAxis = normalize(camTarget - camOrigin);
vec3 up = vec3(0,1,0);
vec3 xAxis = normalize(cross(up, zAxis));
vec3 yAxis = normalize(cross(zAxis, xAxis));

float fov = 2.;

vec3 dir = (normalize(uv.x * xAxis + uv.y * yAxis + zAxis * fov));

return dir;
}

// main is a reserved function that is going to be called first
void main(void)
{
// We are redefining the UV coordinates (aka texcoords) to be 0,0 in the
// middle of the screen this is because its easier to work with the camera at
// (0,0) instead of (0.5,0.5) for the SDFs
vec2 uv = -1. + 2. * (gl_FragCoord.xy/u_resolution.xy);

// unfortunately our screens are not square so we must account for that.
uv.x *= (u_resolution.x / u_resolution.y);

//vec3 rayOrigin = vec3(uv, 0.);
//vec3 camOrigin = vec3(0., 0., -1.);

//vec3 camTarget = vec3(0,0, 2);

//vec3 direction = lookAt(uv, camOrigin, camTarget);

vec3 camOrigin = vec3(0., 0., -2.);
vec3 rayOrigin = vec3(uv + camOrigin.xy, camOrigin + 1.);
vec3 direction = rayOrigin - camOrigin ;

gl_FragColor = trace(camOrigin, direction);
}

The gist for this code is here:

https://gist.github.com/CharStiles/327768a23e5e64b7a9d6163ae305b15b

 

The code we wrote is a little different than the reference code, I wanted to include that especially since we had to put decimal points after all our numbers 🙂

This is the gist: https://gist.github.com/CharStiles/4fca35f81adc6020c4debb86702c2727

 

This is code from another lesson:

 

// Author: char stiles
// Title: raymarcher from Experimental Shaders class

#ifdef GL_ES
precision mediump float;
#endif

uniform vec2 u_resolution;
uniform vec2 u_mouse;
uniform float u_time;

const int step = 128;
const float maxDist = 50.3;
const float PI = 3.141592658;
const float smallNumber = 0.02;

// http://www.iquilezles.org/www/articles/palettes/palettes.htm
// As t runs from 0 to 1 (our normalized palette index or domain),
//the cosine oscilates c times with a phase of d.
//The result is scaled and biased by a and b to meet the desired constrast and brightness.
vec3 cosPalette( float t, vec3 a, vec3 b, vec3 c, vec3 d )
{
return a + b*cos( 6.28318*(c*t+d) );
}

float sphere(vec3 pos, float rad){
return length(pos) - rad;
}
// Cone with correct distances to tip and base circle. Y is up, 0 is in the middle of the base.
float fCone(vec3 p, float radius, float height) {
vec2 q = vec2(length(p.xz), p.y);
vec2 tip = q - vec2(0, height);
vec2 mantleDir = normalize(vec2(height, radius));
float mantle = dot(tip, mantleDir);
float d = max(mantle, -q.y);
float projected = dot(tip, vec2(mantleDir.y, -mantleDir.x));

// distance to tip
if ((q.y > height) && (projected < 0.0)) {
d = max(d, length(tip));
}

// distance to base ring
if ((q.x > radius) && (projected > length(vec2(height, radius)))) {
d = max(d, length(q - vec2(radius, 0)));
}
return d;
}

// Repeat around the origin by a fixed angle.
// For easier use, num of repetitions is use to specify the angle.
float pModPolar(inout vec2 p, float repetitions) {
float angle = 2.*PI/repetitions;
float a = atan(p.y, p.x) + angle/2.;
float r = length(p);
float c = floor(a/angle);
a = mod(a,angle) - angle/2.;
p = vec2(cos(a), sin(a))*r;
// For an odd number of repetitions, fix cell index of the cell in -x direction
// (cell index would be e.g. -5 and 5 in the two halves of the cell):
if (abs(c) >= (repetitions/2.)) c = abs(c);
return c;
}

vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
float smin( float a, float b, float k )
{
float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );
return mix( b, a, h ) - k*h*(1.0-h);
}
float scene(vec3 pos){
pos.z += sin( pos.z);

vec3 modSpace = vec3(3.,4.,5.);
pos = mod(pos,modSpace) - 1.;
pModPolar(pos.xy,-6.0);
float s = fCone(pos, 0.6, 0.3);
pModPolar(pos.xy,3.0);
float s2 = sphere(pos +( sin(u_time)*0.6), 0.2);
return smin(s,s2,(cos(u_time*2.)+1.)* 0.2);
}

vec3 lookAt(vec2 uv, vec3 camOrigin, vec3 camTarget){
vec3 zAxis = normalize(camTarget - camOrigin);
vec3 up = vec3(0,1,0);
vec3 xAxis = normalize(cross(up, zAxis));
vec3 yAxis = normalize(cross(zAxis, xAxis));

float fov = 2.;

vec3 dir = (normalize(uv.x * xAxis + uv.y * yAxis + zAxis * fov));

return dir;
}

vec3 estimateNormal(vec3 p) {
vec3 n = vec3(
scene(vec3(p.x + smallNumber, p.yz)) -
scene(vec3(p.x - smallNumber, p.yz)),
scene(vec3(p.x, p.y + smallNumber, p.z)) -
scene(vec3(p.x, p.y - smallNumber, p.z)),
scene(vec3(p.xy, p.z + smallNumber)) -
scene(vec3(p.xy, p.z - smallNumber))
);
return normalize(n);
}

float lighting(vec3 origin, vec3 dir, vec3 normal) {
vec3 lightPos = vec3(cos(u_time)*20., sin(u_time), 12.);
vec3 light = normalize(lightPos - origin);

float diffuse = max(0., dot(light, normal));
vec3 reflectedRay = 2. * dot(light, normal) * normal - light;

float specular = max(0., (pow(dot(reflectedRay, light), 3.)));

float ambient = 0.05;

return ambient + diffuse + specular;

}

vec4 trace(vec3 camOrigin, vec3 dir){

vec3 ray = camOrigin;
float totalDist = 0.;
float dist;
for(int i = 0 ; i < step; i ++){
dist = scene(ray);
totalDist += dist;
if (dist < 0.001){
vec3 n = estimateNormal(ray);
float l = lighting(ray, dir, n);
vec3 color = cosPalette(totalDist,
vec3(0.5, 0.5, 0.5),
vec3(0.5, 0.5, 0.5),
vec3(1.0, 1.0, 0.5),
vec3(0.80, 0.90, 0.30));
return vec4(color * l, 1.0) *(0.2+ (1.- totalDist/maxDist));
// return vec4(cosPalette(totalDist,
// vec3(0.5, 0.5, 0.5),
// vec3(0.5, 0.5, 0.5),
// vec3(1.0, 1.0, 0.5),
// vec3(0.80, 0.90, 0.30)), 1.0);

//return vec4(hsv2rgb(vec3(totalDist,
//1.0,1.0)),1.0);//;/maxDist);
}
if (totalDist > maxDist){
return vec4(0);
}
ray += dist*dir;
}

return vec4(0.0);
}

void main() {
vec2 uv = gl_FragCoord.xy/u_resolution.xy;
uv = (uv *2.0) - vec2(1.0);
uv.x *= u_resolution.x/u_resolution.y;
vec3 camOrigin = vec3(0.0,0.0,-1.0);

vec3 rayOrigin = vec3(uv.x + camOrigin.x,
uv.y + camOrigin.y,
camOrigin.z + 1.0);
vec3 camTarget = vec3(sin(u_time/20.),
sin(u_time/5.),
cos(u_time/10.));

vec3 dir = lookAt(uv, camOrigin, camTarget);
//vec3 dir = rayOrigin - camOrigin;

vec4 color = trace(camOrigin,dir);

gl_FragColor = color;//vec4(u_time);

}