#### 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.

### 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 &lt; 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 &lt; 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 &gt; 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 code from another lesson:

&nbsp;

// 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) );
}

}
// 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);
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 &gt; height) &amp;&amp; (projected &lt; 0.0)) {
d = max(d, length(tip));
}

// distance to base ring
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) &gt;= (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 &lt; step; i ++){
dist = scene(ray);
totalDist += dist;
if (dist &lt; 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 &gt; 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);

} 