Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Janusz Ganczarski
Biblioteki C++

Bufor szablonowy

[lekcja] Rozdział 17. Budowa i działanie bufora szablonowego; test bufora szablonowego; czyszczenie bufora szablonowego; rozłączny bufor szablonowy; cztery programy przykładowe, w tym odbicie i CSG (ang. Constructive Solid Geometry).
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:
C/C++
void glClearStencil( GLint s )

Sterowanie buforem szablonowym

Działanie bufora szablonowego reguluje kilka funkcji. Podstawową z nich jest funkcja:
C/C++
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:
  • GL_NEVER - wartość testu zawsze negatywna,
  • GL_LESS - wartość testu pozytywna jeżeli parametr ref jest mniejszy od wartości znajdującej się w buforze,
  • GL_LEQUAL wartość testu pozytywna jeżeli parametr ref jest mniejszy lub równy wartości znajdującej się w buforze,
  • GL_GREATER - wartość testu pozytywna jeżeli parametr ref jest większy od wartości znajdującej się w buforze,
  • GL_GEQUAL - wartość testu pozytywna jeżeli parametr ref jest większy lub równy wartości znajdującej się w buforze,
  • GL_EQUAL - wartość testu pozytywna jeżeli parametr ref jest równy wartości znajdującej się w buforze,
  • GL_NOTEQUAL - wartość testu pozytywna jeżeli parametr ref jest różny od wartości znajdującej się w buforze,
  • GL_ALWAYS - wartość testu zawsze pozytywna.
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:
C/C++
void glStencilMask( uint mask )
Trzecią funkcją sterującą działaniem bufora szablonowego jest:
C/C++
void glStencilOp( GLenum sfail, GLenum dpfail, GLenum dppass )
której parametry określają jaka operacja na buforze szablonowym wykonywana jest w przypadku:
  • sfail - negatywnego wyniku testu szablonu,
  • dpfali - pozytywnego wyniku testu szablonu przy negatywnym wyniku testu bufora głębokości,
  • dppass - pozytywnego wyniku testów bufora szablonowego i bufora głębokości (także, gdy bufor głębokości jest wyłączony).
Każdy z parametrów sfail, dpfail i dppass może niezależnie przyjąć jedną z poniższych wartości:
  • GL_KEEP - wartość bufora szablonowego nie jest zmieniana,
  • GL_ZERO - wartość bufora szablonowego jest zerowana,
  • GL_REPLACE - wartość bufora szablonowego jest zamieniana wartością referencyjną określoną w parametrze ref funkcji glStencilFunc,
  • GL_INCR - wartość bufora szablonowego jest zwiększana o 1; w przypadku wystąpienia nadmiaru rezultat przyjmuje maksymalną wartość obsługiwaną przez bufor szablonowy,
  • GL_DECR - wartość bufora szablonowego jest zmniejszana o 1; w przypadku wystąpienia niedomiaru rezultat przyjmuje wartość 0,
  • GL_INVERT - wartość bufora szablonowego jest negowana,
  • GL_DECR_WRAP - wartość bufora szablonowego jest zmniejszana o 1; w przypadku wystąpienia niedomiaru rezultat przyjmuje maksymalną wartość obsługiwaną przez bufor szablonowy,
  • GL_INCR_WRAP - wartość bufora szablonowego jest zwiększana o 1; w przypadku wystąpienia nadmiaru rezultat przyjmuje wartość 0.
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:
C/C++
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:
C/C++
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ą):
C/C++
glStencilFunc( GL_ALWAYS, 0x00000001, 0xFFFFFFFF );
glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE );
Rysunek 1. Program Odbicie - bufor szablonowy wyłączony
Rysunek 1. Program Odbicie - bufor szablonowy wyłączony
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:
C/C++
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.
Rysunek 2. Program Odbicie - bufor szablonowy włączony
Rysunek 2. Program Odbicie - bufor szablonowy włączony
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:
C/C++
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

C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG@enter.net.pl
*/

#include <GL/glut.h>
#include <stdlib.h>
#include "colors.h"

// stałe do obsługi menu podręcznego

enum
{
    STENCIL, // bufor szablonowy włącz/wyłącz
    FULL_WINDOW, // aspekt obrazu - całe okno
    ASPECT_1_1, // aspekt obrazu 1:1
    EXIT // wyjście
};

// aspekt obrazu

int aspect = FULL_WINDOW;

// rozmiary bryły obcinania

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;

// kąty obrotu sceny

GLfloat rotatex = 30.0;
GLfloat rotatey = 0.0;

// kąt obrotu obiektu

GLfloat angle = 0.0;

// wskaźnik naciśnięcia lewego przycisku myszki

int button_state = GLUT_UP;

// położenie kursora myszki

int button_x, button_y;

// identyfikatory list wyświetlania

GLint GROUND_LIST;
GLint WORLD_LIST;

// położenie źródła światła

GLfloat light_position[ 4 ] =
{
    0.0, 10.0, 10.0, 1.0
};

// użycie bufora szablonowego

bool stencil_test = true;

// funkcja generująca scenę 3D

