0.00
60.0 fps

Speed tracer

Simple raytracer showing a lot of spheres. A grid is used as an acceleration structure.

Log in to post a comment.

precision highp float;

uniform float iTime;
uniform vec2  iResolution;

// [SH16B] Speed tracer. Created by Reinder Nijhoff 2016
// Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
// @reindernijhoff
// 
// https://www.shadertoy.com/view/Xlt3Dn
//
// This shader uses code of the Analytical Motionblur 3D shader by Inego and a grid to trace a lot of spheres.
//

#define RAYCASTSTEPS 30

#define GRIDSIZE 10.
#define GRIDSIZESMALL 7.
#define MAXHEIGHT 30.
#define SPEED 18.
#define FPS 30.
#define MAXDISTANCE 260.
#define MAXSHADOWDISTANCE 20.

#define time iTime

#define HASHSCALE1 .1031
#define HASHSCALE3 vec3(.1031, .1030, .0973)
#define HASHSCALE4 vec4(1031, .1030, .0973, .1099)

vec3 pal( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d ) {
    return a + b*cos( 6.28318*(c*t+d) );
}

vec3 getCol( in float t ) {
	return pal(t, vec3(0.5,0.5,0.5),vec3(0.5,0.5,0.5),vec3(1.0,1.0,1.0),vec3(0.0,0.10,0.20) );
}

//----------------------------------------------------------------------------------------
//  1 out, 2 in...
float hash12(vec2 p) {
	vec3 p3  = fract(vec3(p.xyx) * HASHSCALE1);
    p3 += dot(p3, p3.yzx + 19.19);
    return fract((p3.x + p3.y) * p3.z);
}


