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)