void DisplayScene()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru, bufora głębokości i opcjonalnie bufora szablonowego
    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 );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // włączenie testu bufora głębokości
    glEnable( GL_DEPTH_TEST );
   
    // włączenie oświetlenia
    glEnable( GL_LIGHTING );
   
    // włączenie światła GL_LIGHT0
    glEnable( GL_LIGHT0 );
   
    // włączenie automatycznej normalizacji wektorów normalnych
    glEnable( GL_NORMALIZE );
   
    // włączenie obsługi właściwości materiałów
    glEnable( GL_COLOR_MATERIAL );
   
    // właściwości materiału określone przez kolor wierzchołków
    glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
   
    // włączenie nierysowania tylnej strony wielokątów
    glEnable( GL_CULL_FACE );
   
    // przesunięcie układu współrzędnych obiektów do środka bryły odcinania
    glTranslatef( 0, 0, -( near + far ) / 2 );
   
    // obroty całej sceny
    glRotatef( rotatex, 1.0, 0.0, 0.0 );
    glRotatef( rotatey, 0.0, 1.0, 0.0 );
   
    // przy włączonym buforze szblonowym rysujemy "podłogę" w celu utworzenia
    // szablonu, który zostanie wykorzystany do narysowania obiektów odbitych
    if( stencil_test == true )
    {
        // wyłączenie bufora głębokości
        glDisable( GL_DEPTH_TEST );
       
        // włączenie bufora szablonowego
        glEnable( GL_STENCIL_TEST );
       
        // test bufora szablonowego
        glStencilFunc( GL_ALWAYS, 0x00000001, 0xFFFFFFFF );
       
        // określenie operacji na buforze szablonowym
        glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE );
       
        // wyłączenie zapisu składowych RGBA do bufora kolorów
        glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
       
        // wyświetlenie płaszczyzny odbicia
        glCallList( GROUND_LIST );
       
        // włączenie zapisu składowych RGBA do bufora kolorów
        glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
       
        // włączenie testu bufora głębokości
        glEnable( GL_DEPTH_TEST );
       
        // test bufora szablonowego
        glStencilFunc( GL_EQUAL, 0x00000001, 0xFFFFFFFF );
       
        // określenie operacji na buforze szablonowym
        glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
    }
   
    // odłożenie macierzy modelowania na stos
    glPushMatrix();
   
    // skalowanie macierzy modelowania w celu narysownaia obiektów świata "lustrzanego"
    glScalef( 1.0, - 1.0, 1.0 );
   
    // położenie "lustrzanego" źródła światła
    glLightfv( GL_LIGHT0, GL_POSITION, light_position );
   
    // przesunięcie obiektów świata "lustrzanego"
    glTranslatef( 0.0, 1.2, 0.0 );
   
    // obroty obiektów świata "lustrzanego"
    glRotatef( angle, 1.0, 0.0, 0.0 );
    glRotatef( angle, 0.0, 2.0, 0.0 );
    glRotatef( angle, 0.0, 0.0, - 1.0 );
   
    // w świecie lustrzanym trzeba zmienić orientację wierzchołków prymitywów
    glFrontFace( GL_CW );
   
    // wyświetlenie obiektów świata "lustrzanego"
    glCallList( WORLD_LIST );
   
    // powrót do normalnej orientacji wierzchołków prymitywów
    glFrontFace( GL_CCW );
   
    // zdjęcie macierzy modelowania ze stosu
    glPopMatrix();
   
    // koniec rysowania obiektów świata "lustrznaego" to także
    // koniec korzystania z bufora szablonowego
    if( stencil_test == true )
    {
        // wyłączenie bufora szablonowego
        glDisable( GL_STENCIL_TEST );
    }
   
    // wyłączenie oświetlenia
    glDisable( GL_LIGHTING );
   
    // włączenie mieszania kolorów
    glEnable( GL_BLEND );
   
    // współczynniki równania mieszania kolorów
    glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
   
    // wyświetlenie płaszczyzny odbicia
    glCallList( GROUND_LIST );
   
    // wyłączenie mieszania kolorów
    glDisable( GL_BLEND );
   
    // włączenie oświetlenia
    glEnable( GL_LIGHTING );
   
    // położenie źródła światła
    glLightfv( GL_LIGHT0, GL_POSITION, light_position );
   
    // przesunięcie obiektów świata
    glTranslatef( 0.0, 1.2, 0.0 );
   
    // obroty obiektów świata
    glRotatef( angle, 1.0, 0.0, 0.0 );
    glRotatef( angle, 0.0, 2.0, 0.0 );
    glRotatef( angle, 0.0, 0.0, - 1.0 );
   
    // wyświetlenie obiektów świata
    glCallList( WORLD_LIST );
   
    // skierowanie poleceń do wykonania
    glFlush();
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// funkcja timera

void Timer( int value )
{
    // zwiększenie kąta obrotu obiektu sceny
    angle++;
   
    // wyświetlenie sceny
    DisplayScene();
   
    // następne wywołanie funkcji timera
    glutTimerFunc( 20, Timer, 0 );
}

// zmiana wielkości okna

void Reshape( int width, int height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, width, height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // parametry bryły obcinania
    if( aspect == ASPECT_1_1 )
    {
        // wysokość okna większa od wysokości okna
        if( width < height && width > 0 )
             glFrustum( left, right, bottom * height / width, top * height / width, near, far );
        else
       
        // szerokość okna większa lub równa wysokości okna
        if( width >= height && height > 0 )
             glFrustum( left * width / height, right * width / height, bottom, top, near, far );
       
    }
    else
         glFrustum( left, right, bottom, top, near, far );
   
    // generowanie sceny 3D
    DisplayScene();
}

