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

Kolory i cieniowanie

[lekcja] Rozdział 8. Kolory i cieniowanie w OpenGL: bufor koloru; tryb bezpośredni RGB(A), tryb indeksowy; cieniowanie płaskie i gładkie (Gourauda); dithering; przykładowy program.
Standardowo biblioteka OpenGL korzysta z modelu barw RGB, który wykorzystuje trzy barwy podstawowe: czerwoną (R), zieloną (G) i niebieską (B). Jak wspomnieliśmy na początku kursu barwa może być określona bezpośrednio poprzez wartości składowych bądź w trybie indeksowym z użyciem mapy (tablicy) barw. Tryb indeksowy zostanie opisany, ale przykładowe programy nie będą go stosowały. Przedtem jednak poznamy budowę bufora koloru - najważniejszego elementu bufora ramki obrazu.

Bufor koloru

Bufor koloru służy do przechowywania obrazu renderowanej sceny 3D.
Jest on podzielony na kilka buforów. Typowo OpenGL stosuje dwa bufory koloru: przedni i tylny. Bieżąca scena znajduje się w przednim buforze i jest wyświetlana na ekranie monitora, a jednocześnie w tylnym buforze rysowana jest kolejna ramka sceny. Po zakończeniu rysowania, bufory są zamieniane i cały proces zaczyna się od początku. Podwójne buforowanie eliminuje migotanie obrazu występujące przy stosowaniu pojedynczego bufora koloru.
Niektóre implementacje biblioteki OpenGL udostępniają stereoskopowy tryb działania bufora koloru, tj. generowanie dwóch niezależnych obrazów dla prawego i lewego oka. Oba kanały stereoskopowe mają własne niezależne bufory przednie i tylne. Ponadto implementacja OpenGL może posiadać dodatkowe (pomocnicze) bufory koloru.
Wybór trybu działania bufora kolorów dokonywany jest na początku działania programu. W przypadku biblioteki GLUT realizuje to opisywana już funkcja glutInitDisplayMode, której parametrem jest maska bitowa.
Stała GLUT_SINGLE wybiera domyślny w bibliotece GLUT tryb pojedynczego bufora koloru. Podwójne buforowanie wymaga stałej GLUT_DOUBLE. Z kolei tryb stereoskopowy uruchamia stała GLUT_STEREO.
Inna grupa stałych odpowiada, za wybór sposobu zapisu kolorów w buforach koloru: GLUT_RGB lub GLUT_RGBA - bezpośredni tryb RGB wyświetlania kolorów (bez kanału alfa), GLUT_INDEX - tryb indeksowy z mapą kolorów, GLUT_ALPHA - obsługa kanału alfa, GLUT_LUMINANCE - alternatywny tryb wyświetlania obrazu w odcieniach szarości, w którym składowe R pikseli stanowią indeksy do mapy kolorów początkowo zawierającej liniowo uporządkowane odcienie szarości.

Kolor - tryb bezpośredni

W trybie bezpośrednim kolor ustalany jest przy użyciu funkcji z grupy glColor. Funkcje te można podzielić na trzy grupy. Pierwsza to funkcje trójargumentowe, przyjmujące składowe RGB w różnych formatach; druga grupa to funkcje czteroargumentowe umożliwiające określenie także wartości składowej alfa (RGBA); trzecia to funkcje przyjmujące wskaźnik na tablicę ze składowymi RGB lub RGBA. W przypadku, gdy kolor określają trzy składowe wartość składowej alfa wynosi 1.0. Początkową wartość bieżącego koloru określają składowe RGBA: (1, 1, 1, 1).
Funkcje przyjmujące argumenty zmiennoprzecinkowe wymagają wartości z przedziału [0, 1], gdzie 0 określa minimalną wartość składowej koloru, a 1 maksymalną wartość składowej. Wartości przekraczające ten przedział są odpowiednio przycinane. Funkcje przyjmujące argumenty całkowitoliczbowe przekształcają te wartości do liczb zmiennoprzecinkowych z przedziału [0, 1] wg schematu przedstawionego w tabeli 1, gdzie c oznacza składową koloru.

Tabela 1: Konwersja składowych koloru w OpenGL

typ OpenGLkonwersja
GLubytec/(28-1)
GLbyte(2*c+1)/(28-1)
GLushortc/(216-1)
GLshort(2*c+1)/(216-1)
GLuintc/(232-1)
GLint(2*c+1)/(232-1)

Funkcje z grupy glColor3