//----------------------------------------------------------------------------------------
///  2 out, 2 in...
vec2 hash22(vec2 p) {
	vec3 p3 = fract(vec3(p.xyx) * HASHSCALE3);
    p3 += dot(p3, p3.yzx+19.19);
    return fract(vec2((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y));
}

//
// intersection functions
//

bool intersectPlane(const in vec3 ro, const in vec3 rd, const in float height, out float dist) {	
	if (rd.y==0.0) {
		return false;
	}
	
	float d = -(ro.y - height)/rd.y;
	d = min(100000.0, d);
	if( d > 0. ) {
		dist = d;
		return true;
	}
	return false;
}

//
// intersect a MOVING sphere
//
// see: Analytical Motionblur 3D
//      https://www.shadertoy.com/view/MdB3Dw
//
// Created by inigo quilez - iq/2014
//
vec2 iSphere( const in vec3 ro, const in vec3 rd, const in vec4 sp, const in vec3 ve, out vec3 nor )
{
    float t = -1.0;
	float s = 0.0;
	nor = vec3(0.0);
	
	vec3  rc = ro - sp.xyz;
	float A = dot(rc,rd);
	float B = dot(rc,rc) - sp.w*sp.w;
	float C = dot(ve,ve);
	float D = dot(rc,ve);
	float E = dot(rd,ve);
	float aab = A*A - B;
	float eec = E*E - C;
	float aed = A*E - D;
	float k = aed*aed - eec*aab;
		
	if( k>0.0 )
	{
		k = sqrt(k);
		float hb = (aed - k)/eec;
		float ha = (aed + k)/eec;
		
		float ta = max( 0.0, ha );
		float tb = min( 1.0, hb );
		
		if( ta < tb )
		{
            ta = 0.5*(ta+tb);			
            t = -(A-E*ta) - sqrt( (A-E*ta)*(A-E*ta) - (B+C*ta*ta-2.0*D*ta) );
            nor = normalize( (ro+rd*t) - (sp.xyz+ta*ve ) );
            s = 2.0*(tb - ta);
		}
	}

	return vec2(t,s);
}

//
// Shade
//

vec3  lig = normalize( vec3(-0.6, 0.7, -0.5) );

vec3 shade( const in float d, in vec3 col, const in float shadow, const in vec3 nor, const in vec3 ref, const in vec3 sky) {
    float amb = max(0., 0.5+0.5*nor.y);
    float dif = max(0., dot( normalize(nor), lig ) );
    float spe = pow(clamp( dot(normalize(ref), lig ), 0.0, 1.0 ),16.0);

    dif *= shadow;

    vec3 lin = 1.20*dif*getCol(0.);
    lin += 0.50*amb*getCol(0.);
    col = col*lin;
    col += spe*dif;
    
    // fog
    col = mix( col, sky, smoothstep( MAXDISTANCE * .8, MAXDISTANCE, d ) );
    
	return col;
}

//
// Scene
//

void getSphereOffset( const in vec2 grid, inout vec2 center ) {
	center = (hash22( grid ) - vec2(0.5) )*(GRIDSIZESMALL);
}

void getMovingSpherePosition( const in vec2 grid, const in vec2 sphereOffset, inout vec4 center, inout vec3 speed ) {
	// falling?
	float s = 0.1+hash12( grid );
    
	float t = fract(14.*s + time/s*.3);	
	float y =  s * MAXHEIGHT * abs( 4.*t*(1.-t) );
    
    speed = vec3(0, s * MAXHEIGHT * ( 8.*t - 4. ), 0 ) * (1./FPS);
    
	vec2 offset = grid + sphereOffset;
	
	center = vec4(  offset.x + 0.5*GRIDSIZE, 1. + y, offset.y + 0.5*GRIDSIZE, 1. );
}

void getSpherePosition( const in vec2 grid, const in vec2 sphereOffset, inout vec4 center ) {
	vec2 offset = grid + sphereOffset;
	center = vec4( offset.x + 0.5*GRIDSIZE, 1., offset.y + 0.5*GRIDSIZE, 1. );
}

vec3 getSphereColor( vec2 grid ) {
	float m = hash12( grid.yx );
	return getCol(m);
}

vec3 render(const in vec3 ro, const in vec3 rd, const in vec3 cameraSpeed, const in mat3 rot ) {
    vec3 nor, ref, speed;
    
	float dist = MAXDISTANCE;
	
	vec3 sky = clamp( getCol(0.)*2.*(1.0-0.8*rd.y), vec3(0.), vec3(1.));
	vec3 colBackground, sphereSpeed, col = vec3(0.);
    
    vec4 sphereCenter;    
	vec3 pos = floor(ro/GRIDSIZE)*GRIDSIZE;
	vec2 offset;
    
	if( intersectPlane( ro,  rd, 0., dist) ) {
        vec3 interSectionPoint = ro + rd * dist;
        
        
        // HMMMMM this is totaly fake. Hopefully I have enough time to find the analytic
        // solution to get a motion blurred checkerboard
        speed = rot * (interSectionPoint.xyz - ro) + cameraSpeed;   
        
        vec2 c1 = mod(interSectionPoint.xz * .25, vec2(2.));
		
        float w = (abs( fract(c1.x*abs(rd.x)) -.5 ) + abs( fract(c1.y*abs(rd.y)) -.5 ));        

        colBackground = mix(
            mod(floor(c1.x) + floor(c1.y), 2.) < 1. ? vec3( 0.4 ) : vec3( .6 ),
            vec3(.5), clamp( (w + .8) * .007 * length(speed.xz) * FPS , 0., 1.)) * getCol(.5);
            
        // calculate shadow
        float shadow = 0.;
                
        vec3 shadowStartPos = interSectionPoint - lig;
        vec2 shadowGridPos = floor((ro + rd * dist).xz/GRIDSIZE);
        
        for( float x=-1.; x<=1.; x++) {
            for( float y=-1.; y<=1.; y++) {
                vec2 gridpos = (shadowGridPos+vec2(x,y))*GRIDSIZE;
                getSphereOffset( gridpos, offset );

                getMovingSpherePosition( gridpos, -offset, sphereCenter, sphereSpeed );

                vec2 res = iSphere( shadowStartPos, lig, sphereCenter, sphereSpeed + cameraSpeed, nor );
                if( res.x>0.0 ) {            
                    shadow = clamp( shadow+mix(res.y,0., res.x/MAXSHADOWDISTANCE), 0., 1.);
                }

                getSpherePosition( gridpos, offset, sphereCenter );

                res = iSphere( shadowStartPos, lig, sphereCenter, cameraSpeed, nor );
                if( res.x>0.0 )
                {            
                    shadow = clamp( shadow+mix(res.y,0., res.x/MAXSHADOWDISTANCE), 0., 1.);
                }
            }
        }
                
        ref = reflect( rd, vec3( 0., 1., 0. ) );
        colBackground = shade( dist, colBackground, 1.-shadow, vec3( 0., 1., 0. ), ref, sky );            
	} else {
		colBackground = sky;
	}	
		
	// trace grid
	vec3 ri = 1.0/rd;
	vec3 rs = sign(rd) * GRIDSIZE;
	vec3 dis = (pos-ro + 0.5  * GRIDSIZE + rs*0.5) * ri;
	vec3 mm = vec3(0.0);
		
    float alpha = 1.;
    
	for( int i=0; i<RAYCASTSTEPS; i++ )	{  
        if( alpha < .01 ) break;
        
		getSphereOffset( pos.xz, offset );
		
		getMovingSpherePosition( pos.xz, -offset, sphereCenter, sphereSpeed );
		        
        speed = rot * (sphereCenter.xyz - ro) + sphereSpeed + cameraSpeed;
        vec2 res = iSphere( ro, rd, sphereCenter, speed, nor );
        if( res.x>0.0 )
        {            
       		ref = reflect( rd, nor );
            vec3  lcol = shade( res.x, getSphereColor(-offset), 1., nor, ref, sky);
            col += lcol * res.y * alpha;
            alpha *= (1.-res.y);
        }        
                
		getSpherePosition( pos.xz, offset, sphereCenter );
        
        speed = rot * (sphereCenter.xyz - ro) + cameraSpeed;        
		res = iSphere( ro, rd, sphereCenter, speed, nor );
        if( res.x>0.0 )
        {            
       		ref = reflect( rd, nor );
            vec3  lcol = shade( res.x, getSphereColor(-offset), 1., nor, ref, sky);
            col += lcol * res.y * alpha;
            alpha *= (1.-res.y);
        }
        
		mm = step(dis.xyz, dis.zyx);
		dis += mm * rs * ri;
		pos += mm * rs;		
	}	
    
    col += colBackground * alpha;
    
	return col;
}

void path( in float time, out vec3 ro, out vec3 ta ) {
	ro = vec3( 16.0*cos(0.2+0.5*.4*time*1.5) * SPEED, 5.6+3.*sin(time), 16.0*sin(0.1+0.5*0.11*time*1.5) * SPEED);
    time += 1.6;
	ta = vec3( 16.0*cos(0.2+0.5*.4*time*1.5) * SPEED, -.1 + 2.*sin(time), 16.0*sin(0.1+0.5*0.11*time*1.5) * SPEED);
}

mat3 setCamera(in float time, out vec3 ro )
{
    vec3 ta;
    
    path(time, ro, ta);
	float roll = -0.15*sin(.732*time);
    
	vec3 cw = normalize(ta-ro);
	vec3 cp = vec3(sin(roll), cos(roll), 0.);
	vec3 cu = normalize( cross(cw,cp) );
	vec3 cv = normalize( cross(cu,cw) );
    return mat3( cu, cv, cw );
}

void main() {
	vec2 q = gl_FragCoord.xy/iResolution.xy;
	vec2 p = -1.0+2.0*q;
	p.x *= iResolution.x/iResolution.y;
	
	// camera	
	vec3 ro0, ro1, ta;
    
    mat3 ca0 = setCamera( time - 1./FPS, ro0 );
	vec3 rd0 = ca0 * normalize( vec3(p.xy,2.0) );

    mat3 ca1 = setCamera( time, ro1 );
	vec3 rd1 = ca1 * normalize( vec3(p.xy,2.0) );
	        
    mat3 rot = ca1 * mat3( ca0[0].x, ca0[1].x, ca0[2].x,
                           ca0[0].y, ca0[1].y, ca0[2].y,
                           ca0[0].z, ca0[1].z, ca0[2].z);
    
    rot -= mat3( 1,0,0, 0,1,0, 0,0,1);
    
	// raytrace	
	vec3 col = render(ro0, rd0, ro1-ro0, rot );
	
	col = pow( col, vec3(1./2.2) );	
	col = mix(col, smoothstep(vec3(0), vec3(1), col), .25);
	
	// vigneting
	col *= 0.25+0.75*pow( 16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y), 0.15 );
	
	gl_FragColor = vec4( col,1.0);
}