Tablice wierzchołków (ang vertex array) zostały wprowadzone w wersji 1.1 biblioteki OpenGL. Wcześniej technikę tę udostępniało rozszerzenie EXT vertex array. Pod nieco mylącą nazwą ukrywa się mechanizm pozwalający na zupełnie inne podejście do tworzenia grafiki. Dane obejmujące m.in. współrzędne wierzchołków prymitywów i dane kolorów wierzchołków przechowywane są w jednej lub kilku tablicach i renderowane bezpośrednio z pominięciem mechanizmu glBegin/glEnd.
Mechanizm tablic wierzchołków jest tym bardziej elastyczny, ponieważ dane przechowywane w tablicach (tablicy) można w dowolny sposób modyfikować. Stanowi to istotną różnicę w stosunku do list wyświetlania, których głównym ograniczeniem jest statyczność.
Włączenie tablic wierzchołków
Korzystanie z tablic wierzchołków odbywa się w kilku etapach. Na początku musimy włączyć tablice (wszystkie tablice są domyślnie nieaktywne), które będą używane przy renderingu sceny. Realizuje to funkcja:
void glEnableClientState( GLenum array )
której parametr array przyjmuje jedną z poniższych wartości:
Stała
GL_SECONDARY_COLOR_ARRAY została wprowadzona w wersji 1.4 biblioteki OpenGL, a wcześniej w rozszerzeniu EXT secondary color.
Stała
GL_FOG_COORD_ARRAY została wprowadzona w wersji 1.4 biblioteki OpenGL, przy czym jej pierwotna nazwa to
GL_FOG_COORDINATE_ARRAY (nazwę tej i kilku innych stałych zmieniono w specyfikacji wersji 1.5 biblioteki). Wcześniej obsługę tablic współrzędnych mgły definiowało rozszerzenie EXT fog coord.
Wyłączenie wybranej tablicy wierzchołków sprowadza się do wywołania funkcji:
void glDisableClientState( GLenum array )
której parametr array przyjmuje identyczne wartości jak w funkcji glEnableClientState.
Definiowanie danych
Drugim etapem przy tworzeniu tablic wierzchołków jest zdefiniowanie danych, czyli odpowiednie powiązanie przygotowanych w programie tablic z danymi. Dla każdego rodzaju danych wykorzystywana jest do tego odrębna funkcja, ale mają one ten sam zbiór parametrów:
void glVertexPointer( GLint size, GLenum type, GLsizei stride, const void * pointer )
void glNormalPointer( GLenum type, GLsizei stride, const void * pointer )
void glColorPointer( GLint size, GLenum type, GLsizei stride, const GLvoid * pointer )
void glSecondaryColorPointer( GLint size, GLenum type, GLsizei stride, const GLvoid * pointer )
void glIndexPointer( GLenum type, GLsizei stride, const GLvoid * pointer )
void glEdgeFlagPointer( GLsizei stride, const GLvoid * pointer )
void glFogCoordPointer( GLenum type, GLsizei stride, const GLvoid * pointer )
void glTexCoordPointer( GLint size, GLenum type, GLsizei stride, const GLvoid * pointer )
Parametr size (nie występujący w części funkcji) określa liczbę składowych w elemencie tablicy. Przykładowo współrzędne wierzchołków prymitywów (glVertexPointer) mogą być opisywane za pomocą dwóch, trzech lub czterech liczb. Natomiast do zdefiniowania wektora normalnego potrzeba zawsze trzech współrzędnych, stąd np. w funkcji glNormalPointer parametr size nie występuje.
Parametr type określa typ danych zawartych w tablicy. Dopuszczalne wartości zależą od rodzaju definiowanych danych. Zestawienie dopuszczalnych wartości tego parametru oraz parametru size dla poszczególnych funkcji definiujących dane zawiera tabela 1. Jedynym wyjątkiem nie posiadającym tego parametru jest funkcja glEdgeFlagPointer. Jest to związane z tym, że dane o znacznikach krawędzi wierzchołków przechowywane są wyłącznie przy użyciu typu GLboolean.
Przypomnijmy, że stałe
GL_BYTE,
GL_UNSIGNED_BYTE,
GL_SHORT,
GL_UNSIGNED_SHORT,
GL_INT,
GL_UNSIGNED_INT,
GL_FLOAT i
GL_DOUBLE odpowiadają następującym typom danych w bibliotece OpenGL: GLbyte, GLubyte, GLshort, GLushort, GLint, GLuint, GLfloat i GLdouble. Co ciekawe, pomimo iż typ GLdouble jest dostępny w bibliotece OpenGL od początku, stała
GL_DOUBLE została wprowadzona dopiero w wersji 1.1, właśnie w związku z tablicami wierzchołków.
Tabela 1: Zestawienie parametrów funkcji definiujących dane do tablic wierzchołków
Współrzędne wektorów normalnych, kolorów wierzchołków i drugorzędnych kolorów wierzchołków reprezentowane liczbami stałoprzecinkowymi bez znaku (
GL_UNSIGNED_BYTE,
GL_UNSIGNED_SHORT i
GL_UNSIGNED_INT) są normalizowane do przedziału [0, 1]. Podobna zasada dotyczy danych reprezentowanych liczbami stałoprzecinkowymi ze znakiem (
GL_BYTE,
GL_SHORT i
GL_INT), które są normalizowane do przedziału [−1, 1].
Kolejny parametr stride definiuje przesunięcie w bajtach pomiędzy poszczególnymi elementami tablicy. Umożliwia to zdefiniowanie jednej „dużej” tablicy zawierającej jednocześnie np. współrzędne wierzchołków prymitywów, dane o ich kolorach oraz wektorach normalnych.
Ostatni parametr pointer to oczywiście wskaźnik pierwszy element tablicy z właściwymi danymi.
W przypadku, gdy korzystamy techniki wieloteksturowania, dla każdej z jednostek teksturujących definiujemy odrębną tablicę współrzędnych (może to być fizycznie ta sama tablica). Zanim jednak przystąpimy do definiowania tablicy współrzędnych trzeba wybrać numer aktywnej jednostki teksturującej. Realizuje to funkcja:
void glClientActiveTexture( GLenum texture )
której parametr texture określa numer aktywnej jednostki (stała
GL_TEXTURE0 i następne). Domyślnie aktywna jest pierwsza jednostka teksturująca.
Przypomnijmy jeszcze, że zanim technika wieloteksturowania została włączona do wersji 1.3 specyfikacji biblioteki OpenGL, została opisana w rozszerzeniach: SGIS multitexture, EXT multitexture i ARB multitexture.
Tablice przeplatane
W przypadku, gdy korzystamy z tablicy zawierającej przeplatane dane różnego rodzaju, możemy skorzystać z funkcji, która jednocześnie włącza tablice wierzchołków jak też i definiuje odpowiednie dane:
void glInterleavedArrays( GLenum format, GLsizei stride, const GLvoid * pointer )
Jedynym ograniczeniem przy korzystaniu z tej funkcji jest ściśle określona lista formatów w jakich mogą być ułożone dane w tablicy. Format danych tablicy przekazywany jest w parametrze format i przyjmuje jedną z poniższych wartości:
Jak Czytelnik zapewne zauważył rodzaj danych zawartych w tablicy jednoznacznie określa nazwa stałej. Przy definiowaniu tablic zawierających dane różnego typu trzeba pamiętać, że dane opisane czterema liczbami typu GLubyte są wyrównywane do najbliższej wielokrotności liczby typu GLfloat.
Pozostałe parametry stride i pointer to oczywiście określone w bajtach przesunięcie pomiędzy elementami tablicy i wskaźnik na tablicę z danymi.
Renderowanie danych
Ostatnim etapem przy korzystaniu z tablic wierzchołków jest renderowanie danych znajdujących się w tablicach. Biblioteka OpenGL udostępnia do tego celu kilka funkcji.
Pierwsza funkcja pobiera dane tylko z jednej pozycji tablic wierzchołków:
void glArrayElement( GLint i )
Efekt jej użycia nie różni się od wywołania jednej lub kilku funkcji (w zależności od ilości zdefiniowanych danych) określającej wybrane dane. Ponieważ OpenGL nie posiada w tym przypadku informacji o rodzaju renderowanego prymitywu wywołanie funkcji glArrayElement najczęściej umieszcza się pomiędzy wywołaniami funkcji glBegin/glEnd.
Druga funkcja służy do rysowania określonych prymitywów graficznych z danych znajdujących się w tablicach wierzchołków:
void glDrawArrays( GLenum mode, GLint first, GLsizei count )
Parametr mode określa rodzaj rysowanego prymitywu graficznego i przyjmuje takie same wartości jak parametr mode funkcji glBegin. Są to są dobrze już znane stałe:
GL_POINTS,
GL_LINE_STRIP,
GL_LINE_LOOP,
GL_LINES,
GL_TRIANGLE_STRIP,
GL_TRIANGLE_FAN,
GL_TRIANGLES,
GL_QUAD_STRIP,
GL_QUADS i
GL_POLYGON.
Parametr first wskazuje pierwszy element tablic wierzchołków, który ma zostać wykorzystany do renderingu, a parametr count określa ilość renderowanych elementów.
W wersji 1.4 biblioteki OpenGL, a wcześniej w rozszerzeniach EXT multi draw arrays i SUN multi draw arrays, wprowadzono funkcję pozwalającą na rysowanie ciągu dowolnie wybranych elemetów tablic wierzchołków:
void glMultiDrawArrays( GLenum mode, GLint * first, GLsizei * count, GLsizei primcount )
Parametr mode określa rodzaj rysowanego prymitywu i oczywiście przyjmuje takie same wartości jak parametr mode funkcji glDrawArrays. Tablice wskazywane przez parametry first i count zawierają kolejne numery pierwszego renderowanego elementu tablicy wierzchołków i ilość renderowanych elementów. Wielkość tak opisanego podzbioru elementów tablic wierzchołków określa parametr primcount.
Indeksowe tablice wierzchołków
Z indeksowego opisu bryły korzystaliśmy po raz pierwszy w programie prezentującym sposób obliczania wektorów normalnych. Jedna tablica zawiera dane opisujące poszczególne wierzchołki bryły. Druga tablica zawiera opis budowy poszczególnych wierzchołków - czyli indeksy do tablicy z danymi wierzchołków. Ta technika pozwala na zmniejszenie ilości danych, bowiem każdy wierzchołek jest opisany tylko raz, a nie wielokrotnie jak to ma miejsce w standardowym opisie obiektu.
Biblioteka OpenGL udostępnia kilka funkcji obsługujących indeksowe tablice wierzchołków. Pierwsza z nich renderuje prymitywy graficzne z wierzchołkami określonymi w tablicach wierzchołków:
void glDrawElements( GLenum mode, GLsizei count,
GLenum type, const GLvoid * indices )
Parametr mode określa rodzaj rysowanego prymitywu graficznego (wartości takie same jak w funkcji glBegin). Drugi parametr count zawiera ilość indeksów znajdujących się w tablicy indeksów, do której wskaźnik zawiera parametr indices. Format danych tablicy indeksów określa parametr type, który przyjmuje jedną z wartości:
GL_UNSIGNED_BYTE,
GL_UNSIGNED_SHORT i
GL_UNSIGNED_INT. Indeksy numerowane są od 0.
W wersji 1.2 biblioteki OpeneGL, a wcześniej w rozszerzeniu EXT draw - range elements, dodano funkcję:
void glDrawRangeElements( GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const GLvoid * indices )
która różni się od funkcji glDrawElements dwoma dodatkowymi parametrami start i end określającymi minimalną i maksymalną wartość wykorzystywanych indeksów tablic wierzchołków. Pozostałe parametry mają takie same znaczenie jak odpowiadające im parametry funkcji glDrawElements.
Intencją wprowadzenia tej funkcji było umożliwienie optymalizacji wyświetlania indeksowych tablic wierzchołków. W tym celu dodano dwie nowe zależne od implementacji stałe określające rekomendowane przez implementację:
przy których istnieje możliwość przyśpieszenia operacji na indeksowanych tablicach wierzchołków. Wartości stałych można uzyskać np. przy użyciu funkcji glGetInteger.
W wersji 1.4 biblioteki OpenGL, a wcześniej w rozszerzeniach EXT multi draw arrays i SUN multi draw arrays, wprowadzono funkcję pozwalającą na renderowanie prymitywów przy użyciu kilku tablic indeksów:
void glMultiDrawElements( GLenum mode, const GLsizei * count, GLenum type, const GLvoid ** indices, GLsizei primcount )
Parametr mode określa rodzaj rysowanego prymitywu i oczywiście przyjmuje takie same wartości jak parametr mode funkcji glDrawElements. Tablica wskazywana przez parametr count zawierają ilości elementów kolejnych tablic indeksów, które przekazywane są w parametrze indices. Ilość tablic indeksów określa parametr primcount. Wszystkie tablice indeksów zawierają dane określone w parametrze type, który przyjmuje takie same wartości jak odpowiadający mu parametr funkcji glDrawElements.
Pobieranie adresów tablic
Pobieranie adresów tablic wierzchołków oraz buforów selecji i sprzężenia zwrotnego umożliwia funkcja:
void glGetPointerv( GLenum pname, GLvoid ** params )
której parametr pname określa rodzaj wskaźnika zwracanego w parametrze params:
Stos atrybutów klienta OpenGL
Zmienne stanu, czyli tzw. atrybuty, związane z tablicami wierzchołków, a także atrybuty związane z przetwarzaniem pikseli przechowywane są po stronie klienta OpenGL, w odróżnieniu od opisanych wcześniej grup zmiennych stanu przechowywanych po stronie serwera OpenGL (odcinek kursu poświęcony materiałom i oświetleniu). Atrybyty przechowywane po stronie klienta także można odkłądac na stos korzystając z funkcji:
void glPushClientAttrib( GLbitfield mask )
której parametr mask, to maska bitowa określająca jaka grupa lub grupy zmiennych stanu mają zostać odłożone na stos. Możliwe są następujące wartości:
Przywrócenie wartości wcześniej odłożonych na stos realizuje funkcja:
Programy przykładowe
Pierwszy program przykładowy (plik tablice_wierzcholkow.cpp) testuje wydajność tablic wierzchołków w porównaniu z listami wyświetlania. Przeprowadzone testy dały dość zaskakujące wyniki. Okazało się, że przy renderingu punktów listy wyświetlania są szybsze od tablic wierzchołków (patrz rysunki nr 1 i 2). Natomiast przy rysowaniu odcinków i trójkątów różnice w szybkości renderingu praktycznie nie występują.
Podobieństwo okien programu do wcześniej prezentowanego programu testującego wydajność list wyświetlania nie jest przypadkowa, bowiem opisywany program jest jego niewielką przeróbką. Zasadnicza różnica polega na sposobie przechowywania danych o wierzchołkach. Dane o współrzędnych wierzchołków prymitywów oraz o składowych kolorów zebrano w dwóch odrębnych tablicach, a nie sześciu jak było w pierwotnym programie.
Plik tablice_wierzcholkow.cpp
#include <GL/glut.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include "colors.h"
enum
{
POINTS,
POINTS_DL,
POINTS_VA,
LINES,
LINES_DL,
LINES_VA,
TRIANGLES,
TRIANGLES_DL,
TRIANGLES_VA,
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;
int test = POINTS;
GLuint listid;
const int size = 30000;
GLfloat vertex_xyz[ 3 * size ];
GLfloat color_rgb[ 3 * size ];
int frames = 0;
long start_time = 0;
char time_string[ 100 ] = "FPS:";
void DrawString( GLfloat x, GLfloat y, char * string )
{
glRasterPos2f( x, y );
int len = strlen( string );
for( int i = 0; i < len; i++ )
glutBitmapCharacter( GLUT_BITMAP_9_BY_15, string[ i ] );
}
void Points()
{
glBegin( GL_POINTS );
for( int i = 0; i < size; i++ )
{
glColor3fv( color_rgb + 3 * i );
glVertex3fv( vertex_xyz + 3 * i );
}
glEnd();
}
void Lines()
{
glBegin( GL_LINES );
for( int i = 0; i < size / 2; i++ )
{
glColor3fv( color_rgb + 3 * 2 * i + 0 );
glVertex3fv( vertex_xyz + 3 * 2 * i + 0 );
glColor3fv( color_rgb + 3 * 2 * i + 3 );
glVertex3fv( vertex_xyz + 3 * 2 * i + 3 );
}
glEnd();
}
void Triangles()
{
glBegin( GL_TRIANGLES );
for( int i = 0; i < size / 3; i++ )
{
glColor3fv( color_rgb + 3 * 3 * i + 0 );
glVertex3fv( vertex_xyz + 3 * 3 * i + 0 );
glColor3fv( color_rgb + 3 * 3 * i + 3 );
glVertex3fv( vertex_xyz + 3 * 3 * i + 3 );
glColor3fv( color_rgb + 3 * 3 * i + 6 );
glVertex3fv( vertex_xyz + 3 * 3 * i + 6 );
}
glEnd();
}
void DisplayScene()
{
if( !frames++ )
start_time = clock();
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, -( near + far ) / 2 );
glEnable( GL_DEPTH_TEST );
switch( test )
{
case POINTS:
Points();
break;
case POINTS_DL:
glCallList( listid + 0 );
break;
case LINES:
Lines();
break;
case LINES_DL:
glCallList( listid + 1 );
break;
case TRIANGLES:
Triangles();
break;
case TRIANGLES_DL:
glCallList( listid + 2 );
break;
case POINTS_VA:
case LINES_VA:
case TRIANGLES_VA:
glEnableClientState( GL_VERTEX_ARRAY );
glEnableClientState( GL_COLOR_ARRAY );
glVertexPointer( 3, GL_FLOAT, 0, vertex_xyz );
glColorPointer( 3, GL_FLOAT, 0, color_rgb );
if( test == POINTS_VA )
glDrawArrays( GL_POINTS, 0, size );
else
if( test == LINES_VA )
glDrawArrays( GL_LINES, 0, size );
else
glDrawArrays( GL_TRIANGLES, 0, size );
glDisableClientState( GL_VERTEX_ARRAY );
glDisableClientState( GL_COLOR_ARRAY );
break;
};
glLoadIdentity();
glTranslatef( 0, 0, - near );
glColor3fv( Black );
if( frames == 250 )
{
frames = 0;
sprintf( time_string, "FPS: %i",( int )( 250 * CLOCKS_PER_SEC /( float )( clock() - start_time ) ) );
}
DrawString( left, bottom, time_string );
glFlush();
glutSwapBuffers();
}
void Reshape( int width, int height )
{
glViewport( 0, 0, width, height );
glMatrixMode( GL_PROJECTION );
glLoadIdentity();
if( aspect == ASPECT_1_1 )
{
if( width < height && width > 0 )
glFrustum( left, right, bottom * height / width, top * height / width, near, far );
else
if( width >= height && height > 0 )
glFrustum( left * width / height, right * width / height, bottom, top, near, far );
}
else
glFrustum( left, right, bottom, top, near, far );
DisplayScene();
}
void Menu( int value )
{
switch( value )
{
case POINTS:
case POINTS_DL:
case POINTS_VA:
case LINES:
case LINES_DL:
case LINES_VA:
case TRIANGLES:
case TRIANGLES_DL:
case TRIANGLES_VA:
test = 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()
{
srand( time( NULL ) );
for( int i = 0; i < size; i++ )
{
vertex_xyz[ 3 * i + 0 ] =( rand() /( float ) RAND_MAX ) * 4 - 2;
vertex_xyz[ 3 * i + 1 ] =( rand() /( float ) RAND_MAX ) * 4 - 2;
vertex_xyz[ 3 * i + 2 ] =( rand() /( float ) RAND_MAX ) * 4 - 2;
color_rgb[ 3 * i + 0 ] =( rand() /( float ) RAND_MAX );
color_rgb[ 3 * i + 1 ] =( rand() /( float ) RAND_MAX );
color_rgb[ 3 * i + 2 ] =( rand() /( float ) RAND_MAX );
}
listid = glGenLists( 3 );
glNewList( listid + 0, GL_COMPILE );
Points();
glEndList();
glNewList( listid + 1, GL_COMPILE );
Lines();
glEndList();
glNewList( listid + 2, GL_COMPILE );
Triangles();
glEndList();
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Tablice wierzchołków" );
#else
glutCreateWindow( "Tablice wierzcholkow" );
#endif
glutDisplayFunc( DisplayScene );
glutReshapeFunc( Reshape );
int MenuTest = glutCreateMenu( Menu );
#ifdef WIN32
glutAddMenuEntry( "Punkty bez listy wyświetlania", POINTS );
glutAddMenuEntry( "Punkty z listą wyświetlania", POINTS_DL );
glutAddMenuEntry( "Punkty z tablicami wierzchołków", POINTS_VA );
glutAddMenuEntry( "Odcinki bez listy wyświetlania", LINES );
glutAddMenuEntry( "Odcinki z listą wyświetlania", LINES_DL );
glutAddMenuEntry( "Odcinki z tablicami wierzchołków", LINES_VA );
glutAddMenuEntry( "Trójkąty bez listy wyświetlania", TRIANGLES );
glutAddMenuEntry( "Trójkąty z listą wyświetlania", TRIANGLES_DL );
glutAddMenuEntry( "Trójkąty z tablicami wierzchołków", TRIANGLES_VA );
#else
glutAddMenuEntry( "Punkty bez listy wyswietlania", POINTS );
glutAddMenuEntry( "Punkty z lista wyswietlania", POINTS_DL );
glutAddMenuEntry( "Punkty z tablicami wierzcholkow", POINTS_VA );
glutAddMenuEntry( "Odcinki bez listy wyswietlania", LINES );
glutAddMenuEntry( "Odcinki z lista wyswietlania", LINES_DL );
glutAddMenuEntry( "Odcinki z tablicami wierzcholkow", LINES_VA );
glutAddMenuEntry( "Trojkaty bez listy wyswietlania", TRIANGLES );
glutAddMenuEntry( "Trojkaty z lista wyswietlania", TRIANGLES_DL );
glutAddMenuEntry( "Trojkaty z tablicami wierzcholkow", TRIANGLES_VA );
#endif
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
glutAddSubMenu( "Test", MenuTest );
glutAddSubMenu( "Aspekt obrazu", MenuAspect );
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
GenerateDisplayLists();
glutIdleFunc( DisplayScene );
glutMainLoop();
return 0;
}
Drugi i ostatni program przykładowy (plik indeksowe_tablice_wierzcholkow.cpp) korzysta z indeksowych tablic wierzchołków. Dane tablic wierzchołków zebrane są w dwóch tabelach. Pierwsza tabela zawiera współrzędne wektorów normalnych i wierzchołków rysowanego obiektu - dwudziestościanu, natomiast druga tabela zawiera składowe kolorów wierzchołków dwudziestościanu. Stąd przy definiowaniu danych użyto zarówno funkcji glInterleavedArrays jak i glColorPointer.
Dodatkowo program sprawdza czy implementacja biblioteki OpenGL obsługuje rozszerzenie EXT draw range elements. Jeżeli tak to do rysowania dwudziestościanu używana jest funkcja glDrawRangeElements. W przeciwnym wypadku obiekt rysuje funkcja glDrawElements.
Efekt działania programu z przyznajmy szczerze zupełnie przypadkowo dobranymi kolorami wierzchołków przedstawia rysunek 3.
Plik indeksowe_tablice_wierzcholkow.cpp
#include <GL/glut.h>
#include <GL/glext.h>
#ifndef WIN32
#define GLX_GLXEXT_LEGACY
#include <GL/glx.h>
#define wglGetProcAddress glXGetProcAddressARB
#endif
#include <stdlib.h>
#include <stdio.h>
PFNGLDRAWRANGEELEMENTSEXTPROC glDrawRangeElementsEXT = NULL;
enum
{
FULL_WINDOW,
ASPECT_1_1,
EXIT
};
int aspect = FULL_WINDOW;
#ifdef near
#undef near
#endif
#ifdef far
#undef far
#endif
const GLdouble left = - 2.0;
const GLdouble right = 2.0;
const GLdouble bottom = - 2.0;
const GLdouble top = 2.0;
const GLdouble near = 3.0;
const GLdouble far = 7.0;
GLfloat rotatex = 0.0;
GLfloat rotatey = 0.0;
int button_state = GLUT_UP;
int button_x, button_y;
GLfloat scale = 2.0;
GLfloat normal_vertex[ 12 * 6 ] =
{
0.000000, 0.848013, 0.529976, 0.000, 0.667, 0.500,
0.000000, 0.848013, - 0.529976, 0.000, 0.667, - 0.500,
0.000000, - 0.848013, - 0.529976, 0.000, - 0.667, - 0.500,
0.000000, - 0.848013, 0.529976, 0.000, - 0.667, 0.500,
0.848013, 0.529976, 0.000000, 0.667, 0.500, 0.000,
0.848013, - 0.529976, 0.000000, 0.667, - 0.500, 0.000,
- 0.848013, - 0.529976, 0.000000, - 0.667, - 0.500, 0.000,
- 0.848013, 0.529976, 0.000000, - 0.667, 0.500, 0.000,
0.529976, 0.000000, 0.848013, 0.500, 0.000, 0.667,
- 0.529976, 0.000000, 0.848013, - 0.500, 0.000, 0.667,
- 0.529976, 0.000000, - 0.848013, - 0.500, 0.000, - 0.667,
0.529976, 0.000000, - 0.848013, 0.500, 0.000, - 0.667
};
GLfloat color[ 12 * 3 ] =
{
1.0, 0.0, 0.0,
1.0, 0.0, 1.0,
1.0, 0.0, 0.0,
1.0, 0.0, 1.0,
0.0, 1.0, 0.0,
1.0, 1.0, 0.0,
0.0, 1.0, 0.0,
1.0, 1.0, 0.0,
0.0, 0.0, 1.0,
0.0, 1.0, 1.0,
0.0, 0.0, 1.0,
0.0, 1.0, 1.0
};
GLubyte triangles[ 20 * 3 ] =
{
2, 10, 11,
1, 11, 10,
1, 10, 7,
1, 4, 11,
0, 1, 7,
0, 4, 1,
0, 9, 8,
3, 8, 9,
0, 7, 9,
0, 8, 4,
3, 9, 6,
3, 5, 8,
2, 3, 6,
2, 5, 3,
2, 6, 10,
2, 11, 5,
6, 7, 10,
6, 9, 7,
4, 5, 11,
4, 8, 5
};
void Display()
{
glClearColor( 1.0, 1.0, 1.0, 1.0 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glMatrixMode( GL_MODELVIEW );
glLoadIdentity();
glTranslatef( 0, 0, -( near + far ) / 2 );
glRotatef( rotatex, 1.0, 0.0, 0.0 );
glRotatef( rotatey, 0.0, 1.0, 0.0 );
glScalef( scale, scale, scale );
glEnable( GL_DEPTH_TEST );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );
glEnable( GL_NORMALIZE );
glEnable( GL_COLOR_MATERIAL );
glInterleavedArrays( GL_N3F_V3F, 0, normal_vertex );
glEnableClientState( GL_COLOR_ARRAY );
glColorPointer( 3, GL_FLOAT, 0, color );
if( glDrawRangeElementsEXT == NULL )
glDrawElements( GL_TRIANGLES, 20 * 3, GL_UNSIGNED_BYTE, triangles );
else
glDrawRangeElementsEXT( GL_TRIANGLES, 0, 11, 20 * 3, GL_UNSIGNED_BYTE, triangles );
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 );
Display();
}
void Keyboard( unsigned char key, int x, int y )
{
if( key == '+' )
scale += 0.05;
else
if( key == '-' && scale > 0.05 )
scale -= 0.05;
Display();
}
void SpecialKeys( int key, int x, int y )
{
switch( key )
{
case GLUT_KEY_LEFT:
rotatey -= 1;
break;
case GLUT_KEY_UP:
rotatex -= 1;
break;
case GLUT_KEY_RIGHT:
rotatey += 1;
break;
case GLUT_KEY_DOWN:
rotatex += 1;
break;
}
Reshape( glutGet( GLUT_WINDOW_WIDTH ), glutGet( GLUT_WINDOW_HEIGHT ) );
}
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 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 ExtensionSetup()
{
const char * version =( char * ) glGetString( GL_VERSION );
int major = 0, minor = 0;
if( sscanf( version, "%d.%d", & major, & minor ) != 2 )
{
#ifdef WIN32
printf( "Błędny format wersji OpenGL\n" );
#else
printf( "Bledny format wersji OpenGL\n" );
#endif
exit( 0 );
}
if( major > 1 || minor >= 2 )
{
glDrawRangeElementsEXT =
( PFNGLDRAWRANGEELEMENTSEXTPROC ) wglGetProcAddress( "glDrawRangeElements" );
}
else
if( glutExtensionSupported( "GL_EXT_draw_range_elements" ) )
{
glDrawRangeElementsEXT =
( PFNGLDRAWRANGEELEMENTSEXTPROC ) wglGetProcAddress( "glDrawRangeElementsEXT" );
}
else
{
printf( "Brak rozszerzenia EXT_draw_range_elements!\n" );
glDrawRangeElementsEXT = NULL;
}
}
int main( int argc, char * argv[] )
{
glutInit( & argc, argv );
glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize( 500, 500 );
#ifdef WIN32
glutCreateWindow( "Indeksowe tablice wierzchołków" );
#else
glutCreateWindow( "Indeksowe tablice wierzcholkow" );
#endif
glutDisplayFunc( Display );
glutReshapeFunc( Reshape );
glutKeyboardFunc( Keyboard );
glutSpecialFunc( SpecialKeys );
glutMouseFunc( MouseButton );
glutMotionFunc( MouseMotion );
glutCreateMenu( Menu );
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( "Aspekt obrazu", MenuAspect );
#ifdef WIN32
glutAddMenuEntry( "Wyjście", EXIT );
#else
glutAddMenuEntry( "Wyjscie", EXIT );
#endif
glutAttachMenu( GLUT_RIGHT_BUTTON );
ExtensionSetup();
glutMainLoop();
return 0;
}