-
-
Save num3ric/4408481 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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