website articles
box functions

### Intro

A sphere are simple geometry shape described by a simple equation and therefore it accepts analytic solutions to many problems involving projections, occlusion, shadows, motion blur, etc. Similary, boxes are simple shapes that also accept closed form expresions for many such problems. This page collects some of those that I have collected (more often than not, derived myself).

### Functions

Notes on helper functions:
* maxcomp() computes the largest of the coordinates of a vector.
* msign() for each component in a vector, returns -1 if it's negative or zero, and +1 otherwise
Box Distance

// Calcs signed distance to a box of semi-size "rad" centered at // the origin. It works in 2D, 3D, 4D any number of dimensions by // replacing the vec data types. float boxDistance( in vec3 p, in vec3 rad ) { vec2 d = abs(p)-rad; return length(max(d,0.0)) + min(maxcomp(d),0.0); }

// Calcs the gradient of the distance to a box. Works at // any point in space, including the interior of the box. float boxGradient( in vec3 p, in vec3 rad ) { vec3 d = abs(p)-rad; vec3 s = msign(p); float g = maxcomp(d); return s*((g>0.0) ? normalize(max(d,0.0)) : step(d.yzx,d.xyz)*step(d.zxy,d.xyz)); }
Box Intersection

// Calcs intersection and exit distances, and normal at intersection. // The ray must be in box/object space. If you have multiple boxes all // aligned to the same axis, you can precompute 1/rd. If you have // multiple boxes but they are not alligned to each other, use the // "Generic" box intersector bellow this one. vec2 boxIntersection( in vec3 ro, in vec3 rd, in vec3 rad, out vec3 oN ) { vec3 m = 1.0/rd; vec3 n = m*ro; vec3 k = abs(m)*rad; vec3 t1 = -n - k; vec3 t2 = -n + k; float tN = max( max( t1.x, t1.y ), t1.z ); float tF = min( min( t2.x, t2.y ), t2.z ); if( tN>tF || tF<0.0) return vec2(-1.0); // no intersection oN = -sign(rdd)*step(t1.yzx,t1.xyz)*step(t1.zxy,t1.xyz); return vec2( tN, tF ); }
Box Intersection Generic

// Calcs intersection and exit distances, normal, face and UVs // row is the ray origin in world space // rdw is the ray direction in world space // txx is the world-to-box transformation // txi is the box-to-world transformation // ro and rd are in world space // rad is the half-length of the box // // oT contains the entry and exit points // oN is the normal in world space // oU contains the UVs at the intersection point // oF contains the index if the intersected face [0..5] bool boxIntersect( in vec3 row, in vec3 rdw, in mat4 txx, in mat4 txi, in vec3 rad, out vec2 oT, out vec3 oN, out vec2 oU, out int oF ) { // convert from world to box space vec3 rd = (txx*vec4(rdw,0.0)).xyz; vec3 ro = (txx*vec4(row,1.0)).xyz; // ray-box intersection in box space vec3 m = 1.0/rd; vec3 s = vec3((rd.x<0.0)?1.0:-1.0, (rd.y<0.0)?1.0:-1.0, (rd.z<0.0)?1.0:-1.0); vec3 t1 = m*(-ro + s*rad); vec3 t2 = m*(-ro - s*rad); float tN = max( max( t1.x, t1.y ), t1.z ); float tF = min( min( t2.x, t2.y ), t2.z ); if( tN>tF || tF<0.0) return false; // compute normal (in world space), face and UV if( t1.x>t1.y && t1.x>t1.z ) { oN=txi[0].xyz*s.x; oU=ro.yz+rd.yz*t1.x; oF=(1+int(s.x))/2; else if( t1.y>t1.z ) { oN=txi[1].xyz*s.y; oU=ro.zx+rd.zx*t1.y; oF=(5+int(s.y))/2; else { oN=txi[2].xyz*s.z; oU=ro.xy+rd.xy*t1.z; oF=(9+int(s.z))/2; oT = vec2(tN,tF); return true; }
Box Ambient Occlusion

float boxOcclusion( in vec3 pos, in vec3 nor, in mat4 txx, // box rotation+position in vec3 rad ) // box size { vec3 p = (txx*vec4(pos,1.0)).xyz; vec3 n = (txx*vec4(nor,0.0)).xyz; // 8 verts vec3 v0 = normalize( vec3(-1.0,-1.0,-1.0)*rad - p); vec3 v1 = normalize( vec3( 1.0,-1.0,-1.0)*rad - p); vec3 v2 = normalize( vec3(-1.0, 1.0,-1.0)*rad - p); vec3 v3 = normalize( vec3( 1.0, 1.0,-1.0)*rad - p); vec3 v4 = normalize( vec3(-1.0,-1.0, 1.0)*rad - p); vec3 v5 = normalize( vec3( 1.0,-1.0, 1.0)*rad - p); vec3 v6 = normalize( vec3(-1.0, 1.0, 1.0)*rad - p); vec3 v7 = normalize( vec3( 1.0, 1.0, 1.0)*rad - p); // 12 edges float k02 = dot(n,normalize(cross(v2,v0)))*acos(dot(v0,v2)); float k23 = dot(n,normalize(cross(v3,v2)))*acos(dot(v2,v3)); float k31 = dot(n,normalize(cross(v1,v3)))*acos(dot(v3,v1)); float k10 = dot(n,normalize(cross(v0,v1)))*acos(dot(v1,v0)); float k45 = dot(n,normalize(cross(v5,v4)))*acos(dot(v4,v5)); float k57 = dot(n,normalize(cross(v7,v5)))*acos(dot(v5,v7)); float k76 = dot(n,normalize(cross(v6,v7)))*acos(dot(v7,v6)); float k37 = dot(n,normalize(cross(v7,v3)))*acos(dot(v3,v7)); float k64 = dot(n,normalize(cross(v4,v6)))*acos(dot(v6,v4)); float k51 = dot(n,normalize(cross(v1,v5)))*acos(dot(v5,v1)); float k04 = dot(n,normalize(cross(v4,v0)))*acos(dot(v0,v4)); float k62 = dot(n,normalize(cross(v2,v6)))*acos(dot(v6,v2)); // 6 faces float occ = 0.0; occ += ( k02 + k23 + k31 + k10) * step( 0.0, v0.z ); occ += ( k45 + k57 + k76 + k64) * step( 0.0, -v4.z ); occ += ( k51 - k31 + k37 - k57) * step( 0.0, -v5.x ); occ += ( k04 - k64 + k62 - k02) * step( 0.0, v0.x ); occ += (-k76 - k37 - k23 - k62) * step( 0.0, -v6.y ); occ += (-k10 - k51 - k45 - k04) * step( 0.0, v0.y ); return occ / 6.283185; }

This code will work only when the box is fully visible from the shading point.
For boxes that are partially below the visibility horizon, use this code that does