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!
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.
}