Bufor szablonowy (ang. stencil buffer) w budowie i działaniu przypomina bufor głębokości. Bufor ten umożliwia utworzenie specjalnego szablonu (matrycy) określającego obszar w buforze koloru, który będzie używany do renderingu. Stąd właśnie porównanie funkcjonalne do bufora głębokości.
Bufor szablonowy zawiera dane w postaci liczb całkowitych. Ilość bitów przypadających na jeden element bufora zależy od implementacji, ale nie może być mniejsza niż 1. Wielkość tę można ustalić korzystając z funkcji z grupy glGet z parametrem
GL_STENCIL_BITS.
Włączenie i wyłączenie bufora szablonowego
Włączenie bufora szablonowego wymaga wywołania funkcji glEnable z parametrem
GL_STENCIL_TEST. Wcześniej trzeba jeszcze dodać bufor szablonowy przy inicjalizacji bufora ramki. W przypadku biblioteki GLUT sprowadza się to do dodania stałej GLUT STENCIL przy wywołaniu funkcji glutInitDisplayMode. Wyłącznie bufora szablonowego sprowadza się do wywołania funkcji glDisable z parametrem
GL_STENCIL_TEST.
Czyszczenie bufora szablonowego
Zawartość bufora szablonowego, podobnie jak innych buforów wchodzących w skład bufora ramki, można wypełniać (czyścić) stałą wartością przy pomocy znanej już funkcji glClear z parametrem
GL_STENCIL_BUFFER_BIT. Domyślnie bufor szablonowy wypełniany jest zerami, ale wartość tę można zmienić przy użyciu funkcji:
void glClearStencil( GLint s )
Sterowanie buforem szablonowym
Działanie bufora szablonowego reguluje kilka funkcji. Podstawową z nich jest funkcja:
void glStencilFunc( GLenum func, GLint ref, GLuint mask )
która ustala tzw. test szablonu. Parametr func wskazuje rodzaj zastosowanej funkcji testującej i może przyjąć jedną z poniższych wartości:
Zauważmy, że zestaw funkcji testujących bufora szablonowego jest taki sam jak testy bufora głębokości.
Parametr ref określa tzw. wartość referencyjną używaną w teście bufora szablonowego. Wartość ta jest zawsze obcinana do przedziału [0, 2n − 1], gdzie n jest ilością bitów bufora szablonu. Przy wykonywaniu testu bufora szablonowego wykorzystywana jest maska bitowa zawarta w parametrze mask określająca, dla których bitów wartości referencyjnej i wartości przechowywanej w buforze wykonywany jest test szablonu. Operacja ta jest realizowana za pomocą iloczynu logicznego testowanych wartości z wartością maski. Domyślną wartość maski bitowej wynoszącą 1 dla każdego bitu bufora szablonowego można zmienić przy użyciu funkcji:
void glStencilMask( uint mask )
Trzecią funkcją sterującą działaniem bufora szablonowego jest:
void glStencilOp( GLenum sfail, GLenum dpfail, GLenum dppass )
której parametry określają jaka operacja na buforze szablonowym wykonywana jest w przypadku:
Każdy z parametrów sfail, dpfail i dppass może niezależnie przyjąć jedną z poniższych wartości:
Stałe
GL_DECR_WRAP i
GL_INCR_WRAP zostały dodane w wersji 1.4 biblioteki OpenGL, a wcześniej były dostępne w rozszerzeniu EXT stencil wrap.
Dla celów operacji
GL_INCR,
GL_DECR,
GL_DECR_WRAP i
GL_INCR_WRAP wartości bufora szablonowego traktowane są jak liczby całkowite bez znaku.
Rozłączny bufor szablonowy
W wersji 2.0 biblioteki OpenGL zaimplementowano zaproponowany w rozszerzeniach ATI separate stencil i EXT stencil two side mechanizm rozłącznego bufora szablonowego. Rozłączność sprowadza się do możliwości określenia odrębnych operacji na buforze szablonowym dla przednich i tylnych stron tych prymitywów graficznych, dla których OpenGL określa stronę, czyli wielokątów. Natomiast przy renderingu prymitywów, dla których nie określa się stron (m.in. punkty, linie, mapy bitowe i pikselowe) używane są operacje określone dla przednich stron wielokątów.
Specyfikacja wprowadza trzy nowe funkcje sterujące pracą rozłącznego bufora szablonowego:
void glStencilFuncSeparate( enum face, enum func, int ref, uint mask )
void glStencilOpSeparate( enum face, enum sfail, enum dpfail, enum dppass )
void glStencilMaskSeparate( enum face, uint mask )
Funkcje te różnią się od wcześniej opisanych parametrem face określającym strony prymitywów, których dotyczą zmieniane ustawienia. Parametr ten przyjmuje jedną z trzech znanych już wartości:
GL_FRONT (przedni bufor),
GL_BACK (tylni bufor) i
GL_FRONT_AND_BACK (przedni i tylni bufor). Działanie dotychczasowych funkcji glStencilFunc, glStencilOp i glStencilMask odpowiada definiowaniem operacji dla obu stron prymitywów. Jedynie przy czyszczeniu bufora używana jest zawsze maska określona dla przednich stron wielokątów.
Programy przykładowe
Program przykładowy (plik odbicie.cpp) przedstawia sposób wykorzystania mieszania kolorów i bufora szablonowego do uzyskania efektu odbicia na płaskiej, półprzezroczystej powierzchni. Idea rozwiązania jest stosunkowo prosta. Obiekty „świata”, które mają zostać odbite na powierzchni rysowane są dwukrotnie. Pierwszy raz obiekty rysowane są w położeniu „lustrzanym” w stosunku do powierzchni, w którym mają być odbite. W przypadku, gdy płaszczyzna ta jest prostopadła do osi X wystarczy do tego odpowiednie skalowanie macierzy modelowania:
glScalef( 1.0, - 1.0, 1.0 )
Takiemu samemu przekształceniu trzeba poddać współrzędne położenia źródeł światła, które są wykorzystywane do oświetlenia sceny. Pozostałe przekształcenie macierzy modelowania (obroty, przesunięcia itp.) są identyczne jak dla obiektów „rzeczywistego” świata. W przypadku, gdy program rysuje tylko przednie strony obiektów, przy rysowaniu świata „lustrzanego” trzeba zmienić orientację stron wielokąta na przeciwną do stosowanej przy rysowania świata rzeczywistego (funkcja glCullFace).
Po narysowaniu świata lustrzanego rysujemy płaszczyznę, na której realizowany jest efekt odbicia. W tym celu trzeba włączyć mieszanie kolorów oraz ustawić współczynniki mieszania kolorów na
GL_SRC_ALPHA dla koloru źródłowego i
GL_ONE_MINUS_SRC_ALPHA dla koloru przeznaczenia. Jeżeli korzystamy z oświetlenia, tak jak ma to miejsce w przykładowym programie, trzeba na czas rysowania płaszczyzny wyłączyć oświetlenie.
Drugi raz obiekty świata rysowane są już po narysowaniu płaszczyzny odbicia, oczywiście po wyłączeniu mieszania kolorów oraz włączeniu oświetlenia sceny.
Opisany sposób postępowania nie wymaga żadnych dodatkowych zabiegów w przypadku, gdy odbicie obiektów świata nigdy wykracza poza granice płaszczyzny odbicia. W przeciwnym wypadku część sceny lustrzanej zostanie narysowana także poza płaszczyzną odbicia, co jest oczywiście efektem niepożądanym (patrz rysunek 1). Jednym ze sposobów eliminacji tego zjawiska jest wykorzystanie opisanego wyżej bufora szablonowego.
Bufor szablonowy jest wykorzystany przy rysowaniu obiektów świata „lustrzanego”. Wcześniej trzeba jednak utworzyć szablon i wymaga to niestety narysowania powierzchni, na której będzie widoczny efekt odbicia. Test bufora szablonowego definiujemy tak, aby był zawsze spełniony, a poprzednie jego wartości zastępowane (na początku rysowania ramki obrazu bufor jest czyszczony wartością domyślną):
glStencilFunc( GL_ALWAYS, 0x00000001, 0xFFFFFFFF );
glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE );
Aby dwukrotne rysowanie powierzchni odbicia nie spowodowało niepożądanego efektu w postaci zmiany jej kolorów, na czas tworzenia szablonu trzeba wyłączyć zapis składowych RGBA w buforze koloru. Realizuje to opisywana już funkcja glColorMask. Po narysowaniu powierzchni odbicia i otrzymaniu szablonu test bufora szablonowego definiujemy tak, aby był spełniony tylko przy wartości równej do znajdującej się w buforze, a sam bufor pozostawiamy bez modyfikacji:
glStencilFunc( GL_EQUAL, 0x00000001, 0xFFFFFFFF );
glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
Zauważmy, że przy tworzeniu szablonu wykorzystywaliśmy tylko jeden bit bufora szablonowego (parametr ref funkcji glStencilFunc) co czyni program niezależny od implementacji biblioteki OpenGL.
Przy tak określonym teście bufora szablonowego rysujemy tylko obiekty świata „lustrzanego”. Następne operacje, czyli rysowanie powierzchni odbicia i obiektów świata rzeczywistego, już z niego nie korzystają. Efekt działania program z włączonym buforem szablonowym przedstawia rysunek 2.
Do uzyskania ciągłego i jednostajnego ruchu obiektu sceny, który praktycznie nie będzie zależny od wydajności komputera i karty graficznej zastosowano licznik czasu (ang. timer). Funkcję licznika czasu wywołuje się przy użyciu funkcji:
void glutTimerFunc( unsigned int millis,
void( GLUTCALLBACK * func )( int value ), int value )
gdzie millis określa czas w milisekundach, jaki ma upłynąć do wywołania funkcji licznika, do której wskaźnik zawiera parametr func. Ostatni parametr value jest przekazywany jako parametr funkcji licznika.
W przykładowym programie funkcja licznika nazywa się po prostu Timer, która po zwiększeniu kąta obrotu obiektów sceny ponownie ustawia licznik czasu wywołując funkcję glutTimerFunc.
Plik odbicie.cpp
#include <GL/glut.h>
#include <stdlib.h>
#include "colors.h"
enum
{
STENCIL,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
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 = 30.0;
GLfloat rotatey = 0.0;
GLfloat angle = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
GLint GROUND_LIST;
GLint WORLD_LIST;
GLfloat light_position[ 4 ] =
{
0.0, 10.0, 10.0, 1.0
};
bool stencil_test = true;
void DisplayScene()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
if( stencil_test == true )
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
else
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glEnable( GL_DEPTH_TEST );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_NORMALIZE );
glEnable( GL_COLOR_MATERIAL );
glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
glEnable( GL_CULL_FACE );
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0.0, 0.0 );
glRotatef( rotatey, 0.0, 1.0, 0.0 );
if( stencil_test == true )
{
glDisable( GL_DEPTH_TEST );
glEnable( GL_STENCIL_TEST );
glStencilFunc( GL_ALWAYS, 0x00000001, 0xFFFFFFFF );
glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE );
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
glCallList( GROUND_LIST );
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
glEnable( GL_DEPTH_TEST );
glStencilFunc( GL_EQUAL, 0x00000001, 0xFFFFFFFF );
glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
}
glPushMatrix();
glScalef( 1.0, - 1.0, 1.0 );
glLightfv( GL_LIGHT0, GL_POSITION, light_position );
glTranslatef( 0.0, 1.2, 0.0 );
glRotatef( angle, 1.0, 0.0, 0.0 );
glRotatef( angle, 0.0, 2.0, 0.0 );
glRotatef( angle, 0.0, 0.0, - 1.0 );
glFrontFace( GL_CW );
glCallList( WORLD_LIST );
glFrontFace( GL_CCW );
glPopMatrix();
if( stencil_test == true )
{
glDisable( GL_STENCIL_TEST );
}
glDisable( GL_LIGHTING );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glCallList( GROUND_LIST );
glDisable( GL_BLEND );
glEnable( GL_LIGHTING );
glLightfv( GL_LIGHT0, GL_POSITION, light_position );
glTranslatef( 0.0, 1.2, 0.0 );
glRotatef( angle, 1.0, 0.0, 0.0 );
glRotatef( angle, 0.0, 2.0, 0.0 );
glRotatef( angle, 0.0, 0.0, - 1.0 );
glCallList( WORLD_LIST );
glFlush();
glutSwapBuffers();
}
void Timer( int value )
{
angle++;
DisplayScene();
glutTimerFunc( 20, Timer, 0 );
}
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 STENCIL:
stencil_test = !stencil_test;
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()
{
GROUND_LIST = glGenLists( 1 );
glNewList( GROUND_LIST, GL_COMPILE );
glBegin( GL_QUADS );
glNormal3f( 1.0, 0.0, 0.0 );
for( GLfloat z = - 2.0; z < 2.0; z += 1 )
for( GLfloat x = - 2.0; x < 2.0; x += 1 )
{
glColor4f( Blue[ 0 ], Blue[ 1 ], Blue[ 2 ], 0.5 );
glVertex3f( x + 0.0, 0.0, z + 0.0 );
glVertex3f( x + 0.0, 0.0, z + 0.5 );
glVertex3f( x + 0.5, 0.0, z + 0.5 );
glVertex3f( x + 0.5, 0.0, z + 0.0 );
glColor4f( Silver[ 0 ], Silver[ 1 ], Silver[ 2 ], 0.7 );
glVertex3f( x + 0.5, 0.0, z + 0.0 );
glVertex3f( x + 0.5, 0.0, z + 0.5 );
glVertex3f( x + 1.0, 0.0, z + 0.5 );
glVertex3f( x + 1.0, 0.0, z + 0.0 );
glColor4f( Blue[ 0 ], Blue[ 1 ], Blue[ 2 ], 0.5 );
glVertex3f( x + 0.5, 0.0, z + 0.5 );
glVertex3f( x + 0.5, 0.0, z + 1.0 );
glVertex3f( x + 1.0, 0.0, z + 1.0 );
glVertex3f( x + 1.0, 0.0, z + 0.5 );
glColor4f( Silver[ 0 ], Silver[ 1 ], Silver[ 2 ], 0.7 );
glVertex3f( x + 0.0, 0.0, z + 0.5 );
glVertex3f( x + 0.0, 0.0, z + 1.0 );
glVertex3f( x + 0.5, 0.0, z + 1.0 );
glVertex3f( x + 0.5, 0.0, z + 0.5 );
}
glEnd();
glEndList();
WORLD_LIST = glGenLists( 1 );
glNewList( WORLD_LIST, GL_COMPILE );
glColor4fv( Green );
glutSolidTorus( 0.3, 0.7, 50, 40 );
glEndList();
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "Odbicie" );
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( "Bufor szablonowy włącz/wyłącz", STENCIL );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Bufor szablonowy wlacz/wylacz", STENCIL );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
GenerateDisplayLists();
glutTimerFunc( 20, Timer, 0 );
glutMainLoop();
return 0;
}
Drugi program przykładowy (plik csg.cpp) przedstawia techniki wykorzystania bufora szablonowego do konstruowania obiektów przy użyciu operacji CSG (ang. Constructive Solid Geometry), czyli konstruktywnej geometrii brył. W przestrzeni trójwymiarowej CSG polega na wykonywaniu na bryłach (lub innych obiektach geometrycznych) tzw. regularyzowanych operacji logicznych, czyli operacji logicznych w wyniku których zawsze powstaje bryła. Wykonywanie operacji GSG prześledzimy na przykładzie dwóch brył: sześcianu (rysunek 3) i kuli (rysunek 4).
Wykonanie operacji logicznej OR na dwóch obiektach jest bardzo proste i sprowadza się do ich kolejnego wyświetlenia przy włączonym buforze głębokości. Do tej operacji tej nie jest potrzebny bufor szablonowy. Efekt działania operatora OR przedstawiono na rysunku 5. Jak już Czytelnik zauważył jest to domyślny tryb rysowania prymitywów graficznych w bibliotece OpenGL.
Operator AND wymaga już znacznie większego nakładu pracy. Oto kolejno wykonywane operacje:
1. Przy włączonym buforze głębokości, ale bez zapisu składowych RGBA do bufora kolorów, rysujemy przednie strony wielokątów składających się na obiekt A.
2. Przy włączonym buforze szablonowym i wyłączonym buforze głębokości (ciągle bez zapisu danych do bufora kolorów) rysujemy przednie strony wielokątów składających się na obiekt B. Operacje na buforze szablonowym są tak ustawione, że jego zawartość jest przy rysowaniu obiektu B zwiększana o 1.
3. Następnie przy niezmienionych ustawieniach buforów głębokości i koloru rysujemy wyłącznie tylne strony wielokątów składających się na obiekt B. Jednocześnie zmieniamy operacje na buforze szablonowym tak, aby podczas rysowania zmniejszać jego zawartość o 1. W efekcie bufor szablonowy zawiera informacje o tych elementach obiektu A, które znajdują się we wnętrzu B.
4. W kolejnym kroku rysujemy te elementy obiektu A, które znajdują się we wnętrzu B. W tym celu należy odblokować zapis do bufora kolorów oraz zmienić sposób działania bufora szablonowego, tak aby zapis składowych RGBA do bufora kolorów obejmował wyłącznie wcześniej wybrane elementy obiektu A, które znajdują się we wnętrzu B.
5. Przed przystąpieniem do kolejnego etapu wykonywania operacji AND trzeba jeszcze narysować elementy obiektu B zmieniając jednak funkcję testu bufora głębokości z domyślnej
GL_LESS na
GL_ALWAYS, przy czym rysowanie odbywa się przy wyłączonym zapisie składowych RGBA do bufora kolorów oraz wyłączonym buforze szablonowym. Po zakończeniu rysowania przywracamy domyślną funkcję testu bufora głębokości.
6. Następnym etapem jest narysowanie tych elementów obiektu B, które znajdują się we wnętrzu obiektu A. Wykonywane czynności odpowiadają krokom 1-4 z oczywistą różnicą polegającą na zamianie obiektu A na obiekt B i odwrotnie.
Końcowy wynik operacji AND przedstawiono na rysunku 6. Jak łatwo zauważyć jest to część wspólna przenikającego się sześcianu i kuli.
Operacja SUB (odejmowanie) jest w ogólnym przypadku nieprzemienna, stąd różne jej efekty w zależności od kolejności odejmowania obiektów, co wyraźnie widać na rysunkach 7 i 8. Pierwszy etap odejmowania pokrywa się z krokami 1-5 operacji AND. Dalej także rysowane są te elementy obiektu B, które znajdują się we wnętrzu obiektu A, przy czym w kroku 4 zmienia się test bufora szablonowego z
GL_NOTEQUAL na
GL_EQUAL co daje właściwy efekt „odejmowania” obiektów. Zasadniczą część programu zajmującą się wydzielaniem wnętrza obiektów zawiera funkcja Inside.
Plik csg.cpp
#include <GL/glut.h>
#include <stdlib.h>
#include "colors.h"
enum
{
CSG_A,
CSG_B,
CSG_A_OR_B,
CSG_A_AND_B,
CSG_A_SUB_B,
CSG_B_SUB_A,
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
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;
GLint A, B;
int csg_op = CSG_A_OR_B;
void Inside( GLint A, GLint B, GLenum cull_face, GLenum stencil_func )
{
glEnable( GL_DEPTH_TEST );
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
glCullFace( cull_face );
glCallList( A );
glDepthMask( GL_FALSE );
glEnable( GL_STENCIL_TEST );
glStencilFunc( GL_ALWAYS, 0, 0 );
glStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
glCullFace( GL_BACK );
glCallList( B );
glStencilOp( GL_KEEP, GL_KEEP, GL_DECR );
glCullFace( GL_FRONT );
glCallList( B );
glDepthMask( GL_TRUE );
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
glStencilFunc( stencil_func, 0, 1 );
glDisable( GL_DEPTH_TEST );
glCullFace( cull_face );
glCallList( A );
glDisable( GL_STENCIL_TEST );
}
void DisplayScene()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0.0, 0.0 );
glRotatef( rotatey, 0.0, 1.0, 0.0 );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_NORMALIZE );
glEnable( GL_COLOR_MATERIAL );
glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
glEnable( GL_CULL_FACE );
if( csg_op == CSG_A )
{
glEnable( GL_DEPTH_TEST );
glCallList( A );
glDisable( GL_DEPTH_TEST );
}
if( csg_op == CSG_B )
{
glEnable( GL_DEPTH_TEST );
glCallList( B );
glDisable( GL_DEPTH_TEST );
}
if( csg_op == CSG_A_OR_B )
{
glEnable( GL_DEPTH_TEST );
glCallList( A );
glCallList( B );
glDisable( GL_DEPTH_TEST );
}
if( csg_op == CSG_A_AND_B )
{
Inside( A, B, GL_BACK, GL_NOTEQUAL );
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
glEnable( GL_DEPTH_TEST );
glDisable( GL_STENCIL_TEST );
glDepthFunc( GL_ALWAYS );
glCallList( B );
glDepthFunc( GL_LESS );
Inside( B, A, GL_BACK, GL_NOTEQUAL );
}
if( csg_op == CSG_A_SUB_B )
{
Inside( A, B, GL_FRONT, GL_NOTEQUAL );
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
glEnable( GL_DEPTH_TEST );
glDisable( GL_STENCIL_TEST );
glDepthFunc( GL_ALWAYS );
glCallList( B );
glDepthFunc( GL_LESS );
Inside( B, A, GL_BACK, GL_EQUAL );
}
if( csg_op == CSG_B_SUB_A )
{
Inside( B, A, GL_FRONT, GL_NOTEQUAL );
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
glEnable( GL_DEPTH_TEST );
glDisable( GL_STENCIL_TEST );
glDepthFunc( GL_ALWAYS );
glCallList( A );
glDepthFunc( GL_LESS );
Inside( A, B, GL_BACK, GL_EQUAL );
}
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 CSG_A:
case CSG_B:
case CSG_A_OR_B:
case CSG_A_AND_B:
case CSG_A_SUB_B:
case CSG_B_SUB_A:
csg_op = value;
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()
{
A = glGenLists( 1 );
glNewList( A, GL_COMPILE );
glColor4fv( Red );
glutSolidCube( 2.3 );
glEndList();
B = glGenLists( 1 );
glNewList( B, GL_COMPILE );
glColor4fv( Green );
glutSolidSphere( 1.5, 30, 30 );
glEndList();
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL );
glutInitWindowSize( 500, 500 );
glutCreateWindow( "CSG" );
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
int MenuCSGOp = glutCreateMenu( Menu );
glutAddMenuEntry( "A", CSG_A );
glutAddMenuEntry( "B", CSG_B );
glutAddMenuEntry( "A OR B", CSG_A_OR_B );
glutAddMenuEntry( "A AND B", CSG_A_AND_B );
glutAddMenuEntry( "A SUB B", CSG_A_SUB_B );
glutAddMenuEntry( "B SUB A", CSG_B_SUB_A );
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 );
glutAddSubMenu( "Operacja CSG", MenuCSGOp );
#ifdef WIN32
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
GenerateDisplayLists();
glutMainLoop();
return 0;
}
Trzeci program przykładowy (plik krawedzie.cpp) przestawia sposób uzyskania przy użyciu bufora szablonowego krawędzi szkieletowych (ang. silhouette edge) rysowanych obiektów sceny. Efekt jest bardzo łatwy do usykania, ale niestety wymaga czterokrotnego narysowania obiektów sceny przy czym za każdym razem obiekty muszą być przesunięte o jeden piksel odpowiednio w lewo, w prawo, w górę i w dół. W ten sposób w buforze szblonowym znajdą się informacje o „zarysach” obiektów. Następne - piąte - rysowanie obiektów sceny następuje już przy ich normalnym położeniu i służy do usunięcia z bufora szablonowego tej części informacji, która nie jest związana z krawędziami obiektów. Aby narysowanć tak uzyskaną krawędź wystarczy już zwykły prostokąt obejmujący całe okno renderingu i odpowiednio ustawiony test bufora szablonowego. Całość opisywanej operacji znajduje się w funkcji Silhouette.
Efekty działania programu przedstawiają rysunki 9 i 10.
Plik krawedzie.cpp
#include <GL/glut.h>
#include <stdlib.h>
#include <math.h>
#include "colors.h"
enum
{
TORUS_OBJ,
CUBE_OBJ,
OBJECT,
SILHOUETTE,
SILHOUETTE_OBJECT,
EXIT
};
const GLint left = - 5;
const GLint right = 5;
const GLint bottom = - 5;
const GLint top = 5;
const GLint near = - 5;
const GLint far = 5;
GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
GLint TORUS_ID, CUBE_ID;
GLint CURRENT_OBJECT;
int render_mode = SILHOUETTE_OBJECT;
void Silhouette()
{
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
glEnable( GL_STENCIL_TEST );
glStencilFunc( GL_ALWAYS, 1, 1 );
glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
glDisable( GL_DEPTH_TEST );
glDisable( GL_LIGHTING );
int width = glutGet( GLUT_WINDOW_WIDTH );
int height = glutGet( GLUT_WINDOW_HEIGHT );
glViewport( - 1, 0, width - 1, height );
glCallList( CURRENT_OBJECT );
glViewport( 1, 0, width + 1, height );
glCallList( CURRENT_OBJECT );
glViewport( 0, - 1, width, height - 1 );
glCallList( CURRENT_OBJECT );
glViewport( 0, 1, width, height + 1 );
glCallList( CURRENT_OBJECT );
glViewport( 0, 0, width, height );
glStencilFunc( GL_ALWAYS, 0, 0 );
glCallList( CURRENT_OBJECT );
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
glStencilFunc( GL_EQUAL, 1, 1 );
glPushMatrix();
glLoadIdentity();
glColor3fv( Yellow );
glRecti( left, bottom, right, top );
glPopMatrix();
glEnable( GL_DEPTH_TEST );
glEnable( GL_LIGHTING );
glDisable( GL_STENCIL_TEST );
}
void DisplayScene()
{
glClearColor( 0.0, 0.0, 1.0, 1.0 );
if( render_mode == OBJECT )
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
else
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glRotatef( rotatex, 1.0, 0.0, 0.0 );
glRotatef( rotatey, 0.0, 1.0, 0.0 );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_NORMALIZE );
glEnable( GL_COLOR_MATERIAL );
glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
glEnable( GL_DEPTH_TEST );
switch( render_mode )
{
case SILHOUETTE:
Silhouette();
break;
case SILHOUETTE_OBJECT:
Silhouette();
glCallList( CURRENT_OBJECT );
break;
case OBJECT:
glCallList( CURRENT_OBJECT );
break;
}
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
glOrtho( 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 += 50 *( right - left ) /( float ) glutGet( GLUT_WINDOW_WIDTH ) *( x - button_x );
button_x = x;
rotatex -= 50 *( top - bottom ) /( float ) glutGet( GLUT_WINDOW_HEIGHT ) *( button_y - y );
button_y = y;
glutPostRedisplay();
}
}
void Menu( int value )
{
switch( value )
{
case TORUS_OBJ:
CURRENT_OBJECT = TORUS_ID;
DisplayScene();
break;
case CUBE_OBJ:
CURRENT_OBJECT = CUBE_ID;
DisplayScene();
break;
case OBJECT:
case SILHOUETTE:
case SILHOUETTE_OBJECT:
render_mode = value;
DisplayScene();
break;
case EXIT:
exit( 0 );
}
}
void GenerateDisplayLists()
{
TORUS_ID = glGenLists( 1 );
glNewList( TORUS_ID, GL_COMPILE );
glColor4fv( Red );
glutSolidTorus( 1.0, 2.0, 20, 20 );
glEndList();
CUBE_ID = glGenLists( 1 );
glNewList( CUBE_ID, GL_COMPILE );
glColor4fv( Green );
glutSolidCube( 4.0 );
glEndList();
CURRENT_OBJECT = TORUS_ID;
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Krawędzie" );
#else
glutCreateWindow( "Krawedzie" );
#endif
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
int MenuObject = glutCreateMenu( Menu );
glutAddMenuEntry( "Torus", TORUS_OBJ );
#ifdef WIN32
glutAddMenuEntry( "Sześcian", CUBE_OBJ );
#else
glutAddMenuEntry( "Szescian", CUBE_OBJ );
#endif
int MenuRenderMode = glutCreateMenu( Menu );
glutAddMenuEntry( "Tylko obiekt", OBJECT );
#ifdef WIN32
glutAddMenuEntry( "Tylko krawędzie szkieletowe", SILHOUETTE );
glutAddMenuEntry( "Obiekt i krawędzie szkieletowe", SILHOUETTE_OBJECT );
#else
glutAddMenuEntry( "Tylko krawedzie szkieletowe", SILHOUETTE );
glutAddMenuEntry( "Obiekt i krawedzie szkieletowe", SILHOUETTE_OBJECT );
#endif
glutCreateMenu( Menu );
glutAddSubMenu( "Obiekt", MenuObject );
glutAddSubMenu( "Tryb rysowania", MenuRenderMode );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
GenerateDisplayLists();
glutMainLoop();
return 0;
}
Ostatni program przykładowy (plik przejscie_obrazow.cpp) przedstawia sposób uzyskania przy użyciu bufora szablonowego efektu przejścia pomiędzy obrazami (ang. dissolving). Efekt jest bardzo prosty do uzyskania, i daje bardzo ciekawe rezultaty. Przykładowy widok okna programu przedstawia rysunek 11.
Plik przejscie_obrazow.cpp
#include <GL/glut.h>
#include <GL/glext.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "targa.h"
enum
{
CLEAR_STENCIL,
EXIT
};
int button_state = GLUT_UP;
int button_x, button_y;
const int eraser_width = 80;
const int eraser_height = 80;
GLubyte eraser_pixmap[ 4 * eraser_width * eraser_height ];
GLsizei width;
GLsizei height;
GLenum format;
GLenum type;
GLvoid * pixels;
void LoadTARGA( int argc, char * argv[] )
{
if( argc < 2 )
{
printf( "Brak nazwy pliku TARGA\n" );
exit( 1 );
}
if( !load_targa( argv[ 1 ], width, height, format, type, pixels ) )
{
#ifdef WIN32
printf( "Błąd odczytu lub błędny format pliku: %s\n", argv[ 1 ] );
#else
printf( "Blad odczytu lub bledny format pliku: %s\n", argv[ 1 ] );
#endif
exit( 1 );
}
}
void CheckImageFormat()
{
if( format == GL_BGR || format == GL_BGRA )
{
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( 1 );
}
if( major <= 1 && minor < 2 )
{
if( !glutExtensionSupported( "GL_EXT_bgra" ) )
{
printf( "Brak rozszerzenia GL_EXT_bgra\n" );
exit( 1 );
}
}
}
}
void GenerateEraser()
{
memset( eraser_pixmap, 196, 4 * eraser_width * eraser_height );
}
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClearStencil( ~0 );
glClear( GL_COLOR_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glEnable( GL_STENCIL_TEST );
glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
glStencilFunc( GL_EQUAL, 1, ~0 );
glRasterPos2i( 0, 0 );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glPixelZoom(( float ) glutGet( GLUT_WINDOW_WIDTH ) /( float ) width,
( float ) glutGet( GLUT_WINDOW_HEIGHT ) /( float ) height );
glDrawPixels( width, height, format, type, pixels );
glPixelZoom( 1.0, 1.0 );
if( button_state == GLUT_DOWN )
{
glStencilFunc( GL_ALWAYS, 1, 0 );
glStencilOp( GL_KEEP, GL_REPLACE, GL_REPLACE );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glRasterPos2i( button_x, button_y );
glBitmap( 0, 0, 0.0, 0.0, - eraser_width / 2.0, - eraser_height / 2.0, NULL );
glDrawPixels( eraser_width, eraser_height, GL_RGBA, GL_UNSIGNED_BYTE, eraser_pixmap );
glDisable( GL_BLEND );
}
glutSwapBuffers();
}
void Reshape( int Width, int Height )
{
glViewport( 0, 0, Width, Height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
gluOrtho2D( 0.0, Width, 0.0, Height );
glClear( GL_STENCIL_BUFFER_BIT );
Display();
}
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 )
{
button_x = x;
button_y = glutGet( GLUT_WINDOW_HEIGHT ) - y;
glutPostRedisplay();
}
}
void Menu( int value )
{
switch( value )
{
case CLEAR_STENCIL:
glClear( GL_STENCIL_BUFFER_BIT );
glutPostRedisplay();
break;
case EXIT:
exit( 0 );
break;
}
}
main( int argc, char * argv[] )
{
LoadTARGA( argc, argv );
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_STENCIL );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Przejście obrazów" );
#else
glutCreateWindow( "Przejscie obrazow" );
#endif
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
CheckImageFormat();
glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Wyczyść bufor szablonowy", CLEAR_STENCIL );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyczysc bufor szablonowy", CLEAR_STENCIL );
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
GenerateEraser();
glutMainLoop();
return 0;
}