C/C++
void glColor3b( GLbyte red, GLbyte green, GLbyte blue )
void glColor3d( GLdouble red, GLdouble green, GLdouble blue )
void glColor3f( GLfloat red, GLfloat green, GLfloat blue )
void glColor3i( GLint red, GLint green, GLint blue )
void glColor3s( GLshort red, GLshort green, GLshort blue )
void glColor3ub( GLubyte red, GLubyte green, GLubyte blue )
void glColor3ui( GLuint red, GLuint green, GLuint blue )
void glColor3us( GLushort red, GLushort green, GLushort blue )
void glColor3bv( const GLbyte * v )
void glColor3dv( const GLdouble * v )
void glColor3fv( const GLfloat * v )
void glColor3iv( const GLint * v )
void glColor3sv( const GLshort * v )
void glColor3ubv( const GLubyte * v )
void glColor3uiv( const GLuint * v )
void glColor3usv( const GLushort * v )

Funkcje z grupy glColor4

C/C++
void glColor4b( GLbyte red, GLbyte green, GLbyte blue,
GLbyte alpha )
void glColor4d( GLdouble red, GLdouble green, GLdouble blue, GLdouble alpha )
void glColor4f( GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha )
void glColor4i( GLint red, GLint green, GLint blue, GLint alpha )
void glColor4s( GLshort red, GLshort green, GLshort blue, GLshort alpha )
void glColor4ub( GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha )
void glColor4ui( GLuint red, GLuint green, GLuint blue, GLuint alpha )
void glColor4us( GLushort red, GLushort green, GLushort blue, GLushort alpha )
void glColor4bv( const GLbyte * v )
void glColor4dv( const GLdouble * v )
void glColor4fv( const GLfloat * v )
void glColor4iv( const GLint * v )
void glColor4sv( const GLshort * v )
void glColor4ubv( const GLubyte * v )
void glColor4uiv( const GLuint * v )
void glColor4usv( const GLushort * v )

Kolor - tryb indeksowy

W trybie indeksowym kolor określany jest pojedynczą liczbą - indeksem do mapy (tablicy) kolorów, która zawiera informacje o składowych RGB.
Typowo wielkość mapy kolorów wynosi od 256 (28) do 4.096 (212). Zarządzanie mapą kolorów jest uzależnione od konkretnego systemu okienkowego, w którym pracuje program. Biblioteka OpenGL nie posiada żadnych mechanizmów pozwalających na zmianę zawartości mapy kolorów oraz zmiany jej rozmiarów.
Wybór indeksu bieżącego koloru realizują funkcje z grupy glIndex:

C/C++
void glIndexd( GLdouble c )
void glIndexf( GLfloat c )
void glIndexi( GLint c )
void glIndexs( GLshort c )
void glIndexdv( const GLdouble * c )
void glIndexfv( const GLfloat * c )
void glIndexiv( const GLint * c )
void glIndexsv( const GLshort * c )
Początkowa wartość indeksu bieżącego koloru wynosi 1.
Czyszczenie bufora koloru w trybie indeksowym wykonuje funkcja:

C/C++
void glClearIndex( GLfloat c )
gdzie parametr c określa indeks mapy kolorów. Domyślna wartość indeksu wynosi 0. Jest to odpowiednik funkcji glClearColor działającej w trybie bezpośrednim.

Cieniowanie

Cieniowaniem nazywamy metodę ustalania koloru poszczególnych pikseli wielokąta. Biblioteka OpenGL udostępnia standardowo dwa modele cieniowania: cieniowanie płaskie oraz cieniowanie gładkie. W cieniowaniu płaskim wielokąt otrzymuje jeden kolor - określony dla ostatniego wierzchołka (wyjątek stanowi prymityw GL_POLYGON, o kolorze którego decyduje kolor określony dla pierwszego wierzchołka). Cieniowanie gładkie wykorzystuje algorytm Gourauda, który w uproszczeniu polega na liniowej interpolacji wartości składowych koloru określonych dla każdego wierzchołka wielokąta.
Cieniowane gładkie daje zadowalające efekty przy stosunkowo niewielkich wymaganiach obliczeniowych.
Wybór rodzaju cieniowania umożliwia funkcja:

C/C++
void glShadeModel( GLenum mode )
której parametr mode przyjmuje jedną z wartości:
  • GL_FLAT - cieniowanie płaskie,
  • GL_SMOOTH - cieniowanie gładkie.
Domyślnie stosowane jest cieniowanie gładkie.

Dithering

