Panorama -> CubeMap

Sage
Posts: 1,199
Joined: 2004.10
Post: #1
Does anybody have any suggestions for the conversion of a panorama image to a cube map?

By panorama I mean something like the following ( shamefully grabbed from Cheetah3D )

[Image: oldtown_pano_small.png]

I've recently gotten all fired up to implement image based lighting, at least for the ambient pass, and it seems like panorama->cubemap is probably something I'll need to figure out, since panoramas are easy to come by.

It seems straightforward enough to wrap the panorama about the cube's X & Y faces, but the + and - Z faces, I don't know how best to apply the panorama...

Any suggestions would be appreciated.
Quote this message in a reply
Nibbie
Posts: 1
Joined: 2009.01
Post: #2
Hi

Try this software: http://www.clickheredesign.com.au/cubicconverter/

there are lots of other ones out there- but this is the best... other than that there are other ways to do what you want, depending on the game engine as well- simply put- you could even wrap the image around a sphere!

Grin

Regards

Alex
Quote this message in a reply
Sage
Posts: 1,199
Joined: 2004.10
Post: #3
OK, I've managed to make it work, more or less. I'm posting my code below in case somebody wants to do something similar. That being said, my code uses some of my GL apis, but it ought to be easy enough for somebody to take what's useful.

Code:
namespace {

    const float z_height = 0.2f;
    
    struct quad
    {
        float x,y,width,height;
    
        quad( float X, float Y, float Width, float Height ):
            x(X), y(Y), width( Width ), height( Height )
        {}
        
        void submit( int cubeSize, const vec2 &sourceSize, bool rect, int rot )
        {
            vec2 texcoords_rect[4] =
            {
                vec2( x * sourceSize.x, y * sourceSize.y ),
                vec2( (x+width) * sourceSize.x, y * sourceSize.y ),
                vec2( (x+width) * sourceSize.x, (y+height) * sourceSize.y ),
                vec2( x * sourceSize.x, (y+height) * sourceSize.y )
            },
            
            texcoords_pot[4] = {
                vec2( x,y ),
                vec2( x+width,y ),
                vec2( x+width,y+height ),
                vec2( x,y+height )
            },
            
            vertices[4] = {
                vec2( 0,0 ),
                vec2( cubeSize, 0 ),
                vec2( cubeSize, cubeSize ),
                vec2( 0, cubeSize )
            };
                    
            glError();      
            glBegin( GL_QUADS );
            
            vec2 *tc = rect ? texcoords_rect : texcoords_pot;
            for ( int i = 0; i < 4; i++ )
            {
                glMultiTexCoord2fv( GL_TEXTURE0, tc[ (i + rot) % 4 ] );
                glVertex2fv( vertices[i] );
            }          
            
            glEnd();
            glError();
        }      
    };
    
    void draw_cap_triangle( int rot, quad texture, int cubeSize, vec2 texSize )
    {
        vec2 vertices[4] =
        {
            vec2( 0,0 ),
            vec2( cubeSize, 0 ),
            vec2( cubeSize, cubeSize ),
            vec2( 0, cubeSize )
        },
        texcoords[4] =
        {
            vec2( texture.x * texSize.x, texture.y * texSize.y),
            vec2( (texture.x + texture.width) * texSize.x, texture.y * texSize.y ),
            vec2( (texture.x + texture.width) * texSize.x, (texture.y + texture.height) * texSize.y ),
            vec2( texture.x * texSize.x, (texture.y + texture.height) * texSize.y )
        };

        vec2 tria( vertices[ (0 + rot) % 4 ] ),
             trib( vertices[ (1 + rot) % 4 ] ),
             tricenter( cubeSize * 0.5 );
        
        glBegin( GL_QUADS );
        
        const int slices = 64;
        for ( int i = 0; i < slices; i++ )
        {
            float part = float(i)/float(slices),
                  increment = 1.0f / float(slices);
            vec2 a( lrp( part, tria, tricenter )),
                 b( lrp( part, trib, tricenter )),
                 c( lrp( part + increment, trib, tricenter )),
                 d( lrp( part + increment, tria, tricenter ));
                
            vec2 ta( lrp( part, texcoords[0], texcoords[3] )),
                 tb( lrp( part, texcoords[1], texcoords[2] )),
                 tc( lrp( part + increment, texcoords[1], texcoords[2] )),
                 td( lrp( part + increment, texcoords[0], texcoords[3] ));
            
            glMultiTexCoord2fv( GL_TEXTURE0, ta );
            glVertex2fv( a );

            glMultiTexCoord2fv( GL_TEXTURE0, tb );
            glVertex2fv( b );

            glMultiTexCoord2fv( GL_TEXTURE0, tc );
            glVertex2fv( c );

            glMultiTexCoord2fv( GL_TEXTURE0, td );
            glVertex2fv( d );          
        }
        
        glEnd();    
    }
    
    
    ///////////////////////////////////////////////////////////////////

