Dziś będziemy kontynuować naszą przygodę z malowaniem, tudzież rysowaniem. Po ostatnich wywodach pewnie każdy wie jak poprawnie trzymać pędzel w ręku. Dziś w końcu będziemy mogli uwolnić nasz talent i stworzyć nasze pierwsze, wielkie dzieło - "Hello world".
Funkcja glEnableClientState informuje maszynę stanów OpenGL, z jakiego typu tablic będziemy korzystać. Stała GL_VERTEX_ARRAY może być nieco myląca, bo nie odnosi się do wierzchołka jako takiego, lecz tylko do jego pozycji. Przekazując GL_COLOR_ARRAY odblokowujemy możliwość renderowania kolorowych vertexów. Następne dwie funkcje (glVertexPointer oraz glColorPointer) pokazują lokalizacje tablic z pozycją i kolorem wierzchołka. Dlaczego mówię tu o tablicach? Bo dla OpenGL'a są to w zasadzie dwie tablice. Nic nie stoi na przeszkodzie, by także z naszego punktu widzenia były to dwie oddzielne zmienne, jednak z powodów edukacyjnych zostaniemy przy tym rozwiązaniu. Także w przyszłości, gdy poznamy już bufory wierzchołków i indeksów, będziemy stosować takie podejście. Parametry przyjmowane przez obie funkcje są takie same. Na początek podajemy liczbę komponentów składających się na pozycję (x, y, z, w - 4) i kolor (r, g, b - 3). Następnie przekazujemy typ użyty do ich reprezentacji. W naszym przypadku jest to float (stała GL_FLOAT). Inne możliwe to: GL_SHORT, GL_INT oraz GL_DOUBLE. Kolejny parametr pokazuje jak daleko (w bajtach) oddalone są od siebie analogiczne parametry, w określonej tablicy. Jeśli dla pozycji i kolorów chcielibyśmy użyć dwóch oddzielnych tablic, parametr ten miałby wartość zero. Dane byłby wówczas upakowane ściśle obok siebie. Gdy jednak, tablica zawiera więcej informacji, przekazujemy wielkość wierzchołka w bajtach. Ostatni parametr to wskaźnik na zerowy element danej tablicy.
Wierzchołki i indeksy
W świecie OpenGL'a podstawowym budulcem wyświetlanych obiektów są wierzchołki. Za ich pomocą możemy namalować pojedynczy punkt, linię oraz trójkąt. W naszym przypadku nawet koło będzie składać się ze skończonej liczby elementów, choć w grafice dwuwymiarowej niekoniecznie musi to być prawdą. Jak było wspomniane w poprzedniej notce, wierzchołek składa się z atrybutów. Podstawowym atrybutem jaki musi mieć każdy wierzchołek to jego pozycja, w lokalnym układzie współrzędnych modelu. Zazwyczaj jest to wektor czterowymiarowy (x, y, z, w). Ostatnia współrzędna nie jest jawnie używana. Potrzebna jest ona do przekształceń macierzowych i koniec końców, ustawiamy jej wartość na jeden. Dodatkowo wierzchołek może mieć kolor (np. r, g, b, a) lub informacje o położeniu tekstury - texcoord, przekazywane jako wektor dwuwymiarowy.
Wszystko co będziemy renderować, będzie stworzone z trójkątów - jednego z trzech wyżej wymienionych prymitywów. Każdy model, we współczesnych grach trójwymiarowych, jest obiektem stworzonym z dużej liczby trójboków. Dzieje się tak dlatego, że dostępne dziś na rynku układy graficzne, zoptymalizowane są właśnie do pracy z trójkątami. Nawet gdy będziemy operować na zwykłych sprite'ach, znanych choćby z biblioteki SDL, to dalej dla nas będą to wieloboki - choć znacznie prostsze.
![]() |
Kolejność przeciwna do ruchu wskazówek zegara (counterclockwise direction; CCW) |
Tutaj przechodzimy do kilku podstawowych kwestii technicznych. Przede wszystkim kolejność ułożenia wierzchołków: zgodna lub przeciwna z ruchem wskazówek zegara. Dlaczego ma to znaczenie? W grę wchodzą zagadnienia optymalizacyjne. Mając konkretnie zdefiniowaną kolejność, możemy określić przednią oraz tylną stronę wielokąta i renderować tylko jedną z nich (zazwyczaj przednią). Domyślnym trybem jest ten pokazany na rysunku obok - przeciwny do ruchów wskazówek zegara. Oczywiście możemy go zmienić.
Pozostaje jeszcze jedna wątpliwość - sam proces budowania prymitywów z wierzchołków. Rozważmy prosty przykład - prostokąt. Jest to wielokąt składający się z czterech wierzchołków. Aby go wyświetlić jednak potrzebujemy dwóch trójkątów, czyli o dwa wierzchołki więcej. Dla małych modeli taka nadmiarowość ma niewielkie znaczenie, jednak dla wirtualnych środowisk składających się z milionów wielokątów, może być to poważny narzut danych. Jak sobie z tym poradzić? Stosując indeksację. Wystarczy, że do funkcji renderującej przekażemy dwie informacje: samą listę wierzchołków oraz trójki indeksów wierzchołków, budujących trójkąty. Brzmi lekko skomplikowanie, ale w rzeczywistości jest to bardzo proste. Popatrzmy na rysunek obok. Mamy tam przedstawiony kwadrat składający się z dwóch trójkątów i czterech wierzchołków. Wierzchołki dwa i cztery są wspólne dla obu prymitywów. Pierwszy trójkąt zatem będzie się składał z wierzchołków o numerach: zero, jeden i dwa, zaś drugi budują wierzchołki dwa, trzy i zero. To w zasadzie wszystko. Mając te informacje, możemy budować geometrię dla naszych lokacji.
![]() |
Indeksowanie wierzchołków |
Tablice wierzchołków i indeksów
Pokodujmy więc chwilę. Na początek zdefiniujmy wszystkie niezbędne typy:
struct Vec4d
{
float x, y, z, w;
};
struct ColorRGB
{
float r, g, b;
};
struct Vertex
{
Vec4d
position;
ColorRGB
color;
};
struct Face
{
unsigned short int v0, v1, v2;
};
Jak widać, nasz wierzchołek posiada dwa atrybuty: czterowymiarową pozycję i kolor. Dodatkowym typem jest Face. Struktura ta będzie odpowiedzialna za przechowywanie indeksów wielokąta (jak to bywa w C, indeksujemy od zera). Możemy zatem zająć się tworzeniem i ładowaniem geometrii. Przykładem niech będzie wspomniany wcześniej kwadrat. Tablica wierzchołków i indeksów, może być dla niego zdefiniowana następująco:
Vertex vertices[] =
{
/* x, y, z, w, r, g, b */
Myślę, że przedstawiony wyżej kod jest w miarę oczywisty. Współrzędne wierzchołków podajemy w lokalnym dla modelu, układzie współrzędnych.
Vertex vertices[] =
{
/* x, y, z, w, r, g, b */
{0.5f,
0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f},
{-0.5f,
0.5f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f},
{-0.5f,
-0.5f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f},
{0.5f,
-0.5f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f}
};
Face
faces[] =
{
{0,
1, 2},
{2,
3, 0}
};
Myślę, że przedstawiony wyżej kod jest w miarę oczywisty. Współrzędne wierzchołków podajemy w lokalnym dla modelu, układzie współrzędnych.
Malujemy!
Czas na główny gwóźdź programu, czyli renderowanie. Malować będziemy w pętli głównej przed podmianą buforów. Jak to zwykle bywa, wyświetlanie składać się będzie z kilku krótkich kroków.
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(4, GL_FLOAT, sizeof(Vertex), vertices);
glColorPointer(3, GL_FLOAT, sizeof(Vertex),
(float*)(vertices) + 4);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT,
faces);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
![]() |
Wynik działania programu |
Doszliśmy w końcu do malowania - funkcja glDrawElements. Wyrenderuje ona dla nas indeksowaną geometrię. Na wstępie przekazujemy typ rysowanych prymitywów - trójkąty. Jeśli chcielibyśmy zobaczyć punkty przekazalibyśmy GL_POINTS, zaś dla linii GL_LINES. Kolejny parametr to liczba wierzchołków do wyświetlenia, zaś GL_UNSIGNED_SHORT to typ danych użytych jako nasze indeksy wierzchołków (unsigned char -
GL_UNSIGNED_BYTE, unsigned int - GL_UNSIGNED_INT). Ostatni parametr to wskaźnik na indeksy. Jeszcze tylko blokujemy (glDisableClientState) możliwość używania tablic pozycji i koloru wierzchołka.
I to tyle. Naszym oczom powinien ukazać się piękny, wielokolorowy kwadrat (a w zasadzie prostokąt, ale o tym następnym razem).
Solucja do pobrania tu.I to tyle. Naszym oczom powinien ukazać się piękny, wielokolorowy kwadrat (a w zasadzie prostokąt, ale o tym następnym razem).
Brak komentarzy:
Prześlij komentarz