Wewnętrznie biblioteka OpenGL opisuje kolory za pomocą liczb zmiennoprzecinkowych. Natomiast końcowy efekt widoczny na ekranie monitora zależy od możliwości systemu okienkowego oraz karty gra?cznej. W sytuacji, gdy w systemie dostępna jest niewielka ilość barw (np. 256), biblioteka OpenGL może symulować kolory metodą ditheringu. W uproszczeniu algorytm ten umieszcza w pobliżu siebie różnokolorowe plamki (piksele) tak aby sprawiały wrażenie jednolitej barwy. Technika ta bywa określana także mianem rozpraszania, rozsiewania lub roztrząsania.
Włączenie i wyłączenie ditheringu wymaga odpowiedniego użycia funkcji glEnable/glDisable z parametrem GL_DITHER. Domyślnie dithering jest wyłączony.

Program przykładowy

Przykładowy program (plik trojkat.cpp) to nieco uproszczona wersja klasycznego programu wykorzystującego bibliotekę OpenGL. Początkowo barwa każdego z wierzchołków odpowiada maksymalnej wartości każdej z trzech składowych modelu RGB (patrz rysunek 1).
Rysunek 1. Program Trójkąt - widok początkowy
Rysunek 1. Program Trójkąt - widok początkowy
Program umożliwia zmianę kolorów wierzchołków trójkąta. Służą do tego przyciski r, g, b (zmniejszanie wartości składowych RGB) oraz R, G, B (zwiększanie wartości składowych RGB). Ponadto z poziomu menu podręcznego użytkownik może wybrać rodzaj cieniowania oraz włączyć i wyłączyć dithering. Na dole okna program wyświetla m.in. ilość bitów przypadającą na każdą składową RGBA. Wykorzystano do tego funkcję glGetIntegerv z pierwszym parametrem o wartości: GL_RED_BITS, GL_GREEN_BITS, GL_BLUE -BITS i GL_ALPHA_BITS.
Po uruchomieniu programu w trybie 256 kolorowym Czytelnik ma możliwość sprawdzenia zarówno efektów działania algorytmu ditheringu (rysunek 2) jak też i działania cieniowania gładkiego przy ograniczonej ilości barw (rysunek 3). Oba rysunki powstały w systemie Windows XP w trybie zgodności 256 kolorów. Ocenę jakości tak generowanej gra?ki pozostawiamy Czytelnikowi.
Rysunek 2. Program Trójkąt w trybie 256 kolorowym z ditheringiem
Rysunek 2. Program Trójkąt w trybie 256 kolorowym z ditheringiem
Rysunek 3. Program Trójkąt w trybie 256 kolorowym bez ditheringu koniec
Rysunek 3. Program Trójkąt w trybie 256 kolorowym bez ditheringu koniec

Plik trojkat.cpp

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

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

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

enum
{
    FLAT_SHADING, // cieniowanie płaskie
    SMOOTH_SHADING, // cieniowanie gładkie (Gourauda)
    DITHERING, // dithering
    EXIT // wyjście
};

// model cieniowania

GLenum shading_model = GL_SMOOTH;

// dithering

GLboolean dithering = GL_FALSE;

// wartości składowych barw

GLfloat red = 1.0, green = 1.0, blue = 1.0;

// funkcja rysująca napis w wybranym miejscu

void DrawString( GLfloat x, GLfloat y, char * string )
{
    // położenie napisu
    glRasterPos2f( x, y );
   
    // wyświetlenie napisu
    int len = strlen( string );
    for( int i = 0; i < len; i++ )
         glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
   
}

// funkcja generująca scenę 3D

