Gnomonic Projection Background
According to MathWorld, the gnomonic projection is a nonconformal map projection obtained by projecting points (or
) on the surface of sphere from a sphere’s center O to point P in a plane that is tangent to a point S (Coxeter 1969, p. 93). In a gnomonic projection, great circles are mapped to straight lines. The gnomonic projection represents the image formed by a spherical lens, and is sometimes known as the rectilinear projection.
Code Motivation
I haven’t seen a code which is able to generate large amounts of gnomonic projection from 360 images masssively in parallel, so I open sourced my ShaderToy program for such a purpose.
Usually previous methods creates a camera with a projection matrix, a cube or sphere to approximate the rectilinear projection from human eyes. However, texturing a mesh can be expensive and what if you don’t really want to pre-define a projection matrix?
Here comes using a quad to generate gnomonic projection centralized at any given point.
Interaction Instructions
Move the mouse to change the central point.
Press the space key for the Equirectangular Projection;
Press the numbers 1, 2, 3 for three different FoVs.
Live Demo
Code
I think the code is self-explained, and thus can be used as a basic tutorial for projection mapping from a cube map.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
/** * Cubemap to Gnomonic / Rectilinear unwrapping by Ruofei Du (DuRuofei.com) * starea @ ShaderToy, License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. * https://creativecommons.org/licenses/by-nc-sa/3.0/ * * Reference: * [1] Gnomonic projection: https://en.wikipedia.org/wiki/Gnomonic_projection * [2] Weisstein, Eric W. "Gnomonic Projection." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/GnomonicProjection.html * **/ const float PI = 3.1415926536; const float PI_2 = PI * 0.5; const float PI2 = PI * 2.0; const int KEY_SPACE = 32; const int KEY_ENTER = 13; const int KEY_1 = 49; const int KEY_2 = 50; const int KEY_3 = 51; // Forked from fb39ca4's keycode viewer tool @ https://www.shadertoy.com/view/4tt3Wn float keyPressed(int keyCode) { return texture(iChannel1, vec2((float(keyCode) + 0.5) / 256., .5/3.)).r; } // Main function, convert screen coordinate system to spherical coordinates in gnomonic projection // screenCoord: [0, 1], centralPoint: [0, 1], FoVScale: vec2(0.9, 0.2) recommended vec2 calcSphericalCoordsInGnomonicProjection(in vec2 screenCoord, in vec2 centralPoint, in vec2 FoVScale) { vec2 cp = (centralPoint * 2.0 - 1.0) * vec2(PI, PI_2); // [-PI, PI], [-PI_2, PI_2] // Convert screen coord in gnomonic mapping to spherical coord in [PI/2, PI] vec2 convertedScreenCoord = (screenCoord * 2.0 - 1.0) * FoVScale * vec2(PI_2, PI); float x = convertedScreenCoord.x, y = convertedScreenCoord.y; float rou = sqrt(x * x + y * y), c = atan(rou); float sin_c = sin( c ), cos_c = cos( c ); float lat = asin(cos_c * sin(cp.y) + (y * sin_c * cos(cp.y)) / rou); float lon = cp.x + atan(x * sin_c, rou * cos(cp.y) * cos_c - y * sin(cp.y) * sin_c); lat = (lat / PI_2 + 1.0) * 0.5; lon = (lon / PI + 1.0) * 0.5; //[0, 1] // uncomment the following if centralPoint ranges out of [0, PI/2] [0, PI] // while (lon > 1.0) lon -= 1.0; while (lon < 0.0) lon += 1.0; // while (lat > 1.0) lat -= 1.0; while (lat < 0.0) lat += 1.0; // convert spherical coord to cubemap coord return (bool(keyPressed(KEY_SPACE)) ? screenCoord : vec2(lon, lat)) * vec2(PI2, PI); } // convert cubemap coordinates to spherical coordinates: vec3 sphericalToCubemap(in vec2 sph) { return vec3(sin(sph.y) * sin(sph.x), cos(sph.y), sin(sph.y) * cos(sph.x)); } // convert screen coordinate system to cube map coordinates in rectilinear projection vec3 calcCubeCoordsInGnomonicProjection(in vec2 screenCoord, in vec2 centralPoint, in vec2 FoVScale) { return sphericalToCubemap( calcSphericalCoordsInGnomonicProjection(screenCoord, centralPoint, FoVScale) ); } // the inverse function of calcSphericalCoordsInGnomonicProjection() vec2 calcEquirectangularFromGnomonicProjection(in vec2 sph, in vec2 centralPoint) { vec2 cp = (centralPoint * 2.0 - 1.0) * vec2(PI, PI_2); float cos_c = sin(cp.y) * sin(sph.y) + cos(cp.y) * cos(sph.y) * cos(sph.y - cp.y); float x = cos(sph.y) * sin(sph.y - cp.y) / cos_c; float y = ( cos(cp.y) * sin(sph.y) - sin(cp.y) * cos(sph.y) * cos(sph.y - cp.y) ) / cos_c; return vec2(x, y) + vec2(PI, PI_2); } // Forked from: https://www.shadertoy.com/view/MsXGz4, press enter for comparison vec3 iqCubemap(in vec2 q, in vec2 mo) { vec2 p = -1.0 + 2.0 * q; p.x *= iResolution.x / iResolution.y; // camera float an1 = -6.2831 * (mo.x + 0.25); float an2 = clamp( (1.0-mo.y) * 2.0, 0.0, 2.0 ); vec3 ro = 2.5 * normalize(vec3(sin(an2)*cos(an1), cos(an2)-0.5, sin(an2)*sin(an1))); vec3 ww = normalize(vec3(0.0, 0.0, 0.0) - ro); vec3 uu = normalize(cross( vec3(0.0, -1.0, 0.0), ww )); vec3 vv = normalize(cross(ww, uu)); return normalize( p.x * uu + p.y * vv + 1.4 * ww ); } void mainImage( out vec4 fragColor, in vec2 fragCoord ) { vec2 q = fragCoord.xy / iResolution.xy; // Modify this to adjust the field of view vec2 FoVScale = vec2(0.9, 0.2); if (bool(keyPressed(KEY_1))) { FoVScale = vec2(0.45, 0.1); } else if (bool(keyPressed(KEY_2))) { FoVScale = vec2(1.0, 1.0); } else if (bool(keyPressed(KEY_3))) { FoVScale = vec2(0.8, 0.3); } // central / foveated point, iMouse.xy corresponds to longitude and latitude vec2 centralPoint = (length(iMouse.xy) < 1e-4) ? vec2(0.25, 0.5) : (iMouse.xy / iResolution.xy); // press enter to compare with iq's cubemaps https://www.shadertoy.com/view/MsXGz4 vec3 dir = (!bool(keyPressed(KEY_ENTER))) ? calcCubeCoordsInGnomonicProjection(q, centralPoint, FoVScale) : iqCubemap(q, centralPoint); vec3 col = texture(iChannel0, dir).rgb; // test if the inverse function works correctly by pressing z if (bool(keyPressed(90))) { vec2 sph = calcSphericalCoordsInGnomonicProjection(q, centralPoint, FoVScale); sph = calcEquirectangularFromGnomonicProjection(sph, centralPoint); col = vec3(sph / vec2(PI2, PI), 0.5); } col *= 0.25 + 0.75 * pow( 16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y), 0.15 ); fragColor = vec4(col, 1.0); } |
Hi,
I am new to the coding language you have used in the above example. Can you tell me what library has been used here? As i can see some objects like texture, vec2 which i am unaware of. I want to implement same thing in python. Any help on this would be appreciated.
Sorry for the late reply. I am using GLSL, while this is very easy to port to native C code or Python code. Please refer to the link for a live demo: https://www.shadertoy.com/view/4sjcz1 If you need more help, feel free to reach me via me [A`T] duruofei [dot] com