// obsługa przycisków myszki

void MouseButton( int button, int state, int x, int y )
{
    if( button == GLUT_LEFT_BUTTON )
    {
        // zapamiętanie stanu lewego przycisku myszki
        button_state = state;
       
        // zapamiętanie położenia kursora myszki
        if( state == GLUT_DOWN )
        {
            button_x = x;
            button_y = y;
        }
    }
}

// obsługa ruchu kursora myszki

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();
    }
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // bufor szablonowy włącz/wyłącz
    case STENCIL:
        stencil_test = !stencil_test;
        DisplayScene();
        break;
       
        // obszar renderingu - całe okno
    case FULL_WINDOW:
        aspect = FULL_WINDOW;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // obszar renderingu - aspekt 1:1
    case ASPECT_1_1:
        aspect = ASPECT_1_1;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // wyjście
    case EXIT:
        exit( 0 );
    }
}

// utworzenie list wyświetlania

void GenerateDisplayLists()
{
    // generowanie identyfikatora pierwszej listy wyświetlania
    GROUND_LIST = glGenLists( 1 );
   
    // pierwsza lista wyświetlania - podłoga
    glNewList( GROUND_LIST, GL_COMPILE );
   
    // "podłoga" rysowana jest jako szachownica o rozmiarach
    // 4 x 4 jednostki i zawiera się w płaszczyźnie y = 0
    glBegin( GL_QUADS );
    glNormal3f( 1.0, 0.0, 0.0 );
   
    // przy każdej iteracji pętli rysowane są cztery prostokąty szachownicy
    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();
   
    // koniec pierwszej listy wyświetlania
    glEndList();
   
    // generowanie identyfikatora drugiej listy wyświetlania
    WORLD_LIST = glGenLists( 1 );
   
    // druga lista wyświetlania - obiekt "świata"
    glNewList( WORLD_LIST, GL_COMPILE );
   
    // zielony torus
    glColor4fv( Green );
    glutSolidTorus( 0.3, 0.7, 50, 40 );
   
    // koniec drugiej listy wyświetlania
    glEndList();
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 500, 500 );
   
    // utworzenie głównego okna programu
    glutCreateWindow( "Odbicie" );
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( DisplayScene );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // obsługa przycisków myszki
    glutMouseFunc( MouseButton );
   
    // obsługa ruchu kursora myszki
    glutMotionFunc( MouseMotion );
   
    // utworzenie podmenu - Aspekt obrazu
    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 );
   
    // menu główne
    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
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // utworzenie list wyświetlania
    GenerateDisplayLists();
   
    // wywołanie funkcji timera
    glutTimerFunc( 20, Timer, 0 );
   
    // wprowadzenie programu do obsługi pętli komunikatów
    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).
Rysunek 3. Program CSG - obiekt A
Rysunek 3. Program CSG - obiekt A
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.
Rysunek 4. Program CSG - obiekt B
Rysunek 4. Program CSG - obiekt B
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.
Rysunek 5. Program CSG - A OR B
Rysunek 5. Program CSG - A OR 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.
Rysunek 6. Program CSG - A AND B
Rysunek 6. Program CSG - A AND B
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.
Rysunek 7. Program CSG - A SUB B
Rysunek 7. Program CSG - A SUB B

Plik csg.cpp

Rysunek 8. Program CSG - B SUB A
Rysunek 8. Program CSG - B SUB A
C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG@enter.net.pl
*/

#include <GL/glut.h>
#include <stdlib.h>
#include "colors.h"

// stałe do obsługi menu podręcznego

enum
{
    CSG_A, // tylko obiekt A
    CSG_B, // tylko obiekt A
    CSG_A_OR_B, // A OR B
    CSG_A_AND_B, // A AND B
    CSG_A_SUB_B, // A SUB B
    CSG_B_SUB_A, // B SUB A
    FULL_WINDOW, // aspekt obrazu - całe okno
    ASPECT_1_1, // aspekt obrazu 1:1
    EXIT // wyjście
};

// aspekt obrazu

int aspect = FULL_WINDOW;

// rozmiary bryły obcinania

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;

// kąty obrotu sceny

GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;

// wskaźnik naciśnięcia lewego przycisku myszki

int button_state = GLUT_UP;

// położenie kursora myszki

int button_x, button_y;

// identyfikatory list wyświetlania

GLint A, B;

// rodzaj operacji CSG

int csg_op = CSG_A_OR_B;

// ustawienie bufora szablonowego tak, aby wydzielić i wyświetlić
// te elementy obiektu A, które znajdują się we wnętrzu obiektu B;
// stronę (przednią lub tylną) wyszukiwanych elementów obiektu A
// określa parametr cull_face