void Display()
{
    // kolor tła - zawartość bufora koloru
    glClearColor( 1.0, 1.0, 1.0, 1.0 );
   
    // czyszczenie bufora koloru
    glClear( GL_COLOR_BUFFER_BIT );
   
    // wybór macierzy modelowania
    glMatrixMode( GL_MODELVIEW );
   
    // macierz modelowania = macierz jednostkowa
    glLoadIdentity();
   
    // włączenie ditheringu
    if( dithering == GL_TRUE )
         glEnable( GL_DITHER );
   
    // wybór modelu cieniowania
    glShadeModel( shading_model );
   
    // rysowanie trójkąta
    glBegin( GL_TRIANGLES );
   
    // "czerwony" wierzchołek
    glColor3f( red, 0.0, 0.0 );
    glVertex2f( 0.1, 0.1 );
   
    // "zielony" wierzchołek
    glColor3f( 0.0, green, 0.0 );
    glVertex2f( 0.9, 0.3 );
   
    // "niebieski" wierzchołek
    glColor3f( 0.0, 0.0, blue );
    glVertex2f( 0.3, 0.9 );
    glEnd();
   
    // wyświetlenie parametrów renderingu
    char string[ 100 ];
    glColor3f( 0.0, 0.0, 0.0 );
   
    // ilośc bitów na poszczególne składowe RGBA
    GLint r, b, g, a;
    glGetIntegerv( GL_RED_BITS, & r );
    glGetIntegerv( GL_GREEN_BITS, & g );
    glGetIntegerv( GL_BLUE_BITS, & b );
    glGetIntegerv( GL_ALPHA_BITS, & a );
    sprintf( string, "BITS: R=%i G=%i B=%i A=%i", r, g, b, a );
    DrawString( 0, 0.01, string );
   
    // dithering włączony/wyłączony
    if( glIsEnabled( GL_DITHER ) == GL_TRUE )
         strcpy( string, "GL_DITHER: true" );
    else
         strcpy( string, "GL_DITHER: false" );
   
    DrawString( 0, 0.05, string );
   
    // wyłączenie ditheringu
    glDisable( GL_DITHER );
   
    // 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();
   
    // rzutowanie prostokątne
    gluOrtho2D( 0, 1, 0, 1 );
   
    // generowanie sceny 3D
    Display();
}

// obsługa klawiatury

void Keyboard( unsigned char key, int x, int y )
{
    // zmiana wartości składowej R
    if( key == 'R' && red < 1.0 )
         red += 0.05;
    else
    if( key == 'r' && red > 0.0 )
         red -= 0.05;
   
    // zmiana wartości składowej G
    if( key == 'G' && green < 1.0 )
         green += 0.05;
    else
    if( key == 'g' && green > 0.0 )
         green -= 0.05;
   
    // zmiana wartości składowej B
    if( key == 'B' && blue < 1.0 )
         blue += 0.05;
    else
    if( key == 'b' && blue > 0.0 )
         blue -= 0.05;
   
    // narysowanie sceny
    Display();
}

// obsługa menu podręcznego

void Menu( int value )
{
    switch( value )
    {
        // cieniowanie płaskie
    case FLAT_SHADING:
        shading_model = GL_FLAT;
        Display();
        break;
       
        // cieniowanie gładkie (Gourauda)
    case SMOOTH_SHADING:
        shading_model = GL_SMOOTH;
        Display();
        break;
       
        // dithering
    case DITHERING:
        dithering = !dithering;
        Display();
        break;
       
        // wyjście
    case EXIT:
        exit( 0 );
    }
}

int main( int argc, char * argv[] )
{
    // inicjalizacja biblioteki GLUT
    glutInit( & argc, argv );
   
    // inicjalizacja bufora ramki
    glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
   
    // rozmiary głównego okna programu
    glutInitWindowSize( 500, 500 );
   
    // utworzenie głównego okna programu
    #ifdef WIN32
   
    glutCreateWindow( "Trójkąt" );
    #else
   
    glutCreateWindow( "Trojkat" );
    #endif
   
    // dołączenie funkcji generującej scenę 3D
    glutDisplayFunc( Display );
   
    // dołączenie funkcji wywoływanej przy zmianie rozmiaru okna
    glutReshapeFunc( Reshape );
   
    // dołączenie funkcji obsługi klawiatury
    glutKeyboardFunc( Keyboard );
   
    // utworzneie podmenu - Model cieniowania
    int ShadingModel = glutCreateMenu( Menu );
    #ifdef WIN32
   
    glutAddMenuEntry( "Cieniowanie plaskie", FLAT_SHADING );
    glutAddMenuEntry( "Cieniowanie gladkie (Gourauda)", SMOOTH_SHADING );
    #else
    #endif
   
    // menu główne
    glutCreateMenu( Menu );
    glutAddSubMenu( "Model cieniowania", ShadingModel );
    #ifdef WIN32
   
    glutAddMenuEntry( "Dithering: włącz/wyłącz", DITHERING );
    glutAddMenuEntry( "Wyjście", EXIT );
    #else
   
    glutAddMenuEntry( "Dithering: wlacz/wylacz", DITHERING );
    glutAddMenuEntry( "Wyjscie", EXIT );
    #endif
   
    // określenie przycisku myszki obsługującego menu podręczne
    glutAttachMenu( GLUT_RIGHT_BUTTON );
   
    // wprowadzenie programu do obsługi pętli komunikatów
    glutMainLoop();
    return 0;
}
Poprzedni dokument Następny dokument
Wielokąty Rozszerzenia