## Transformation from unit cube to view frustum

Sage
Posts: 1,199
Joined: 2004.10
Post: #1
I'm working on a problem where I want to manage particle systems inside the view frustum, only.

Now, to do this, I've decided the most efficient way would be to represent my particles inside of a unit cube, and to use a transformation to move that cube from untransformed eye-space to the world space. My thought was that I could use a concatenation of the projection and modelview matrices to do this, but that doesn't seem to work, though it's more than possible that I screwed it up.

Here's an illustration of what I'm looking to do:

I *know* I've seen OpenGL demos which showed a frustum box for different cameras, so it seems possible to me. I guess, then, my question is what matrices do I need to use, and in what manner? I'm a little flummuxed, to be honest.
Moderator
Posts: 869
Joined: 2003.01
Post: #2
You need to look into the projection and modelview transformation matrices. OpenGL does pretty much what you are trying to do with all the matrix stuff, except it does not transform to the unit cube, but a cube where x and y range from -1 to +1 and z ranges from 0 to 1.

But, what I am more interested in, is what did you use to make that drawing?
Sage
Posts: 1,199
Joined: 2004.10
Post: #3
Heh. I sketched it in flash with my wacom ( I *am* a graphic designer after all )

So, anyway, I'm using the modelview and projection matrices, but so far I'm not having much luck. I'll post code when I have a chance.
Sage
Posts: 1,199
Joined: 2004.10
Post: #4
Some details, but first a couple screenshots:

What I'm getting:

What I'm aiming for:

So, here's some code:

Code:
```void displayRainViewCamera( void ) {     /*         Get the "rainview" camera     */     if ( CameraCollection::currentCamera()->name() != CAMERA_RAINVIEW )     {                  Camera *rv = CameraCollection::cameraNamed( CAMERA_RAINVIEW );         vec3 pos( rv->position() ), look( rv->looking());              /*             Draw a sphere at its position, and a line going             in the direction the camera's facing         */         glPushMatrix();         glTranslatef( pos.x, pos.y, pos.z );         glColor3f( 0.75, 0.75, 1 );         glutSolidSphere( 2, 10, 10 );             glBegin( GL_LINES );                              glVertex3f( 0,0,0 );                 glVertex3fv( (look * 10 ).v );                          glEnd();         glPopMatrix();                  /*             Make a large cube. I will use a unit cube, but for             now I'm making a cube of size 40, with z=0 at the near plane,             like DoG suggests.         */         float size = 20;         vec3 cube[8] =         {             vec3( -size, -size, 0 ),             vec3( size, -size, 0 ),             vec3( size, size, 0 ),             vec3( -size, size, 0 ),             vec3( -size, -size, size ),             vec3( size, -size, size ),             vec3( size, size, size ),             vec3( -size, size, size )         };                  /*             Get the modelview and projection matrices from             the "rainview" camera ( this is *not* what's being used by the             current camera ). Get their inverses, and concatenate.                          NOTE: My cameras use an infinite projection matrix for             stencil shadows. The method projectionWithFarPlane() gives             you a projection matrix with a "fake" far plane, which             I've set to 500 elsewhere.         */         mat4 projection( rv->projectionWithFarPlane() ),              modelview( rv->modelview() ),              projectionInverse( projection.inverse() ),              modelviewInverse( modelview.inverse() );         mat4 m = modelviewInverse * projectionInverse;                  /*             Transform the cube's points         */         for ( int i = 0; i < 8; i++ )         {             cube[i] = m * cube[i];         }                  /*             Draw it as lines         */         glColor3f( 1, 0.75, 0.75 );         glBegin( GL_LINE_LOOP );                  glVertex3fv( cube[0] );             glVertex3fv( cube[1] );             glVertex3fv( cube[2] );             glVertex3fv( cube[3] );              glEnd();         glBegin( GL_LINE_LOOP );                  glVertex3fv( cube[4] );             glVertex3fv( cube[5] );             glVertex3fv( cube[6] );             glVertex3fv( cube[7] );              glEnd();         glBegin( GL_LINES );                  glVertex3fv( cube[0] );             glVertex3fv( cube[4] );             glVertex3fv( cube[1] );             glVertex3fv( cube[5] );             glVertex3fv( cube[2] );             glVertex3fv( cube[6] );                  glVertex3fv( cube[3] );             glVertex3fv( cube[7] );         glEnd();              } }```

So, as you can see from the first screenshot, I *do* get a box which follows the "rainview" camera's position and orientation. But it's a box, not a perspective representing frustum.

Any ideas?

EDIT: Here's how my camera's projection matrix is set up:

Code:
```<snip> mat4 Camera::projectionWithFarPlane( void ) {         mat4 proj( _projection );         proj.mat[10] = -(( _farPlane + _nearPlane ) / ( _farPlane - _nearPlane ));         proj.mat[14] = -(( 2.0f * _farPlane * _nearPlane ) / ( _farPlane - _nearPlane ));         return proj; } <snip> void Camera::updateFOV( void ) {         const float aspect = (float) _width / (float) _height;         const float cotan = 1.0f / tanf( _fov * DEG2RAD );         /*                 Create an infinite projection matrix, with far = infinity                 See NVIDIA's "Practical and Robust Stencil Shadow Volumes for                 Hardware-Accelerated Rendering"         */         _projection.zero();         _projection.mat[0] = cotan / aspect;         _projection.mat[5] = cotan;         _projection.mat[14] = -2.0f * _nearPlane;         _projection.mat[10] = -1;         _projection.mat[11] = -1;                          glMatrixMode( GL_PROJECTION );         glLoadMatrixf( _projection.mat );                  _fovChanged = false; }```
Member
Posts: 153
Joined: 2004.12
Post: #5
Is this what your looking for?

Plane Extraction

Note:Scroll down for openGL info

There was a long silence...
'I claim them all,' said the Savage at last.
Sage
Posts: 1,199
Joined: 2004.10
Post: #6
hangt5 Wrote:Is this what your looking for?

Plane Extraction

Note:Scroll down for openGL info

Actually, I've read that paper and it's the basis of my frustum culled quadtree scenegraph.

The thing is, it's not specifically what I need. I need a matrix transform which will transform a unit cube at the origin to the view frustum ( translated to wherever the camera is, and along the camera's direction ).
Sage
Posts: 1,199
Joined: 2004.10
Post: #7
So, I decided that I'm probably not going to be able to make a magic matrix to perform the perspective division in eye-space, so transforming a unit-cube to correlate to the world space frustum is likely not to be possible.

That said, I figured I could create a non-transformed frustum in eye-space, using info from the camera such as fov, width/height aspect, etc. I managed to create a frustum which works for all valid FOV, but fails when the aspect diverges from 1.0.

Can anybody look over it and tell me why it fails for non-1.0 aspect ratios?

Code:
```/* Get camera params */ EyeSpaceFrustum::EyeSpaceFrustum( Camera *camera )     :_near( camera->nearPlane() ), _far( camera->farPlane() ),     _fov( camera->FOV() ), _nearWidth( camera->screenWidth() ),     _nearHeight( camera->screenHeight() ) {} <snip> void EyeSpaceFrustum::createBox( vec4 points[8] ) {     float aspect = _nearWidth / _nearHeight;     float hNearWidth = 1.0 / 2.0,           hNearHeight = ( 1.0 / aspect ) / 2.0;     printf( "aspect: %f\nhNearWidth: %f\nhNearHeight: %f\n", aspect, hNearWidth, hNearHeight );     /*         Basic trig, tan(theta) = opposite / adjacent                  tan( theta ) = width / focalLength         focalLength = width / tan( theta )     */     float tanHFov = tanf( _fov * 0.5f * DEG2RAD );     float focalLength = hNearWidth / tanHFov;     float nearDistance = focalLength,           farDistance = focalLength + (_far - _near),           farOverNear = farDistance / nearDistance;                printf( "fov: %f\n", _fov );     printf( "\n\n\n" );                points[0] = vec4( -hNearWidth, hNearHeight / aspect, -_near, 1 );     points[1] = vec4( hNearWidth, hNearHeight / aspect, -_near, 1 );     points[2] = vec4( hNearWidth, -hNearHeight / aspect, -_near, 1 );     points[3] = vec4( -hNearWidth, -hNearHeight / aspect, -_near, 1 );     points[4] = vec4( -hNearWidth * farOverNear, hNearHeight * farOverNear / aspect, -_far, 1 );     points[5] = vec4( hNearWidth * farOverNear, hNearHeight * farOverNear / aspect, -_far, 1 );     points[6] = vec4( hNearWidth * farOverNear, -hNearHeight * farOverNear / aspect, -_far, 1 );     points[7] = vec4( -hNearWidth * farOverNear, -hNearHeight * farOverNear / aspect, -_far, 1 ); }```