    void draw_pos_x( TextureInterfaceRef panorama, FrameBufferObjectRef cubemap, int size, bool rect )
    {
        cubemap->beginCubemapFace( GL_TEXTURE_CUBE_MAP_POSITIVE_X );
        quad( 0.00f, z_height, 0.25f, 1.0f - 2*z_height ).submit( size, vec2( panorama->width(), panorama->height()), rect, 3 );
    }

    void draw_neg_x( TextureInterfaceRef panorama, FrameBufferObjectRef cubemap, int size, bool rect )
    {
        cubemap->beginCubemapFace( GL_TEXTURE_CUBE_MAP_NEGATIVE_X );        
        quad( 0.50f, z_height, 0.25f, 1.0f - 2*z_height ).submit( size, vec2( panorama->width(), panorama->height()), rect, 1 );
    }

    void draw_pos_y( TextureInterfaceRef panorama, FrameBufferObjectRef cubemap, int size, bool rect )
    {
        cubemap->beginCubemapFace( GL_TEXTURE_CUBE_MAP_POSITIVE_Y );            
        quad( 0.25f, z_height, 0.25f, 1.0f - 2*z_height ).submit( size, vec2( panorama->width(), panorama->height()), rect, 2 );
    }

    void draw_neg_y( TextureInterfaceRef panorama, FrameBufferObjectRef cubemap, int size, bool rect )
    {
        cubemap->beginCubemapFace( GL_TEXTURE_CUBE_MAP_NEGATIVE_Y );        
        quad( 0.75f, z_height, 0.25f, 1.0f - 2*z_height ).submit( size, vec2( panorama->width(), panorama->height()), rect, 0 );
    }

    void draw_pos_z( TextureInterfaceRef panorama, FrameBufferObjectRef cubemap, int size, bool rect )
    {
        cubemap->beginCubemapFace( GL_TEXTURE_CUBE_MAP_POSITIVE_Z );
        
        vec2 texSize = rect ? vec2( panorama->width(), panorama->height() ) : vec2( 1,1 );      
        draw_cap_triangle(1, quad( 0.00f, 1.0f - z_height, 0.25f, z_height ), size, texSize );
        draw_cap_triangle(2, quad( 0.25f, 1.0f - z_height, 0.25f, z_height ), size, texSize );
        draw_cap_triangle(3, quad( 0.50f, 1.0f - z_height, 0.25f, z_height ), size, texSize );
        draw_cap_triangle(0, quad( 0.75f, 1.0f - z_height, 0.25f, z_height ), size, texSize );
    }

