/* | |
* Reviewing ray-tracing basics in glsl. Loosely based on Inigo Quilez's articles. | |
* Éric Renaud-Houde - num3ric.com | |
* December 2012 | |
*/ | |
#ifdef GL_ES | |
precision highp float; | |
#endif | |
#define EPS 0.0001 | |
#define PI 3.14159265 | |
#define TWO_PI 6.28318530 | |
uniform float time; | |
uniform vec2 mouse; | |
uniform vec2 resolution; | |
struct Ray { | |
vec3 o; //origin | |
vec3 d; //direction (should always be normalized) | |
}; | |
struct Sphere { | |
vec3 pos; //center of sphere position | |
float rad; //radius | |
vec4 col; //surface color | |
}; | |
struct Camera { | |
vec3 pos; //camera position | |
vec3 aim; //view target | |
float fov; //field of view | |
}; | |
float cosr = cos(TWO_PI*mouse.x-PI); | |
float sinr = sin(TWO_PI*mouse.x-PI); | |
mat3 rotationMatrix = mat3(cosr, 0.0, sinr, 0.0, 1.0, 0.0, -sinr, 0.0, cosr); | |
float focusDistance = 5.0; | |
Camera cam = Camera(rotationMatrix*vec3(0.0, 1.5, focusDistance), | |
vec3(0.0, 0.5, 0.0), 10.0); | |
float rrad = 1.35; | |
float rspeed = 0.01*sin(0.001*time)+1.0; | |
Sphere sphere1 = Sphere(vec3(rrad*cos(rspeed*time), 1.0, rrad*sin(rspeed*time)), | |
1.0, vec4(0.7, 0.9, 0.0, 1.0)); | |
Sphere sphere2 = Sphere(vec3(-rrad*cos(rspeed*time), 1.0, -rrad*sin(rspeed*time)), | |
1.0, vec4(1.0, 0.2, 0.0, 1.0)); | |
float reflection_factor = 0.25; | |
vec4 specularColor = vec4(1.0); | |
vec3 lightPos = vec3(10.0, 10.0, 10.0); | |
vec4 amb = vec4(0.1, 0.2, 0.4, 1.0); | |
/* ---------- Object intersection functions ---------- */ | |
float intersectSphere(in Ray ray, in Sphere sphere) | |
{ | |
vec3 oc = ray.o - sphere.pos; | |
float b = 2.0 * dot(ray.d, oc); | |
float c = dot(oc, oc) - sphere.rad*sphere.rad; | |
float disc = b * b - 4.0 * c; | |
if (disc < 0.0) | |
return -1.0; | |
// compute q as described above | |
float q; | |
if (b < 0.0) | |
q = (-b - sqrt(disc))/2.0; | |
else | |
q = (-b + sqrt(disc))/2.0; | |
float t0 = q; | |
float t1 = c / q; | |
// make sure t0 is smaller than t1 | |
if (t0 > t1) { | |
// if t0 is bigger than t1 swap them around | |
float temp = t0; | |
t0 = t1; | |
t1 = temp; | |
} | |
// if t1 is less than zero, the object is in the ray's negative direction | |
// and consequently the ray misses the sphere | |
if (t1 < 0.0) | |
return -1.0; | |
// if t0 is less than zero, the intersection point is at t1 | |
if (t0 < 0.0) { | |
return t1; | |
} else { | |
return t0; | |
} | |
} | |
float intersectPlane(in Ray ray) | |
{ | |
return -ray.o.y/ray.d.y; | |
} | |
int worldIntersect(in Ray ray, in float maxlen, in int id, inout float t) | |
{ | |
t = maxlen; | |
float ts1 = intersectSphere(ray, sphere1); | |
float ts2 = intersectSphere(ray, sphere2); | |
float tp = intersectPlane(ray); | |
//FIXME: why is id needed to prevent surface acne (idem in worldShadow)? | |
if (ts1 > EPS && id !=1) { | |
t = ts1; | |
id = 1; | |
} | |
if (ts2 > EPS && ts2 < t && id !=2 ) { | |
t = ts2; | |
id = 2; | |
} | |
if ( tp > EPS && tp < t && id !=3 ) { | |
t = tp; | |
id = 3; | |
} | |
return id; | |
} | |
/* ---------- Object normals functions ---------- */ | |
vec3 sphereNormal(in vec3 pos, in Sphere sphere) | |
{ | |
return normalize((pos - sphere.pos)/sphere.rad); | |
} | |
vec3 worldNormal(in vec3 pos, in int id) | |
{ | |
if (id == 1) { | |
return sphereNormal(pos, sphere1); | |
} else if (id == 2) { | |
return sphereNormal(pos, sphere2); | |
} else if (id == 3) { | |
return vec3(0.0, 1.0, 0.0); | |
} | |
} | |
/* ----------------------------------------------- */ | |
float worldShadow(in Ray ray, in float maxlen, in int id) | |
{ | |
float ts1 = intersectSphere(ray, sphere1); | |
float ts2 = intersectSphere(ray, sphere2); | |
if(ts1 > EPS && id !=1 ) | |
return 0.0; | |
if(ts2 > EPS && id !=2 ) | |
return 0.0; | |
return 1.0; | |
} | |
float diffuseFactor(in vec3 surfaceNormal, in vec3 lightDir) { | |
return clamp(dot(surfaceNormal, lightDir), 0.0, 1.0); | |
} | |
float specularFactor(in vec3 surfaceNormal, in vec3 lightDir) | |
{ | |
vec3 viewDirection = normalize(cam.pos); | |
vec3 halfAngle = normalize(lightDir + viewDirection); | |
float ks = dot(surfaceNormal, halfAngle); | |
ks = clamp(ks, 0.0, 1.0); | |
ks = pow(ks, 50.0); | |
return ks; | |
} | |
void applyFog(in float t, inout vec4 col) | |
{ | |
col = mix(col, amb, clamp(sqrt(t*t)/10.0, 0.0, 1.0)); | |
} | |
void reflect(inout Ray ray, in vec3 surfaceNormal) | |
{ | |
float cosI = -dot(surfaceNormal, ray.d); | |
ray.d = ray.d + 2.0*cosI*surfaceNormal; | |
} | |
vec4 rendererCalculateColor(inout Ray ray, inout int id) | |
{ | |
vec4 col = vec4(0.0); | |
float t = 0.0; | |
float maxlen = 1000.0; | |
bool hit = false; | |
// Find the ray's closest intersection in the world scene | |
id = worldIntersect(ray, maxlen, id, t); | |
if (t<0.0 || t+EPS>maxlen) { | |
applyFog(maxlen, col); | |
return col; | |
} | |
// Compute the color (diffuse & specular reflection) | |
vec3 pos = ray.o + t*ray.d; | |
vec3 lightDir = normalize(lightPos-pos); | |
vec3 surfaceNormal = worldNormal(pos, id); | |
if (id == 1) { | |
float dif = diffuseFactor(surfaceNormal, lightDir); | |
float spec = specularFactor(surfaceNormal, lightDir); | |
col = dif*sphere1.col+spec*specularColor; | |
} else if (id == 2) { | |
float dif = diffuseFactor(surfaceNormal, lightDir); | |
float spec = specularFactor(surfaceNormal, lightDir); | |
col = dif*sphere2.col+spec*specularColor; | |
} else if (id == 3) { | |
col = vec4(0.7, 0.7, 0.7, 1.0); | |
} | |
// Darken the color if it's in the shadow | |
col *= worldShadow(Ray(pos, lightDir), 100.0, id); | |
col = col + 0.2*amb; | |
// Apply fog to the color using the distance to the intersection | |
applyFog(t, col); | |
// Update the ray for the next reflection iteration | |
reflect(ray, surfaceNormal); | |
ray.o = pos; | |
return col; | |
} | |
void main( void ) { | |
vec2 uv = gl_FragCoord.xy/resolution.xy - 0.5; | |
vec3 d = (cam.aim - cam.pos) + rotationMatrix*vec3(cam.fov*uv, 0.0); | |
d.y *= resolution.y/resolution.x; | |
Ray ray = Ray(cam.pos, normalize(d)); | |
vec3 surfaceNormal; | |
int id = 0; | |
vec4 col = rendererCalculateColor(ray, id); | |
//We compute reflected rays iteratively (no recursion in this version of glsl) | |
//Both 'ray' and intersection 'id' are updated by renderedCalculateColor. | |
for(int i=1; i<=4; ++i) { | |
vec4 new_col = rendererCalculateColor(ray, id); | |
col = (1.0-reflection_factor)*col + reflection_factor*mix(new_col, col, 1.0-reflection_factor); | |
} | |
gl_FragColor = col; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment