Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00302 011118 20690593 na godz. na dobę w sumie
OpenGL. Receptury dla programisty - ebook/pdf
OpenGL. Receptury dla programisty - ebook/pdf
Autor: Liczba stron: 320
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-283-0021-7 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> grafika komputerowa >> inne
Porównaj ceny (książka, ebook (-45%), audiobook).

Ponad 40 gotowych przepisów pokazujących możliwości zastosowania biblioteki OpenGL w nowoczesnych aplikacjach

OpenGL dostarcza programistom API do renderowania zaawansowanych grafik i animacji. To oprogramowanie umożliwia im tworzenie niezwykle atrakcyjnych gier, prezentacji oraz efektów graficznych. Jeżeli chcesz poznać możliwości OpenGL, jeżeli szukasz odpowiedzi na nurtujące Cię pytania, to trafiłeś na świetną książkę!

Znajdziesz w niej zbiór receptur cenionych przez programistów. Dzięki nim błyskawicznie rozwiążesz typowe problemy oraz zobaczysz, jak podejść do przeróżnych zagadnień związanych z OpenGL. Sięgnij po tę lekturę, a nauczysz się wybierać obiekty na podstawie ich różnych właściwości, mapować środowisko, filtrować obraz oraz tworzyć realne scenerie za pomocą odpowiedniej gry światła i cienia. Ponadto zobaczysz, jak śledzić promienie, ścieżki oraz tworzyć animacje szkieletowe i symulacje fizyczne. Ta książka to kopalnia najlepszych przepisów na wykorzystanie OpenGL!

