Postanowiłem napisać trochę o aspekcie programowania i o tym, jak komponenty ze sobą rozmawiają. Może rzuci trochę światła na niektóre obszary.
Prezentacja
Co trzeba zrobić, aby ten pojedynczy obraz, który opublikowałeś w swoim pytaniu, został narysowany na ekranie?
Istnieje wiele sposobów narysowania trójkąta na ekranie. Dla uproszczenia załóżmy, że nie użyto buforów wierzchołków. ( Bufor wierzchołków to obszar pamięci, w którym przechowujesz współrzędne.) Załóżmy, że program po prostu powiedział potokowi przetwarzania grafiki o każdym pojedynczym wierzchołku (wierzchołek jest tylko współrzędną w przestrzeni) z rzędu.
Ale zanim cokolwiek narysujemy, najpierw musimy uruchomić rusztowanie. Zobaczymy, dlaczego później:
// Clear The Screen And The Depth Buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Reset The Current Modelview Matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Drawing Using Triangles
glBegin(GL_TRIANGLES);
// Red
glColor3f(1.0f,0.0f,0.0f);
// Top Of Triangle (Front)
glVertex3f( 0.0f, 1.0f, 0.0f);
// Green
glColor3f(0.0f,1.0f,0.0f);
// Left Of Triangle (Front)
glVertex3f(-1.0f,-1.0f, 1.0f);
// Blue
glColor3f(0.0f,0.0f,1.0f);
// Right Of Triangle (Front)
glVertex3f( 1.0f,-1.0f, 1.0f);
// Done Drawing
glEnd();
Co to zrobiło?
Kiedy piszesz program, który chce korzystać z karty graficznej, zwykle wybierasz interfejs sterownika. Niektóre dobrze znane interfejsy do sterownika to:
W tym przykładzie będziemy trzymać się OpenGL. Teraz interfejs do sterownika zapewnia wszystkie narzędzia potrzebne do tego, aby program komunikował się z kartą graficzną (lub sterownikiem, który następnie komunikuje się z kartą).
Ten interfejs ma pewne narzędzia . Narzędzia te mają kształt interfejsu API, który można wywołać z poziomu programu.
Ten interfejs API jest tym, co widzimy w powyższym przykładzie. Przyjrzyjmy się bliżej.
Rusztowanie
Zanim naprawdę będziesz mógł wykonać rzeczywisty rysunek, musisz wykonać konfigurację . Musisz zdefiniować swoją rzutnię (obszar, który będzie faktycznie renderowany), swoją perspektywę ( aparat do swojego świata), jakiego antyaliasingu będziesz używał (aby wygładzić krawędź trójkąta) ...
Ale na to nie spojrzymy. Rzućmy okiem na rzeczy, które będziesz musiał zrobić w każdej klatce . Lubić:
Czyszczenie ekranu
Potok grafiki nie wyczyści ekranu dla każdej klatki. Musisz to powiedzieć. Dlaczego? Dlatego:
Jeśli nie wyczyścisz ekranu, po prostu narysujesz go na każdej klatce. Dlatego dzwonimy glClear
z GL_COLOR_BUFFER_BIT
zestawem. Drugi bit ( GL_DEPTH_BUFFER_BIT
) mówi OpenGL, aby wyczyścił bufor głębokości . Bufor ten służy do określania, które piksele znajdują się przed (lub za) innymi pikselami.
Transformacja
Źródło obrazu
Transformacja to część, w której bierzemy wszystkie współrzędne wejściowe (wierzchołki naszego trójkąta) i stosujemy naszą macierz ModelView. Jest to macierz, która wyjaśnia, w jaki sposób nasz model (wierzchołki) są obracane, skalowane i tłumaczone (przenoszone).
Następnie stosujemy naszą macierz projekcji. Powoduje to przesuwanie wszystkich współrzędnych, aby były skierowane w stronę naszego aparatu.
Teraz przekształcamy się jeszcze raz, dzięki naszej matrycy Viewport. Robimy to, aby skalować nasz model do rozmiaru naszego monitora. Teraz mamy zestaw wierzchołków, które są gotowe do renderowania!
Wrócimy do transformacji nieco później.
Rysunek
Narysować trójkąt, możemy po prostu powiedzieć OpenGL, aby rozpocząć nową listę trójkątów wywołując glBegin
przy GL_TRIANGLES
stałej.
Można również narysować inne formy. Jak trójkątny pasek lub trójkątny wachlarz . Są to przede wszystkim optymalizacje, ponieważ wymagają mniejszej komunikacji między CPU a GPU, aby narysować taką samą liczbę trójkątów.
Następnie możemy dostarczyć listę zestawów 3 wierzchołków, które powinny tworzyć każdy trójkąt. Każdy trójkąt używa 3 współrzędnych (jak w przestrzeni 3D). Dodatkowo podaję również kolor dla każdego wierzchołka, dzwoniąc glColor3f
przed wywołaniem glVertex3f
.
Cień między 3 wierzchołkami (3 narożniki trójkąta) jest obliczany automatycznie przez OpenGL . Będzie interpolować kolor na całej powierzchni wielokąta.
Interakcja
Teraz, kiedy klikniesz okno. Aplikacja musi tylko przechwycić komunikat okna sygnalizujący kliknięcie. Następnie możesz uruchomić dowolną akcję w swoim programie.
Staje się to o wiele trudniejsze, gdy chcesz rozpocząć interakcję ze sceną 3D.
Najpierw musisz wyraźnie wiedzieć, przy którym pikselu użytkownik kliknął okno. Następnie, biorąc pod uwagę twoją perspektywę , możesz obliczyć kierunek promienia od momentu kliknięcia myszą w twoją scenę. Następnie możesz obliczyć, czy jakikolwiek obiekt w scenie przecina się z tym promieniem . Teraz wiesz, czy użytkownik kliknął obiekt.
Jak więc się obraca?
Transformacja
Mam świadomość dwóch rodzajów transformacji, które są ogólnie stosowane:
- Transformacja macierzowa
- Transformacja kości
Różnica polega na tym, że kości wpływają na pojedyncze wierzchołki . Macierze zawsze wpływają na wszystkie narysowane wierzchołki w ten sam sposób. Spójrzmy na przykład.
Przykład
Wcześniej ładowaliśmy naszą matrycę tożsamości przed narysowaniem naszego trójkąta. Macierz tożsamości to taka, która po prostu nie zapewnia żadnej transformacji . Tak więc na cokolwiek rysuję, wpływa tylko moja perspektywa. Tak więc trójkąt w ogóle nie zostanie obrócony.
Jeśli chcę, aby obrócić go teraz, mogę też zrobić matematyki ja (CPU) i po prostu zadzwonić glVertex3f
z innych współrzędnych (które są obrócone). Albo mógłbym pozwolić GPU wykonać całą pracę, dzwoniąc glRotatef
przed rysowaniem:
// Rotate The Triangle On The Y axis
glRotatef(amount,0.0f,1.0f,0.0f);
amount
jest oczywiście tylko stałą wartością. Jeśli chcesz animować , musisz śledzić amount
i zwiększać ją w każdej klatce.
Więc poczekaj, co się stało z całą rozmową matrycy wcześniej?
W tym prostym przykładzie nie musimy przejmować się matrycami. Po prostu dzwonimy glRotatef
i dba o to wszystko za nas.
glRotate
tworzy obrót angle
stopni wokół wektora xyz. Bieżąca macierz (patrz glMatrixMode ) jest mnożona przez macierz obrotu, a produkt zastępuje bieżącą macierz, tak jakby glMultMatrix został wywołany z następującą macierzą jako argumentem:
x 2 1 - c + cx y 1 - c - z sx z 1 - c + y s 0 y x 1 - c + z sy 2 1 - c + cy z 1 - c - x s 0 x z 1 - c - y sy z 1 - c + x sz 2 1 - c + c 0 0 0 0 1
Dzięki za to!
Wniosek
Staje się oczywiste, że wiele mówi się o OpenGL. Ale to nic nam nie mówi . Gdzie jest komunikacja?
Jedyne, co OpenGL mówi nam w tym przykładzie, to kiedy to się skończy . Każda operacja zajmie określoną ilość czasu. Niektóre operacje trwają niewiarygodnie długo, inne są niezwykle szybkie.
Wysłanie wierzchołka do GPU będzie tak szybkie, że nawet nie wiedziałbym, jak to wyrazić. Przesłanie tysięcy wierzchołków z CPU do GPU, każdej pojedynczej klatki, najprawdopodobniej nie stanowi żadnego problemu.
Wyczyszczenie ekranu może potrwać milisekundę lub gorzej (pamiętaj, że zwykle masz tylko około 16 milisekund czasu na narysowanie każdej klatki), w zależności od tego, jak duża jest twoja rzutnia. Aby go wyczyścić, OpenGL musi narysować każdy piksel w kolorze, który chcesz wyczyścić, może to być miliony pikseli.
Poza tym możemy jedynie zapytać OpenGL o możliwości naszego adaptera graficznego (maksymalna rozdzielczość, maksymalne wygładzanie, maksymalna głębia kolorów, ...).
Ale możemy również wypełnić teksturę pikselami, z których każdy ma określony kolor. Każdy piksel ma zatem wartość, a tekstura jest gigantycznym „plikiem” wypełnionym danymi. Możemy załadować to na kartę graficzną (poprzez utworzenie bufora tekstur), a następnie załadować moduł cieniujący , powiedzieć temu modułowi cieniującemu, aby użył naszej tekstury jako danych wejściowych i wykonał bardzo ciężkie obliczenia na naszym „pliku”.
Następnie możemy „renderować” wynik naszych obliczeń (w postaci nowych kolorów) na nową teksturę.
W ten sposób możesz sprawić, by procesor graficzny działał dla Ciebie na inne sposoby. Zakładam, że CUDA działa podobnie do tego aspektu, ale nigdy nie miałem okazji z nim pracować.
Naprawdę tylko nieznacznie dotknęliśmy całego tematu. Programowanie grafiki 3D to piekielnie bestia.
Źródło obrazu