Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00802 010674 7469746 na godz. na dobę w sumie
OpenGL. Księga eksperta. Wydanie VII - ebook/pdf
OpenGL. Księga eksperta. Wydanie VII - ebook/pdf
Autor: , , Liczba stron: 680
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-283-2110-6 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> gry >> opengl - programowanie
Porównaj ceny (książka, ebook (-20%), audiobook).
Biblioteka OpenGL jest potężnym systemem graficznym, doskonałym API do generowania grafiki trójwymiarowej w czasie rzeczywistym. System ten nadaje się znakomicie do wizualizacji wszelkiego rodzaju odwzorowań zjawisk fizycznych czy obiektów technicznych, a także do przedstawiania symulacji ze zmieniającymi się parametrami. Często jest wykorzystywany do pisania gier komputerowych. Daje możliwość tworzenia świetnej grafiki na wiele różnych platform z wykorzystaniem tych samych zestawów instrukcji. Co ważne, OpenGL jest całkowicie darmową biblioteką, a dostępność licznych rozszerzeń znakomicie zwiększa wachlarz jej zastosowań.

Niniejsza książka stanowi doskonałe wprowadzenie w tematykę OpenGL dla każdego programisty, nawet dla osób niezbyt biegłych w zagadnieniach grafiki komputerowej. Zawiera opis całego głównego API, kluczowych rozszerzeń i wszystkich typów shaderów z uwzględnieniem najnowszych elementów biblioteki. Wyjaśniono tu zasady działania OpenGL i opisano zagadnienia potoków graficznych. Stopniowo czytelnik jest zaznajamiany z coraz bardziej złożonymi technikami. W książce znalazły się liczne przykłady kodu działającego na kilku popularnych platformach. Warto podkreślić, że autorzy poza API przedstawili również najlepsze praktyki programistyczne.

W tej książce opisano między innymi:

OpenGL? Kreatywnych ogranicza tylko wyobraźnia!

Znajdź podobne książki

Darmowy fragment publikacji:

Tytuł oryginału: OpenGL Superbible: Comprehensive Tutorial and Reference (7th Edition) Tłumaczenie: Rafał Jońca ISBN: 978-83-283-2107-6 Authorized translation from the English language edition, entitled: OPENGL SUPERBIBLE: COMPREHENSIVE TUTORIAL AND REFERENCE, Seventh Edition; ISBN 0672337479; by Graham Sellers; and by Richard S. Wright, Jr; and by Nicholas Haemel; published by Pearson Education, Inc, publishing as Addison Wesley. Copyright © 2016 Pearson Education, Inc. All rights reserved. No part of this book may by reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc. Polish language edition published by HELION S.A. Copyright © 2016. OpenGL® is a registered trademark of Silicon Graphics Inc. and is used by permission of Khronos. Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: helion@helion.pl WWW: http://helion.pl (księgarnia internetowa, katalog książek) Pliki z przykładami omawianymi w książce można znaleźć pod adresem: ftp://ftp.helion.pl/przyklady/opglk7.zip Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/opglk7 Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. Printed in Poland. • Kup książkę • Poleć książkę • Oceń książkę • Księgarnia internetowa • Lubię to! » Nasza społeczność Spis treści Spis rysunków ............................................................................................ 11 Spis tabel .................................................................................................... 17 Spis listingów ............................................................................................. 19 O autorze .................................................................................................... 27 Przedmowa ................................................................................................. 29 Wstęp .......................................................................................................... 31 Cz(cid:246)(cid:264)(cid:232) I Podstawy Rozdział 1. Wprowadzenie ........................................................................................... 39 OpenGL i potok graficzny ................................................................................................39 Początki i ewolucja OpenGL ............................................................................................41 Prymitywy, potoki i piksele ..............................................................................................44 Podsumowanie ...................................................................................................................45 Rozdział 2. Pierwszy program OpenGL ....................................................................... 47 Tworzenie prostej aplikacji ..............................................................................................47 Korzystanie z shaderów ....................................................................................................49 Rysowanie pierwszego trójkąta ........................................................................................55 Podsumowanie ...................................................................................................................57 Rozdział 3. Wzdłuż potoku graficznego ...................................................................... 59 Przekazywanie danych do shadera wierzchołków ........................................................59 Przekazywanie danych z jednego etapu do drugiego ...................................................61 Teselacja ..............................................................................................................................64 Poleć książkęKup książkę Spis treści Shadery geometrii ............................................................................................................. 66 Składanie prymitywów, przycinanie i rasteryzacja ...................................................... 68 Shadery fragmentów ......................................................................................................... 71 Operacje dotyczące bufora ramki ................................................................................... 74 Shadery obliczeniowe ....................................................................................................... 75 Korzystanie z rozszerzeń OpenGL ................................................................................. 76 Podsumowanie .................................................................................................................. 80 Rozdział 4. Matematyka w grafice 3D ..........................................................................81 Czy to ten okrutny rozdział o matematyce? .................................................................. 81 Krótki kurs matematyki dotyczącej grafiki 3D ............................................................. 82 Zasady działania przekształceń ....................................................................................... 91 Interpolacja, linie, krzywe i powierzchnie parametryczne ........................................ 106 Podsumowanie ................................................................................................................ 112 Rozdział 5. Dane ...........................................................................................................115 Bufory ............................................................................................................................... 115 Zmienne typu uniform ................................................................................................... 129 Bloki magazynowe shadera ........................................................................................... 147 Liczniki niepodzielne ..................................................................................................... 152 Tekstury ............................................................................................................................ 156 Podsumowanie ................................................................................................................ 196 Rozdział 6. Shadery i programy ..................................................................................197 Opis języka ....................................................................................................................... 197 Kompilacja, łączenie i sprawdzanie programów ........................................................ 207 Podsumowanie ................................................................................................................ 222 Cz(cid:246)(cid:264)(cid:232) II Zg(cid:228)(cid:246)bianie tematu Rozdział 7. Przetwarzanie wierzchołków i polecenia rysowania ............................225 Przetwarzanie wierzchołków ......................................................................................... 225 Polecenia rysowania ....................................................................................................... 231 Przechowywanie przekształconych wierzchołków .................................................... 254 Przycinanie ...................................................................................................................... 267 Podsumowanie ................................................................................................................ 273 Rozdział 8. Przetwarzanie prymitywów .....................................................................275 Teselacja ........................................................................................................................... 275 Shadery geometrii ........................................................................................................... 297 Podsumowanie ................................................................................................................ 322 8 Poleć książkęKup książkę Spis treści Rozdział 9. Przetwarzanie fragmentów i bufor ramki ............................................. 323 Shadery fragmentów ...................................................................................................... 323 Testy dla poszczególnych fragmentów ........................................................................ 326 Wyjście koloru ................................................................................................................ 336 Rendering pozaekranowy .............................................................................................. 342 Antyaliasing ..................................................................................................................... 360 Zaawansowane formaty bufora ramki ......................................................................... 373 Sprite’y punktów ............................................................................................................. 390 Pobieranie utworzonego obrazu ................................................................................... 398 Podsumowanie ................................................................................................................ 404 Rozdział 10. Shadery obliczeniowe ............................................................................. 405 Korzystanie z shaderów obliczeniowych ..................................................................... 405 Przykłady ......................................................................................................................... 414 Podsumowanie ................................................................................................................ 433 Rozdział 11. Zaawansowane zarządzanie danymi ..................................................... 435 Eliminacja dowiązań ...................................................................................................... 435 Rzadko wypełnione tekstury ......................................................................................... 440 Kompresja tekstur .......................................................................................................... 445 Upakowane formaty danych ......................................................................................... 454 Wysokiej jakości filtrowanie tekstur ............................................................................ 455 Podsumowanie ................................................................................................................ 458 Rozdział 12. Sterowanie potokiem graficznym i monitorowanie go ....................... 459 Kolejki .............................................................................................................................. 459 Synchronizacja w OpenGL ............................................................................................ 477 Podsumowanie ................................................................................................................ 481 Cz(cid:246)(cid:264)(cid:232) III W praktyce Rozdział 13. Techniki renderowania ........................................................................... 485 Modele oświetlenia ......................................................................................................... 485 Rendering niefotorealistyczny ...................................................................................... 520 Alternatywne metody renderowania ........................................................................... 524 Grafika dwuwymiarowa ................................................................................................ 551 Podsumowanie ................................................................................................................ 561 Rozdział 14. OpenGL o wysokiej wydajności .............................................................. 563 Optymalizacja wydajności CPU ................................................................................... 563 OpenGL o niskim narzucie ........................................................................................... 576 Narzędzia do analizy wydajności ................................................................................. 595 Podsumowanie ................................................................................................................ 615 9 Poleć książkęKup książkę Spis treści Rozdział 15. Debugowanie i stabilność .......................................................................617 Debugowanie własnych aplikacji .................................................................................. 617 Bezpieczeństwo i szybkość ............................................................................................. 623 Podsumowanie ................................................................................................................ 626 Dodatek A Narzędzia związane z przykładami .........................................................627 Dodatek B Format pliku SBM .....................................................................................633 Dodatek C Funkcje i wersje OpenGL .........................................................................641 Dodatek D Słowniczek ................................................................................................663 Skorowidz ..................................................................................................669 10 Poleć książkęKup książkę ROZDZIAŁ 3. Wzdłuż potoku graficznego W tym rozdziale: (cid:120) Działanie każdego z etapów potoku graficznego OpenGL. (cid:120) Połączenie shaderów z etapami potoku o ustalonej funkcji. (cid:120) W jaki sposób napisać program wykorzystujący jednocześnie wszystkie etapy potoku gra- ficznego. Ten rozdział poświęcimy omówieniu potoku graficznego OpenGL, od jego etapu początkowego aż do samego końca. Opiszemy zarówno etapy dotyczące programowalnych bloków, jak i bloki o stałych funkcjach. W poprzednim rozdziale pokrótce przyjrzeliśmy się etapom shaderów wierzchołków i frag- mentów. Opis ten był jednak uproszczony, bo aplikacja dotyczyła tylko rysowania jednego, statycz- nego trójkąta. Jeśli chcemy narysować coś interesującego w OpenGL, musimy znacznie lepiej poznać OpenGL i jego potok. W tym rozdziale zajmiemy się wszystkimi elementami potoku, ich wzajem- nym połączeniem i zaprezentujemy przykład shadera dla każdego etapu potoku. Przekazywanie danych do shadera wierzchołków Shader wierzchołków to pierwszy programowalny etap potoku OpenGL. Jest to etap szczególny, bo to jedyny wymagany etap w całym potoku. Zanim jednak zostanie uruchomiony shader wierzchoł- ków, ma miejsce etap o stałej funkcji, nazywany pobieraniem wierzchołków (ang. vertex fetchning). Zapewnia on dane wejściowe dla shadera wierzchołków. Atrybuty wierzchołka W GLSL mechanizm pobierania i wysyłania danych z shaderów polega na zadeklarowaniu zmien- nych globalnych z kwalifikatorami in lub out. W rozdziale 2. pojawił się już kwalifikator out, gdy na listingu 2.4 ustawialiśmy kolor wyjściowy shadera fragmentów. Na początku potoku OpenGL wykorzystamy słowo kluczowe in do uzyskania danych wejściowych dla shadera wierzchołków. W etapach pośrednich mogą być stosowane zarówno elementy in, jak i out, aby móc przekazywać dane między etapami. Wkrótce do tego dojdziemy. Na razie pozostańmy przy zmiennej z kwalifi- katorem in. Taka zmienna przyjmie dane z zewnątrz, czyli tak naprawdę otrzyma dane z potoku Poleć książkęKup książkę Rozdział 3. Wzdłuż potoku graficznego OpenGL. W praktyce zostanie wypełniona automatycznie w etapie pobierania wierzchołków (etap o stałej funkcji). Zmienną tę nazywa się atrybutem wierzchołka (ang. vertex attribute). Atrybuty wierzchołka to sposób na przekazywanie danych wierzchołków do potoku OpenGL. Aby zadeklarować atrybut wierzchołka, zadeklaruj w shaderze wierzchołków zmienną z kwalifikatorem in. Przykład tego typu prezentuje listing 3.1, który jako dane wejściowe deklaruje zmienną offset. Listing 3.1. Deklaracja atrybutu wierzchołka #version 450 core // (cid:397)offset(cid:397) to wej(cid:286)ciowy atrybut wierzcho(cid:225)ka. layout (location = 0) in vec4 offset; void main(void) { const vec4 vertices[3] = vec4[3](vec4( 0.25, -0.25, 0.5, 1.0), vec4(t0.25, -0.25, 0.5, 1.0), vec4( 0.25, 0.25, 0.5, 1.0)); // Dodaj (cid:397)offset(cid:397) do umieszczonych na sztywno warto(cid:286)ci. gl_Position = vertices[gl_VertexID] + offset; } Listing 3.1 dodaje zmienną offset jako element wejściowy shadera wierzchołków. Ponieważ jest to wejście do pierwszego shadera w potoku, zostanie on wypełniony automatycznie przez etap pobierania wierzchołków. Możemy wskazać, co ma się znaleźć w zmiennej, wywołując jeden z wielu wariantów funkcji atrybutu wierzchołka — glVertexAttrib*(). Prototyp dla glVertexAttrib4fv() używanego w tym przykładzie ma postać: void glVertexAttrib4fv(GLuint index, const GLfloat * v); Parametr index służy jako referencja do atrybutu, a v jest wskaźnikiem nowych danych do umiesz- czenia w atrybucie. Trudno nie zauważyć, że w przykładzie tuż przed deklaracją atrybutu offset pojawił się fragment layout (location = 0). To kwalifikator układu (ang. layout qualifier), któ- rego użyliśmy do ustawienia pozycji atrybutu wierzchołka na wartość 0. Lokalizacja to wartość, którą przekażemy w index, aby odnieść się do atrybutu. Za każdym razem, gdy wywołujemy jedną z funkcji glVertexAttrib*() (a jest ich całkiem sporo), aktualizuje ona wartość atrybutu wierzchołka przekazywanego do shadera wierzchołków. Pozwoli nam to na animowanie trójkąta. Listing 3.2 zawiera uaktualnioną wersję funkcji renderującej, która aktualizuje w każdej klatce wartość zmiennej offset. Listing 3.2. Aktualizacja atrybutu wierzchołka // Funkcja renderuj(cid:261)ca. virtual void render(double currentTime) { const GLfloat color[] = { (float)sin(currentTime) * 0.5f + 0.5f, (float)cos(currentTime) * 0.5f + 0.5f, 0.0f, 1.0f }; glClearBufferfv(GL_COLOR, 0, color); 60 Poleć książkęKup książkę Przekazywanie danych z jednego etapu do drugiego // Utworzony wcze(cid:286)niej obiekt programu u(cid:298)ywany do renderingu. glUseProgram(rendering_program); GLfloat attrib[] = { (float)sin(currentTime) * 0.5f, (float)cos(currentTime) * 0.6f, 0.0f, 0.0f }; // Aktualizacja warto(cid:286)ci atrybutu wej(cid:286)ciowego 0. glVertexAttrib4fv(0, attrib); // Rysowanie trójk(cid:261)ta. glDrawArrays(GL_TRIANGLES, 0, 3); } Wskutek uruchomienia programu z listingu 3.2 trójkąt będzie się płynnie obracał wokół okna w płasz- czyźnie owalnej. Przekazywanie danych z jednego etapu do drugiego Do tej pory przekazywaliśmy dane wejściowe do shadera wierzchołków za pomocą atrybutu wierz- chołka i słowa kluczowego in. Dodatkowo wykorzystaliśmy bloki o stałej funkcji, doczytując lub zapisując dane we wbudowanych zmiennych typu gl_VertexID i gl_Position. Wykorzystaliśmy rów- nież słowo kluczowe out do wysłania danych na zewnątrz w shaderze fragmentów. Istnieje również możliwość wysyłania danych z shadera jednego etapu do shadera następnego etapu — służą do tego te same słowa kluczowe: in i out. W podobny sposób, jak powstała zmienna ze słowem klu- czowym out w shaderze fragmentów, co pozwoliło przesłać informację o kolorze, tak w shaderze wierzchołków można użyć zmiennych ze słowem kluczowym out. Wszystko, co jest oznaczone jako zmienna wyjściowa w jednym shaderze, trafia do zmiennej wejściowej o tej samej nazwie w następ- nym shaderze, o ile tylko użyto słowa kluczowego in. Jeśli shader wierzchołków zadeklaruje zmienną o nazwie vs_color i doda słowo kluczowe out, to shader fragmentów może odczytać wartość tej zmiennej, o ile również nosi ona nazwę vs_color i posiada słowo kluczowe in. Jeśli zmodyfikujemy nasz prosty shader wierzchołków zgodnie z listingiem 3.3 i wprowadzimy vs_color jako zmienną wyjściową oraz dodatkowo zmienimy shader fragmentów zgodnie z listin- giem 3.4, aby przyjmował zmienną wejściową vs_color, uzyskamy przekazanie wartości z jednego shadera do drugiego. W ten sposób shader fragmentów, zamiast wpisywać stałą wartość, może przeka- zać kolor uzyskany od shadera wierzchołków. Listing 3.3. Shader wierzchołków wysyłający dane #version 450 core // (cid:397)offset(cid:397) i (cid:397)color(cid:397) to wej(cid:286)ciowe atrybuty wierzcho(cid:225)ka. layout (location = 0) in vec4 offset; layout (location = 1) in vec4 color; // vs_color to warto(cid:286)(cid:252) wyj(cid:286)ciowa do przekazania do nast(cid:266)pnego shadera. out vec4 vs_color; void main(void) 61 Poleć książkęKup książkę Rozdział 3. Wzdłuż potoku graficznego { const vec4 vertices[3] = vec4[3](vec4( 0.25, -0.25, 0.5, 1.0), vec4(-0.25, -0.25, 0.5, 1.0), vec4( 0.25, 0.25, 0.5, 1.0)); // Dodaj (cid:397)offset(cid:397) do umieszczonych na sztywno warto(cid:286)ci. gl_Position = vertices[gl_VertexID] + offset; // Przekazanie do vs_color sta(cid:225)ej warto(cid:286)ci. vs_color = color; } Listing 3.4. Shader fragmentów z danymi wejściowymi #version 450 core // Dane z shadera wierzcho(cid:225)ków. in vec4 vs_color; // Wynik kierowany do bufora ramki. out vec4 color; void main(void) { // Proste przypisanie danych koloru z shadera wierzcho(cid:225)ków do bufora ramki. color = vs_color; } Listing 3.3 zawiera deklarację drugiej wartości wejściowej, o nazwie color, shadera wierzchołków (na pozycji 1.) i zapisuje ją w zmiennej vs_output. Shader fragmentów z listingu 3.4 pobiera tę war- tość i zapisuje w buforze ramki. W ten sposób wartość koloru przekazujemy bezpośrednio z wywo- łań glVertexAttrib*() przez shader wierzchołków i shader fragmentów aż do wynikowego bufora ramki. Innymi słowy, uzyskaliśmy możliwość rysowania trójkątów o różnych kolorach! Bloki interfejsu Deklarowanie zmiennych interfejsu pojedynczo jest najprostszym sposobem komunikacji między poszczególnymi shaderami. Z drugiej strony niemalże wszystkie bardziej złożone aplikacje będą przekazywały dosyć skomplikowane struktury między poszczególnymi etapami — mogą to być tablice, struktury i inne złożone układy zmiennych. Na szczęście istnieje mechanizm łączenia ze sobą kilku zmiennych, noszący nazwę bloku interfejsu (ang. interface block). Deklaracja bloku inter- fejsu przypomina deklarację struktury, ale korzysta ze słów kluczowych in lub out w zależności od tego, czy ma służyć jako wejście, czy wyjście shadera. Przykładową definicję bloku interfejsu przed- stawia listing 3.5. Listing 3.5. Shader wierzchołków z wyjściowym blokiem interfejsu #version 450 core // (cid:397)offset(cid:397) i (cid:397)color(cid:397) to wej(cid:286)ciowe atrybuty wierzcho(cid:225)ka. layout (location = 0) in vec4 offset; layout (location = 1) in vec4 color; 62 Poleć książkęKup książkę Przekazywanie danych z jednego etapu do drugiego // Deklaracja VS_OUT jako deklaracja wyj(cid:286)ciowego bloku interfejsu. out VS_OUT { vec4 color; // Wys(cid:225)anie (cid:397)color(cid:397) do nast(cid:266)pnego etapu. } vs_out; void main(void) { const vec4 vertices[3] = vec4[3](vec4( 0.25, -0.25, 0.5, 1.0), vec4(-0.25, -0.25, 0.5, 1.0), vec4( 0.25, 0.25, 0.5, 1.0)); // Dodaj (cid:397)offset(cid:397) do umieszczonych na sztywno warto(cid:286)ci. gl_Position = vertices[gl_VertexID] + offset; // Przekazanie do vs_color sta(cid:225)ej warto(cid:286)ci. vs_out.color = color; } Zauważ, że blok interfejsu z listingu 3.5 zawiera zarówno nazwę bloku (VS_OUT), jak i nazwę instancji (vs_out). Bloki interfejsu są parowane pomiędzy etapami na podstawie nazwy bloku (w tym wypadku VS_OUT), ale wewnątrz shadera stosuje się nazwę instancji (w tym wypadku vs_out). Modyfikacja shadera fragmentów, aby używał bloku interfejsu, może wyglądać tak jak na listingu 3.6. Listing 3.6. Shader fragmentów z wejściowym blokiem interfejsu #version 450 core // Deklaracja VS_OUT jako wej(cid:286)ciowy blok interfejsu. in VS_OUT { vec4 color; // Odebranie koloru z poprzedniego etapu. } fs_in; // Wynik kierowany do bufora ramki. out vec4 color; void main(void) { // Proste przypisanie danych koloru z shadera wierzcho(cid:225)ków do bufora ramki. color = fs_in.color; } Dopasowywanie bloków interfejsów po nazwie bloku z utrzymaniem niezależności nazw wewnętrz- nych ma dwa bardzo istotne cele. Po pierwsze, nazwa wewnętrzna stosowana w każdym z etapów może być inna, co pozwala między innymi uniknąć pewnej niezręczności w stosowaniu nazwy vs_out w shaderze fragmentów. Po drugie, umożliwia interfejsowi przejście z pojedynczego ele- mentu na tablicę, gdy przekracza granice niektórych etapów. Dotyczy to na przykład etapów zwią- zanych z shaderami wierzchołków, teselacji i geometrii, o czym wkrótce się przekonasz. Zauważ, że bloki interfejsu dotyczą tylko przenoszenia danych z jednego etapu do drugiego. Nie można ich użyć do grupowania danych wejściowych shadera wierzchołków i wyjścia z shadera fragmentów. 63 Poleć książkęKup książkę Rozdział 3. Wzdłuż potoku graficznego Teselacja Teselacja to proces przekształcania prymitywów wysokiego rzędu (nazywanych w OpenGL płatami lub powierzchniami parametrycznymi) na mniejsze i prostsze prymitywy (trójkąty) w celu wła- ściwego zrenderowania. OpenGL posiada wbudowaną i konfigurowalną funkcję obsługującą tese- lację — potrafi ona rozbijać czworoboki, trójkąty i linie na znacznie większą liczbę mniejszych trójkątów, linii i punktów, które można bezpośrednio przekazać do sprzętowego mechanizmu raste- ryzacji. Faza teselacji znajduje się w potoku OpenGL za etapem shadera wierzchołków i składa się z trzech części: shadera sterowania teselacją, mechanizmu teselacji o stałej funkcji i shadera wyli- czenia teselacji. Shadery sterowania teselacją Pierwszą fazą trójelementowego etapu teselacji jest shader sterowania teselacją (nazywany czasem po prostu shaderem sterującym, ang. tessellation control shader). Shader ten przyjmuje dane od shadera wierzchołków i ma dwa główne zadania: określić poziom teselacji dla mechanizmu teselacji i wygenerować dane wysyłane do shadera wyliczenia teselacji uruchamianego po głównej fazie dziele- nia wierzchołków. Teselacja w OpenGL działa poprzez rozbicie powierzchni wysokiego poziomu nazywanych płatami (ang. patch) na punkty, linie i trójkąty. Każdy płat składa się z pewnej liczby punktów sterujących (ang. control points). Ich liczbę konfiguruje się, wywołując funkcję glPatchParameteri() z para- metrem pname ustawionym na GL_PATCH_VERTICES i parametrem value ustawionym na liczbę punk- tów mających tworzyć płat. Prototyp funkcji glPatchParameteri() ma postać: void glPatchParameteri(GLenum pname, GLint value); Domyślnie liczba punktów sterujących na płat wynosi 3. Jeśli właśnie taka liczba punktów jest niezbędna (jak to ma miejsce w przykładowej aplikacji), nie trzeba tej funkcji w ogóle wywoływać. Maksymalna liczba punktów sterujących dla pojedynczego płata zależy od implementacji sterow- ników, ale OpenGL gwarantuje, że nie będzie mniejsza niż 32. Gdy teselacja zostaje uaktywniona, shader wierzchołków uruchamia się jeden raz na każdy punkt sterujący, a shader sterujący uaktywnia się dla każdej grupy punktów sterujących (każda grupa odpowiada liczbie wierzchołków płata). Innymi słowy, wierzchołki stają się punktami sterującymi, a wynik działania shadera wierzchołków trafia grupami do shadera sterującego jako jego dane wej- ściowe. Liczbę punktów sterujących na płat można zmienić wewnątrz shadera, więc liczba punktów wejściowych nie musi odpowiadać liczbie punktów wyjściowych. Liczbę punktów sterujących two- rzonych przez shader sterujący ustawia się za pomocą odpowiedniego kwalifikatora wyjściowego w kodzie źródłowym shadera. Kwalifikator ma postać: layout (vertices = N) out; W kodzie N oznacza liczbę punktów sterujących na płat. Shader sterujący odpowiada za wyliczenie wartości wynikowych punktów sterujących i za ustawienie współczynników teselacji płata wyni- kowego, który trafi do mechanizmu teselacji o stałej funkcji. Współczynniki teselacji umieszcza się we wbudowanych zmiennych wyjściowych gl_TessLevelInner i gl_TessLevelOuter. Wszystkie inne dane dla dalszych etapów potoków przekazuje się do zmiennych wyjściowych zdefiniowanych przez użytkownika (dotyczy to zarówno słowa kluczowego out, jak i specjalnej, wbudowanej tablicy gl_out). 64 Poleć książkęKup książkę Teselacja Listing 3.7 przedstawia prosty shader sterowania teselacją. Ustawia liczbę wyjściowych punktów sterujących na 3 (czyli taką samą jak liczba wejściowych punktów sterujących) za pomocą kon- strukcji layout (vertices = 3) out;. Kopiuje dane wejściowe na wyjście (używa wbudowanych zmiennych gl_in i gl_out) oraz ustawia oba poziomy teselacji (wewnętrzny i zewnętrzny) na poziom 5. Wyższe wartości spowodują powstanie gęstszej struktury, a niższe — mniej gęstej. Ustawienie wartości 0 jako poziomu teselacji w zasadzie spowoduje pominięcie całego płata. Listing 3.7. Pierwszy shader sterowania teselacją #version 450 core layout (vertices = 3) out; void main(void) { // Tylko, je(cid:286)li to wywo(cid:225)anie o identyfikatorze 0… if (gl_InvocationID == 0) { gl_TessLevelInner[0] = 5.0; gl_TessLevelOuter[0] = 5.0; gl_TessLevelOuter[1] = 5.0; gl_TessLevelOuter[2] = 5.0; } // Zawsze kopiuj wej(cid:286)cie na wyj(cid:286)cie. gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; } Wbudowana zmienna wejściowa gl_InvocationID służy jako indeks dla tablic gl_in i gl_out. Zmienna zawiera bazujący na zerze indeks punktu sterującego płata, nad którym w danym momencie pracuje shader sterowania teselacją. Mechanizm teselacji Mechanizm teselacji to część potoku OpenGL o stałej funkcji. Odpowiada za przetworzenie powierzchni wyższego rzędu, reprezentowanych przez płaty, na mniejsze elementy podstawowe, takie jak punkty, linie i trójkąty. Zanim mechanizm otrzyma konkretny płat, shader sterujący uzy- skuje punkty sterujące i ustawia współczynniki teselacji odpowiadające za proces konwersji. Utworzone przez mechanizm teselacji wynikowe wierzchołki trafiają do shadera wyliczenia teselacji. Mechanizm teselacji odpowiada za wyliczenie parametrów, które trafią do shadera wyliczenia — shader w razie potrzeby może przekształcić wynikowe prymitywy, zanim będą gotowe do rasteryzacji. Shadery wyliczenia teselacji Po wykonaniu teselacji jako stałej funkcji otrzymujemy zbiór wierzchołków utworzonych na pod- stawie źródłowego płata. Nowe wierzchołki trafiają do shadera wyliczenia teselacji (ang. tessellation evaluation shader). Shader ten, nazywany często w skrócie shaderem wyliczenia, zostaje urucho- miony dla każdego wierzchołka utworzonego przez mechanizm teselacji. Z tego powodu należy uważać ze złożonymi shaderami wyliczenia, ponieważ mogą być wykonane ogromną liczbę razy. W szczególności należy ograniczyć korzystanie ze złożonych shaderów wyliczenia przy wysokich poziomach teselacji. 65 Poleć książkęKup książkę Rozdział 3. Wzdłuż potoku graficznego Listing 3.8 przedstawia shader wyliczenia, który przyjmuje wierzchołki wejściowe utworzone przez mechanizm teselacji sterowany shaderem sterującym z listingu 3.7. Na początku kwalifikator określa tryb teselacji. W tym wypadku wybraliśmy tryb generowania trójkątów. Pozostałe kwalifikatory, czyli equal_spacing i cw, wskazują, że wierzchołki mają być równo rozłożone na obszarze płata poddawanego teselacji, a kierunek podawania wierzchołków ma być zgodny z ruchem wskazówek zegara. Wszystkimi dostępnymi opcjami zajmiemy się w rozdziale 8., opisującym szczegółowo proces teselacji. Listing 3.8. Pierwszy shader wyliczenia teselacji #version 450 core layout (triangles, equal_spacing, cw) in; void main(void) { gl_Position = (gl_TessCoord.x * gl_in[0].gl_Position + gl_TessCoord.y * gl_in[1].gl_Position + gl_TessCoord.z * gl_in[2].gl_Position); } Pozostała część shadera przypisuje zmiennej gl_Position wartość, podobnie jak robi to shader wierz- chołków. Wyliczenie odbywa się przy użyciu dwóch dodatkowych zmiennych wbudowanych. Pierwszą zmienną jest gl_TessCoord, czyli współrzędna barycentryczna (ang. barycentric coordinate) wierz- chołka wygenerowanego przez mechanizm teselacji. Drugą jest składowa gl_Position struktury tablicy gl_in[]. Odpowiada ona strukturze gl_out wykorzystywanej w shaderze sterującym z lis- tingu 3.7. W zasadzie przedstawiony shader realizuje proste przekazanie efektów teselacji. Innymi słowy, obiekt wynikowy ma dokładnie taki sam kształt jak płat wejściowy. Aby zobaczyć wynik działania mechanizmu teselacji, trzeba poinformować OpenGL, żeby rysował jedynie zarysy wynikowych trójkątów. W tym celu użyjemy funkcji glPolygonMode() o następującym prototypie: void glPolygonMode(GLenum face, GLenum mode); Parametr face określa typ wieloboków, które chcemy zmienić. Ponieważ zamierzamy zmienić wszystko, przekazujemy stałą GL_FRONT_AND_BACK. Inne typy opiszemy już wkrótce. Parametr mode pokazuje sposób renderowania wieloboków. Jako że chcemy rysować w trybie siatki (czyli tylko linie), stosujemy stałą GL_LINE. Wynik renderowania jednego trójkąta przy włączonej teselacji i shade- rach z listingów 3.7 i 3.8 pokazuje rysunek 3.1. Shadery geometrii Shader geometrii (ang. geometry shader) to ostatni shader znajdujący się po tak zwanej stronie przed- niej — umieszczony jest za etapami wierzchołków i teselacji, ale przed rasteryzacją. Shader geo- metrii zostaje uruchomiony raz dla prymitywu i ma dostęp do wszystkich danych wejściowych wszystkich wierzchołków tworzących konkretny prymityw. Shader ten jest w pewnym sensie wyjąt- kowy, jako że potrafi zwiększyć lub zmniejszyć w sposób programowy ilość danych przechodzących przez potok. Co prawda shadery teselacji również wpływają na ilość pracy realizowanej przez potok, 66 Poleć książkęKup książkę Rysunek 3.1. Pierwszy trójkąt z teselacją Shadery geometrii ale robią to tylko pośrednio w wyniku zmiany poziomu teselacji. Shadery geometrii zawierają dwie funkcje — EmitVertex() i EndPrimitive() — które jawnie tworzą wierzchołki przekazywane póź- niej do etapu rasteryzacji. Jeszcze jedną ciekawą cechą shaderów geometrii jest to, że potrafią zmienić tryb prymitywu w środku potoku. Na przykład jako wejście mogą przyjmować trójkąty, ale jako wynik emitować linie lub punkty, a nawet tworzyć trójkąty z niezależnych punktów. Przykładowy shader geometrii przed- stawiono na listingu 3.9. Listing 3.9. Pierwszy shader geometrii #version 450 core layout (triangles) in; layout (points, max_vertices = 3) out; void main(void) { int i; for (i = 0; i gl_in.length(); i++) { gl_Position = gl_in[i].gl_Position; EmitVertex(); } } Shader przedstawiony na listingu 3.9 jest bardzo prostym shaderem przekazującym dane dalej, ale przy okazji konwertującym trójkąt na punkty, co umożliwia zobaczenie ich wierzchołków. Pierw- szy kwalifikator wskazuje, że shader geometrii oczekuje trójkątów jako danych wejściowych. Drugi kwalifikator informuje, że shader będzie tworzył punkty i będzie ich maksymalnie 3. W funkcji main() przekazuje dalej poszczególne składowe tablicy gl_in, wykorzystując w tym celu jej funkcję length(). 67 Poleć książkęKup książkę Rozdział 3. Wzdłuż potoku graficznego Tak naprawdę wiemy, że tablica będzie miała długość 3, ponieważ przetwarzamy trójkąty, a każdy trójkąt ma 3 wierzchołki. Wynik działania shadera geometrii przypomina wynik działania shadera wierzchołków. Przede wszystkim zapisujemy dane położenia w zmiennej gl_Position. Następnie wywołujemy EmitVertex(), aby utworzyć nowy wierzchołek w danych wyjściowych. Shadery geo- metrii automatycznie wywołują funkcję EndPrimitive() na końcu shadera, więc w tym konkretnym przykładzie użycie funkcji jest całkowicie zbędne. Shader spowoduje, że przekazany trójkąt zosta- nie zrenderowany jako 3 osobne punkty. Po wstawieniu shadera geometrii do przykładu z teselacją pojedynczego trójkąta uzyskamy efekt przedstawiony na rysunku 3.2. Aby efekt był lepiej widoczny, ustawiliśmy rozmiar punktów na 5.0, wywołując funkcję glPointSize(). Rysunek 3.2. Trójkąt po teselacji i dodaniu shadera geometrii Składanie prymitywów, przycinanie i rasteryzacja Po zrealizowaniu wszystkich zadań przedniej części potoku (czyli shadera wierzchołków, teselacji i shadera geometrii) stała część potoku realizuje serię zadań przetwarzających dane reprezentowane przez wierzchołki na serię pikseli, które zostaną pokolorowane i zapisane na ekranie jako element sceny. Pierwszy krok tego procesu polega na składaniu prymitywów, czyli grupowaniu wierzchoł- ków w linie i trójkąty. Składanie odbywa się również dla punktów, choć w tej sytuacji proces jest nie- zwykle prosty. Po utworzeniu prymitywów z poszczególnych wierzchołków prymitywy zostają przycięte do widocz- nego obszaru, czyli do okna lub ekranu. Obszar ten może być też mniejszy, więc nazywa się go cza- sem obszarem renderingu (ang. viewport). Gdy zostaną określone potencjalnie widoczne obszary prymitywów, następuje przesłanie danych do następnego podsystemu o stałej funkcji, przeprowa- dzającego rasteryzację. Blok ten określa, które piksele obejmuje swym zasięgiem prymityw (punkt, linię lub trójkąt), i wysyła listę pikseli do następnego etapu, czyli shadera fragmentów. 68 Poleć książkęKup książkę Składanie prymitywów, przycinanie i rasteryzacja Przycinanie Gdy wierzchołki opuszczają przednią część potoku, znajdują się w tak zwanej przestrzeni przycięcia (ang. clip space). To jeden z wielu systemów współrzędnych wykorzystywanych do reprezentacji położenia. Być może zwróciłeś uwagę na to, że zmienna gl_Position, w której w shaderach umiesz- czaliśmy wartości, jest typu vec4, więc położenia wierzchołków wykorzystują pełny, czterokompo- nentowy wektor. To tak zwane współrzędne jednorodne (ang. homogeneous coordinates). Do geometrii rzutowej wykorzystywany jest system współrzędnych jednorodnych, ponieważ kryjąca się za nim matematyka jest prostsza niż w wypadku standardowej przestrzeni kartezjańskiej. Współ- rzędne jednorodne wykorzystują o jeden komponent więcej niż współrzędne kartezjańskie, więc wektor w przestrzeni trójwymiarowej reprezentuje zmienna o czterech komponentach. Choć wynik działania przedniej części potoku dotyczy czterokomponentowych współrzędnych jed- norodnych, przycięcie następuje w przestrzeni kartezjańskiej. W trakcie konwersji OpenGL prze- prowadza tak zwane dzielenie rzutowe (ang. perspective division), czyli dzielenie wszystkich czterech komponentów położenia przez ostatni komponent, nazywany w. Po tej operacji następuje odwzo- rowanie przestrzeni jednorodnej na przestrzeń kartezjańską, a w otrzymuje wartość 1.0. Ponieważ do tej pory ustawialiśmy komponent w zmiennej gl_Position właśnie na 1.0, dzielenie nie będzie miało żadnego efektu. Gdy wkrótce dokładniej zapoznamy się z geometrią rzutową, omówimy efekt ustawienia w na wartość inną niż 1.0. Po dzieleniu rzutowym wynikowa pozycja znajduje się w znormalizowanej przestrzeni urządzenia (ang. normalized device space). W OpenGL widoczny obszar znormalizowanej przestrzeni urzą- dzenia to obszar od –1.0 do 1.0 w wymiarach x i y oraz od 0.0 do 1.0 w wymiarze z. Tylko geo- metria znajdująca się w tym obszarze może być dla użytkownika widoczna. Wszystko, co znajduje się poza nią, można pominąć. Sześć boków tego sześcianu tworzy płaszczyzny w przestrzeni trój- wymiarowej. Ponieważ płaszczyzna dzieli przestrzeń współrzędnych na dwie części, obszar po każdej ze stron nazywa się półprzestrzenią (ang. half-space). Przed przekazaniem prymitywów do następnego etapu OpenGL przeprowadza przycięcie na pod- stawie tego, po której stronie płaszczyzny znajduje się każdy z wierzchołków prymitywu. Płaszczy- zna ma część „wewnętrzną” i „zewnętrzną”. Jeśli wszystkie wierzchołki prymitywu znajdują się w części zewnętrznej, cały prymityw jest pomijany. Jeśli wszystkie wierzchołki prymitywu znajdują się w części wewnętrznej, jest on przekazywany dalej bez żadnych zmian. Prymitywy widoczne częściowo (przecinające jedną z płaszczyzn) muszą zostać obsłużone w sposób szczególny. Więcej informacji na ten temat znajduje się w rozdziale 7. Transformacja obszaru renderingu Po przycięciu wszystkie wierzchołki geometrii mają współrzędne znajdujące się w zakresie od –1.0 do 1.0 (koordynaty x i y). Po dołączeniu współrzędnej z w zakresie od 0.0 do 1.0 mówimy o znor- malizowanych przestrzeniach urządzenia. Z drugiej strony okno, w którym ma nastąpić rysowanie, ma najczęściej1 wymiary od (0, 0) w lewym dolnym narożniku do (w – 1, h – 1) w prawym górnym narożniku, gdzie w i h to, odpowiednio, szerokość i wysokość okna w pikselach. Aby umieścić geome- trię w oknie, OpenGL stosuje przekształcenie obszaru renderingu (ang. viewport transform), które 1 Możliwa jest zmiana konwencji dotyczących współrzędnych tak, aby (0, 0) znajdowało się w lewym górnym narożniku ekranu, czyli było stosowane rozwiązanie przyjęte w wielu innych systemach graficznych. 69 Poleć książkęKup książkę Rozdział 3. Wzdłuż potoku graficznego skaluje i przenosi wierzchołki ze współrzędnych znormalizowanych na współrzędne okna (ang. window coordinates). Skalę i przesunięcie określa się za pomocą granic obszaru renderingu definiowa- nych przy użyciu funkcji glViewport() i glDepthRange(). Prototypy obu funkcji są następujące: void glViewport(GLint x, GLint y, GLsizei width, GLsizei height); void glDepthRange(GLdouble nearVal, GLdouble farVal); Transformacja przyjmuje taką oto postać: x w y z w w (cid:167) (cid:168) (cid:168) (cid:168) (cid:169) (cid:32) (cid:183) (cid:184) (cid:184) (cid:184) (cid:185) (cid:167) (cid:168) (cid:168) (cid:168) (cid:168) (cid:168) (cid:168) (cid:169) f xp x 2 p y y 2 zn d – 2 d d (cid:14) o x y o (cid:14) n (cid:14)(cid:14) 2 f (cid:183) (cid:184) (cid:184) (cid:184) (cid:184) (cid:184) (cid:184) (cid:185) We wzorze xw, yw i zw są wynikowymi współrzędnymi wierzchołka w przestrzeni okna, a xd, yd i zd są współrzędnymi wejściowymi wierzchołka w przestrzeni znormalizowanej. Wartości px i py to szerokość i wysokość okna w pikselach, a n i f to bliska i daleka płaszczyzna we współrzędnych z. Wartości ox, oy i oz oznaczają początek układu współrzędnych. Usuwanie zbędnych trójkątów Zanim trójkąty zaczną być przetwarzane, mogą zostać opcjonalnie przekazane do jeszcze jednego etapu, o nazwie culling. Określa on, czy płaszczyzna trójkąta jest zwrócona do oglądającego przodem, czy tyłem. Na tej podstawie można określić, czy w ogóle warto takim trójkątem się dalej zajmować. Jeśli trójkąt jest zwrócony przodem do oglądającego, mówimy o odwróceniu frontem; w przeciwnym razie mówimy o odwróceniu tyłem. Bardzo często pomija się trójkąty odwrócone tyłem, bo jeśli obiekt jest zamknięty, każdy odwrócony tyłem trójkąt będzie zakryty przez trójkąt odwrócony przodem. Aby ustalić odwrócenie trójkąta, OpenGL określi znak obszaru w przestrzeni okna. Jednym ze sposobów określenia obszaru trójkąta jest wyliczenie iloczynu wektorowego dwóch jego krawędzi. Oto wzór: a (cid:32)(cid:166)(cid:32) n i 1 2 1– 0 yx i w i 1 (cid:134) w x – (cid:134) i 1 w y i w w i yi We wzorze xi w to współrzędne i-tego wierzchołka trójkąta w przestrzeni okna, a i (cid:134) 1 to (i + 1) modulo 3. Jeśli obszar jest dodatni, trójkąt uważa się za odwrócony frontem. Wartość ujemna oznacza odwrócenie tyłem. Sens tych wyliczeń można zamienić, wywołując funkcję glFrontFace() i ustawiając parametr dir na wartość GL_CW lub GL_CCW (gdzie CW oznacza zgodnie z ruchem wska- zówek zegara, a CCW przeciwnie do ruchu wskazówek zegara). Jest to tak zwany kierunek rysowania (ang. winding order) trójkąta. Ruch wskazówek zegara lub jego odwrotność określa kolejność pojawiania się wierzchołków w przestrzeni okna. Domyślną wartością jest GL_CCW, co oznacza, że trójkąty, których wierzchołki są ułożone przeciwnie do ruchu wskazówek zegara, uważa się za ułożone przodem, a te ułożone w odwrotnym kierunku — za skierowane tyłem. Ustawienie wartości GL_CW spowoduje po prostu zanegowanie wartości a, efekt będzie więc dokładnie odwrotny. Rysunek 3.3 ilustruje działanie opisanego algorytmu w praktyce. Gdy już uda się określić kierunek zwrotu trójkąta, OpenGL może pominąć trójkąty zwrócone przo- dem, tyłem lub nawet oba rodzaje. Domyślnie OpenGL renderuje wszystkie trójkąty niezależnie od sposobu ich zwrotu. Aby włączyć usuwanie zbędnych trójkątów, wywołaj funkcję glEnable() z war- 70 Poleć książkęKup książkę Shadery fragmentów Rysunek 3.3. Ułożenie zgodnie ze wskazówkami zegara (po lewej) i przeciwnie do ruchu wskazówek zegara (po prawej) tością stałej GL_CULL_FACE. Domyślnie OpenGL usunie trójkąty skierowane tyłem. Aby zmienić rodzaj usuwanych trójkątów, wywołaj funkcję glCullFace() i przekaż jej wartość GL_FRONT, GL_BACK lub GL_FRONT_AND_BACK. Ponieważ punkty i linie nie mają żadnego obszaru geometrycznego2, obliczenia dotyczące strony nie mają dla nich żadnego zastosowania i nie można ich usunąć na tym etapie. Rasteryzacja W procesie rasteryzacji określa się, które fragmenty mogą zostać przysłonięte przez prymityw taki jak linia lub trójkąt. Istnieją dziesiątki algorytmów do obsługi tego zadania, ale większość systemów OpenGL bazuje w wypadku trójkątów na metodzie z półprzestrzeniami, bo umożliwia to zrówno- leglenie działań. W dużym skrócie — OpenGL określa dla trójkąta otaczający go prostokąt we współ- rzędnych okna, a następnie testuje każdy fragment, aby stwierdzić, czy znajduje się wewnątrz, czy na zewnątrz trójkąta. W tym celu każdą z trzech krawędzi trójkąta traktuje jako półprzestrzenie dzielące obszar okna na dwie części. Fragmenty znajdujące się wewnątrz wszystkich trzech krawędzi traktuje się jako miejsce wewnątrz trójkąta, a fragmenty, które choć dla jednej z trzech półpłaszczyzn znajdują się na zewnątrz — jako obszary poza trójkątem. Ponieważ algorytm określający, po której stronie znajduje się punkt, jest relatywnie prosty i nie zależy od niczego poza położeniem linii i sprawdzanym punktem, wiele takich testów można przeprowadzać w tym samym czasie, co daje szansę na ogromne zrównoleglenie tej operacji. Shadery fragmentów Shader fragmentów3 (ang. fragment shader) jest ostatnim programowalnym etapem w potoku gra- ficznym OpenGL. Etap ten ma za zadanie określić kolor każdego fragmentu przed wysłaniem go do bufora ramki w celu umieszczenia w oknie systemowym. Po obsłużeniu prymitywu przez proces 2 Oczywiście po narysowaniu na ekranie punkty i linie zajmują przestrzeń — inaczej nie byłyby widoczne. Ich pole jest jednak tworzone sztucznie i nie może zostać obliczone na podstawie położenia wierzchołków. 3 Termin fragment oznacza element, który ostatecznie najprawdopodobniej zdecyduje o kolorze piksela. Końcowy piksel może nie mieć koloru wyliczonego przez konkretne wywołanie shadera fragmentów ze względu na stosowanie jeszcze wielu innych efektów, takich jak test szablonu, mieszanie kolorów czy wielokrotne próbkowanie. Wszystkie te operacje zostaną omówione w dalszej części książki. 71 Poleć książkęKup książkę Rozdział 3. Wzdłuż potoku graficznego rasteryzacji powstaje lista fragmentów, która musi zostać pokolorowana. Lista ta trafia do shadera fragmentów. To właśnie tu odbywa się największa praca w całym potoku graficznym, bo każdy trójkąt mógł przekształcić się w setki, tysiące, a nawet miliony fragmentów. Listing 2.4 z rozdziału 2. zawiera przykład pierwszego wykonanego w tej książce shadera fragmen- tów. Ten bardzo prosty shader deklaruje pojedynczą wartość wejściową, a następnie przypisuje jej konkretną wartość. W aplikacji działającej produkcyjnie shader fragmentów będzie znacznie bar- dziej złożony i będzie realizował zadania dotyczące wyliczenia oświetlenia, nałożenia materiałów, a nawet wyliczenia głębi fragmentu. Shader fragmentu ma dostęp do kilku wbudowanych zmien- nych, na przykład gl_FragCoord, która zawiera informację o położeniu fragmentu w oknie. Dzięki tym informacjom można utworzyć unikatowy kolor dla każdego fragmentu. Listing 3.10 przedstawia shader fragmentów wyliczający kolor na podstawie zmiennej gl_FragCoord. Rysunek 3.4 pokazuje wynik działania oryginalnego programu z pojedynczym trójkątem po zmia- nie shadera na nowy. Listing 3.10. Tworzenie koloru fragmentu na podstawie położenia #version 450 core out vec4 color; void main(void) { color = vec4(sin(gl_FragCoord.x * 0.25) * 0.5 + 0.5, cos(gl_FragCoord.y * 0.25) * 0.5 + 0.5, sin(gl_FragCoord.x * 0.15) * cos(gl_FragCoord.y * 0.15), 1.0); } Kolor każdego piksela na rysunku 3.4 zależy od jego położenia, powstał więc wzorzec układający się zgodnie ze współrzędnymi na ekranie. Shader z listingu 3.10 spowodował powstanie wzorca przy- pominającego szachownicę. Zmienna gl_FragCoord jest jedną z wbudowanych zmiennych dostępnych na poziomie shadera. Podobnie jak w wypadku innych shaderów możemy również zastosować własne dane wejściowe. To, co otrzyma shader fragmentów, zależy od danych wyjściowych ostatniego etapu przed rastery- zacją. Jeśli program korzysta tylko z shadera wierzchołków i shadera fragmentów, możemy prze- kazać dane do shadera fragmentów bezpośrednio z poziomu shadera wierzchołków. Dane wejściowe shadera fragmentów nie przypominają danych wejściowych innych etapów, ponie- waż OpenGL interpoluje wartości względem renderowanego prymitywu. Aby to zademonstrować, zastosujmy shader wierzchołków z listingu 3.3 i zmodyfikujmy go tak, aby każdemu wierzchołkowi przypisywał inny kolor (patrz listing 3.11). Listing 3.11. Shader wierzchołków z danymi wyjściowymi #version 450 core // vs_color to dane wyj(cid:286)ciowe wysy(cid:225)ane do nast(cid:266)pnego shadera. out vec4 vs_color; void main(void) 72 Poleć książkęKup książkę Rysunek 3.4. Wynik działania listingu 3.10 Shadery fragmentów { const vec4 vertices[3] = vec4[3](vec4( 0.25, -0.25, 0.5, 1.0), vec4(-0.25, -0.25, 0.5, 1.0), vec4( 0.25, 0.25, 0.5, 1.0)); const vec4 colors[] = vec4[3](vec4( 1.0, 0.0, 0.0, 1.0), vec4( 0.0, 1.0, 0.0, 1.0), vec4( 0.0, 0.0, 1.0, 1.0)); // Dodaj (cid:397)offset(cid:397) do umieszczonej na sztywno pozycji. gl_Position = vertices[gl_VertexID] + offset; // Prze(cid:286)lij w vs_color inny kolor dla ka(cid:298)dego wierzcho(cid:225)ka. vs_color = color[gl_VertexID]; } Jak można zauważyć, w kodzie z listingu 3.11 pojawiła się druga tablica ze stałymi, zawierająca kolory. Dodatkowo kod wykorzystuje wartość gl_VertexID do przypisania zmiennej vs_color innego koloru. Listing 3.12 zawiera zmodyfikowaną wersję shadera fragmentów, który po prostu wykorzystuje dane wejściowe. Listing 3.12. Określanie koloru fragmentu na podstawie położenia i danych wejściowych #version 450 core // vs_color to kolor tworzony przez shader wierzcho(cid:225)ków. in vec4 vs_color; out vec4 color; void main(void) { color = vs_color; } 73 Poleć książkęKup książkę Rozdział 3. Wzdłuż potoku graficznego Zastosowanie nowej pary shaderów owocuje wynikiem przedstawionym na rysunku 3.5. Kolor zmie- nia się płynnie między poszczególnymi wierzchołkami trójkąta. Rysunek 3.5. Wynik działania listingu 3.12 Operacje dotyczące bufora ramki Bufor ramki to ostatni etap potoku graficznego OpenGL. Może reprezentować widoczny obszar ekranu lub kilka innych obszarów pamięci wykorzystywanych do przechowywania wartości bazu- jących na pikselach, ale niedotyczących koloru. Na większości platform oznacza to okno, które widać na komputerze (a czasem nawet cały ekran w trybie pełnoekranowym). Oknem zarządza system operacyjny (a w zasadzie menedżer okien). Bufor ramki oferowany przez menedżer okien to tak zwany domyślny bufor ramki, ale można także użyć własnego bufora ramki renderującego poza widocznym obszarem. Bufor ramki przechowuje takie informacje jak miejsce zapisu danych gene- rowanych przez shader fragmentów czy sposób ich zapisu. Za stan odpowiada tak zwany obiekt bufora ramki (ang. framebuffer object). Jako część bufora ramki, choć nie jest przechowywana w obiek- cie bufora ramki, uważa się również stan operacji na pikselach. Operacje na pikselach Po tym, gdy shader fragmentów wygenerował dane wyjściowe z fragmentem, może się stać kilka rzeczy, zanim trafi on ostatecznie do okna, o ile w ogóle do jakiegoś okna trafi. Aplikacja może włączać lub wyłączać różne funkcjonalności. Przede wszystkim możliwe jest użycie testu nożyco- wego (ang. scissor test), który sprawdza fragment pod kątem istnienia w zdefiniowanym prostokącie. Jeśli znajduje się wewnątrz, będzie dalej przetwarzany; jeśli znajduje się poza, zostanie odrzucony. Następnie pojawia się test szablonu (ang. stencil test). Porównuje on wartość referencyjną zapew- nianą przez aplikację z zawartością bufora szablonu, który przechowuje dla każdego piksela poje- 74 Poleć książkęKup książkę Shadery obliczeniowe dynczą4 wartość. Zawartość bufora szablonu nie ma żadnego konkretnego znaczenia semantycz- nego i może służyć dowolnym celom. Po teście szablonu pojawia się test głębi (ang. depth test). To test, w którym porównuje się wartość współrzędnej z fragmentu z zawartością bufora głębi (ang. depth buffer). Bufor głębi to obszar w pamięci, podobnie jak bufor szablonu, który ma wystarczająco miejsca, aby dla każdego piksela przechować jedną wartość. W tym wypadku jest to głębia (odległość od oglądającego) dotycząca każ- dego piksela. Standardowo wartości w buforze głębi mieszczą się w zakresie od 0 do 1, gdzie 0 oznacza najbliższy możliwy punkt w buforze, a 1 — najdalszy. Aby określić, czy fragment znajduje się bliżej niż inne fragmenty zrenderowane w tym samym miejscu, OpenGL porównuje komponent z fragmentu z wartością już umieszczoną w buforze. Jeśli wartość jest mniejsza, fragment jest widoczny. Sens tego testu można zmienić. Można poprosić OpenGL o przepuszczanie fragmentów o współrzędnej z większej od wartości z bufora głębi, równej jej lub też różnej od tejże wartości. To, co się dzieje w teście głębi, wpływa również na to, co OpenGL realizuje w buforze szablonu. Następnie kolor fragmentu trafia albo do mieszania, albo do etapu operacji logicznej. Wszystko zależy od tego, czy bufor ramki ma przechowywać wartości zmiennoprzecinkowe, znormalizowane czy całkowite. Jeśli zawartość bufora ramki jest zmiennoprzecinkową lub znormalizowaną wartością całkowitą, dochodzi do mieszania. Mieszanie to w OpenGL etap o bardzo dużych możliwościach konfiguracyjnych, poświęcimy mu więc osobny dział. W dużym skrócie możemy powiedzieć, że OpenGL udostępnia wiele różnych funkcji pobierających komponenty wyjściowe z shadera fragmentów i aktualną zawartość bufora ramki, aby wyliczyć nową wartość ponownie zapisywaną w buforze ramki. Jeżeli bufor ramki zawiera nieznormalizowane wartości całkowite, wówczas można stosować operacje logiczne takie jak AND, OR lub XOR. Wynik takiej operacji ponownie trafia do bufora ramki. Shadery obliczeniowe W pierwszej części rozdziału omówiliśmy etapy potoku graficznego OpenGL. OpenGL zawiera jednak również etap shadera obliczeniowego (ang. compute shader), który w zasadzie warto trak- tować jako potok niezależny od wszystkich innych etapów związanych bezpośrednio z grafiką. Shadery obliczeniowe to sposób na uzyskanie dostępu do mocy obliczeniowej drzemiącej w nowo- czesnych procesorach graficznych. W odróżnieniu od ukierunkowanych graficznie shaderów wierz- chołków, teselacji, geometrii i fragmentów shadery obliczeniowe należy traktować jako osobny, jed- noetapowy potok. Każdy shader obliczeniowy działa jako jedna jednostka zadaniowa nazywana elementem roboczym (ang. work item); elementy te zbiera się razem w grupy nazywane lokalnymi grupami roboczymi (ang. local workgroups). Zbiory grup roboczych mogą być przesłane do OpenGL w celu ich realizacji w potoku obliczeniowym. Shader obliczeniowy nie posiada żadnych ustalonych komponentów wejściowych i wyjściowych poza kilkoma wbudowanymi zmiennymi informującymi 4 Bufor ramki może przechowywać wiele wartości dotyczących koloru, szablonu lub głębi, jeśli zastosuje się technikę nazywaną wielokrotnym próbkowaniem (ang. multi-sampling). Tematem tym zajmiemy się w dalszej części książki. 75 Poleć książkęKup książkę Rozdział 3. Wzdłuż potoku graficznego o tym, nad którym elementem pracuje obecnie shader. Wszystkie przetwarzane przez shader dane są jawnie zapisywane w pamięci przez kod shadera — nie są w żaden sposób modyfikowane i przeka- zywane przez potok. Najprostszy shader obliczeniowy został przedstawiony na listingu 3.13. Listing 3.13. Shader obliczeniowy, który nic nie robi #version 450 core layout (local_size_x = 32, local_size_y = 32) in; void main(void) { // Nic nie rób. } Pod wszystkimi innymi względami shader obliczeniowy przypomina każdy inny shader OpenGL. Aby go skompilować, należy utworzyć obiekt shadera typu GL_COMPUTE_SHADER, przypisać kod źró- dłowy za pomocą funkcji glShaderSource(), skompilować go przy użyciu funkcji glCompileShader() i dołączyć do programu, stosując funkcje glAttachShader() oraz glLinkProgram(). W ten sposób powstanie obiekt programu ze skompilowanym shaderem, który można uruchomić w dowolnym momencie. Shader z listingu 3.13 informuje OpenGL, że wielkość lokalnej grupy to 32 na 32 elementy, ale nie realizuje później żadnych innych działań. Aby utworzyć shader wykonujący prawdziwą pracę, trzeba lepiej poznać sposób działania OpenGL, więc do tematu wrócimy w dalszej części książki. Korzystanie z rozszerzeń OpenGL Wszystkie przykłady przedstawione do tej pory bazowały na podstawowej funkcjonalności OpenGL. Jedną z istotnych zalet OpenGL jest to, że może być rozszerzany przez producentów sprzętu, twór- ców systemów operacyjnych, a nawet przez wydawców dodatkowych narzędzi. Rozszerzenia mogą mieć bardzo istotny i różnoraki wpływ na funkcjonalność OpenGL. Rozszerzenie to dodatek do bazowej wersji OpenGL. Dostępne rozszerzenia są wymienione na stronie WWW OpenGL w tak zwanym rejestrze rozszerzeń5. Rozszerzenia opisane są jako lista różnic względem konkretnej specyfikacji OpenGL wraz z informacją o numerze wersji. Oznacza to, że tekst rozszerzenia opisuje, jak musi się zmienić specyfikacja głównego OpenGL, jeśli rozszerzenie jest obsługiwane. Z drugiej strony popularne i powszechnie obsługiwane rozszerzenia są z czasem „promowane” do głównej wersji OpenGL. Wynika z tego, że gdy stosujemy najnowsze wersje OpenGL, może się okazać, że nie mamy zbyt wielu interesujących rozszerzeń, bo wszystkie ważne trafiły już do głównej specyfikacji. Pełna lista rozszerzeń wraz z informacją o wersji głównego OpenGL, w której się znalazły, jest dostępna w dodatku C. Istnieją trzy główne klasyfikacje rozszerzeń: dostawcy, EXT i ARB. Rozszerzenia dostawcy zostały napisane i zaimplementowane na sprzęcie konkretnego dostawcy. Inicjały producenta znajdują się najczęściej w nazwie rozszerzenia — AMD oznacza firmę Advanced Micro Devices, a NV — firmę NVDIA. Zdarza się, że kilku producentów obsługuje konkretne rozszerzenie, szczególnie jeśli wzrasta jego popularność. Rozszerzenia EXT powstają przy współudziale dwóch twórców sprzętu lub ich 5 Rejestr jest dostępny pod adresem http://www.opengl.org/registry/. 76 Poleć książkęKup książkę Korzystanie z rozszerzeń OpenGL większej liczby. Bardzo często na początku istniały jako rozszerzenia jednego producenta, ale gdy inny producent postanowił je wprowadzić (być może z drobnymi poprawkami), powstała wersja EXT. Rozszerzenia ARB stanowią oficjalną część OpenGL, ponieważ zostały zatwierdzone przez ciało standaryzujące OpenGL, czyli Architecture Review Board (ARB). Rozszerzenia tego typu są często obsługiwane przez większość głównych dostawców sprzętu i w wielu wypadkach bazują na rozsze- rzeniach dostawców lub EXT. System rozszerzeń może początkowo przerażać, bo istnieją setki rozszerzeń! Z drugiej strony nowa wersja OpenGL powstaje na bazie rozszerzeń, które programiści uznali za przydatne. Mechanizm rozszerzeń pozwala ocenić konkretną propozycję w praktyce. Te, które się sprawdzą, trafiają do głównej specyfikacji; te mniej udane pozostają rozszerzeniami. Ten proces „selekcji naturalnej” zapewnia przenoszenie do głównego OpenGL tylko tych nowych funkcji, które przeszły wcześniej „chrzest bojowy”. Przydatnym narzędziem umożliwiającym łatwe i szybkie sprawdzenie, które rozszerzenia OpenGL obsługuje sprzęt i sterownik zainstalowany w komputerze, jest OpenGL Extensions Viewer firmy Realtech VR. Narzędzie to można pobrać bezpłatnie ze stron firmy (patrz rysunek 3.6). Wzbogacanie OpenGL rozszerzeniami Przed użyciem rozszerzenia musimy się upewnić, że jest ono obsługiwane przez implementację OpenGL działającą na naszym komputerze. W celu sprawdzenia obsługi rozszerzenia można skorzystać z dwóch dostępnych funkcji. Aby poznać liczbę obsługiwanych rozszerzeń, wywołaj funkcję glGetIntegerv() z parametrem GL_NUM_EXTENSIONS. Następnie pobierz nazwę każdego obsługiwanego rozszerzenia, wywołując funkcję o następującym prototypie: const GLubyt
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

OpenGL. Księga eksperta. Wydanie VII
Autor:
, ,

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: