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:

[Image: FrustumTransform.png]

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.
Quote this message in a reply
DoG
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? Smile
Quote this message in a reply
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.
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #4
Some details, but first a couple screenshots:

What I'm getting:
[Image: NotWhatIExpected.png]

What I'm aiming for:
[Image: WhatIExpected.png]

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;
}
Quote this message in a reply
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.
Quote this message in a reply
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 ).
Quote this message in a reply
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?
Quote this message in a reply
Luminary
Posts: 5,143
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?
Quote this message in a reply
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.
Quote this message in a reply
Luminary
Posts: 5,143
Joined: 2002.04
Post: #10
I still think you just wanted MVP, or maybe the inverse. Maybe I'm just being dense, though.
Quote this message in a reply
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
[Image: Rain.png]

Looking from a different camera, at the rain centered on the rain camera
[Image: RainExternal.png]

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. Grin

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!
[Image: Legion-2005-07-12-00.png]

Looks good, runs fast.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  When to create custom OpenGL view instead of subclass NSOpenGL view Coyote 37 22,600 Oct 20, 2009 08:16 PM
Last Post: Coyote
  Cube auto-rotate sakrist 2 3,067 May 5, 2009 03:31 AM
Last Post: Ingemar
  OpenGL ES - Drawing a simple cube help. MattCairns 7 12,588 Oct 10, 2008 05:26 PM
Last Post: Frogblast
  New to OpenGL -- Transformation Help Dash Rantic 4 3,181 Mar 9, 2008 06:08 PM
Last Post: Dash Rantic
  Pre-generated cube map Fenris 2 3,631 Apr 5, 2007 07:01 PM
Last Post: nich