+};
+
+void main()
+{
+ StackFrame stack[MAX_LEVEL];
+
+ vec3 color = vec3(0.0, 0.0, 0.0);
+
+ stack[0].op = 0;
+ stack[0].ray.org = v_rorg;
+ stack[0].ray.dir = normalize(v_rdir);
+ stack[0].ray.energy = stack[0].ray.ior = 1.0;
+
+ int top = 0;
+ while(top >= 0) {
+ if(top >= MAX_LEVEL - 1) {
+ color += backdrop(stack[top].ray.dir) * stack[top].ray.energy;
+ top--;
+ continue;
+ }
+ if(stack[top].ray.energy < 1e-3) {
+ top--;
+ continue;
+ }
+
+ if(!cast_ray(stack[top].ray, color, stack[top].hit)) {
+ /* no hit, return */
+ top--;
+ continue;
+ }
+
+ /* found a hit, recurse for reflection/refraction */
+ HitPoint hit = stack[top].hit;
+
+ // 1.0 when entering, 0.0 when leaving
+ float entering = step(0.0, dot(-stack[top].ray.dir, hit.norm));
+ vec3 norm = faceforward(hit.norm, stack[top].ray.dir, hit.norm);
+
+ int op = stack[top].op++;
+ if(op == 0) {
+ // reflection
+ float energy = stack[top].ray.energy * hit.mtl.refl;
+ if(energy > 1e-4) {
+ int next = top + 1;
+ stack[next].op = 0;
+ stack[next].ray.org = hit.pos + norm * 1e-5;
+ stack[next].ray.dir = reflect(stack[top].ray.dir, norm);
+ stack[next].ray.energy = energy;
+ top = next;
+ }
+ } else if(op == 1) {
+ // refraction
+ float energy = stack[top].ray.energy * hit.mtl.refr;
+ if(energy > 1e-4) {
+ float next_ior = mix(stack[top - 1].ray.ior, hit.mtl.ior, entering);
+ float ior = stack[top].ray.ior / next_ior;
+ int next = top + 1;
+ stack[next].op = 0;
+ stack[next].ray.org = hit.pos - norm * 1e-5;
+ stack[next].ray.dir = refract(stack[top].ray.dir, norm, ior);
+ stack[next].ray.energy = energy;
+ stack[next].ray.ior = next_ior;
+ top = next;
+ }
+ } else if(op == 2) {
+ // return
+ top--;
+ }