W wersji 1.5 biblioteki OpenGL (rozszerzenie ARB occlusion query) wprowadzono technikę testowania zasłaniania niewidocznych obiektów przy użyciu brył ograniczających. Pozwala to na uniknięcie rysowania tych elementów sceny, które nie są w danym momencie widoczne. Odpowiednie użycie testów zasłaniania umożliwia znaczące przyspieszenie rysowania sceny.
Bryły ograniczające
Bryła ograniczająca jest obiektem zbudowanym z prymitywów OpenGL, która w całości zawiera wewnątrz (otacza) wybrane elementy sceny. Efektywność działania bryły ograniczającej zależy od dwóch sprzecznych wymagań. Im mniejsza ilość wielokątów opisujących bryłę ograniczającą tym szybciej wykonywany jest test zasłaniania. I przeciwnie, im bryła ograniczająca ma kształt bardziej zbliżony (przylegający) do otaczanych elementów sceny, tym dokładniej wykonywany jest test zasłaniania. W praktyce jako brył ograniczających używa się różnego rodzaju wielościanów.
Technika brył ograniczających wykorzystana w bibliotece OpenGL nie jest oczywiście niczym nowym. Jest ona znana i stosowana od wielu lat w specjalistycznych bibliotekach i programach do grafiki 3D.
Obiekty analizy przesłonięć
Przy analizie przesłonięć biblioteka OpenGL korzysta ze specjalnych obiektów. Obiekty analizy przesłonięć posiadają unikatowe identyfikatory, które generuje funkcja:
void glGenQueries( GLsizei n, GLuint * ids )
Parametr n określa ilość generowanych identyfikatorów obiektów analizy przesłonięć, które umieszczane są w tablicy ids.
Usuwanie wybranych obiektów analizy przesłonięć wymaga wywołania funkcji:
void glDeleteQueries( GLsizei n, const GLuint * ids )
której parametr n określa ilość usuwanych obiektów, a tablica ids zawiera ich identyfikatory.
Sprawdzenie, czy dany identyfikator jest związany z obiektem analizy przesłonięć umożliwia funkcja logiczna:
GLboolean glIsQuery( GLuint id )
Analiza przesłonięć
Rozpoczęcie analizy przesłonięć wymaga wywołania funkcji:
void glBeginQuery( GLenum target, GLuint id )
Parametr target określa rodzaj wykonywanego zapytania i przyjmuje wartość
GL_SAMPLES_PASSED. Drugi parametr id zawiera numer identyfikatora obiektu analizy.
Funkcja glBeginQuery tworzy nowy obiekt analizy przesłonięć. Jeżeli z identyfikatorem id związany jest już inny obiekt analizy przesłonięć, lub wartość tego identyfikatora wynosi zero, generowany jest błąd
GL_INVALID - OPERATION.
Zakończenie analizy przesłonięć dla bieżącego obiektu analizy wymaga wywołania funkcji:
void glEndQuery( GLenum target )
której parametr target może przyjąć jedynie wartość
GL_SAMPLES_PASSED. W danym czasie OpenGL może dokonywać tylko analizy przesłonięć tylko dla jednego obiektu analizy.
Sprawdzenie stanu analiz określonego typu (aktualnie dostępne są tylko analizy przesłonięć) umożliwia funkcja:
void glGetQueryiv( GLenum target, GLenum pname, GLint * params )
Parametr target może przyjąć jedynie wartość
GL_SAMPLES_PASSED, a sprawdzany parametr stanu analiz określa pname, który przyjmuje jedną z dwóch wartości:
Warto zauważyć, że liczba bitów licznika analizy może wynosić 0, co w praktyce oznacza niedostępność testu zasłaniania. Minimalna ilość bitów licznika zależy od implementacji i wynika z maksymalnych obsługiwanych rozmiarów okna renderingu OpenGL.
Właściwości obiektu analizy przesłonięć
Pobieranie właściwości obiektu analizy przesłonięć umożliwiają funkcje z grupy glGetQueryObject:
void glGetQueryObjectiv( GLuint id, GLenum pname, GLint * params )
void glGetQueryObjectuiv( GLuint id, GLenum pname, GLuint * params )
Parametr id zawiera unikatowy identyfikator obiektu analizy przesłonięć. Drugi parametr pname wskazuje rodzaj pobieranej właściwości obiektu, której wartość zostanie zwrócona w parametrze params. Dostępne są następując właściwości obiektów analizy przesłonięć:
Wyjaśnienia wymaga jeszcze jak należy interpretować wynik analizy przesłonięć. Jest to po prostu ilość fragmentów bryły ograniczającej, które przeszły pomyślnie test głębokości. Wartość zero licznika analizy oznacza, że żaden fragment bryły ograniczającej nie jest widoczny, a zatem nie jest widoczny także właściwy obiekt.
Sprawdzanie dostępności wyników analizy jest niezbędne, ponieważ mogą zdarzyć się sytuacje, w których wynik jednaj analizy jest zależny od wyniku innych analiz. W czasie oczekiwania na wynik analizy aplikacja może wykonywać inne operacje.
Program przykładowy
Program przykładowy prezentowany na listingu test zaslaniania.cpp zawiera prosty test skuteczności techniki analizy przesłonięć. Program rysuje dwie skomplikowane kule, które przedzielone są tak ułożonym prostopadłościanem, aby istniała możliwość całkowitego zasłonięcia jednej z kul. Jako bryłę ograniczającą wykorzystano sześcian, który przy tak skonstruowanej scenie jest wystarczająco efektywny.
Kolejność postępowania przy rysowaniu sceny z testem zasłaniania jest następująca:
1. rysujemy wszystkie elementy sceny, które nie są objęte testem zasłaniania,
2. wyłączamy wszystkie zbędne właściwości sceny (np. oświetlenie) pozostawiając jedynie włączony bufor głębokości,
3. wyłączamy zapis do bufora głębokości i do bufora koloru,
4. wykonujemy analizę zasłonięć dla wybranych obiektów,
5. po zakończeniu analizy przesłonięć na podstawie jej wyników rysujemy wszystkie widoczne obiekty sceny, oczywiście po wcześniejszym włączeniu zapisu do buforów głębokości i koloru oraz ponownej aktywacji pozostałych efektów wykorzystywanych w scenie.
Wymienione w punkcie drugim wyłącznie efektów stosowanych w scenie za wyjątkiem bufora głębokości ma na celu przyspieszenie wykonywania testu zasłaniania, w którym przykładowo parametry oświetlenia nie mają żadnego znaczenia dla ostatecznego wyniku.
Wyniki działania przykładowego programu pokazują, że analiza zasłania potrafi znacząco przyspieszyć rysowanie sceny. Wystarczy porównać ilość wyświetlanych ramek na sekundę przy włączonym (rysunek 1) i wyłączonym (rysunek 2) teście zasłaniania. Jak należało się spodziewać rysowanie szybkość sceny przy wyłączony teście zasłaniana nie jest w żaden sposób zależna od tego, które elementy są aktualnie widoczne - patrz rysunek 3. Na ostatnim rysunku 4 przedstawiono scenę z widocznymi krawędziami brył ograniczających.
Plik test_zaslaniania.cpp
#include <GL/glut.h>
#include <GL/glext.h>
#ifndef WIN32
#define GLX_GLXEXT_LEGACY
#include <GL/glx.h>
#define wglGetProcAddress glXGetProcAddressARB
#endif
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include "colors.h"
PFNGLWINDOWPOS2IPROC glWindowPos2i = NULL;
PFNGLGENQUERIESPROC glGenQueries = NULL;
PFNGLBEGINQUERYPROC glBeginQuery = NULL;
PFNGLENDQUERYPROC glEndQuery = NULL;
PFNGLGETQUERYOBJECTIVPROC glGetQueryObjectiv = NULL;
PFNGLDELETEQUERIESPROC glDeleteQueries = NULL;
enum
{
OCCLUSION_TEST,
OCCLUSION_BOX,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif
const GLdouble left = - 2.0;
const GLdouble right = 2.0;
const GLdouble bottom = - 2.0;
const GLdouble top = 2.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
bool occlusion_test = true;
bool occlusion_box = false;
GLuint SPHERE_0, SPHERE_1, WALL, CUBE_0, CUBE_1;
int frames = 0;
long start_time = 0;
char time_string[ 100 ] = "FPS:";
void DrawString( GLint x, GLint y, char * string )
{
glWindowPos2i( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void DisplayScene()
{
if( !frames++ )
start_time = clock();
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0.0, 0.0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0.0, 0.0 );
glRotatef( rotatey, 0.0, 1.0, 0.0 );
glEnable( GL_DEPTH_TEST );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_COLOR_MATERIAL );
glEnable( GL_NORMALIZE );
glCallList( WALL );
if( occlusion_test == true )
{
glDisable( GL_LIGHTING );
glDisable( GL_LIGHT0 );
glDisable( GL_COLOR_MATERIAL );
glDisable( GL_NORMALIZE );
glDepthMask( GL_FALSE );
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
GLuint query_id[ 2 ];
glGenQueries( 2, query_id );
glBeginQuery( GL_SAMPLES_PASSED, query_id[ 0 ] );
glCallList( CUBE_0 );
glEndQuery( GL_SAMPLES_PASSED );
glBeginQuery( GL_SAMPLES_PASSED, query_id[ 1 ] );
glCallList( CUBE_1 );
glEndQuery( GL_SAMPLES_PASSED );
glFlush();
GLint available;
do
{
glGetQueryObjectiv( query_id[ 0 ], GL_QUERY_RESULT_AVAILABLE, & available );
}
while( !available );
do
{
glGetQueryObjectiv( query_id[ 1 ], GL_QUERY_RESULT_AVAILABLE, & available );
}
while( !available );
glDepthMask( GL_TRUE );
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_COLOR_MATERIAL );
glEnable( GL_NORMALIZE );
GLint result;
glGetQueryObjectiv( query_id[ 0 ], GL_QUERY_RESULT, & result );
if( result )
glCallList( SPHERE_0 );
glGetQueryObjectiv( query_id[ 1 ], GL_QUERY_RESULT, & result );
if( result )
glCallList( SPHERE_1 );
glDeleteQueries( 2, query_id );
}
else
{
glCallList( SPHERE_0 );
glCallList( SPHERE_1 );
}
if( occlusion_box == true )
{
glColor3fv( Black );
glPushMatrix();
glTranslatef( 0.0, 0.0, 1.2 );
glutWireCube( 1.5 );
glTranslatef( 0.0, 0.0, - 2.4 );
glutWireCube( 1.5 );
glPopMatrix();
}
glColor3fv( Black );
if( frames == 100 )
{
frames = 0;
sprintf( time_string, "FPS: %i",( int )( 100 * CLOCKS_PER_SEC /( float )( clock() - start_time ) ) );
}
DrawString( 1, 1, time_string );
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if( aspect == ASPECT_1_1 )
{
if( width < height && width > 0 )
glFrustum( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
DisplayScene();
}
void MouseButton( int button, int state, int x, int y )
{
if( button == GLUT_LEFT_BUTTON )
{
button_state = state;
if( state == GLUT_DOWN )
{
button_x = x;
button_y = y;
}
}
}
void MouseMotion( int x, int y )
{
if( button_state == GLUT_DOWN )
{
rotatey += 30 *( right - left ) / glutGet( GLUT_WINDOW_WIDTH ) *( x - button_x );
button_x = x;
rotatex -= 30 *( top - bottom ) / glutGet( GLUT_WINDOW_HEIGHT ) *( button_y - y );
button_y = y;
glutPostRedisplay();
}
}
void Menu( int value )
{
switch( value )
{
case OCCLUSION_TEST:
occlusion_test = !occlusion_test;
DisplayScene();
break;
case OCCLUSION_BOX:
occlusion_box = !occlusion_box;
DisplayScene();
break;
case FULL_WINDOW:
aspect = FULL_WINDOW;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case ASPECT_1_1:
aspect = ASPECT_1_1;
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
break;
case EXIT:
exit( 0 );
}
}
void GenerateDisplayLists()
{
SPHERE_0 = glGenLists( 1 );
glNewList( SPHERE_0, GL_COMPILE );
glColor3fv( Blue );
glPushMatrix();
glTranslatef( 0.0, 0.0, 1.2 );
glutSolidSphere( 0.7, 200, 200 );
glPopMatrix();
glEndList();
SPHERE_1 = glGenLists( 1 );
glNewList( SPHERE_1, GL_COMPILE );
glColor3fv( Lime );
glPushMatrix();
glTranslatef( 0.0, 0.0, - 1.2 );
glutSolidSphere( 0.7, 200, 200 );
glPopMatrix();
glEndList();
WALL = glGenLists( 1 );
glNewList( WALL, GL_COMPILE );
glColor3fv( Red );
glPushMatrix();
glScalef( 1.0, 1.0, 0.1 );
glutSolidCube( 2.8 );
glPopMatrix();
glEndList();
CUBE_0 = glGenLists( 1 );
glNewList( CUBE_0, GL_COMPILE );
glPushMatrix();
glTranslatef( 0.0, 0.0, 1.2 );
glutSolidCube( 1.5 );
glPopMatrix();
glEndList();
CUBE_1 = glGenLists( 1 );
glNewList( CUBE_1, GL_COMPILE );
glPushMatrix();
glTranslatef( 0.0, 0.0, - 1.2 );
glutSolidCube( 1.5 );
glPopMatrix();
glEndList();
}
void ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifdef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 4 )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress( "glWindowPos2i" );
}
else
if( glutExtensionSupported( "GL_ARB_window_pos" ) )
{
glWindowPos2i =( PFNGLWINDOWPOS2IPROC ) wglGetProcAddress
( "glWindowPos2iARB" );
}
else
{
printf( "Brak rozszerzenia ARB_window_pos!\n" );
exit( 0 );
}
if( major > 1 || minor >= 5 )
{
glGenQueries =( PFNGLGENQUERIESPROC ) wglGetProcAddress( "glGenQueries" );
glBeginQuery =( PFNGLBEGINQUERYPROC ) wglGetProcAddress( "glBeginQuery" );
glEndQuery =( PFNGLENDQUERYPROC ) wglGetProcAddress( "glEndQuery" );
glGetQueryObjectiv =( PFNGLGETQUERYOBJECTIVPROC )
wglGetProcAddress( "glGetQueryObjectiv" );
glDeleteQueries =( PFNGLDELETEQUERIESPROC )
wglGetProcAddress( "glDeleteQueries" );
}
else
if( glutExtensionSupported( "GL_ARB_occlusion_query" ) )
{
glGenQueries =( PFNGLGENQUERIESPROC ) wglGetProcAddress( "glGenQueriesARB" );
glBeginQuery =( PFNGLBEGINQUERYPROC ) wglGetProcAddress( "glBeginQueryARB" );
glEndQuery =( PFNGLENDQUERYPROC ) wglGetProcAddress( "glEndQueryARB" );
glGetQueryObjectiv =( PFNGLGETQUERYOBJECTIVPROC )
wglGetProcAddress( "glGetQueryObjectivARB" );
glDeleteQueries =( PFNGLDELETEQUERIESPROC )
wglGetProcAddress( "glDeleteQueriesARB" );
}
else
{
printf( "Brak rozszerzenia ARB_window_pos!\n" );
exit( 0 );
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Test zasłaniania" );
#else
glutCreateWindow( "Test zaslaniania" );
#endif
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
int MenuAspect = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Aspekt obrazu - całe okno", FULL_WINDOW );
#else
glutAddMenuEntry( "Aspekt obrazu - cale okno", FULL_WINDOW );
#endif
glutAddMenuEntry( "Aspekt obrazu 1:1", ASPECT_1_1 );
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Test zasłaniania włącz/wyłącz", OCCLUSION_TEST );
glutAddMenuEntry( "Rysowania brył ograniczających włącz/wyłącz", OCCLUSION_BOX );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Test zaslaniania wlacz/wylacz", OCCLUSION_TEST );
glutAddMenuEntry( "Rysowania bryly ograniczajacych wlacz/wylacz", OCCLUSION_BOX );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
ExtensionSetup();
GenerateDisplayLists();
glutIdleFunc( DisplayScene );
glutMainLoop();
return 0;
}