void Inside( GLint A, GLint B, GLenum cull_face, GLenum stencil_func )
{
    // początkowo rysujemy obiekt A w buforze głębokości przy
    // wyłączonym zapisie składowych RGBA do bufora kolorów
   
    // włączenie testu bufora głębokości
    glEnable( GL_DEPTH_TEST );
   
    // wyłączenie zapisu składowych RGBA do bufora kolorów
    glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
   
    // rysowanie wybranej strony wielokątów
    glCullFace( cull_face );
   
    // wyświetlenie obiektu A
    glCallList( A );
   
    // następnie przy użyciu bufora szablonowego wykrywamy te elementy
    // obiektu A, które znajdują się wewnątrz obiektu B; w tym celu
    // zawartość bufora szablonowego jest zwiększana o 1, wszędzie gdzie
    // będą przednie strony wielokątów składających się na obiekt B
   
    // wyłączenie zapisu do bufora głębokości
    glDepthMask( GL_FALSE );
   
    // włączenie bufora szablonowego
    glEnable( GL_STENCIL_TEST );
   
    // test bufora szablonowego
    glStencilFunc( GL_ALWAYS, 0, 0 );
   
    // określenie operacji na buforze szablonowym
    glStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
   
    // rysowanie tylko przedniej strony wielokątów
    glCullFace( GL_BACK );
   
    // wyświetlenie obiektu B
    glCallList( B );
   
    // w kolejnym etapie zmniejszamy zawartość bufora szablonowego o 1
    // wszędzie tam, gdzie są tylne strony wielokątów obiektu B
   
    // określenie operacji na buforze szablonowym
    glStencilOp( GL_KEEP, GL_KEEP, GL_DECR );
   
    // rysowanie tylko tylnej strony wielokątów
    glCullFace( GL_FRONT );
   
    // wyświetlenie obiektu B
    glCallList( B );
   
    // dalej wyświetlamy te elementy obiektu A, które
    // znajdują się we wnętrzu obiektu B
   
    // włączenie zapisu do bufora głębokości
    glDepthMask( GL_TRUE );
   
    // włączenie zapisu składowych RGBA do bufora kolorów
    glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
   
    // test bufora szablonowego
    glStencilFunc( stencil_func, 0, 1 );
   
    // wyłączenie testu bufora głębokości
    glDisable( GL_DEPTH_TEST );
   
    // rysowanie wybranej strony wielokątów
    glCullFace( cull_face );
   
    // wyświetlenie obiektu A
    glCallList( A );
   
    // wyłączenie bufora szablonowego
    glDisable( GL_STENCIL_TEST );
}

// funkcja generująca scenę 3D

void DisplayScene()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru, bufora głębokości i bufora szablonowego
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // przesunięcie układu współrzędnych obiektów do środka bryły odcinania
    glTranslatef( 0, 0, -( near + far ) / 2 );
   
    // obroty całej sceny
    glRotatef( rotatex, 1.0, 0.0, 0.0 );
    glRotatef( rotatey, 0.0, 1.0, 0.0 );
   
    // włączenie oświetlenia
    glEnable( GL_LIGHTING );
   
    // włączenie światła GL_LIGHT0
    glEnable( GL_LIGHT0 );
   
    // włączenie automatycznej normalizacji wektorów normalnych
    glEnable( GL_NORMALIZE );
   
    // włączenie obsługi właściwości materiałów
    glEnable( GL_COLOR_MATERIAL );
   
    // właściwości materiału określone przez kolor wierzchołków
    glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
   
    // włączenie rysowania wybranej strony wielokątów
    glEnable( GL_CULL_FACE );
   
    // operacja CSG - tylko obiekt A
    if( csg_op == CSG_A )
    {
        // włączenie testu bufora głębokości
        glEnable( GL_DEPTH_TEST );
       
        // wyświetlenie obiektu A
        glCallList( A );
       
        // wyłączenie testu bufora głębokości
        glDisable( GL_DEPTH_TEST );
    }
   
    // operacja CSG - tylko obiekt B
    if( csg_op == CSG_B )
    {
        // włączenie testu bufora głębokości
        glEnable( GL_DEPTH_TEST );
       
        // wyświetlenie obiektu B
        glCallList( B );
       
        // wyłączenie testu bufora głębokości
        glDisable( GL_DEPTH_TEST );
    }
   
    // operacja CSG A lub B
    if( csg_op == CSG_A_OR_B )
    {
        // włączenie testu bufora głębokości
        glEnable( GL_DEPTH_TEST );
       
        // wyświetlenie obiektu A i B
        glCallList( A );
        glCallList( B );
       
        // wyłączenie testu bufora głębokości
        glDisable( GL_DEPTH_TEST );
    }
   
    // operacja CSG A AND B
    if( csg_op == CSG_A_AND_B )
    {
        // elementy obiektu A znajdujące się we wnętrzu B
        Inside( A, B, GL_BACK, GL_NOTEQUAL );
       
        // wyłączenie zapisu składowych RGBA do bufora kolorów
        glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
       
        // włączenie testu bufora głębokości
        glEnable( GL_DEPTH_TEST );
       
        // wyłączenie bufora szablonowego
        glDisable( GL_STENCIL_TEST );
       
        // wybór funkcji do testu bufora głębokości
        glDepthFunc( GL_ALWAYS );
       
        // wyświetlenie obiektu B
        glCallList( B );
       
        // wybór funkcji do testu bufora głębokości
        glDepthFunc( GL_LESS );
       
        // elementy obiektu B znajdujące się we wnętrzu A
        Inside( B, A, GL_BACK, GL_NOTEQUAL );
    }
   
    // operacja CSG A SUB B
    if( csg_op == CSG_A_SUB_B )
    {
        // elementy obiektu A znajdujące się we wnętrzu B
        Inside( A, B, GL_FRONT, GL_NOTEQUAL );
       
        // wyłączenie zapisu składowych RGBA do bufora kolorów
        glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
       
        // włączenie testu bufora głębokości
        glEnable( GL_DEPTH_TEST );
       
        // wyłączenie bufora szablonowego
        glDisable( GL_STENCIL_TEST );
       
        // wybór funkcji do testu bufora głębokości
        glDepthFunc( GL_ALWAYS );
       
        // wyświetlenie obiektu B
        glCallList( B );
       
        // wybór funkcji do testu bufora głębokości
        glDepthFunc( GL_LESS );
       
        // elementy obiektu B znajdujące się we wnętrzu A
        Inside( B, A, GL_BACK, GL_EQUAL );
    }
   
    // operacja CSG B SUB A
    if( csg_op == CSG_B_SUB_A )
    {
        // elementy obiektu B znajdujące się we wnętrzu A
        Inside( B, A, GL_FRONT, GL_NOTEQUAL );
       
        // wyłączenie zapisu składowych RGBA do bufora kolorów
        glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
       
        // włączenie testu bufora głębokości
        glEnable( GL_DEPTH_TEST );
       
        // wyłączenie bufora szablonowego
        glDisable( GL_STENCIL_TEST );
       
        // wybór funkcji do testu bufora głębokości
        glDepthFunc( GL_ALWAYS );
       
        // wyświetlenie obiektu A
        glCallList( A );
       
        // wybór funkcji do testu bufora głębokości
        glDepthFunc( GL_LESS );
       
        // elementy obiektu A znajdujące się we wnętrzu B
        Inside( A, B, GL_BACK, GL_EQUAL );
    }
   
    // skierowanie poleceń do wykonania
    glFlush();
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// zmiana wielkości okna