    void draw_neg_z( TextureInterfaceRef panorama, FrameBufferObjectRef cubemap, int size, bool rect )
    {
        cubemap->beginCubemapFace( GL_TEXTURE_CUBE_MAP_NEGATIVE_Z );

        vec2 texSize = rect ? vec2( panorama->width(), panorama->height() ) : vec2( 1,1 );      
        draw_cap_triangle(3, quad( 0.25f, z_height, -0.25f, -z_height ), size, texSize );
        draw_cap_triangle(2, quad( 0.50f, z_height, -0.25f, -z_height ), size, texSize );
        draw_cap_triangle(1, quad( 0.75f, z_height, -0.25f, -z_height ), size, texSize );
        draw_cap_triangle(0, quad( 1.00f, z_height, -0.25f, -z_height ), size, texSize );
    }

}

TextureInterfaceRef
GenerateCubemapFromPanorama( TextureInterfaceRef panorama, int cubeSize, int blur, int blurDownsample )
{
    if ( !isPow2( cubeSize ))
    {
        cubeSize = nextPowerOf2(cubeSize);
    }

    FrameBufferObjectRef cubemap( new FrameBufferObject( cubeSize, cubeSize, GL_TEXTURE_CUBE_MAP, false, 0 ));
    
    if ( blur > 0 )
    {
        GaussianConvolution gaussian;
        gaussian.setDownsampling( blurDownsample );
        gaussian.setKernelDiameter( blur );
        panorama = gaussian.convolve( panorama );
    }

    //
    //  1) push current modelview & projection
    //  2) Store current viewport
    //  3) Set up ortho projection and appropriate viewport
    //  4) unwrap the panorama
    //  5) restore previous configuration
    //
    
    GLint viewport[4];
    glGetIntegerv( GL_VIEWPORT, viewport );
        
    glDisable( GL_LIGHTING );
    glDisable( GL_DEPTH_TEST );
    glDisable( GL_CULL_FACE );
    glDisable( GL_BLEND );

    glMatrixMode( GL_PROJECTION );
    glPushMatrix();
    {
        glLoadIdentity();
        glViewport( 0, 0, cubeSize, cubeSize );
        gluOrtho2D( 0, cubeSize, 0, cubeSize );

        glMatrixMode( GL_MODELVIEW );
        glPushMatrix();
        {
            glLoadIdentity();
            
            glActiveTexture( GL_TEXTURE0 );
            glEnable( panorama->target() );
            glBindTexture( panorama->target(), panorama->textureID() );
            glColor3f( 1,1,1 );
            
            cubemap->begin();
            
            bool rect = panorama->target() == GL_TEXTURE_RECTANGLE_EXT;
            draw_pos_x( panorama, cubemap, cubeSize, rect );
            draw_neg_x( panorama, cubemap, cubeSize, rect );

            draw_pos_y( panorama, cubemap, cubeSize, rect );
            draw_neg_y( panorama, cubemap, cubeSize, rect );

            draw_pos_z( panorama, cubemap, cubeSize, rect );
            draw_neg_z( panorama, cubemap, cubeSize, rect );
            
            cubemap->end();
            
            glBindTexture( panorama->target(), 0 );
            glDisable( panorama->target() );
        }
        glPopMatrix();
        
        glMatrixMode( GL_PROJECTION );
        glViewport( viewport[0], viewport[1], viewport[2], viewport[3] );
    }
    glPopMatrix();      
    
    return cubemap;
}

The result has a fair bit of distortion. I'm not attempting to correct the spherical/barrel distortion of the panorama, I'm just trying to make certain that the result tiles. I'm using this, after all, for subtle global term lighting, not for a skybox...

EDIT: I make no claims for the beauty of the code above. It was hacked out late last night and early this morning. The code's meant to sit in a .cpp file with only the GenerateCubemapFromPanorama prototype exposed in the header.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  Cubemap lookup with normalmap "bumps" TomorrowPlusX 15 6,530 Jan 13, 2009 08:13 AM
Last Post: TomorrowPlusX
  cubemap always reflects same direction. NYGhost 6 3,164 Jul 6, 2006 05:56 PM
Last Post: NYGhost