Search code examples
cameraglslraymarching

GLSL Raymarching special camera setup


I've been fiddling a little into raymarching SDFs in GLSL ES (Shadertoy) and found myself wanting to describe a camera as follows:

vec2 VISIBLE_SIZE = vec2( 20.0, 15.0 );     // how much World Resolution should be visible
vec3 CAM_POI      = vec3( 0.0,0.0,0.0 );    // the Point of Interest in my scene
vec3 CAM_UP       = vec3( 0.0,1.0,0.0 );    // camera up-direction
vec3 CAM_DIR      = vec3( 0.0,0.0,1.0 );    // camera forward-direction
float CAM_FL      = 2.5;                    // focal length of our camera

Thinking about how that might work brought up this:

//+ -VS.x/2   + POI       + VS.x/2          || with d = length( POI - cam ):
// \          |          /                  || -----------------------------
//  \         |         /                   || VS.x/2    1
//   \        |        /                    || ------ = --  =>  d = fl * VS.x / 2
//    \       |       /                     ||   d      FL
//     \      |      /                      || 
//      \     |     /                       || for both dimensions: d = fl * VS/2 gives a vec2
//       \    |    /                        || => which d do we choose?  Probably the maximum of
//   -1.0 +   +   + 1.0                     || both, so: d = max( fl*VS.x/2, fl*VS.y/2 )
//         \  |  /                          || 
//          \f|l/                           || Now that would make POI - d * CAM_DIR be our RayOrigin
//           \|/                            || 
//            + cam                         || Also Aspect ratio should be used to reshape VS.x!

vec2 vis = CAM_FL * VISIBLE_SIZE * vec2( uResolution.y / uResolution.y, 1. ) / 2.0;
float d = max( vis.x, vis.y );

// that gives us a cam Position:
vec3 ro = CAM_POI - d * CAM_DIR

// Now we can build a coordinate base
vec3 forward = normalize( CAM_DIR );
vec3 right = cross( CAM_UP, forward );
vec3 up = cross( forward, right );

// Now we can calculate the screen center intersection point
vec3 sc = CAM_POI + forward * ( CAM_FL - d );
// Here UV applies -> we get the intersection point
vec3 ip = sc + ndc.x * right + ndc.y * up;
// From camera origin to intersection point is this pixels raydir
rd = normalize( ip - ro );
// Finally things between Camera and Screen are of no interest
ro = ip;

Now, trying this shows not to really work. Calculating this example thru gives:

vis:        2.5 * (20.0) * (600./800.) / 2.0    = ( 18.75 )
                  (15.0)   (1.)                   ( 18.75 )
d:          max( 18.75, 18.75 )                 = 18.75
ro:         (0,0,0) - (0,0,1)*18,75             = ( 0, 0, -18.75 )
forward:                                        = ( 0, 0, 1 )
right:      cross( ( 0,1,0 ), ( 0,0,1 ) )       = ( 1, 0, 0 )
up:         cross( ( 0,0,1 ), ( 1,0,0 ) )       = ( 0, 1, 0 )
sc:         ( 0,0,0 ) + ( 0,0,1 )*( 2.5-18.75 ) = ( 0, 0, -16.25 )
ip: with ndc=(1,1) 
            ( 0, 0, -16.25 ) + ( 1, 1, 0 )      = ( 1, 1, -16.25 )
rd:    norm(( 1, 1, -16.25 ) - ( 0, 0, -18.75 ))= normalize( 1, 1, 2.5 ) = ( 0.34816, 0.34816, 0.87039 )
ro:                                             = ( 1, 1, -16.25 )

This looks pretty correct in a way. I AM MISSING SOMETHING, please - if you find it - point me towards it!


Solution

  • Since nobody had an answer, I had to continue finding my solution. Finally it was the same as always: one sign wasn't right, the per-definition forward vector. Solution - Refresher: the camera shall be defined by a Point of View, its focal Length and a certain frame of world coordinates to be visible at the POI:

    // A load of definitions to control the camera
    #define CAM_SIZE vec2( 25., 10. )
    #define CAM_POI vec3( 0., 0., 0. )
    #define CAM_FL 2.5
    #define CAM_UP vec3( 0, 1, 0 )
    #define CAM_FWD vec3( 0, 0, -1 )
    #define CAM_BASE_PITCH radians( 30. )
    #define ROTATE( vector, angle ) vector = cos(angle)*vector+sin(angle)*vec2(vector.y, -vector.x)
    #define CAM_RELAX 0.0
    #define CAM_ROTxz 0.0
    #define CAM_SWINGxz vec2( 0.0, 1.0 )
    #define CAM_SWINGyz vec2( 0.0, 1.0 )
    
    void camera( out vec3 ro, out vec3 rd, in vec3 nc, in float time )
    {
        // Calculate outer lengths( eye - (max_x,max_y) ), taking aspect ratio into account
        vec2 cs = CAM_FL * CAM_SIZE * 0.5 * vec2( iResolution.y / iResolution.x, 1.0 );
        float z = max( cs.x, cs.y );
    
        // basic camera direction (by definition - this actually was the wrong sign!!!)
        vec3 CAM_DIR = CAM_FWD;
    
        // We want to be able to steer the camera while mouse-clicking. In that moment it shall
        // simply use the current camera position and hover around it using pitch and yaw.
        vec2 msw = ( saturate( iMouse.z ) * vec2( radians( 90. ), radians( 89. ) - CAM_BASE_PITCH ) )
                 * ( iMouse.xy / iResolution.xy - vec2( 0.5, 0.0 ) );
        // Also we want to be able to say: rotate around the POI's y-axis over time, swing around
        // it, or swing around x (changing the pitch).  These motions shall rise and fall using
        // a relax factor (if it's ==0, it simply multiplies with 1, thus not changing a thing)
        float relax = 0.5 * ( 1.0 + cos( time * CAM_RELAX ) );
        // Now rotate our direction, so we can begin creating an orthonormal base for our cam
        ROTATE(CAM_DIR.yz, CAM_BASE_PITCH // looking from slightly above
                           + msw.y       // Mouse influence
                                        // swinging up and down
                           + relax * CAM_SWINGyz.x * ( radians( 89. ) - CAM_BASE_PITCH ) * 0.5 * ( 1. - cos( time * CAM_SWINGyz.y ) ) );
        ROTATE(CAM_DIR.xz, msw.x                                        // Mouse influence
                           + CAM_ROTxz * time                           // rotation around Y
                           + relax * CAM_SWINGxz.x * ( sin( time * CAM_SWINGxz.y ) ) );   // Swing around Y
        // create orthonormal camera system:
        vec3 forward = normalize( CAM_DIR );
        vec3 right = cross( CAM_UP, forward );
        vec3 up = cross( forward, right );
        // Focal length and NDC help us find the current incident ray
        vec3 NDC_center = CAM_POI - ( z - CAM_FL ) * forward;
        vec3 intersection = NDC_center + nc.x * right + nc.y * up;
        // and that's where our incident ray should point: ( intersection - eye )
        rd = normalize( intersection - ( CAM_POI - z * forward ) );
        // We'll start at our near plane intersection point, as any-
        // thing between cam and near plane is of no interest.
        ro = intersection;
    }
    

    So that might be the complete solution. A short camera-calculation would be the following: given an Eye-Point and a Point of Interest, as well as a focal length, the pixel incident ray can be calculated as follows:

    void camera( out vec3 ro, out vec3 rd )
    {   // simply build an orthonormal base for the camera system
        vec3 fwd = normalize( iEye - iPoi );            // get forward vector
        vec3 lft = cross( fwd, vec3( 0., 1., 0. ) );    // use per-definition up vector to get left vector
        rd = normalize(                                 // get up-vector from left and forward -> use the vectors
                mat3( lft, cross( lft, fwd ), fwd )     // to build the inv. cam matrix, then use it to rotate
                * vec3( ( 2.0 * gl_FragCoord.xy - iResolution.xy ) / max(iResolution.x,iResolution.y), -iFocalLen )
             );                                         // the incident ray from camera space to world space.
        ro = iEye;                                      // Self-explaining: ray origin is the eye point.
    }