void Reshape( int width, int height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, width, height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // parametry bryły obcinania
    if( aspect == ASPECT_1_1 )
    {
        // wysokość okna większa od wysokości okna
        if( width < height && width > 0 )
             glFrustum( left, right, bottom * height / width, top * height / width, near, far );
        else
       
        // szerokość okna większa lub równa wysokości okna
        if( width >= height && height > 0 )
             glFrustum( left * width / height, right * width / height, bottom, top, near, far );
       
    }
    else
         glFrustum( left, right, bottom, top, near, far );
   
    // generowanie sceny 3D
    DisplayScene();
}

// obsługa przycisków myszki

void MouseButton( int button, int state, int x, int y )
{
    if( button == GLUT_LEFT_BUTTON )
    {
        // zapamiętanie stanu lewego przycisku myszki
        button_state = state;
       
        // zapamiętanie położenia kursora myszki
        if( state == GLUT_DOWN )
        {
            button_x = x;
            button_y = y;
        }
    }
}

// obsługa ruchu kursora myszki

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();
    }
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // operacja CSG
    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;
       
        // obszar renderingu - całe okno
    case FULL_WINDOW:
        aspect = FULL_WINDOW;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // obszar renderingu - aspekt 1:1
    case ASPECT_1_1:
        aspect = ASPECT_1_1;
        Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
        break;
       
        // wyjście
    case EXIT:
        exit( 0 );
    }
}

// utworzenie list wyświetlania

void GenerateDisplayLists()
{
    // generowanie identyfikatora pierwszej listy wyświetlania
    A = glGenLists( 1 );
   
    // pierwsza lista wyświetlania
    glNewList( A, GL_COMPILE );
   
    // czerwony sześcian
    glColor4fv( Red );
    glutSolidCube( 2.3 );
   
    // koniec pierwszej listy wyświetlania
    glEndList();
   
    // generowanie identyfikatora drugiej listy wyświetlania
    B = glGenLists( 1 );
   
    // druga lista wyświetlania
    glNewList( B, GL_COMPILE );
   
    // zielona kula
    glColor4fv( Green );
    glutSolidSphere( 1.5, 30, 30 );
   
    // koniec drugiej listy wyświetlania
    glEndList();
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 500, 500 );
   
    // utworzenie głównego okna programu
    glutCreateWindow( "CSG" );
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( DisplayScene );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // obsługa przycisków myszki
    glutMouseFunc( MouseButton );
   
    // obsługa ruchu kursora myszki
    glutMotionFunc( MouseMotion );
   
    // utworzenie podmenu - Operacja CSG
    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 );
   
    // utworzenie podmenu - Aspekt obrazu
    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 );
   
    // menu główne
    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
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // utworzenie list wyświetlania
    GenerateDisplayLists();
   
    // wprowadzenie programu do obsługi pętli komunikatów
    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.
Rysunek 9. Program Krawędzie - obiekt z krawędzią
Rysunek 9. Program Krawędzie - obiekt z krawędzią

Plik krawedzie.cpp

Rysunek 10. Program Krawędzie - zarys obiektu
Rysunek 10. Program Krawędzie - zarys obiektu
C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG@enter.net.pl
*/

#include <GL/glut.h>
#include <stdlib.h>
#include <math.h>
#include "colors.h"

// stałe do obsługi menu podręcznego

enum
{
    TORUS_OBJ, // torus
    CUBE_OBJ, // sześcian
    OBJECT, // rysowanie tylko obiektu
    SILHOUETTE, // rysowanie tylko krawędzi szkieletowych
    SILHOUETTE_OBJECT, // rysowanie obiektu i krawędzi szkieletowych
    EXIT // wyjście
};

// rozmiary bryły obcinania

const GLint left = - 5;
const GLint right = 5;
const GLint bottom = - 5;
const GLint top = 5;
const GLint near = - 5;
const GLint far = 5;

// kąty obrotu sceny

GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;

// wskaźnik naciśnięcia lewego przycisku myszki

int button_state = GLUT_UP;

// położenie kursora myszki

int button_x, button_y;

// identyfikatory list wyświetlania

GLint TORUS_ID, CUBE_ID;

// identyfikator bieżącej listy wyświetlania

GLint CURRENT_OBJECT;

// tryb rysowania

int render_mode = SILHOUETTE_OBJECT;

// rysowanie krawędzi szkieletowych

void Silhouette()
{
    // wyłączenie zapisu składowych RGBA do bufora kolorów
    glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
   
    // włączenie bufora szablonowego
    glEnable( GL_STENCIL_TEST );
   
    // test bufora szablonowego
    glStencilFunc( GL_ALWAYS, 1, 1 );
   
    // określenie operacji na buforze szablonowym
    glStencilOp( GL_KEEP, GL_KEEP, GL_REPLACE );
   
    // wyłączenie bufora głębokości
    glDisable( GL_DEPTH_TEST );
   
    // wyłącznie oświetlenia
    glDisable( GL_LIGHTING );
   
    // pobranie rozmiarów okna renderingu
    int width = glutGet( GLUT_WINDOW_WIDTH );
    int height = glutGet( GLUT_WINDOW_HEIGHT );
   
    // czterokrotne narysowanie obiektu przesuniętego
    // o jeden piksel w lewo, prawo,dół i górę
    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 );
   
    // powrót do podstawowego obszaru renderingu
    glViewport( 0, 0, width, height );
   
    // test bufora szablonowego
    glStencilFunc( GL_ALWAYS, 0, 0 );
   
    // narysowanie bieżącego obiektu
    glCallList( CURRENT_OBJECT );
   
    // włączenie zapisu składowych RGBA do bufora kolorów
    glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
   
    // test bufora szablonowego
    glStencilFunc( GL_EQUAL, 1, 1 );
   
    // odłożenie macierzy modelowania na stos
    glPushMatrix();
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // kolor krawędzi szkieletowych
    glColor3fv( Yellow );
   
    // narysowanie prostokąta - faktycznie krawędzi szkieletowych
    glRecti( left, bottom, right, top );
   
    // zdjęcie macierzy modelowania ze stosu
    glPopMatrix();
   
    // włączenie bufora głębokości
    glEnable( GL_DEPTH_TEST );
   
    // włączenie oświetlenia
    glEnable( GL_LIGHTING );
   
    // wyłączenie bufora szablonowego
    glDisable( GL_STENCIL_TEST );
}

// funkcja generująca scenę 3D

void DisplayScene()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 0.0, 0.0, 1.0, 1.0 );
   
    // w zależności od trybu rysowania czyszczenie bufora koloru, bufora
    // głębokości i bufora szablonowego
    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 );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // obroty całej sceny
    glRotatef( rotatex, 1.0, 0.0, 0.0 );
    glRotatef( rotatey, 0.0, 1.0, 0.0 );
   
    // włączenie oświetlenia
    glEnable( GL_LIGHTING );
   
    // włączenie światła GL_LIGHT0
    glEnable( GL_LIGHT0 );
   
    // włączenie automatycznej normalizacji wektorów normalnych
    glEnable( GL_NORMALIZE );
   
    // włączenie obsługi właściwości materiałów
    glEnable( GL_COLOR_MATERIAL );
   
    // właściwości materiału określone przez kolor wierzchołków
    glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );
   
    // włączenie bufora głębokości
    glEnable( GL_DEPTH_TEST );
   
    // narysowanie wybranych elementów sceny
    switch( render_mode )
    {
        // rysowanie tylko krawędzi szkieletowych
    case SILHOUETTE:
        Silhouette();
        break;
       
        // rysowanie obiektu i krawędzi szkieletowych
    case SILHOUETTE_OBJECT:
        Silhouette();
        glCallList( CURRENT_OBJECT );
        break;
       
        // rysowanie tylko obiektu
    case OBJECT:
        glCallList( CURRENT_OBJECT );
        break;
    }
   
    // skierowanie poleceń do wykonania
    glFlush();
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// zmiana wielkości okna

void Reshape( int width, int height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, width, height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // parametry bryły obcinania
    glOrtho( left, right, bottom, top, near, far );
   
    // generowanie sceny 3D
    DisplayScene();
}

// obsługa przycisków myszki

void MouseButton( int button, int state, int x, int y )
{
    if( button == GLUT_LEFT_BUTTON )
    {
        // zapamiętanie stanu lewego przycisku myszki
        button_state = state;
       
        // zapamiętanie położenia kursora myszki
        if( state == GLUT_DOWN )
        {
            button_x = x;
            button_y = y;
        }
    }
}

// obsługa ruchu kursora myszki

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();
    }
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // torus
    case TORUS_OBJ:
        CURRENT_OBJECT = TORUS_ID;
        DisplayScene();
        break;
       
        // sześcian
    case CUBE_OBJ:
        CURRENT_OBJECT = CUBE_ID;
        DisplayScene();
        break;
       
        // rysowanie tylko obiektu
    case OBJECT:
       
        // rysowanie tylko krawędzi szkieletowych
    case SILHOUETTE:
       
        // rysowanie obiektu i krawędzi szkieletowych
    case SILHOUETTE_OBJECT:
        render_mode = value;
        DisplayScene();
        break;
       
        // wyjście
    case EXIT:
        exit( 0 );
    }
}

// utworzenie list wyświetlania

void GenerateDisplayLists()
{
    // generowanie identyfikatora pierwszej listy wyświetlania
    TORUS_ID = glGenLists( 1 );
   
    // pierwsza lista wyświetlania
    glNewList( TORUS_ID, GL_COMPILE );
   
    // czerwony torus
    glColor4fv( Red );
    glutSolidTorus( 1.0, 2.0, 20, 20 );
   
    // koniec pierwszej listy wyświetlania
    glEndList();
   
    // generowanie identyfikatora drugiej listy wyświetlania
    CUBE_ID = glGenLists( 1 );
   
    // druga lista wyświetlania
    glNewList( CUBE_ID, GL_COMPILE );
   
    // zielony sześcian
    glColor4fv( Green );
    glutSolidCube( 4.0 );
   
    // koniec drugiej listy wyświetlania
    glEndList();
   
    // bieżący obiekt
    CURRENT_OBJECT = TORUS_ID;
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_STENCIL );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 500, 500 );
   
    // utworzenie głównego okna programu
    #ifdef WIN32
   
    glutCreateWindow( "Krawędzie" );
    #else
   
    glutCreateWindow( "Krawedzie" );
    #endif
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( DisplayScene );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // obsługa przycisków myszki
    glutMouseFunc( MouseButton );
   
    // obsługa ruchu kursora myszki
    glutMotionFunc( MouseMotion );
   
    // utworzenie podmenu - Obiekt
    int MenuObject = glutCreateMenu( Menu );
    glutAddMenuEntry( "Torus", TORUS_OBJ );
    #ifdef WIN32
   
    glutAddMenuEntry( "Sześcian", CUBE_OBJ );
    #else
   
    glutAddMenuEntry( "Szescian", CUBE_OBJ );
    #endif
   
    // utworzenie podmenu - Tryb rysowania
    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
   
    // menu główne
    glutCreateMenu( Menu );
    glutAddSubMenu( "Obiekt", MenuObject );
    glutAddSubMenu( "Tryb rysowania", MenuRenderMode );
   
    #ifdef WIN32
   
    glutAddMenuEntry( "Wyjście", EXIT );
    #else
   
    glutAddMenuEntry( "Wyjscie", EXIT );
    #endif
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // utworzenie list wyświetlania
    GenerateDisplayLists();
   
    // wprowadzenie programu do obsługi pętli komunikatów
    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

Rysunek 11. Program Przejście obrazów
Rysunek 11. Program Przejście obrazów
C/C++
/*
(c) Janusz Ganczarski
http://www.januszg.hg.pl
JanuszG@enter.net.pl
*/

#include <GL/glut.h>
#include <GL/glext.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "targa.h"

// stałe do obsługi menu podręcznego

enum
{
    CLEAR_STENCIL, // czyszczenie bufora szablonowego
    EXIT // wyjście
};
// wskaźnik naciśnięcia lewego przycisku myszki

int button_state = GLUT_UP;

// położenie kursora myszki

int button_x, button_y;

// rozmiary gumki

const int eraser_width = 80;
const int eraser_height = 80;

// tablica na dane mapy pikselowej z gumką

GLubyte eraser_pixmap[ 4 * eraser_width * eraser_height ];

// dane opisujące obraz odczytany z pliku TARGA

GLsizei width; // szerokość obrazu
GLsizei height; // wysokość obrazu
GLenum format; // format danych obrazu
GLenum type; // format danych pikseli obrazu
GLvoid * pixels; // wskaźnik na tablicę z danymi obrazu

// wczytanie pliku graficznego w formacie TARGA

void LoadTARGA( int argc, char * argv[] )
{
    // sprawdzenie czy jest parametr
    if( argc < 2 )
    {
        printf( "Brak nazwy pliku TARGA\n" );
        exit( 1 );
    }
   
    // odczyt pliku TARGA i ewentualny komunikat o błędzie
    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 );
    }
}

// sprawdzenie czy dany format pliku TARGA jest obsługiwany

void CheckImageFormat()
{
    // obraz w formacie BGR i BGRA
    if( format == GL_BGR || format == GL_BGRA )
    {
        // odczyt wersji OpenGL
        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 );
        }
       
        // sprawdzenie czy jest co najmniej wersja 1.2 OpenGL
        if( major <= 1 && minor < 2 )
        {
            // jeżeli jest starsza wersja OpenGL sprawdzenie
            // czy jest obsługa rozszerzenia GL_EXT_bgra
            if( !glutExtensionSupported( "GL_EXT_bgra" ) )
            {
                // komunikat o błędzie - w tym miejscu można wykonać
                // konwersję danych z formatu BGR/BGRA na RGB/RGBA
                printf( "Brak rozszerzenia GL_EXT_bgra\n" );
                exit( 1 );
            }
        }
    }
}

// wygenerowanie danych mapy pikselowej z gumką

void GenerateEraser()
{
    // pokolorowanie całej gumki na jasno szaro
    memset( eraser_pixmap, 196, 4 * eraser_width * eraser_height );
}

// funkcja generująca scenę 3D

void Display()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // wartość czyszcząca bufor szablonowy
    glClearStencil( ~0 );
   
    // czyszczenie bufora koloru
    glClear( GL_COLOR_BUFFER_BIT );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // włączenie bufora szablonowego
    glEnable( GL_STENCIL_TEST );
   
    // określenie operacji na buforze szablonowym
    glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
   
    // test bufora szablonowego
    glStencilFunc( GL_EQUAL, 1, ~0 );
   
    // pozycja mapy pikselowej
    glRasterPos2i( 0, 0 );
   
    // wyrównywanie wiersza mapy pikselowej do pojedyńczego bajta
    glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
   
    // skalowanie mapy pikselowej do rozmiarów okna
    glPixelZoom(( float ) glutGet( GLUT_WINDOW_WIDTH ) /( float ) width,
    ( float ) glutGet( GLUT_WINDOW_HEIGHT ) /( float ) height );
   
    // wyświetlenie obrazu zawartego w mapie pikselowej
    glDrawPixels( width, height, format, type, pixels );
   
    // neutralne skalowanie mapy pikselowej
    glPixelZoom( 1.0, 1.0 );
   
    // rysowanie gumki
    if( button_state == GLUT_DOWN )
    {
        // test bufora szablonowego
        glStencilFunc( GL_ALWAYS, 1, 0 );
       
        // określenie operacji na buforze szablonowym
        glStencilOp( GL_KEEP, GL_REPLACE, GL_REPLACE );
       
        // włączenie mieszania kolorów
        glEnable( GL_BLEND );
       
        // funkcja mieszania kolorów
        glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
       
        // pozycja gumki
        glRasterPos2i( button_x, button_y );
       
        // przesunięcie pozycji kursora do środka gumki
        glBitmap( 0, 0, 0.0, 0.0, - eraser_width / 2.0, - eraser_height / 2.0, NULL );
       
        // narysowanie gumkii
        glDrawPixels( eraser_width, eraser_height, GL_RGBA, GL_UNSIGNED_BYTE, eraser_pixmap );
       
        // wyłączenie mieszania kolorów
        glDisable( GL_BLEND );
    }
   
    // zamiana buforów koloru
    glutSwapBuffers();
}

// zmiana wielkości okna

void Reshape( int Width, int Height )
{
    // obszar renderingu - całe okno
    glViewport( 0, 0, Width, Height );
   
    // wybór macierzy rzutowania
    glMatrixMode( GL_PROJECTION );
   
    // macierz rzutowania = macierz jednostkowa
    glLoadIdentity();
   
    // parametry bryły obcinania
    gluOrtho2D( 0.0, Width, 0.0, Height );
   
    // czyszczenie bufora szablonowego
    glClear( GL_STENCIL_BUFFER_BIT );
   
    // generowanie sceny 3D
    Display();
}

// obsługa przycisków myszki

void MouseButton( int button, int state, int x, int y )
{
    if( button == GLUT_LEFT_BUTTON )
    {
        // zapamiętanie stanu lewego przycisku myszki
        button_state = state;
       
        // zapamiętanie położenia kursora myszki
        if( state == GLUT_DOWN )
        {
            button_x = x;
            button_y = y;
        }
    }
}

// obsługa ruchu kursora myszki

void MouseMotion( int x, int y )
{
    if( button_state == GLUT_DOWN )
    {
        button_x = x;
        button_y = glutGet( GLUT_WINDOW_HEIGHT ) - y;
        glutPostRedisplay();
    }
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // czyszczenie bufora szablonowego
    case CLEAR_STENCIL:
        glClear( GL_STENCIL_BUFFER_BIT );
        glutPostRedisplay();
        break;
       
        // wyjście
    case EXIT:
        exit( 0 );
        break;
    }
}

main( int argc, char * argv[] )
{
    // odczyt pliku graficznego TARGA
    LoadTARGA( argc, argv );
   
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_STENCIL );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 500, 500 );
   
    // utworzenie głównego okna programu
    #ifdef WIN32
   
    glutCreateWindow( "Przejście obrazów" );
    #else
   
    glutCreateWindow( "Przejscie obrazow" );
    #endif
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( Display );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // obsługa przycisków myszki
    glutMouseFunc( MouseButton );
   
    // obsługa ruchu kursora myszki
    glutMotionFunc( MouseMotion );
   
    // sprawdzenie czy dany format pliku TARGA jest obsługiwany
    CheckImageFormat();
   
    // utworzenie menu podręcznego
    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
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // wygenerowanie danych mapy pikselowej z gumką
    GenerateEraser();
   
    // wprowadzenie programu do obsługi pętli komunikatów
    glutMainLoop();
    return 0;
}
Poprzedni dokument Następny dokument
Przetwarzanie obrazów Bufor akumulacyjny