Na licznych przykładach poznasz zasady: Najlepsze przepisy na OpenGL!
Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Tytuł oryginału: OpenGL Development Cookbook Tłumaczenie: Zbigniew Waśko ISBN: 978-83-283-0018-7 Copyright © Packt Publishing 2013. First published in the English language under the title ‘OpenGL Development Cookbook’ © 2015 Helion S.A. All rights reserved. All rights reserved. No part of this book may be 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 the Publisher. 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 bierze jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Wydawnictwo HELION nie ponosi 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/openrp.zip Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/openrp 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(cid:258)ci O autorze O recenzentach Wst(cid:218)p Rozdzia(cid:239) 1. Wprowadzenie do nowoczesnego OpenGL Wst(cid:218)p Instalacja rdzennego profilu OpenGL 3.3 w Visual Studio 2013 przy u(cid:285)yciu bibliotek GLEW i freeglut Projektowanie klasy shadera w GLSL Renderowanie kolorowego trójk(cid:200)ta za pomoc(cid:200) shaderów Wykonanie deformatora siatki przy u(cid:285)yciu shadera wierzcho(cid:239)ków Dynamiczne zag(cid:218)szczanie podzia(cid:239)u p(cid:239)aszczyzny przy u(cid:285)yciu shadera geometrii Dynamiczne zag(cid:218)szczanie podzia(cid:239)u p(cid:239)aszczyzny przy u(cid:285)yciu shadera geometrii i renderingu instancyjnego Rysowanie obrazu 2D przy u(cid:285)yciu shadera fragmentów i biblioteki SOIL Rozdzia(cid:239) 2. Wy(cid:258)wietlanie i wskazywanie obiektów 3D Wst(cid:218)p Implementacja wektorowego modelu kamery z obs(cid:239)ug(cid:200) ruchów w stylu gier FPS Implementacja kamery swobodnej Implementacja kamery wycelowanej Ukrywanie elementów spoza bry(cid:239)y widzenia Wskazywanie obiektów z u(cid:285)yciem bufora g(cid:239)(cid:218)bi Wskazywanie obiektów na podstawie koloru Wskazywanie obiektów na podstawie ich przeci(cid:218)(cid:202) z promieniem oka 7 9 11 17 17 18 26 29 38 46 53 57 63 63 64 68 70 74 79 83 85 Poleć książkęKup książkę OpenGL. Receptury dla programisty Rozdzia(cid:239) 3. Rendering pozaekranowy i mapowanie (cid:258)rodowiska 89 89 Wst(cid:218)p 90 Implementacja filtra wirowego przy u(cid:285)yciu shadera fragmentów 93 Renderowanie sze(cid:258)cianu nieba metod(cid:200) statycznego mapowania sze(cid:258)ciennego 97 Implementacja lustra z renderowaniem pozaekranowym przy u(cid:285)yciu FBO 101 Renderowanie obiektów lustrzanych z u(cid:285)yciem dynamicznego mapowania sze(cid:258)ciennego Implementacja filtrowania obrazu (wyostrzania, rozmywania, wyt(cid:239)aczania) metod(cid:200) splotu 106 Implementacja efektu po(cid:258)wiaty 109 Rozdzia(cid:239) 4. (cid:165)wiat(cid:239)a i cienie Wst(cid:218)p Implementacja o(cid:258)wietlenia punktowego na poziomie wierzcho(cid:239)ków i fragmentów Implementacja (cid:258)wiat(cid:239)a kierunkowego na poziomie fragmentów Implementacja zanikaj(cid:200)cego (cid:258)wiat(cid:239)a punktowego na poziomie fragmentów Implementacja o(cid:258)wietlenia reflektorowego na poziomie fragmentów Mapowanie cieni przy u(cid:285)yciu FBO Przygotowania Mapowanie cieni z filtrowaniem PCF Wariancyjne mapowanie cieni Rozdzia(cid:239) 5. Formaty modeli siatkowych i systemy cz(cid:200)steczkowe Wst(cid:218)p Modelowanie terenu przy u(cid:285)yciu mapy wysoko(cid:258)ci Wczytywanie modeli 3ds przy u(cid:285)yciu odr(cid:218)bnych buforów Wczytywanie modeli OBJ przy u(cid:285)yciu buforów z przeplotem Wczytywanie modeli w formacie EZMesh Implementacja prostego systemu cz(cid:200)steczkowego Rozdzia(cid:239) 6. Mieszanie alfa i o(cid:258)wietlenie globalne na GPU Wst(cid:218)p Implementacja przezroczysto(cid:258)ci technik(cid:200) peelingu jednokierunkowego Implementacja przezroczysto(cid:258)ci technik(cid:200) peelingu dualnego Implementacja okluzji otoczenia w przestrzeni ekranu (SSAO) Implementacja metody harmonik sferycznych w o(cid:258)wietleniu globalnym (cid:165)ledzenie promieni realizowane przez GPU (cid:165)ledzenie (cid:258)cie(cid:285)ek realizowane przez GPU Rozdzia(cid:239) 7. Techniki renderingu wolumetrycznego bazuj(cid:200)ce na GPU Wst(cid:218)p Implementacja renderingu wolumetrycznego z ci(cid:218)ciem tekstury 3D na p(cid:239)aty Implementacja renderingu wolumetrycznego z jednoprzebiegowym rzucaniem promieni 4 115 115 116 122 124 128 130 131 136 141 151 151 152 156 166 171 178 189 189 190 197 203 210 216 221 227 227 228 236 Poleć książkęKup książkę Spis tre(cid:286)ci 241 Pseudoizopowierzchniowy rendering w jednoprzebiegowym rzucaniu promieni 245 Rendering wolumetryczny z u(cid:285)yciem splattingu 252 Implementacja funkcji przej(cid:258)cia dla klasyfikacji obj(cid:218)to(cid:258)ciowej Implementacja wydzielania wielok(cid:200)tnej izopowierzchni metod(cid:200) maszeruj(cid:200)cych sze(cid:258)cianów 255 Wolumetryczne o(cid:258)wietlenie oparte na technice ci(cid:218)cia po(cid:239)ówkowok(cid:200)towego 262 Rozdzia(cid:239) 8. Animacje szkieletowe i symulacje fizyczne na GPU Wst(cid:218)p Implementacja animacji szkieletowej z palet(cid:200) macierzy skinningowych Implementacja animacji szkieletowej ze skinningiem wykonanym przy u(cid:285)yciu kwaternionu dualnego Modelowanie tkanin z u(cid:285)yciem transformacyjnego sprz(cid:218)(cid:285)enia zwrotnego Implementacja wykrywania kolizji z tkanin(cid:200) i reagowania na nie Implementacja systemu cz(cid:200)steczkowego z transformacyjnym sprz(cid:218)(cid:285)eniem zwrotnym Skorowidz 269 269 270 280 287 296 301 311 5 Poleć książkęKup książkę OpenGL. Receptury dla programisty 6 Poleć książkęKup książkę 7 Techniki renderingu wolumetrycznego bazuj(cid:200)ce na GPU W tym rozdziale: (cid:81) Implementacja renderingu wolumetrycznego z ci(cid:218)ciem tekstury 3D na p(cid:239)aty (cid:81) Implementacja renderingu wolumetrycznego z jednoprzebiegowym rzucaniem promieni (cid:81) Pseudoizopowierzchniowy rendering w jednoprzebiegowym rzucaniu promieni (cid:81) Rendering wolumetryczny z u(cid:285)yciem splattingu (cid:81) Implementacja funkcji przej(cid:258)cia dla klasyfikacji obj(cid:218)to(cid:258)ciowej (cid:81) Implementacja wydzielania wielok(cid:200)tnej izopowierzchni metod(cid:200) maszeruj(cid:200)cych sze(cid:258)cianów (cid:81) Wolumetryczne o(cid:258)wietlenie oparte na technice ci(cid:218)cia po(cid:239)ówkowok(cid:200)towego Wst(cid:218)p Techniki renderowania obj(cid:218)to(cid:258)ciowego znajduj(cid:200) wiele zastosowa(cid:241) w biomedycynie i in(cid:285)ynierii. W biomedycynie s(cid:200) u(cid:285)ywane do wizualizacji wyników tomografii komputerowej i rezonansu magnetycznego. W in(cid:285)ynierii s(cid:239)u(cid:285)(cid:200) do wizualizacji po(cid:258)rednich etapów symulacji FEM, przep(cid:239)y- wów i analiz strukturalnych. Wraz z pojawieniem si(cid:218) procesorów graficznych wszystkie modele i metody wizualizacyjne zosta(cid:239)y przeprojektowane pod k(cid:200)tem pe(cid:239)niejszego wykorzystania Poleć książkęKup książkę OpenGL. Receptury dla programisty mocy obliczeniowych tych procesorów. W tym rozdziale zaprezentuj(cid:218) kilka algorytmów wizu- alizacji wolumetrycznej, które mo(cid:285)na w taki w(cid:239)a(cid:258)nie sposób zrealizowa(cid:202) za pomoc(cid:200) funkcji z biblioteki OpenGL w wersji 3.3 lub nowszej. W szczególno(cid:258)ci b(cid:218)d(cid:200) to trzy najbardziej roz- powszechnione metody polegaj(cid:200)ce na ci(cid:218)ciu trójwymiarowej tekstury, jednoprzebiegowym rzu- caniu promieni z komponowaniem alfa i renderowaniu izopowierzchni. Po zapoznaniu si(cid:218) z tymi podstawowymi metodami przyjrzymy si(cid:218) technice klasyfikacji obj(cid:218)to- (cid:258)ciowej z odpowiedni(cid:200) funkcj(cid:200) przej(cid:258)cia. Do wydobywania klasyfikowanych obszarów, takich jak (cid:258)cianki komórek, cz(cid:218)sto stosuje si(cid:218) metody generowania izopowierzchni. Jedn(cid:200) z nich jest metoda maszeruj(cid:200)cego czworo(cid:258)cianu (marching tetrahedra)1. Rendering wolumetryczny to tak(cid:285)e rozmaite techniki o(cid:258)wietlenia obj(cid:218)to(cid:258)ciowego. Jedn(cid:200) z popularnych technik jest tu ci(cid:218)cie po(cid:239)ów- kowok(cid:200)towe i w(cid:239)a(cid:258)nie to spróbujemy zaimplementowa(cid:202). Implementacja renderingu wolumetrycznego z ci(cid:218)ciem tekstury 3D na p(cid:239)aty Rendering wolumetryczny stanowi specyficzn(cid:200) odmian(cid:218) algorytmów renderuj(cid:200)cych, które pozwalaj(cid:200) na obrazowanie obiektów i zjawisk o strukturze przestrzennej, takich jak na przy- k(cid:239)ad dym. Algorytmów takich jest wiele, ale nasz przegl(cid:200)d zaczniemy od metody najprostszej, znanej jako ci(cid:218)cie trójwymiarowej tekstury na p(cid:239)aty. Polega ona na aproksymowaniu funkcji opisuj(cid:200)cej przestrzenny rozk(cid:239)ad g(cid:218)sto(cid:258)ci przez rozcinanie zbioru danych na p(cid:239)aty w kierunku od przodu ku ty(cid:239)owi lub od ty(cid:239)u ku przodowi, a nast(cid:218)pnie sklejaniu tych p(cid:239)atów przez wspoma- gane sprz(cid:218)towo mieszanie. Jako (cid:285)e wszystko to mo(cid:285)e by(cid:202) realizowane przez sprz(cid:218)t rasteryzu- j(cid:200)cy, szybko(cid:258)(cid:202) dzia(cid:239)ania tej metody jest bardzo du(cid:285)a. Pseudokod ci(cid:218)cia trójwymiarowej tekstury na p(cid:239)aty prostopad(cid:239)e do kierunku patrzenia przed- stawia si(cid:218) nast(cid:218)puj(cid:200)co: 1. Wyznacz wektor kierunkowy bie(cid:285)(cid:200)cego widoku. 2. Oblicz minimaln(cid:200) i maksymaln(cid:200) odleg(cid:239)o(cid:258)(cid:202) wierzcho(cid:239)ków jednostkowego sze(cid:258)cianu, mno(cid:285)(cid:200)c skalarnie ka(cid:285)dy z tych wierzcho(cid:239)ków przez wektor kierunkowy widoku. 3. Wyznacz wszystkie warto(cid:258)ci parametru (cid:540) okre(cid:258)laj(cid:200)cego mo(cid:285)liwe przeci(cid:218)cia kraw(cid:218)dzi jednostkowego sze(cid:258)cianu przez p(cid:239)aszczyzn(cid:218) prostopad(cid:239)(cid:200) do kierunku widoku, pocz(cid:200)wszy od wierzcho(cid:239)ka najbli(cid:285)szego a(cid:285) do najdalszego. Wykorzystaj do tego odleg(cid:239)o(cid:258)ci minimaln(cid:200) i maksymaln(cid:200) z punktu 1. 1 Autor pos(cid:239)uguje si(cid:218) tutaj nazw(cid:200) Marching Tetrahedra (maszeruj(cid:200)ce czworo(cid:258)ciany), ale tak naprawd(cid:218) prezentuje algorytm o nazwie Marching Cubes (maszeruj(cid:200)ce sze(cid:258)ciany) — przyp. t(cid:239)um. 228 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU 4. Pos(cid:239)uguj(cid:200)c si(cid:218) parametrem (cid:540) (z punktu 3.), przesuwaj si(cid:218) zgodnie z kierunkiem widoku i znajd(cid:283) punkty przeci(cid:218)cia. Powinno ich by(cid:202) od 3 do 6. 5. Zapisz po(cid:239)o(cid:285)enia tych punktów we w(cid:239)a(cid:258)ciwej kolejno(cid:258)ci i wygeneruj na ich podstawie trójk(cid:200)ty jako zast(cid:218)pcz(cid:200) geometri(cid:218). 6. Do obiektu bufora wprowad(cid:283) nowe wierzcho(cid:239)ki. Przygotowania Pe(cid:239)ny kod dla tej receptury znajdziesz w folderze Rozdzia(cid:239)7/Ci(cid:218)cieTekstury3D. Jak to zrobi(cid:202)? Zacznij od nast(cid:218)puj(cid:200)cych czynno(cid:258)ci: 1. Wczytaj dane obj(cid:218)to(cid:258)ciowe z zewn(cid:218)trznego pliku i umie(cid:258)(cid:202) je w OpenGL-owej teksturze. W(cid:239)(cid:200)cz te(cid:285) sprz(cid:218)towe generowanie mipmap. Zazwyczaj dane obj(cid:218)to(cid:258)ciowe s(cid:200) zbiorem skanów wykonanych metod(cid:200) rezonansu magnetycznego lub tomografii komputerowej. Ka(cid:285)dy taki skan jest dwuwymiarowym p(cid:239)atem. U(cid:239)o(cid:285)one na stosie w kierunku osi Z tworz(cid:200) trójwymiarow(cid:200) tekstur(cid:218), któr(cid:200) mo(cid:285)na równie(cid:285) traktowa(cid:202) jak tablic(cid:218) tekstur dwuwymiarowych. Zapisane w niej warto(cid:258)ci okre(cid:258)laj(cid:200) g(cid:218)sto(cid:258)(cid:202) prze(cid:258)wietlanej materii, np. g(cid:218)sto(cid:258)ci z zakresu od 0 do 20 s(cid:200) typowe dla powietrza. Je(cid:258)li s(cid:200) to liczby 8-bitowe bez znaku, mo(cid:285)emy je zapisa(cid:202) w tablicy typu GLubyte. Dane 16-bitowe bez znaku zapiszemy w tablicy typu GLushort. W przypadku tekstur 3D oprócz parametrów S i T mamy jeszcze parametr R, który okre(cid:258)la bie(cid:285)(cid:200)cy p(cid:239)at tekstury. std::ifstream infile(volume_file.c_str(), std::ios_base::binary); if(infile.good()) { GLubyte* pData = new GLubyte[XDIM*YDIM*ZDIM]; infile.read(reinterpret_cast char* (pData), (cid:180)XDIM*YDIM*ZDIM*sizeof(GLubyte)); infile.close(); glGenTextures(1, textureID); glBindTexture(GL_TEXTURE_3D, textureID); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, (cid:180)GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_BASE_LEVEL, 0); glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 4); glTexImage3D(GL_TEXTURE_3D,0,GL_RED,XDIM,YDIM,ZDIM,0,GL_RED,GL_ (cid:180)UNSIGNED_BYTE,pData); 229 Poleć książkęKup książkę OpenGL. Receptury dla programisty glGenerateMipmap(GL_TEXTURE_3D); return true; } else { return false; } Parametry filtrowania dla tekstur 3D s(cid:200) podobne do tych, z jakimi mieli(cid:258)my do czynienia do tej pory. Mipmapy to zestaw odpowiednio przeskalowanych wersji tej samej tekstury, które stosuje si(cid:218) w zale(cid:285)no(cid:258)ci od wymaganego poziomu szczegó(cid:239)owo(cid:258)ci (LOD — level of detail). Gdy teksturowany obiekt znajduje si(cid:218) daleko od widza (kamery), wybierana jest wersja o odpowiednio ma(cid:239)ych wymiarach, dzi(cid:218)ki czemu wzrasta szybko(cid:258)(cid:202) dzia(cid:239)ania aplikacji. Warto(cid:258)(cid:202) GL_TEXTURE_MAX_LEVEL okre(cid:258)la liczb(cid:218) takich poziomów szczegó(cid:239)owo(cid:258)ci, a tym samym liczb(cid:218) mipmap wygenerowanych z danej tekstury. Poziom podstawowy, czyli numer mipmapy stosowanej przy najmniejszej odleg(cid:239)o(cid:258)ci obiektu od kamery, okre(cid:258)la parametr GL_TEXTURE_BASE_LEVEL. Funkcja glGenerateMipMap generuje pochodne tablice teksturowe przez redukuj(cid:200)ce filtrowanie poprzedniego poziomu. Przyk(cid:239)adowo za(cid:239)ó(cid:285)my, (cid:285)e mamy mie(cid:202) trzy poziomy mipmap, a na poziomie 0 ma by(cid:202) tekstura 3D o wymiarach 256×256×256. Dla poziomu 1. trzeba wi(cid:218)c wygenerowa(cid:202) tekstur(cid:218) o wymiarach o po(cid:239)ow(cid:218) mniejszych, czyli 128×128×128. Dla poziomu 2. trzeba znów o po(cid:239)ow(cid:218) zmniejszy(cid:202) wymiary tekstury z poziomu 1., czyli do warto(cid:258)ci 64×64×64. Na poziomie 3. b(cid:218)dzie tekstura zredukowana do wymiarów 32×32×32. 2. Przygotuj obiekty tablicy i bufora wierzcho(cid:239)ków, w których zapiszesz geometri(cid:218) zast(cid:218)pczych p(cid:239)atów. Upewnij si(cid:218), (cid:285)e przeznaczenie obiektu bufora jest okre(cid:258)lone jako GL_DYNAMIC_DRAW. Pami(cid:218)(cid:202) GPU niezb(cid:218)dn(cid:200) do przechowania maksymalnej liczby p(cid:239)atów alokuje funkcja glBufferData. Tablica vTextureSlices jest zdefiniowana globalnie i w niej zapisane s(cid:200) wszystkie wierzcho(cid:239)ki wyznaczone w procesie ci(cid:218)cia tekstury na p(cid:239)aty. Warto(cid:258)(cid:202) zerowa wska(cid:283)nika do danych oznacza, (cid:285)e dane te b(cid:218)d(cid:200) wprowadzane do bufora dopiero podczas dzia(cid:239)ania aplikacji. const int MAX_SLICES = 512; glm::vec3 vTextureSlices[MAX_SLICES*12]; glGenVertexArrays(1, volumeVAO); glGenBuffers(1, volumeVBO); glBindVertexArray(volumeVAO); glBindBuffer (GL_ARRAY_BUFFER, volumeVBO); glBufferData (GL_ARRAY_BUFFER, sizeof(vTextureSlices), 0, (cid:180)GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,0,0); glBindVertexArray(0); 3. Zaimplementuj ci(cid:218)cie badanego obszaru przestrzeni przez wyznaczanie przeci(cid:218)(cid:202) jednostkowego sze(cid:258)cianu p(cid:239)atami prostopad(cid:239)ymi do kierunku patrzenia. W naszej aplikacji zadanie to wykonuje funkcja SliceVolume. Stosujemy sze(cid:258)cian jednostkowy, 230 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU poniewa(cid:285) nasze dane zajmuj(cid:200) obszar o takich samych wymiarach wzgl(cid:218)dem wszystkich trzech osi (256×256×256). Gdyby te wymiary nie by(cid:239)y jednakowe, nale(cid:285)a(cid:239)oby odpowiednio przeskalowa(cid:202) sze(cid:258)cian jednostkowy. //wyznacz odleg(cid:239)o(cid:258)ci max i min glm::vec3 vecStart[12]; glm::vec3 vecDir[12]; float lambda[12]; float lambda_inc[12]; float denom = 0; float plane_dist = min_dist; float plane_dist_inc = (max_dist-min_dist)/float(num_slices); //wyznacz vecStart i vecDir glm::vec3 intersection[6]; float dL[12]; for(int i=num_slices-1;i =0;i--) { for(int e = 0; e 12; e++) { dL[e] = lambda[e] + i*lambda_inc[e]; } if ((dL[0] = 0.0) (dL[0] 1.0)) { intersection[0] = vecStart[0] + dL[0]*vecDir[0]; } //podobnie dla wszystkich punktów przeci(cid:218)cia int indices[]={0,1,2, 0,2,3, 0,3,4, 0,4,5}; for(int i=0;i 12;i++) vTextureSlices[count++]=intersection[indices[i]]; } //uaktualnij obiekt bufora glBindBuffer(GL_ARRAY_BUFFER, volumeVBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vTextureSlices), (cid:180) (vTextureSlices[0].x)); 4. W funkcji renderuj(cid:200)cej ustaw mieszanie nak(cid:239)adkowe, zwi(cid:200)(cid:285) obiekt tablicy wierzcho(cid:239)ków, uruchom shader i wywo(cid:239)aj funkcj(cid:218) glDrawArrays. glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindVertexArray(volumeVAO); shader.Use(); glUniformMatrix4fv(shader( MVP ), 1, GL_FALSE, glm::value_ptr(MVP)); glDrawArrays(GL_TRIANGLES, 0, sizeof(vTextureSlices)/ (cid:180)sizeof(vTextureSlices[0])); shader.UnUse(); glDisable(GL_BLEND); 231 Poleć książkęKup książkę OpenGL. Receptury dla programisty Jak to dzia(cid:239)a? Metoda ci(cid:218)cia tekstury 3D na p(cid:239)aty aproksymuje ca(cid:239)k(cid:218) renderingu wolumetrycznego przez mieszanie alfa poteksturowanych p(cid:239)atów. Pierwszy krok to wczytanie danych wolumetrycznych i umieszczenie ich w teksturze 3D. Potem nast(cid:218)puje ci(cid:218)cie obszaru zajmowanego przez te dane na tymczasowe p(cid:239)aty ustawione prostopadle do kierunku patrzenia. W procesie tym wyzna- czane s(cid:200) punkty przeci(cid:218)cia tymi p(cid:239)atami jednostkowego sze(cid:258)cianu. Zadanie to wykonuje funkcja SliceVolume. Dzia(cid:239)a ona tylko wtedy, gdy zmienia si(cid:218) kierunek patrzenia. Najpierw wyznaczamy wektor kierunku patrzenia (viewDir), którego wspó(cid:239)rz(cid:218)dne stanowi(cid:200) trzeci(cid:200) kolumn(cid:218) macierzy modelu i widoku. Pierwsza kolumna tej macierzy to wektor zwró- cony w prawo, a kolumna druga to wektor zwrócony w gór(cid:218). Zobaczmy teraz, jak dok(cid:239)adnie dzia(cid:239)a funkcja SliceVolume. Zaczyna od wyznaczenia maksymalnej i minimalnej odleg(cid:239)o(cid:258)ci do wierzcho(cid:239)ków sze(cid:258)cianu jednostkowego w kierunku patrzenia. W tym celu mno(cid:285)y skalarnie po(cid:239)o(cid:285)enie ka(cid:285)dego z tych wierzcho(cid:239)ków przez wektor kierunku patrzenia. float max_dist = glm::dot(viewDir, vertexList[0]); float min_dist = max_dist; int max_index = 0; int count = 0; for(int i=1;i 8;i++) { float dist = glm::dot(viewDir, vertexList[i]); if(dist max_dist) { max_dist = dist; max_index = i; } if(dist min_dist) min_dist = dist; } int max_dim = FindAbsMax(viewDir); min_dist -= EPSILON; max_dist += EPSILON; S(cid:200) tylko trzy unikatowe (cid:258)cie(cid:285)ki wiod(cid:200)ce od wierzcho(cid:239)ka najbli(cid:285)szego do najdalszego. Ka(cid:285)d(cid:200) z nich dla wszystkich wierzcho(cid:239)ków umieszczamy w tablicy kraw(cid:218)dzi zdefiniowanej w sposób nast(cid:218)puj(cid:200)cy: int edgeList[8][12]={{0,1,5,6, 4,8,11,9, 3,7,2,10 }, //v0 z przodu {0,4,3,11, 1,2,6,7, 5,9,8,10 }, // v1 z przodu {1,5,0,8, 2,3,7,4, 6,10,9,11}, // v2 z przodu { 7,11,10,8, 2,6,1,9, 3,0,4,5 }, // v3 z przodu { 8,5,9,1, 11,10,7,6, 4,3,0,2 }, // v4 z przodu { 9,6,10,2, 8,11,4,7, 5,0,1,3 }, // v5 z przodu { 9,8,5,4, 6,1,2,0, 10,7,11,3}, // v6 z przodu { 10,9,6,5, 7,2,3,1, 11,4,8,0 } // v7 z przodu 232 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU Nast(cid:218)pnie wyznaczane s(cid:200) odleg(cid:239)o(cid:258)ci do punktów przeci(cid:218)cia p(cid:239)atów z ka(cid:285)d(cid:200) z 12 kraw(cid:218)dzi sze(cid:258)cianu: glm::vec3 vecStart[12]; glm::vec3 vecDir[12]; float lambda[12]; float lambda_inc[12]; float denom = 0; float plane_dist = min_dist; float plane_dist_inc = (max_dist-min_dist)/float(num_slices); for(int i=0;i 12;i++) { vecStart[i]=vertexList[edges[edgeList[max_index][i]][0]]; vecDir[i]=vertexList[edges[edgeList[max_index][i]][1]]-vecStart[i]; denom = glm::dot(vecDir[i], viewDir); if (1.0 + denom != 1.0) { lambda_inc[i] = plane_dist_inc/denom; lambda[i]=(plane_dist-glm::dot(vecStart[i],viewDir))/denom; } else { lambda[i] = -1.0; lambda_inc[i] = 0.0; } } Na koniec przeprowadzana jest interpolacja punktów przeci(cid:218)(cid:202) z kraw(cid:218)dziami sze(cid:258)cianu, id(cid:200)c od ty(cid:239)u ku przodowi w kierunku wyznaczonym przez wektor widoku. Po wygenerowaniu p(cid:239)atów nowe dane s(cid:200) umieszczane w obiekcie bufora wierzcho(cid:239)ków. for(int i=num_slices-1;i =0;i--) { for(int e = 0; e 12; e++) { dL[e] = lambda[e] + i*lambda_inc[e]; } if ((dL[0] = 0.0) (dL[0] 1.0)) { intersection[0] = vecStart[0] + dL[0]*vecDir[0]; } else if ((dL[1] = 0.0) (dL[1] 1.0)) { intersection[0] = vecStart[1] + dL[1]*vecDir[1]; } else if ((dL[3] = 0.0) (dL[3] 1.0)) { intersection[0] = vecStart[3] + dL[3]*vecDir[3]; } else continue; if ((dL[2] = 0.0) (dL[2] 1.0)){ intersection[1] = vecStart[2] + dL[2]*vecDir[2]; } else if ((dL[0] = 0.0) (dL[0] 1.0)){ intersection[1] = vecStart[0] + dL[0]*vecDir[0]; } else if ((dL[1] = 0.0) (dL[1] 1.0)){ intersection[1] = vecStart[1] + dL[1]*vecDir[1]; } else { intersection[1] = vecStart[3] + dL[3]*vecDir[3]; } //podobnie dla pozosta(cid:239)ych kraw(cid:218)dzi, a(cid:285) do intersection[5] int indices[]={0,1,2, 0,2,3, 0,3,4, 0,4,5}; 233 Poleć książkęKup książkę OpenGL. Receptury dla programisty for(int i=0;i 12;i++) vTextureSlices[count++]=intersection[indices[i]]; } glBindBuffer(GL_ARRAY_BUFFER, volumeVBO); glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vTextureSlices), (cid:180) (vTextureSlices[0].x)); W funkcji renderuj(cid:200)cej uruchamiamy odpowiedni program shaderowy. Shader wierzcho(cid:239)ków wyznacza po(cid:239)o(cid:285)enia wierzcho(cid:239)ków w przestrzeni przyci(cid:218)cia, mno(cid:285)(cid:200)c ich po(cid:239)o(cid:285)enia w prze- strzeni obiektu (vPosition) przez po(cid:239)(cid:200)czon(cid:200) macierz modelu, widoku i rzutowania (MVP). Obli- cza te(cid:285) wspó(cid:239)rz(cid:218)dne tekstury 3D (vUV) dla danych wolumetrycznych. Stosujemy sze(cid:258)cian jed- nostkowy, a zatem najmniejsze wspó(cid:239)rz(cid:218)dne wierzcho(cid:239)ka b(cid:218)d(cid:200) wynosi(cid:239)y (-0.5, -0.5, -0.5), a najwi(cid:218)ksze — (0.5, 0.5, 0.5). (cid:191)eby mo(cid:285)na by(cid:239)o ich u(cid:285)y(cid:202) jako wspó(cid:239)rz(cid:218)dnych tekstury 3D, trzeba je przesun(cid:200)(cid:202) do przedzia(cid:239)u od (0, 0, 0) do (1, 1, 1), a wi(cid:218)c trzeba je zwi(cid:218)kszy(cid:202) o (0.5, 0.5, 0.5). smooth out vec3 vUV; void main() { gl_Position = MVP*vec4(vVertex.xyz,1); vUV = vVertex + vec3(0.5); } Shader fragmentów u(cid:285)ywa tych wspó(cid:239)rz(cid:218)dnych do próbkowania danych wolumetrycznych (dost(cid:218)pnych teraz poprzez nowy typ samplera dla tekstur trójwymiarowych sampler3D) w celu okre(cid:258)lenia koloru fragmentu na podstawie odczytanej g(cid:218)sto(cid:258)ci. Podczas tworzenia tekstury 3D okre(cid:258)lili(cid:258)my jej wewn(cid:218)trzny format jako GL_RED (trzeci parametr funkcji glTexImage3D), a zatem teraz mo(cid:285)emy pobiera(cid:202) g(cid:218)sto(cid:258)(cid:202) z czerwonego kana(cid:239)u samplera tekstury. Aby uzyska(cid:202) odcie(cid:241) szaro(cid:258)ci, ustawiamy tak(cid:200) sam(cid:200) warto(cid:258)(cid:202) w pozosta(cid:239)ych kana(cid:239)ach koloru. smooth in vec3 vUV; uniform sampler3D volume; void main(void) { vFragColor = texture(volume, vUV).rrrr; } We wcze(cid:258)niejszych wersjach OpenGL zapisaliby(cid:258)my g(cid:218)sto(cid:258)ci wolumetryczne w specjalnie do tego przeznaczonym formacie GL_INTENSITY. Niestety zosta(cid:239) on usuni(cid:218)ty z rdzennego profilu biblioteki w wersji 3.3 i musimy pos(cid:239)ugiwa(cid:202) si(cid:218) formatami GL_RED, GL_GREEN, GL_BLUE lub GL_ALPHA. I jeszcze jedno? Przyk(cid:239)adowa aplikacja zbudowana na podstawie powy(cid:285)szej receptury wizualizuje dane wolume- tryczne fragmentu silnika. Za pomoc(cid:200) klawiszy + (plus) i – (minus) mo(cid:285)na zmienia(cid:202) liczb(cid:218) p(cid:239)atów. 234 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU Zobaczmy teraz, jak powstaje taki obraz, wy(cid:258)wietlaj(cid:200)c coraz wi(cid:218)ksz(cid:200) liczb(cid:218) p(cid:239)atów tekstury 3D, pocz(cid:200)wszy od 8 a(cid:285) po pe(cid:239)ne 256. Rezultaty wida(cid:202) na poni(cid:285)szym rysunku. W górnym rz(cid:218)dzie pokazane s(cid:200) widoki konturowe p(cid:239)atów, a u do(cid:239)u — rezultaty ich mieszania. 235 Poleć książkęKup książkę OpenGL. Receptury dla programisty Jak (cid:239)atwo zauwa(cid:285)y(cid:202), zwi(cid:218)kszanie liczby p(cid:239)atów poprawia wygl(cid:200)d renderowanego obrazu. Jed- nak po przekroczeniu warto(cid:258)ci 256 nie wida(cid:202) ju(cid:285) znacz(cid:200)cej poprawy, a powy(cid:285)ej 350 zaczyna by(cid:202) zauwa(cid:285)alne spowolnienie dzia(cid:239)ania aplikacji. Przyczyn(cid:200) jest konieczno(cid:258)(cid:202) przesy(cid:239)ania do GPU coraz wi(cid:218)kszych ilo(cid:258)ci geometrii. Zwró(cid:202) uwag(cid:218) na czarn(cid:200) chmur(cid:218) otaczaj(cid:200)c(cid:200) obiekt wolumetryczny. Jej obecno(cid:258)(cid:202) jest wynikiem b(cid:239)(cid:218)dów, jakie wyst(cid:200)pi(cid:239)y w trakcie rejestrowania danych (np. szum aparatury rejestruj(cid:200)cej lub zanieczyszczenie powietrza wokó(cid:239) skanowanego obiektu). Artefakty tego typu mo(cid:285)na usun(cid:200)(cid:202) b(cid:200)d(cid:283) to przez zastosowanie odpowiedniej funkcji przej(cid:258)cia, b(cid:200)d(cid:283) przez wyeliminowanie ich w shaderze fragmentów, co zrobimy pó(cid:283)niej w recepturze „Wolumetryczne o(cid:258)wietlenie oparte na technice ci(cid:218)cia po(cid:239)ówkowok(cid:200)towego”. Dowiedz si(cid:218) wi(cid:218)cej (cid:81) Real-Time Volume Graphics, A K Peters/CRC Press, rozdzia(cid:239) 3. „GPU-based Volume Rendering”, punkt 3.5.2 „Viewport-Aligned Slices”, s. 73 – 79. Implementacja renderingu wolumetrycznego z jednoprzebiegowym rzucaniem promieni W tej recepturze poka(cid:285)(cid:218), jak mo(cid:285)na zaimplementowa(cid:202) na GPU rendering wolumetryczny z jednoprzebiegowym rzucaniem promieni. Ogólnie rzucanie promieni mo(cid:285)e by(cid:202) wieloprze- biegowe lub jednoprzebiegowe. Podej(cid:258)cia te ró(cid:285)ni(cid:200) si(cid:218) sposobem ustalania kierunków krocz(cid:200)- cych promieni. Podej(cid:258)cie jednoprzebiegowe korzysta z jednego shadera fragmentów, które- go zasad(cid:218) dzia(cid:239)ania najlepiej wyja(cid:258)ni poni(cid:285)szy rysunek. Najpierw wyznaczamy kierunek promienia wysy(cid:239)anego z kamery. W tym celu odejmujemy po(cid:239)o- (cid:285)enie kamery od po(cid:239)o(cid:285)enia wierzcho(cid:239)ka wolumetrycznego. Pocz(cid:200)tkowym po(cid:239)o(cid:285)eniem krocz(cid:200)cego 236 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU promienia (punktem wej(cid:258)cia) jest po(cid:239)o(cid:285)enie wierzcho(cid:239)ka. Nast(cid:218)pnie przesuwamy promie(cid:241) wzd(cid:239)u(cid:285) wyznaczonego kierunku o ustalony krok. W ka(cid:285)dym punkcie pobieramy próbk(cid:218) danych wolumetrycznych. W(cid:218)drówk(cid:218) promienia ko(cid:241)czymy, gdy ten wyjdzie poza obszar danych lub kumulowany kolor fragmentu stanie si(cid:218) kompletnie nieprzezroczysty. Próbki zebrane przez w(cid:218)druj(cid:200)cy promie(cid:241) (cid:239)(cid:200)czymy ze sob(cid:200) wed(cid:239)ug okre(cid:258)lonego przepisu. Je(cid:258)li stosujemy u(cid:258)rednianie, to po prostu sumujemy wszystkie próbki i wynik dzielimy przez ich liczb(cid:218). Je(cid:258)li za(cid:258) stosujemy mieszanie alfa w kolejno(cid:258)ci od przodu do ty(cid:239)u, to mno(cid:285)ymy pobran(cid:200) próbk(cid:218) przez sk(cid:239)adow(cid:200) alfa zakumulowanego koloru i wynik odejmujemy od pobranej próbki. To daje nam sk(cid:239)adow(cid:200) alfa z poprzednich kroków. Warto(cid:258)(cid:202) t(cid:218) mno(cid:285)ymy przez pobran(cid:200) próbk(cid:218) i wynik dodajemy do zakumulowanego koloru, a na koniec dodajemy j(cid:200) do sk(cid:239)adowej alfa zaku- mulowanego koloru. Ostatecznie jako kolor fragmentu wyprowadzamy kolor zakumulowany. Przygotowania Pe(cid:239)ny kod dla tej receptury znajdziesz w folderze Rozdzia(cid:239)7/RzucaniePromieni. Jak to zrobi(cid:202)? Aby zaimplementowa(cid:202) na GPU jednoprzebiegowe rzucanie promieni, wykonaj nast(cid:218)puj(cid:200)ce czynno(cid:258)ci: 1. Podobnie jak w poprzedniej recepturze wczytaj dane wolumetryczne do trójwymiarowej tekstury OpenGL-owej. Dodatkowe obja(cid:258)nienia tego fragmentu implementacji znajdziesz w definicji funkcji LoadVolume podanej w pliku Rozdzia(cid:239)7/ RzucaniePromieni/main.cpp. 2. Przygotuj obiekty tablicy i bufora wierzcho(cid:239)ków potrzebne do wyrenderowania jednostkowego sze(cid:258)cianu. Zrób to w sposób nast(cid:218)puj(cid:200)cy: glGenVertexArrays(1, cubeVAOID); glGenBuffers(1, cubeVBOID); glGenBuffers(1, cubeIndicesID); glm::vec3 vertices[8]={ glm::vec3(-0.5f,-0.5f,-0.5f), (cid:180)glm::vec3( 0.5f,-0.5f,-0.5f),glm::vec3( 0.5f, 0.5f,-0.5f), (cid:180)glm::vec3(-0.5f, 0.5f,-0.5f),glm::vec3(-0.5f,-0.5f, 0.5f), (cid:180)glm::vec3( 0.5f,-0.5f, 0.5f),glm::vec3( 0.5f, 0.5f, 0.5f), (cid:180)glm::vec3(-0.5f, 0.5f, 0.5f)}; GLushort cubeIndices[36]={0,5,4,5,0,1,3,7,6,3,6,2,7,4,6,6,4,5,2,1,3,3,1, (cid:180)0,3,0,7,7,0,4,6,5,2,2,5,1}; glBindVertexArray(cubeVAOID); glBindBuffer (GL_ARRAY_BUFFER, cubeVBOID); glBufferData (GL_ARRAY_BUFFER, sizeof(vertices), (vertices[0].x), (cid:180)GL_STATIC_DRAW); 237 Poleć książkęKup książkę OpenGL. Receptury dla programisty glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,0,0); glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, cubeIndicesID); glBufferData (GL_ELEMENT_ARRAY_BUFFER, sizeof(cubeIndices), cubeIndices[0], GL_STATIC_DRAW); glBindVertexArray(0); 3. W funkcji renderuj(cid:200)cej uaktywnij program shaderowy rzucania promieni (Rozdzia(cid:239)7/ RzucaniePromieni/shadery/raycaster.[vert, frag]) i wyrenderuj sze(cid:258)cian jednostkowy. glEnable(GL_BLEND); glBindVertexArray(cubeVAOID); shader.Use(); glUniformMatrix4fv(shader( MVP ), 1, GL_FALSE, glm::value_ptr(MVP)); glUniform3fv(shader( camPos ), 1, (camPos.x)); glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0); shader.UnUse(); glDisable(GL_BLEND); 4. Z shadera wierzcho(cid:239)ków wyprowad(cid:283), poza po(cid:239)o(cid:285)eniem wierzcho(cid:239)ka w przestrzeni przyci(cid:218)cia, wspó(cid:239)rz(cid:218)dne trójwymiarowej tekstury potrzebne do jej próbkowania w shaderze fragmentów. Aby uzyska(cid:202) te wspó(cid:239)rz(cid:218)dne, po prostu przesu(cid:241) wspó(cid:239)rz(cid:218)dne wierzcho(cid:239)ka z przestrzeni obiektu o wektor (0.5, 0.5, 0.5). smooth out vec3 vUV; void main() { gl_Position = MVP*vec4(vVertex.xyz,1); vUV = vVertex + vec3(0.5); } 5. W shaderze fragmentów utwórz p(cid:218)tl(cid:218) przesuwaj(cid:200)c(cid:200) promie(cid:241) wzd(cid:239)u(cid:285) kierunku wyznaczonego na podstawie po(cid:239)o(cid:285)enia kamery i pocz(cid:200)tkowego wierzcho(cid:239)ka wolumetrycznego. Zatrzymaj wykonywanie p(cid:218)tli, gdy promie(cid:241) wyjdzie poza obszar danych wolumetrycznych lub zakumulowany kolor stanie si(cid:218) ca(cid:239)kowicie nieprzezroczysty. vec3 dataPos = vUV; vec3 geomDir = normalize((vUV-vec3(0.5)) - camPos); vec3 dirStep = geomDir * step_size; bool stop = false; for (int i = 0; i MAX_SAMPLES; i++) { // przesu(cid:241) promie(cid:241) o jeden krok dataPos = dataPos + dirStep; // warunek zako(cid:241)czenia stop=dot(sign(dataPos-texMin),sign(texMax-dataPos)) 3.0; if (stop) break; 6. Skomponuj pobran(cid:200) próbk(cid:218) z istniej(cid:200)cym ju(cid:285) kolorem i zwró(cid:202) wypadkow(cid:200) warto(cid:258)(cid:202) jako kolor fragmentu. 238 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU float sample = texture(volume, dataPos).r; float prev_alpha = sample - (sample * vFragColor.a); vFragColor.rgb = prev_alpha * vec3(sample) + vFragColor.rgb; vFragColor.a += prev_alpha; //wcze(cid:258)niejsze zako(cid:241)czenie p(cid:218)tli if( vFragColor.a 0.99) break; } Jak to dzia(cid:239)a? Receptura sk(cid:239)ada si(cid:218) dwóch zasadniczych cz(cid:218)(cid:258)ci. W pierwszej generujemy i renderujemy geometri(cid:218) sze(cid:258)cianu, w którym ma dzia(cid:239)a(cid:202) shader fragmentów. Mogliby(cid:258)my u(cid:285)y(cid:202) pe(cid:239)noekra- nowego czworok(cid:200)ta, tak jak to robili(cid:258)my przy implementowaniu wieloprzebiegowego (cid:258)ledzenia promieni, ale dla renderingu wolumetrycznego korzystniejsze jest zastosowanie jednostkowego sze(cid:258)cianu. Cz(cid:218)(cid:258)(cid:202) druga odbywa si(cid:218) w shaderach. W shaderze wierzcho(cid:239)ków (Rozdzia(cid:239)7/RzucaniePromieni/shadery/raycaster.vert) wyznaczane s(cid:200) wspó(cid:239)rz(cid:218)dne trójwymiarowej tekstury na podstawie po(cid:239)o(cid:285)e(cid:241) wierzcho(cid:239)ków sze(cid:258)cianu jednost- kowego. Poniewa(cid:285) sze(cid:258)cian jest po(cid:239)o(cid:285)ony w (cid:258)rodku uk(cid:239)adu wspó(cid:239)rz(cid:218)dnych, dodajemy do ka(cid:285)- dego wierzcho(cid:239)ka wektor vec(0.5), aby uzyska(cid:202) wspó(cid:239)rz(cid:218)dne tekstury w zakresie od 0 do 1. #version 330 core layout(location = 0) in vec3 vVertex; uniform mat4 MVP; smooth out vec3 vUV; void main() { gl_Position = MVP*vec4(vVertex.xyz,1); vUV = vVertex + vec3(0.5); } Potem shader fragmentów na podstawie wspó(cid:239)rz(cid:218)dnych trójwymiarowej tekstury i wspó(cid:239)rz(cid:218)d- nych kamery wyznacza kierunki krocz(cid:200)cych promieni. P(cid:218)tla (pokazana w punkcie 5.) przesuwa promie(cid:241) wzd(cid:239)u(cid:285) ustalonego kierunku, pobiera próbki danych wolumetrycznych i zgodnie z wybranym schematem komponuje z nich wypadkowy kolor fragmentu. Proces ten trwa, dopóki promie(cid:241) nie opu(cid:258)ci obszaru wolumetrycznego lub sk(cid:239)adowa alfa zakumulowanego koloru nie osi(cid:200)gnie pe(cid:239)nej swojej warto(cid:258)ci. Sta(cid:239)e texMin i texMax maj(cid:200) warto(cid:258)ci, odpowiednio, vec3(-1,-1,-1) i vec3(1,1,1). Aby okre(cid:258)li(cid:202), czy promie(cid:241) opu(cid:258)ci(cid:239) obszar danych, u(cid:285)ywamy funkcji sign, która zwraca -1, je(cid:258)li jej argument ma warto(cid:258)(cid:202) mniejsz(cid:200) od zera, 0, je(cid:258)li jest on równy zero, i 1, je(cid:258)li jest wi(cid:218)kszy od zera. Zatem dla po(cid:239)o- (cid:285)e(cid:241) skrajnych wywo(cid:239)ania tej funkcji w postaci sign(dataPos-texMin) i sign(texMax-dataPos) dadz(cid:200) vec3(1,1,1). Je(cid:258)li wymno(cid:285)ymy skalarnie dwa takie wektory, otrzymamy warto(cid:258)(cid:202) 3. A zatem, je(cid:258)li promie(cid:241) b(cid:218)dzie w obszarze danych, iloczyn skalarny da warto(cid:258)(cid:202) mniejsz(cid:200) ni(cid:285) 3. Je(cid:258)li wyjdzie wi(cid:218)cej, b(cid:218)dzie to oznacza(cid:239)o, (cid:285)e promie(cid:241) jest ju(cid:285) poza obszarem danych. 239 Poleć książkęKup książkę OpenGL. Receptury dla programisty I jeszcze jedno… Przyk(cid:239)adowa aplikacja renderuje dane wolumetryczne fragmentu silnika, wykorzystuj(cid:200)c metod(cid:218) jednoprzebiegowego rzucania promieni. Po(cid:239)o(cid:285)enie kamery mo(cid:285)na zmienia(cid:202) przez przeci(cid:200)ganie mysz(cid:200) z wci(cid:258)ni(cid:218)tym lewym przyciskiem, a przeci(cid:200)ganie z wci(cid:258)ni(cid:218)tym przyciskiem (cid:258)rodkowym powoduje przybli(cid:285)anie lub oddalanie widoku. Dowiedz si(cid:218) wi(cid:218)cej Zapoznaj si(cid:218) z nast(cid:218)puj(cid:200)cymi publikacjami: (cid:81) Real-Time Volume Graphics, A K Peters/CRC Press, rozdzia(cid:239) 7. „GPU-based Ray Casting”, s. 163 – 184. (cid:81) Single-Pass Raycasting w serwisie The Little Grasshopper, http://prideout.net/blog/ ?p=64. 240 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU Pseudoizopowierzchniowy rendering w jednoprzebiegowym rzucaniu promieni Teraz zaimplementujemy renderowanie pseudoizopowierzchni w jednoprzebiegowym rzucaniu promieni. Wi(cid:218)kszo(cid:258)(cid:202) kodu b(cid:218)dzie taka sama jak w poprzedniej recepturze, a jedyna ró(cid:285)nica b(cid:218)dzie polega(cid:239)a na zastosowaniu innego schematu komponowania próbek w shaderze frag- mentów rzucaj(cid:200)cym promienie. B(cid:218)dzie on próbowa(cid:239) znale(cid:283)(cid:202) okre(cid:258)lon(cid:200) izopowierzchni(cid:218) i je(cid:258)li j(cid:200) znajdzie, wyznaczy dla niej normaln(cid:200) w punkcie próbkowania, a nast(cid:218)pnie przeprowadzi obliczenia o(cid:258)wietleniowe dla tego punktu. Rozwi(cid:200)zanie to zapisane w pseudokodzie wygl(cid:200)da nast(cid:218)puj(cid:200)co: Wyznacz kierunek patrzenia kamery i pocz(cid:200)tkowe po(cid:239)o(cid:285)enie promienia Okre(cid:258)l d(cid:239)ugo(cid:258)(cid:202) promienia Dla ka(cid:285)dej próbki na drodze promienia Pobierz pierwsz(cid:200) próbk(cid:218) danych (sample1) z bie(cid:285)(cid:200)cego po(cid:239)o(cid:285)enia promienia Pobierz drug(cid:200) próbk(cid:218) (sample2) z nast(cid:218)pnego po(cid:239)o(cid:285)enia promienia Je(cid:258)li (sample1-isoValue) 0 i (sample2-isoValue) 0 U(cid:258)ci(cid:258)lij po(cid:239)o(cid:285)enie punktu przeci(cid:218)cia, stosuj(cid:200)c metod(cid:218) bisekcji Wyznacz gradient w punkcie przeci(cid:218)cia Zastosuj cieniowanie Phonga w punkcie przeci(cid:218)cia Przypisz fragmentowi bie(cid:285)(cid:200)cy kolor Przerwij Koniec warunku Koniec p(cid:218)tli Przygotowania Pe(cid:239)ny kod dla tej receptury znajdziesz w folderze Rozdzia(cid:239)7/Izoppowierzchnia. Samodzielne tworzenie mo(cid:285)esz rozpocz(cid:200)(cid:202) od skopiowania kodu poprzedniej receptury — z jednoprzebie- gowym rzucaniem promieni. Jak to zrobi(cid:202)? Zacznij od nast(cid:218)puj(cid:200)cych prostych czynno(cid:258)ci: 1. Podobnie jak w poprzedniej recepturze wczytaj dane wolumetryczne do trójwymiarowej tekstury OpenGL-owej. Dodatkowe obja(cid:258)nienia znajdziesz w definicji funkcji LoadVolume podanej w pliku Rozdzia(cid:239)7/Izopowierzchnia/main.cpp. 2. Przygotuj obiekty tablicy i bufora wierzcho(cid:239)ków potrzebne do wyrenderowania jednostkowego sze(cid:258)cianu — tak jak poprzednio. 3. W funkcji renderuj(cid:200)cej uaktywnij program shaderowy rzucania promieni (Rozdzia(cid:239)7/ Izopowierzchnia/shadery/raycaster.[vert, frag]) i wyrenderuj sze(cid:258)cian jednostkowy. 241 Poleć książkęKup książkę OpenGL. Receptury dla programisty glEnable(GL_BLEND); glBindVertexArray(cubeVAOID); shader.Use(); glUniformMatrix4fv(shader( MVP ), 1, GL_FALSE, glm::value_ptr(MVP)); glUniform3fv(shader( camPos ), 1, (camPos.x)); glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0); shader.UnUse(); glDisable(GL_BLEND); 4. Z shadera wierzcho(cid:239)ków wyprowad(cid:283), poza po(cid:239)o(cid:285)eniem wierzcho(cid:239)ka w przestrzeni przyci(cid:218)cia, wspó(cid:239)rz(cid:218)dne trójwymiarowej tekstury potrzebne do jej próbkowania w shaderze fragmentów. Aby uzyska(cid:202) te wspó(cid:239)rz(cid:218)dne, po prostu przesu(cid:241) obiektowe wspó(cid:239)rz(cid:218)dne wierzcho(cid:239)ka w sposób nast(cid:218)puj(cid:200)cy: smooth out vec3 vUV; void main() { gl_Position = MVP*vec4(vVertex.xyz,1); vUV = vVertex + vec3(0.5); } 5. W shaderze fragmentów utwórz p(cid:218)tl(cid:218) przesuwaj(cid:200)c(cid:200) promie(cid:241) wzd(cid:239)u(cid:285) kierunku wyznaczonego na podstawie po(cid:239)o(cid:285)enia kamery i pocz(cid:200)tkowego wierzcho(cid:239)ka wolumetrycznego. Zatrzymaj wykonywanie p(cid:218)tli, gdy promie(cid:241) wyjdzie poza obszar danych lub zakumulowany kolor stanie si(cid:218) ca(cid:239)kowicie nieprzezroczysty. vec3 dataPos = vUV; vec3 geomDir = normalize((vUV-vec3(0.5)) - camPos); vec3 dirStep = geomDir * step_size; bool stop = false; for (int i = 0; i MAX_SAMPLES; i++) { // przesu(cid:241) promie(cid:241) o jeden krok dataPos = dataPos + dirStep; // warunek zako(cid:241)czenia stop=dot(sign(dataPos-texMin),sign(texMax-dataPos)) 3.0; if (stop) break; 6. W celu wyznaczenia izopowierzchni bierz po dwie próbki i sprawdzaj, czy przy przej(cid:258)ciu od jednej do drugiej promie(cid:241) przeci(cid:200)(cid:239) izopowierzchni(cid:218). Gdy co(cid:258) takiego stwierdzisz, ustal dok(cid:239)adne miejsce przeci(cid:218)cia, stosuj(cid:200)c metod(cid:218) bisekcji. Na koniec zastosuj na izopowierzchni cieniowanie Phonga, przyjmuj(cid:200)c, (cid:285)e (cid:283)ród(cid:239)o (cid:258)wiat(cid:239)a znajduje si(cid:218) tam, gdzie kamera. float sample=texture(volume, dataPos).r; float sample2=texture(volume, dataPos+dirStep).r; if( (sample -isoValue) 0 (sample2-isoValue) = 0.0) { vec3 xN = dataPos; vec3 xF = dataPos+dirStep; vec3 tc = Bisection(xN, xF, isoValue); 242 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU vec3 N = GetGradient(tc); vec3 V = -geomDir; vec3 L = V; vFragColor = PhongLighting(L,N,V,250, vec3(0.5)); break; } } Funkcj(cid:218) Bisection zdefiniuj nast(cid:218)puj(cid:200)co: vec3 Bisection(vec3 left, vec3 right , float iso) { for(int i=0;i 4;i++) { vec3 midpoint = (right + left) * 0.5; float cM = texture(volume, midpoint).x ; if(cM iso) left = midpoint; else right = midpoint; } return vec3(right + left) * 0.5; } Funkcja ta bierze dwie próbki, mi(cid:218)dzy którymi le(cid:285)y zadana warto(cid:258)(cid:202), i stara si(cid:218) wyznaczy(cid:202) jej dok(cid:239)adne po(cid:239)o(cid:285)enie. W tym celu uruchamia p(cid:218)tl(cid:218), w której cyklicznie wyznacza punkt (cid:258)rodkowy mi(cid:218)dzy próbkami i porównuje jego warto(cid:258)(cid:202) wolumetryczn(cid:200) z zadan(cid:200) warto(cid:258)ci(cid:200) izopowierzchni. Je(cid:258)li warto(cid:258)(cid:202) w punkcie (cid:258)rodkowym jest mniejsza od warto(cid:258)ci izopowierzchni, punkt (cid:258)rodkowy zast(cid:218)puje lew(cid:200) próbk(cid:218). Gdy jest inaczej, zast(cid:218)puje próbk(cid:218) praw(cid:200). W ten sposób szybko zaw(cid:218)(cid:285)a si(cid:218) obszar poszukiwa(cid:241). Po wykonaniu okre(cid:258)lonej liczby takich operacji zwracane jest po(cid:239)o(cid:285)enie punktu (cid:258)rodkowego. Funkcja Gradient wyznacza gradient warto(cid:258)ci wolumetrycznych metod(cid:200) sko(cid:241)czonych ró(cid:285)nic centralnych. vec3 GetGradient(vec3 uvw) { vec3 s1, s2; //wyznaczanie centralnego ilorazu ró(cid:285)nicowego s1.x = texture(volume, uvw-vec3(DELTA,0.0,0.0)).x ; s2.x = texture(volume, uvw+vec3(DELTA,0.0,0.0)).x ; s1.y = texture(volume, uvw-vec3(0.0,DELTA,0.0)).x ; s2.y = texture(volume, uvw+vec3(0.0,DELTA,0.0)).x ; s1.z = texture(volume, uvw-vec3(0.0,0.0,DELTA)).x ; s2.z = texture(volume, uvw+vec3(0.0,0.0,DELTA)).x ; return normalize((s1-s2)/2.0); } 243 Poleć książkęKup książkę OpenGL. Receptury dla programisty Jak to dzia(cid:239)a? Wi(cid:218)kszo(cid:258)(cid:202) kodu jest podobna do tego, który napisali(cid:258)my w recepturze z jednoprzebiegowym rzucaniem promieni. Ró(cid:285)nica pojawia si(cid:218) dopiero w p(cid:218)tli realizuj(cid:200)cej ruch promienia poprzez obszar wolumetryczny. Tym razem nie stosujemy (cid:285)adnego komponowania koloru, lecz wyzna- czamy miejsca zerowe funkcji opisuj(cid:200)cej izopowierzchni(cid:218) przez sprawdzanie próbek z dwóch kolejnych kroków. Dobrze ilustruje to poni(cid:285)szy rysunek. Je(cid:258)li mi(cid:218)dzy badanymi próbkami jest miejsce zerowe, precyzujemy jego po(cid:239)o(cid:285)enie, stosuj(cid:200)c metod(cid:218) bisekcji. Nast(cid:218)pnie renderujemy izopowierzchni(cid:218), stosuj(cid:200)c model o(cid:258)wietleniowy Phonga, i opuszczamy p(cid:218)tl(cid:218) maszeruj(cid:200)cego promienia. W ten sposób wyrenderujemy izopowierzchni(cid:218) po(cid:239)o(cid:285)on(cid:200) naj- bli(cid:285)ej kamery. Gdyby(cid:258)my chcieli wyrenderowa(cid:202) wszystkie izopowierzchnie o zadanej warto(cid:258)ci, musieliby(cid:258)my usun(cid:200)(cid:202) instrukcj(cid:218) przerywaj(cid:200)c(cid:200) wykonywanie p(cid:218)tli. I jeszcze jedno… Przyk(cid:239)adowa aplikacja stanowi(cid:200)ca implementacj(cid:218) powy(cid:285)szej receptury renderuje dane wolu- metryczne zeskanowanego fragmentu silnika. Po uruchomieniu wy(cid:258)wietla obraz pokazany na rysunku na nast(cid:218)pnej stronie. Dowiedz si(cid:218) wi(cid:218)cej (cid:81) Advanced Illumination Techniques for GPU-based Volume Rendering, notatki z konferencji SIGGRAPH 2008, dost(cid:218)pne pod adresem http://www.voreen.org/ files/sa08-coursenotes_1.pdf. 244 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU Rendering wolumetryczny z u(cid:285)yciem splattingu Tym razem zaimplementujemy technik(cid:218) zwan(cid:200) splattingiem. Jej algorytm sprowadza si(cid:218) do zamiany wokseli na placki (splats) przez splatanie ich z j(cid:200)drem filtra gaussowskiego. Gaus- sowskie j(cid:200)dro wyg(cid:239)adzaj(cid:200)ce usuwa wy(cid:285)sze cz(cid:218)stotliwo(cid:258)ci i wyg(cid:239)adza kraw(cid:218)dzie, przez co wyren- derowany obraz wygl(cid:200)da na lepiej dopracowany. Przygotowania Gotowy kod receptury znajduje si(cid:218) w folderze Chapter7/Splatting. 245 Poleć książkęKup książkę OpenGL. Receptury dla programisty Jak to zrobi(cid:202)? Zacznij od nast(cid:218)puj(cid:200)cych prostych czynno(cid:258)ci: 1. Wczytaj dane wolumetryczne i umie(cid:258)(cid:202) je w tablicy. std::ifstream infile(filename.c_str(), std::ios_base::binary); if(infile.good()) { pVolume = new GLubyte[XDIM*YDIM*ZDIM]; infile.read(reinterpret_cast char* (pVolume), XDIM*YDIM*ZDIM*sizeof(GLubyte)); infile.close(); return true; } else { return false; } 2. Utwórz trzy p(cid:218)tle, które b(cid:218)d(cid:200) przebiega(cid:239)y przez ca(cid:239)(cid:200) obj(cid:218)to(cid:258)(cid:202) danych wolumetrycznych, woksel po wokselu. vertices.clear(); int dx = XDIM/X_SAMPLING_DIST; int dy = YDIM/Y_SAMPLING_DIST; int dz = ZDIM/Z_SAMPLING_DIST; scale = glm::vec3(dx,dy,dz); for(int z=0;z ZDIM;z+=dz) { for(int y=0;y YDIM;y+=dy) { for(int x=0;x XDIM;x+=dx) { SampleVoxel(x,y,z); } } } Funkcja SampleVoxel jest zdefiniowana w klasie VolumeSplatter nast(cid:218)puj(cid:200)co: void VolumeSplatter::SampleVoxel(const int x, const int y, const int z) { GLubyte data = SampleVolume(x, y, z); if(data isoValue) { Vertex v; v.pos.x = x; v.pos.y = y; v.pos.z = z; v.normal = GetNormal(x, y, z); v.pos *= invDim; vertices.push_back(v); } } 3. W ka(cid:285)dym kroku pobierz próbk(cid:218) warto(cid:258)ci wolumetrycznych z bie(cid:285)(cid:200)cego woksela. Je(cid:258)li pobrana warto(cid:258)(cid:202) jest wi(cid:218)ksza ni(cid:285) warto(cid:258)(cid:202) okre(cid:258)laj(cid:200)ca izopowierzchni(cid:218), zapisz po(cid:239)o(cid:285)enie woksela i jego normaln(cid:200) w tablicy wierzcho(cid:239)ków. 246 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU GLubyte data = SampleVolume(x, y, z); if(data isoValue) { Vertex v; v.pos.x = x; v.pos.y = y; v.pos.z = z; v.normal = GetNormal(x, y, z); v.pos *= invDim; vertices.push_back(v); } Funkcja SampleVolume bierze wspó(cid:239)rz(cid:218)dne wskazanego punktu i zwraca najbli(cid:285)sz(cid:200) warto(cid:258)(cid:202) wolumetryczn(cid:200). Jest ona zdefiniowana w klasie VolumeSplatter w sposób nast(cid:218)puj(cid:200)cy: GLubyte VolumeSplatter::SampleVolume(const int x, const int y, const int (cid:180)z) { int index = (x+(y*XDIM)) + z*(XDIM*YDIM); if(index 0) index = 0; if(index = XDIM*YDIM*ZDIM) index = (XDIM*YDIM*ZDIM)-1; return pVolume[index]; } 4. Po pobraniu próbek przeka(cid:285) wygenerowane wierzcho(cid:239)ki do obiektu tablicy wierzcho(cid:239)ków (VAO) zawieraj(cid:200)cego obiekt bufora wierzcho(cid:239)ków (VBO). glGenVertexArrays(1, volumeSplatterVAO); glGenBuffers(1, volumeSplatterVBO); glBindVertexArray(volumeSplatterVAO); glBindBuffer (GL_ARRAY_BUFFER, volumeSplatterVBO); glBufferData (GL_ARRAY_BUFFER, splatter- GetTotalVertices()*sizeof(Vertex), splatter- GetVertexPointer(), (cid:180)GL_STATIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,sizeof(Vertex), 0); glEnableVertexAttribArray(1); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,sizeof(Vertex), (const (cid:180)GLvoid*) offsetof(Vertex, normal)); 5. Przygotuj dwa FBO dla renderingu pozaekranowego. Pierwszego z nich (filterFBOID) u(cid:285)yj do wyg(cid:239)adzania gaussowskiego. glGenFramebuffers(1, filterFBOID); glBindFramebuffer(GL_FRAMEBUFFER,filterFBOID); glGenTextures(2, blurTexID); for(int i=0;i 2;i++) { glActiveTexture(GL_TEXTURE1+i); glBindTexture(GL_TEXTURE_2D, blurTexID[i]); //ustaw parametry tekstury 247 Poleć książkęKup książkę OpenGL. Receptury dla programisty glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA32F,IMAGE_WIDTH,IMAGE_HEIGHT,0, (cid:180)GL_RGBA,GL_FLOAT,NULL); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0+ (cid:180)i,GL_TEXTURE_2D,blurTexID[i],0); } GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if(status == GL_FRAMEBUFFER_COMPLETE) { cout Ustawienie filtrujacego FBO powiodlo sie. endl; } else { cout Problem z ustawieniem filtrujacego FBO. endl; } 6. Drugiego FBO (fboID) u(cid:285)yj do wyrenderowania sceny, która b(cid:218)dzie wyg(cid:239)adzana po pierwszym przebiegu. Dodaj do tego FBO obiekt bufora renderingu, aby umo(cid:285)liwi(cid:202) testowanie g(cid:239)(cid:218)bi. glGenFramebuffers(1, fboID); glGenRenderbuffers(1, rboID); glGenTextures(1, texID); glBindFramebuffer(GL_FRAMEBUFFER,fboID); glBindRenderbuffer(GL_RENDERBUFFER, rboID); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texID); //ustaw parametry tekstury glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, IMAGE_WIDTH, IMAGE_HEIGHT, 0, (cid:180)GL_RGBA, GL_FLOAT, NULL); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, (cid:180)GL_TEXTURE_2D, texID, 0); glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, (cid:180)GL_RENDERBUFFER, rboID); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, IMAGE_WIDTH, (cid:180)IMAGE_HEIGHT); status = glCheckFramebufferStatus(GL_FRAMEBUFFER); if(status == GL_FRAMEBUFFER_COMPLETE) { cout Ustawienie pozaekranowego FBO powiodlo sie. endl; } else { cout Problem z ustawieniem pozaekranowego FBO. endl; } 7. W funkcji renderuj(cid:200)cej najpierw wyrenderuj do tekstury punktowe placki. U(cid:285)yj do tego celu drugiego FBO (fboID). glBindFramebuffer(GL_FRAMEBUFFER,fboID); glViewport(0,0, IMAGE_WIDTH, IMAGE_HEIGHT); glDrawBuffer(GL_COLOR_ATTACHMENT0); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); glm::mat4 T = glm::translate(glm::mat4(1), glm::vec3(-0.5,-0.5,-0.5)); glBindVertexArray(volumeSplatterVAO); shader.Use(); glUniformMatrix4fv(shader( MV ), 1, GL_FALSE, glm::value_ptr(MV*T)); 248 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU glUniformMatrix3fv(shader( N ), 1, GL_FALSE, (cid:180)glm::value_ptr(glm::inverseTranspose(glm::mat3(MV*T)))); glUniformMatrix4fv(shader( P ), 1, GL_FALSE, glm::value_ptr(P)); glDrawArrays(GL_POINTS, 0, splatter- GetTotalVertices()); shader.UnUse(); Budowa shadera wierzcho(cid:239)ków realizuj(cid:200)cego splatting (Rozdzia(cid:239)7/Splatting/ shadery/splatShader.vert) jest podana poni(cid:285)ej. Jest tu wyznaczana normalna w przestrzeni oka. Rozmiar placka jest obliczany na podstawie rozmiarów obszaru wolumetrycznego i próbkowanego woksela. Po uwzgl(cid:218)dnieniu po(cid:239)o(cid:285)enia placka wzgl(cid:218)dem kamery jego rozmiar jest zapisywany przez shader w zmiennej gl_PointSize. #version 330 core layout(location = 0) in vec3 vVertex; layout(location = 1) in vec3 vNormal; uniform mat4 MV; uniform mat3 N; uniform mat4 P; smooth out vec3 outNormal; uniform float splatSize; void main() { vec4 eyeSpaceVertex = MV*vec4(vVertex,1); gl_PointSize = 2*splatSize/-eyeSpaceVertex.z; gl_Position = P * eyeSpaceVertex; outNormal = N*vNormal; } Shader fragmentów bior(cid:200)cy udzia(cid:239) w realizacji splattingu (Rozdzia(cid:239)7/Splatting/ shadery/splatShader.frag) ma budow(cid:218) nast(cid:218)puj(cid:200)c(cid:200): #version 330 core layout(location = 0) out vec4 vFragColor; smooth in vec3 outNormal; const vec3 L = vec3(0,0,1); const vec3 V = L; const vec4 diffuse_color = vec4(0.75,0.5,0.5,1); const vec4 specular_color = vec4(1); void main() { vec3 N; N = normalize(outNormal); vec2 P = gl_PointCoord*2.0 - vec2(1.0); float mag = dot(P.xy,P.xy); if (mag 1) discard; float diffuse = max(0, dot(N,L)); vec3 halfVec = normalize(L+V); float specular=pow(max(0, dot(halfVec,N)),400); vFragColor = (specular*specular_color) + (diffuse*diffuse_color); } 249 Poleć książkęKup książkę OpenGL. Receptury dla programisty 8. Nast(cid:218)pnie ustaw filtruj(cid:200)cy FBO i rysuj(cid:200)c pe(cid:239)noekranowy czworok(cid:200)t zastosuj gaussowskie wyg(cid:239)adzanie najpierw w pionie, a potem w poziomie — tak jak w recepturze z wariancyjnym mapowaniem cieni z rozdzia(cid:239)u 4. glBindVertexArray(quadVAOID); glBindFramebuffer(GL_FRAMEBUFFER, filterFBOID); glDrawBuffer(GL_COLOR_ATTACHMENT0); gaussianV_shader.Use(); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); glDrawBuffer(GL_COLOR_ATTACHMENT1); gaussianH_shader.Use(); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); 9. Odwi(cid:200)(cid:285) filtruj(cid:200)cy FBO, przywró(cid:202) domy(cid:258)lny bufor rysowania i wyrenderuj przefiltrowany rezultat na ekranie. glBindFramebuffer(GL_FRAMEBUFFER,0); glDrawBuffer(GL_BACK_LEFT); glViewport(0,0,WIDTH, HEIGHT); quadShader.Use(); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); quadShader.UnUse(); glBindVertexArray(0); Jak to dzia(cid:239)a? Algorytm splattingu polega na renderowaniu wokseli jako gaussowskich plam i rzutowaniu ich na ekran. Aby to zrealizowa(cid:202), najpierw wybieramy z obszaru danych wolumetrycznych odpo- wiednie woksele. W tym celu przegl(cid:200)damy ca(cid:239)y obszar w poszukiwaniu wokseli o zadanej izowarto(cid:258)ci. Gdy napotykamy w(cid:239)a(cid:258)ciwy, zapisujemy jego po(cid:239)o(cid:285)enie i normaln(cid:200) w tablicy wierz- cho(cid:239)ków. Dla w(cid:239)asnej wygody wszystkie potrzebne do tego funkcje umieszczamy w klasie VolumeSplatter. Po utworzeniu nowej instancji klasy VolumeSplatter (o nazwie splatter) ustalamy wymiary obszaru z danymi wolumetrycznymi i wczytujemy te dane. Nast(cid:218)pnie okre(cid:258)lamy warto(cid:258)(cid:202) wyzna- czaj(cid:200)c(cid:200) izopowierzchni(cid:218) i liczb(cid:218) próbkowanych wokseli. Na koniec wywo(cid:239)ujemy funkcj(cid:218) Volume (cid:180)Splatter::SplatVolume, która dokonuje przegl(cid:200)du ca(cid:239)ego obszaru wolumetrycznego woksel po wokselu. splatter = new VolumeSplatter(); splatter- SetVolumeDimensions(256,256,256); splatter- LoadVolume(volume_file); splatter- SetIsosurfaceValue(40); splatter- SetNumSamplingVoxels(64,64,64); std::cout Generuje punktowe placki ... ; splatter- SplatVolume(); std::cout Gotowe. std::endl; 250 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU Obiekt splatter umieszcza wygenerowane wierzcho(cid:239)ki i ich normalne w tablicy wierzcho(cid:239)ków i w wi(cid:200)zanym z ni(cid:200) obiekcie bufora wierzcho(cid:239)ków. W funkcji renderuj(cid:200)cej najpierw rysujemy w jednym przebiegu ca(cid:239)y zbiór placków do pozaekranowego celu, a rezultat poddajemy filtrowa- niu przez dwa gaussowskie filtry splotowe. Na koniec wy(cid:258)wietlamy przefiltrowany obraz na pe(cid:239)noekranowym czworok(cid:200)cie. Shader wierzcho(cid:239)ków (Rozdzia(cid:239)7/Splatting/shadery/splatShader.vert) oblicza rozmiary punktów wy(cid:258)wietlanych na ekranie w zale(cid:285)no(cid:258)ci od g(cid:239)(cid:218)boko(cid:258)ci, na jakiej le(cid:285)y dany placek. (cid:191)eby to by(cid:239)o mo(cid:285)liwe do wykonania w shaderze wierzcho(cid:239)ków, musi by(cid:202) w(cid:239)(cid:200)czony stan GL_VERTEX_ (cid:180)PROGRAM_POINT_SIZE, wi(cid:218)c wywo(cid:239)ujemy funkcj(cid:218) glEnable(GL_VERTEX_PROGRAM_POINT_SIZE). Shader ten wyznacza równie(cid:285) normalne dla placków w przestrzeni oka. vec4 eyeSpaceVertex = MV*vec4(vVertex,1); gl_PointSize = 2*splatSize/-eyeSpaceVertex.z; gl_Position = P * eyeSpaceVertex; outNormal = N*vNormal; Aby nada(cid:202) plackom okr(cid:200)g(cid:239)e kszta(cid:239)ty na ekranie, shader fragmentów (Rozdzia(cid:239)7/Splatting/shadery/ splatShader.frag) odrzuca wszystkie fragmenty le(cid:285)(cid:200)ce poza okr(cid:218)giem o promieniu równym promieniowi wy(cid:258)wietlanego placka. vec3 N; N = normalize(outNormal); vec2 P = gl_PointCoord*2.0 - vec2(1.0); float mag = dot(P.xy,P.xy); if (mag 1) discard; Potem shader wyznacza sk(cid:239)adowe rozproszenia i odblasku, aby po uwzgl(cid:218)dnieniu jeszcze normalnej wy(cid:258)wietlanego placka poda(cid:202) na wyj(cid:258)cie ostateczny kolor bie(cid:285)(cid:200)cego fragmentu. float diffuse = max(0, dot(N,L)); vec3 halfVec = normalize(L+V); float specular = pow(max(0, dot(halfVec,N)),400); vFragColor = (specular*specular_color) + (diffuse*diffuse_color); I jeszcze jedno… Przyk(cid:239)adowa aplikacja, podobnie jak poprzednie, renderuje dane wolumetryczne zeskanowa- nego fragmentu silnika. Jak wida(cid:202) na poni(cid:285)szym rysunku, obraz uzyskany metod(cid:200) splattingu jest nieco rozmyty, a jest to skutek dzia(cid:239)ania wyg(cid:239)adzaj(cid:200)cych filtrów gaussowskich. Zaprezentowana receptura umo(cid:285)liwia poznanie metody splattingu, ale zastosowane przez nas rozwi(cid:200)zanie polegaj(cid:200)ce na sprawdzaniu wszystkich wokseli nie jest zbyt wyszukane i w przypadku wi(cid:218)kszego zbioru danych wolumetrycznych nale(cid:285)a(cid:239)oby u(cid:285)y(cid:202) jakiej(cid:258) struktury, np. drzewa ósem- kowego, która pozwoli(cid:239)aby szybciej zlokalizowa(cid:202) woksele o odpowiednich warto(cid:258)ciach. 251 Poleć książkęKup książkę OpenGL. Receptury dla programisty Dowiedz si(cid:218) wi(cid:218)cej Zapoznaj si(cid:218) z nast(cid:218)puj(cid:200)cymi projektami: (cid:81) Projekt Qsplat, http://graphics.stanford.edu/software/qsplat/. (cid:81) Prace nad rozwojem splattingu w ETH Zurych, http://graphics.ethz.ch/research/ past_projects/surfels/surfacesplatting/. Implementacja funkcji przej(cid:258)cia dla klasyfikacji obj(cid:218)to(cid:258)ciowej W tej recepturze poka(cid:285)(cid:218), jak mo(cid:285)na zaimplementowa(cid:202) klasyfikacj(cid:218) danych wolumetrycznych w po(cid:239)(cid:200)czeniu z prezentowan(cid:200) wcze(cid:258)niej metod(cid:200) ci(cid:218)cia trójwymiarowej tekstury na p(cid:239)aty. Klasy- fikacja b(cid:218)dzie polega(cid:239)a na przypisywaniu okre(cid:258)lonym warto(cid:258)ciom wolumetrycznym kolorów pobieranych z wygenerowanej w tym celu jednowymiarowej tekstury. Przydzielanie w(cid:239)a(cid:258)ciwych kolorów b(cid:218)dzie wykonywa(cid:239) specjalny shader fragmentów. Rezultatem jego dzia(cid:239)ania b(cid:218)dzie wi(cid:218)c kolor fragmentu uzale(cid:285)niony od warto(cid:258)ci wolumetrycznej reprezentowanej przez ten fragment. 252 Poleć książkęKup książkę Rozdzia(cid:225) 7. • Techniki renderingu wolumetrycznego bazuj(cid:261)ce na GPU Ca(cid:239)a reszta receptury wygl(cid:200)da tak samo jak w przypadku ci(cid:218)cia tekstury 3D. Oczywi(cid:258)cie kla- syfikacj(cid:218) danych wolumetrycznych mo(cid:285)na stosowa(cid:202) w po(cid:239)(cid:200)czeniu z dowolnym algorytmem renderowania. Przygotowania Gotowy kod dla tej receptury znajdziesz w folderze Rozdzia(cid:239)7/Ci(cid:218)cieTekstury3DKlasyfikacja. Jak to zrobi(cid:202)? Zacznij od nast(cid:218)puj(cid:200)cych prostych czynno(cid:258)ci: 1. Wczytaj dane wolumetryczne i ustaw ci(cid:218)cie tekstury tak samo jak w recepturze „Implementacja renderingu wolumetrycznego z ci(cid:218)ciem tekstury 3D na p(cid:239)aty”. 2. Dla funkcji przej(cid:258)cia przygotuj zestaw kolorów. Zakoduj tylko niektóre, a wszystkie po(cid:258)rednie niech zostan(cid:200) wygenerowane na zasadzie interpolacji w trakcie dzia(cid:239)ania aplikacji. Szczegó(cid:239)y takiego rozwi(cid:200)zania znajdziesz w pliku Rozdzia(cid:239)7/ Ci(cid:218)cieTekstury3DKlasyfikacja/main.cpp. float pData[256][4]; int indices[9]; for(int i=0;i 9;i++) { int index = i*28; pData[index][0] = jet_values[i].x; pData[index][1] = jet_values[i].y; pData[index][2] = jet_values[i].z; pData[index][3] = jet_values[i].w; indices[i] = index; } for(int j=0;j 9-1;j++) { float dDataR = (pData[indices[j+1]][0] - pData[indices[j]][0]); float dDataG = (pData[indices[j+1]][1] - pData[indices[j]][1]); float dDataB = (pData[indices[j+1]][2] - pData[indices[j]][2]); float dDataA = (pData[indices[j+1]][3] - pData[indices[j]][3]); int dIndex = indices[j+1]-indices[j]; float dDataIncR = dDataR/float(dIndex); float dDataIncG = dDataG/float(dIndex); float dDataIncB = dDataB/float(dIndex); float dDataIncA = dDataA/float(dIndex); for(int i=indices[j]+1;i indices[j+1];i++) { pData[i][0] = (pData[i-1][0] + dDataIncR); pData[i][1] = (pData[i-1][1] + dDataIncG); pData[i][2] = (pData[i-1][2] + dDataIncB); 253 Poleć książkęKup książkę OpenGL. Receptury dla programisty pData[i][3] = (pData
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

OpenGL. Receptury dla programisty
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ą: