vec3 oct_to_vec3(vec2 e) { vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y)); float t = max(-v.z, 0.0); v.xy += t * -sign(v.xy); return normalize(v); } // border_size: 1.0 - padding_in_uv_space * 2.0. vec3 oct_to_vec3_with_border(vec2 uv, float border_size) { // Convert into [-1,1] space and add border which extends beyond [-1,1]. uv = (uv - 0.5) * (2.0 / border_size); // Calculate octahedral mirroring for values outside of [-1,1]. // Inspired by Timothy Lottes' code here: https://gpuopen.com/learn/fetching-from-cubes-and-octahedrons/ vec2 mask = step(vec2(1.0), abs(uv)); uv = 2.0 * clamp(uv, -1.0, 1.0) - uv; uv = mix(uv, -uv, mask.yx); return oct_to_vec3(uv); } vec2 oct_wrap(vec2 v) { vec2 signVal; signVal.x = v.x >= 0.0 ? 1.0 : -1.0; signVal.y = v.y >= 0.0 ? 1.0 : -1.0; return (1.0 - abs(v.yx)) * signVal; } vec2 vec3_to_oct(vec3 n) { // Reference: https://twitter.com/Stubbesaurus/status/937994790553227264 n /= (abs(n.x) + abs(n.y) + abs(n.z)); n.xy = (n.z >= 0.0) ? n.xy : oct_wrap(n.xy); n.xy = n.xy * 0.5 + 0.5; return n.xy; } // border_size.x: padding_in_uv_space // border_size.y: 1.0 - padding_in_uv_space * 2.0 vec2 vec3_to_oct_with_border(vec3 n, vec2 border_size) { vec2 uv = vec3_to_oct(n); return uv * border_size.y + border_size.x; } float vec3_to_oct_lod(vec3 n_ddx, vec3 n_ddy, float pixel_size) { // Approximate UV space derivatives by a factor of 0.5 because // vec3_to_oct maps from [-1,1] to [0,1]. float pixel_size_sqr = 4.0 * pixel_size * pixel_size; float ddx = dot(n_ddx, n_ddx) / pixel_size_sqr; float ddy = dot(n_ddy, n_ddy) / pixel_size_sqr; float dd_sqr = max(ddx, ddy); return 0.25 * log2(dd_sqr + 1e-6f); }