And to draw the frustum:
Code:
```    /*         Draw the camera's frustum. Furst, create an EyeSpaceFrustum         and hand it the rainview camera, so it can get the camera's         properties.     */     EyeSpaceFrustum esf( camera );     vec4 frustum[8];     esf.createBox( frustum );          /*         Multiply the box's points by the inverse modelview,         to project it into world space     */     mat4 modelviewInverse( camera->modelview().inverse() );          for ( int i = 0; i < 8; i++ )     {         frustum[i] = modelviewInverse * frustum[i];     }               /*         Draw the frustum, with the near color as pink         and the far color ay cyan, smooth blended.     */     vec3 nearColor( 1, 0.5, 0.5 ),          farColor( 1, 0, 1 );     glShadeModel( GL_SMOOTH );          // near     glColor3fv( nearColor );     glBegin( GL_LINE_LOOP );         glVertex3fv( frustum[0] );         glVertex3fv( frustum[1] );         glVertex3fv( frustum[2] );         glVertex3fv( frustum[3] );     glEnd();     // far     glColor3fv( farColor );     glBegin( GL_LINE_LOOP );         glVertex3fv( frustum[4] );         glVertex3fv( frustum[5] );         glVertex3fv( frustum[6] );         glVertex3fv( frustum[7] );     glEnd();     // connectors     glBegin( GL_LINES );         glColor3fv( nearColor );         glVertex3fv( frustum[0] );         glColor3fv( farColor );         glVertex3fv( frustum[4] );         glColor3fv( nearColor );         glVertex3fv( frustum[1] );         glColor3fv( farColor );         glVertex3fv( frustum[5] );         glColor3fv( nearColor );         glVertex3fv( frustum[2] );         glColor3fv( farColor );         glVertex3fv( frustum[6] );         glColor3fv( nearColor );         glVertex3fv( frustum[3] );         glColor3fv( farColor );         glVertex3fv( frustum[7] );     glEnd();          glShadeModel( GL_FLAT );```

This code works, so long as the aspect ratio is 1. I thought I got my trig right creating the box, but it's just slightly off. Anybody have any ideas?
Luminary
Posts: 5,139
Joined: 2002.04
Post: #8
I'm not sure what the original question is really. If you want a matrix that'll take world space to a cube, that's MVP, as DoG said in post #2. The cube in question is the radius-1 cube, so if you want a unit cube you'd have to do an extra scale operation, but it's quite straightforward.

If that's not what you're after, what is it that you want?
Sage
Posts: 1,199
Joined: 2004.10
Post: #9
It was a hard question to formulate. I wanted to project a unit cube from a cube, in eye-space, to the frustum in world space. I got it positioned and oriented easily enough using the modelview, but I couldn't get it to form a trapezoidal/pyramidical shape like the frustum.

Anyway, I solved it. It was a matter of generating a non-transformed frustum in eye space and positioning it with the inverse modelview of the camera.

I got it working about 5 minutes ago:
Code:
```void EyeSpaceFrustum::createBox( vec4 points[8] ) {     /*         Basic trig, tan(theta) = opposite / adjacent         theta = 1/2 fov         adjacent = near or far distance         opposite = plane width, or height     */     float tanHFov = tanf( _fov * 0.5f * DEG2RAD );     float aspect = _nearWidth / _nearHeight;          float hNearWidth = tanHFov * _near * aspect,           hNearHeight = tanHFov * _near,           hFarWidth = tanHFov * _far * aspect,           hFarHeight = tanHFov * _far;                points[0] = vec4( -hNearWidth, hNearHeight, -_near, 1 );     points[1] = vec4( hNearWidth, hNearHeight, -_near, 1 );     points[2] = vec4( hNearWidth, -hNearHeight, -_near, 1 );     points[3] = vec4( -hNearWidth, -hNearHeight, -_near, 1 );     points[4] = vec4( -hFarWidth, hFarHeight, -_far, 1 );     points[5] = vec4( hFarWidth, hFarHeight, -_far, 1 );     points[6] = vec4( hFarWidth, -hFarHeight, -_far, 1 );     points[7] = vec4( -hFarWidth, -hFarHeight, -_far, 1 ); }```

The key was my misunderstanding of the focal point of the projection. For various reasons I thought the focal point was behind the camera. I had made it too complicated. This works, and is easily represented in such a manner that I can do fast inside/outside checks, which is what I need it for, to create in-frustum particle effects.

See, I'd just do it all in worldspace, but keeping it in eyespace makes the math simpler for particle clipping and the respawning hoo hah I intend to do.
Luminary
Posts: 5,139
Joined: 2002.04
Post: #10
I still think you just wanted MVP, or maybe the inverse. Maybe I'm just being dense, though.
Sage
Posts: 1,199
Joined: 2004.10
Post: #11
Well, I actually had tried MVP, but it gave me trouble when I tried to take into account screen aspect and other factors. Given your cred here, however, I'm more apt to assume *I* was being dense than you.

That said, I got it working and now have a surprisingly good volumetric rain demo:

Looking from the rain camera

Looking from a different camera, at the rain centered on the rain camera

I ended up going with a box, rather than a frustum, since it made for much faster boundary checking as well as an easier density calculation. What's cool is you can specify desired particle density and it'll increase or decrease the number of particles to maintain that target density. So you can muss with your FOV at runtime and it'll still look right.

Plus, what's most AWESOME is that as you swing the camera around, or move about, it looks completely right. You look up and the rain falls on you, you look down and you see the rain fall and converge. It looks so much better than the crappy planar-rain I've seen in old games. And what I'm totally stoked about is that I can adapt this super easily to do volumetric patchy fog, like in Silent Hill 2, for PS2, but hopefully less oppressive.

Now, I'm working on integrating it gracefully with my game.

EDIT:

W00t!

Looks good, runs fast.