GLSL + OpenGL shader tutorial

poruka: 9
|
čitano: 10.095
|
moderatori: Lazarus Long, XXX-Man, vincimus
1
+/- sve poruke
ravni prikaz
starije poruke gore
15 godina
neaktivan
offline
Shader tutorial (OpenGL + GLSL)

En taro Adun!


Potaknut svim silnim pitanjima o tome kako se radi igra na forumu, odlucio sam sastaviti osnovni tutorial koji objasnjava zasto profesionalne igre izgledaju, eh, profesionalno.

Kratki odgovor na to je programabilni pipeline video adaptera, nasuprot fiksnom, puno algebre, matrica, teorije o aproksimaciji svijetla i sjena.

Tutorial nije namijenjen pocetnicima, nego ljudima koji znaju napraviti sandbox OpenGL aplikaciju, amjestiti scenu, bindati teksture i izrendati. Ovo ce samo pomoci da ta scena izgleda nesto bolje. Takodjer ce pomoci svim pocetnicima da uvide koliko u izradi igre ima posla i znanja. Ne planiram obeshrabriti nikoga, nego pomoci ljudima da razbiju neke iluzije o izradi igara i onda ih navesti da eventualno razmisle jos jednom o svim izborima koje su donijeli u vezi svojeg projekta. Ne planiram postati OpenGL sandbox kod, nego samo isjecke iz shadera. Bezobrazan sam namjerno jer zelim da ljudi sjednu i iskoriste mozak radeci na ovome, ne da iskopiraju gotov kod -cak i kopiranje tih isjecaka i njihovo ljepljenje skupa ce biti dovoljno.


Jedan VELIKI DISLAIMER:

molim SVAKOGA da mi ne pise nista na PM trazeci pomoc osobno; pored toga sto mi se ne da odgovarati na PM koji podrzava znakova koliko i SMS poruka, postavite pitanje ovdje tako da i drugi mogu beneficirati od odgovora koji mogu napisati ili ja ili netko drugi. To je, inace, namjena foruma.

Teme koje cemo obraditi:
- perpixel vs. pervertex shading
- Lambertova jednadzba difuznog osvijetljenja
- phong spekularna refleksija
- normal mapping
- ambient occlusion mapping


Alati koji vam trebaju:
- Blender (besplatni profesionalni staticki 3D renderer i modeler)
- OpenGL SDK
- Algebra
- Zivci
- Moj blender file koji sadrzi highpoly i lowpoly verziju modela iz primjera (download)



Evo nekoliko slika da vizuelno objasne tech:

a) Fixed pipeline (lambert/blinn shading):

Lowpoly monkey sa fixed pipeline sjencanjem (pervertex interpolacija, Lambert/Blinn) Lowpoly monkey sa fixed pipeline sjencanjem (pervertex interpolacija, Lambert/Blinn)

b) Shaderi u igri, perpixel sjencanje: lambertova jednadzba za diffuzno svijetlo i phong jednadzba za specular. Razlika se najvise vidi na desnoj obrvi i lijevom uhu, gdje je spekularno svijetlo doslo do izrazaja zbog pozicije kamere. Kako je ovo vrlo low poly model, da bolje prikaze vizualnu razliku medju efektima, perpixel shading se puno bolje vidi kad dodamo normal mape (c)
Lowpoly monkey sa shaderom - perpixel Lambert/Phong Lowpoly monkey sa shaderom - perpixel Lambert/Phong
 

c) normal mapping. Textura je 512x512, bakeana u blenderu iz highpoly modela. Za slucaj da nije jasno - broj poligona se NIJE povecao! Model je potpuno jednak modelu iz prethodne dvije slike.
Lowpoly monkey, perpixel lambert/phong i normal mapping Lowpoly monkey, perpixel lambert/phong i normal mapping
 

d) ambient occlusion mapping. Textura je 512x512, takodjer bakeana s blenderom. Primjetite razlike u podrucjima gdje konture lica normalno osjencavaju model. Ova tehnika je u Crytek engineu i Mirror's edge engineu implementirana u realtimeu. Kalkulacija ambient occlusiona je teska za GPU, a tehnika koju ja razradjujem ovdje je puno jednostavnija i zahtijeva teksturu, no drawback je sto moze biti primjenjena samo za neanimirane modele.Usput, ekstremno crna podrucja su artifakti nastali mojim nedostatkom volje da popravljam UV mapping.
Lowpoly monkey, perpixel lambert/phong, normal mapping i ambient occlusions Lowpoly monkey, perpixel lambert/phong, normal mapping i ambient occlusions


e) Finalni model - perpixel lambert/phong, normal mapping, ambient occlusions i difuzna mapa, nacrtana by yours truly. Kao sto vidite, i ovdje sam lijeno sve pobojao u prekrasnu smedju boju, i cak se nisam trudio ni traziti poligone u teksturi koji odgovaraju dijelovima oka (tako da je uho ispalo malo obojano).
Lowpoly monkey, perpixel lambert/phong, normal mapping, ambient occlusion i normalna diffuzna mapa (katastrofalno odradjena by myself) Lowpoly monkey, perpixel lambert/phong, normal mapping, ambient occlusion i normalna diffuzna mapa (katastrofalno odradjena by myself)
 



Slijedeci post krece s kratkim objasnjenjem o tome sto su shaderi, kakva je razlika izmedju vertex i fragment shadera, i kako implementirati perpixel lambert difuzno sjencanje.

"Fans are clinging complaining dipshits who will never ever be happy for any concession you make. The sooner you shut up their shrilled tremolous voices, the happier are you going to be for it.&q
Poruka je uređivana zadnji put ned 26.7.2009 14:59 (Deus ex machina).
 
9 0 hvala 22
15 godina
neaktivan
offline
Shader tutorial (OpenGL + GLSL)

Krenimo jednostavno:

perpixel difuzno sjencanje, bez spekularne komponente. Ovo ce vec biti znatni napredak u odnosu na fixni pipeline, pogotovu kad svjetlo postavite blizu poligona bez puno faceova.

 

Potrebno je znati da jednom kad krenete u programabilni pipeline, ne mozete vise iskoristiti nista iz fixnog. Primjerice, ako dodamo perpixel fragment shader, to znaci da ako zelite teksture, morate sami dodati tu funkcionalnost. Stalno spominjem fragment shader, sta je to? Postoje dvije vrste programabilnih unita na vasem video adapteru, to su vertex shader i fragment shader.

Program u vertex shaderu se izvrsava za svaki vertex koji prolazi kroz pipeline - njegov posao je da transformira vertex u poziciju na ekranu i prenese u fragment shader informacije koje ondje nisu dostupne.

Program u fragment shaderu se izvrsava za svaki fragment, odnosno podatkovnu jedinicu iz koje se generira boja pixela. Fragment ultimativno zavrsava postavljanjem boje na vidljivi pixel. Da ne bi pomijesali dva termina skupa - pixel je iskljucivo jedinica na ekranu definirana bojom, dok fragment sadrzi dodatne informacije kao sto su alpha, stencil, dubina i slicno.

 

Dakle, nas vertex shader nema tezak posao: transformirati vertex.

Fragment shader ce imati nesto vise posla, odnosno iskalkulirati Lambertovu jednadzbu difuznog osvijetljenja:

 

Id = Kd * dot(L, N) * Ld

 

gdje je

I  = 4D vektor, RGBA osvijetljenost pixela

Kd = 4D vektor, RGBA difuzna refleksija materijala

L  = 3D vektor, jedinicni direkcijski vektor svijetla

N  = 3D vektor, normala

Ld = 4D vektor, difuzna jakost svijetla

 

 

Ako vec niste, sad je vrijeme da sredite vasu OpenGL sandbox scenu. Treba nam jedno tockasto svijetlo i jedan objekt - predlazem sferu ili omnipopular teapot, koji ce najbolje prikazati razliku.

...

...

...

Vec smo gotovi? OK.

Svaki shader sadrzi jednu jedinu main funkciju:

 

void main() {}

Kao sto smo vec napomenuli, najmanji posao koji vertex shader mora odraditi jest odrediti poziciju vertexa na ekranu. To zvuci kompliciranije nego sto jest - matematicki, potrebno je pomnoziti 3D vektor koji predstavlja translaciju vertexa sa projekcijskom matricom i model matricom. Translatirana pozicija vertexa sprema se u predefiniranu varijablu (uniform) gl_Position.

 

U GLSL-u postoji funkcija nazvana ftransform(), koja taj posao odradjuje za nas. Ta funkcija je bijesno optimizirana u hardwareu, i ne postoji niti jedan razlog zbog kojeg bi rucno isli mnoziti vertex i matricu, sto znaci da se translacija vertexa svodi na jednu liniju koda:

 

gl_Position = ftransform();

 

Komplicirano, ha?

 

Sad je na redu fragment shader.

Pocnimo jednostavno - boja fragmenta bit ce cista ambijentna:

Ia = La * Ka

 

gdje je

Ia = 4D vektor, ambijentni intezitet svijetla na fragmentu

La = 4D vektor, ambijentna jakost svijetla

Ka = 4D vektor, ambijentni faktor refleksije materijala

 

Nakon dodavanja obaveznog

void main() {}

 

unutra dopisujemo:

vec4 globalAmbient = gl_FrontMaterial.ambient * gl_LightModel.ambient;

vec4 lightAmbient  = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;

gl_FragColor = globalAmbient + lightAmbient;

 

Citatelji koji citaju ovaj kod primjetit ce da je shader hardkodiran da uzme u obzir samo jedno svijetlo u sceni (prvo). To je namjerno, da vam da posla. Nije tako tesko prepraviti kod da radi sa svim svijetlima u sceni, i to vam ostavljam za vjezbu.

Pisanje na predefinirani uniform gl_FragColor je jedini posao fragment shadera - boja koja se ovdje nalazi predstavljat ce boju pixela na ekranu. Ako vam nije jasno, cisto za test, mozete promjeniti liniju u:

 

gl_FragColor = vec4(1.0, 0.0, 0.0, 0.0);  // Vektor je RGBA jakost svijetla

 

 

Kratki FAQ ako vam je ekran crn:

da li je refleksija ambijentnog svijetla na materijalu 0? Ako je to znaci da materijal upija svo ambijentno svijetlo; povisite vrijednost svih komponenti na recimo 0.5, u ne-shader dijelu aplikacije gdje definirate svoje primitive i konfigurirate materijal za iste.

da li je ambijentna jakost svijetlosnog izvora 0? Povisite je.

 

 

OK, sad kad to radi (radi li?), vrijeme je da dodamo difuzno svijetlo. Za kalkulaciju potrebno nam je nekoliko stvari iz vertex shadera u fragment shaderu - predat cemo te vrijednosti putem varying varijabli.

Potrebna nam je normala i jedinicni direkcijski vektor svijetla.

Nulto, potrebno je definirati varying varijable koje ce se prenijeti u fragment shader. To znaci da prije main() funkcije pisemo deklaracije:

 

varying vec3 normal;

varying vec3 lightDirection;

 

Prvo, treba transformirati vertex u projekcijski prostor:

vec3 transformedVertex = vec3(gl_ModelViewMatrix * gl_Vertex);

 

onda, iskalkulirati direkcijski vektor

lightDirection = normalize(vec3(gl_LightSource[0].position.xyz) - transformedVertex);

 

onda treba postaviti normalu:

normal = gl_NormalMatrix * gl_Normal;

 

i na kraju, obavezno postaviti transformirati vertex u gl_Position

gl_Position = ftransform();

 

 

 

A sad 'komplicirani' fragment shader.

Prije svega, treba definirati varying varijable koje stizu iz vertex shadera:

varying vec3 normal;

varying vec3 lightDirection;

 

obavezno ih normalizirati (to znaci da ih se pretvara u jedinicne vektore), jer prilikom dolaska u fragment shader, normale se interpoliraju.

vec3 N = normalize(normal);

vec3 L = normalize(lightDirection);

 

Sada imamo na raspolaganju sve informacije za iskalkulirati Lambertovu difuznu jednadzbu. Dakle, difuzna jakost svijetla je:

vec4 diffuse = gl_FrontMaterial.diffuse * dot(N, L) * gl_LightSource[0].diffuse;

 

Uz maloprijasnji kod koji se bavio ambijentnim svijetlom, zbroj to dvoje je jakost osvijetljenja na trenutnom fragmentu:

gl_FragColor = globalAmbient + lightAmbient + diffuse;

 

 

Eto, to je to!

Slike za usporedbu:

Sfera osvijetljena bez shadera Sfera osvijetljena bez shadera
Ista sfera osvijetljena sa difuznim perpixel shaderom Ista sfera osvijetljena sa difuznim perpixel shaderom
 

"Fans are clinging complaining dipshits who will never ever be happy for any concession you make. The sooner you shut up their shrilled tremolous voices, the happier are you going to be for it.&q
Poruka je uređivana zadnji put ned 26.7.2009 16:38 (Deus ex machina).
 
7 0 hvala 14
16 godina
protjeran
offline
Shader tutorial

Odličan post,

možda bi bilo bolje objašnjavanje vertex i pixel shader  preko RenderMonkey ili  FX Composer 2.5.

Usput preporučam knjigu "Introduction to 3D Game Programming with DirectX9.0c A Shader Approach" ima po mom mišljenju jako dobro opisane razne efekte i barem je meni kao amateru za grafiku s srednjim znanjem matematike lagana za pratiti.

I preporučam NVIDIA SDK jer ima hrpu shader primjera i odličnu dokumentaciju.

Programko http://programko.bloger.hr
Poruka je uređivana zadnji put ned 26.7.2009 16:50 (Programko).
 
0 0 hvala 1
15 godina
neaktivan
offline
GLSL + OpenGL shader tutorial

Dobrodosli u lekciju #2 GLSL tutoriala.
Mozete sjesti.
Danas cemo obraditi Phong spekularno sjencanje. Spajanje tehnike sa prethodnim tutorialom o lambertovom difuznom sjencanju - Brgicu, ne pricaj nego prati, dobit ces keca - dobijamo mogucnost imati sjajne materijale sa realno prikazanim odsjajem.

 


Zasto? Zato jer mozemo. :-P

Dakle, pocnimo od Phongove jednazdbe spekularne svijetlosti povrsine:
Is = Ks * pow(dot(R, E), a) * Ls)
gdje je
Is = intenzitet spekularnog osvijetljenja na povrsini
Ks = spekularni refleksijski faktor materijala
R  = direkcijski jedinicni vektor zrake koja se odbija od povrsine tocno po normali
E  = direkcijski jedinicni vektor od povrsine prema kameri
Ls = spekularni intenzitet svijetla
a  = faktor sjajnosti materijala (shininess)


Pa krenimo.
Za pocetak, potrebno je znati kako se definira R:
R = 2 * dot(L, N) *  N - L

gdje je
N = normalizirana normala povrsine, definiran u proslom tutorialu
L = direkcijski jedinicni vektor od povrsine prema svijetlu, definiran u proslom tutorialu

 

Od podataka za razrijesenje jednadzbe, nedostaje nam direkcijski jedinicni vektor od povrsine prema kameri. S obzirom da poziciju kamere ne mozemo dobiti u shaderu, potrebno je predati poziciju kroz uniform varijablu, i jednu varying varijablu koja ce prenijeti iskalkuliranu direkciju u fragment shader. Obecao sam da necu pisati OpenGL kod za definiciju uniforma, tako da nudim samo deklaraciju u vertex shaderu.
Modifikacija prva:
uniform vec3 cameraPos;
varying vec3 cameraDir;

Kalkulacija direkcijskog vektora je jednostavna, transformirani vertex vec imamo u proslom primjeru ali napisat cu ovdje radi cistoce koda.
Modifikacija druga:
vec3 transformedVertex = vec3(gl_ModelViewMatrix * gl_Vertex);
cameraDir = normalize(cameraPos - transformedVertex);


Unutar fragment shadera imamo sve podatke, no potrebno je deklarirati novi varying:
varying vec3 cameraDir;

pa se onda upustiti u rijesavanje Phongove jednadzbe:
vec3 specular = 0.0F;
if(shininess != 0.0) {
    vec3 R = normalize(2 * dot(L, N) *  N - L);
    vec3 E = normalize(cameraDir);
    float s = max(dot(R, E), 0.0); // Cisto za dodatak - nedostatak ovog max-a me gnjavio dobra tri dana
    specular = gl_FrontMaterial.specular * pow(s, gl_FrontMaterial.shininess) * gl_LightSource[0].specular;
}


Boja fragmenta se odredjuje jednostavnim zbrajanjem, pa zadnja linija iz proslog tutoriala postaje:
gl_FragColor = globalAmbient + lightAmbient + diffuse + specular;

Uvjet na shininess postoji da sprijecimo skupocjene kalkulacije ukoliko je sjajnost materijala 0 - to znaci da se aplicira iskljucivo difuzno svijetlo.


Eto, kraj.

Bilo je lakse ovaj put, nadam se. Slike za primjer i stay tuned za normal mapping sutradan ;-):

Lowpoly sfera iz proslog primjera sa perpixel difuznim sjencanjem Lowpoly sfera iz proslog primjera sa perpixel difuznim sjencanjem
Spekularni odsjaj u igri, ista pozicija kamere, svijetla i sfere Spekularni odsjaj u igri, ista pozicija kamere, svijetla i sfere
Pozicija kamere je promjenjena - gleda na model odozgo Pozicija kamere je promjenjena - gleda na model odozgo
Pozicija kamere je promjenjena - gleda na mode odostraga Pozicija kamere je promjenjena - gleda na mode odostraga
 

"Fans are clinging complaining dipshits who will never ever be happy for any concession you make. The sooner you shut up their shrilled tremolous voices, the happier are you going to be for it.&q
Poruka je uređivana zadnji put pon 27.7.2009 16:49 (Deus ex machina).
 
7 0 hvala 11
15 godina
neaktivan
offline
GLSL + OpenGL shader tutorial

Dobrodosli u lekciju #3 GLSL tutoriala - normal mapping.

Moram priznati da ste zaista uporni ako ste procitali sve do ovdje.

Sto je normal mapa?
To je normalna RGB textura u kojoj se boja pixela tretira kao normala. S obzirom da RGB pixel sadrzi tri komponente, to znaci da se on moze tretirati kao normalni 3D vector. Normal mapping je jedan od nacina ostvarivanja 'bumpa' na materijalu. Normal mape se u pravilu bakeaju u 3D produkcijskom softwareu (kakav je npr. Blender, opensource aplikacija) i onda u 3D engineu iskoriste u shaderima kao efekt.

Ako vec niste, downloadirajte moj resource file iz prvog posta. Ako sam ga vec obrisao, onda cete morati napravit sami svoj, i izbakeati normal mapu. Slijedi kratko objasnjenje kako to napraviti uz pomoc Blendera.

Za bake normal mape potrebna su dva asseta - highpoly model i lowpoly model. Proces bakeanja znaci da ce se na lowpoly model UV mapirati slika koja sadrzi interpolirane vertex normale iz highpoly modela. Blender za taj proces zahtijeva:
- da se na jednakom prostoru nalaze oba modela
- da lowpoly model ima definiranu teksturu koja se tretira kao normal mapa
- da lowpolymodel ima UV unwrap na slici koja se koristi za normal-teksturu

Pod pretpostavkom da su modeli spremni, idemo prvo pripremiti normal mapu za bakeanje.
Razdvojite glavni radni prostor na dva dijela po sredini srednjim klikom na rub izmedju menija i 3D prostora i odaberite split. U desnom prozoru u izborniku Viewova odaberite UV Image.
Oznacite lowpoly model desnim klikom, pritisnite Tab da udjete u Edit mode, pritisnite "u" na tipkovnici i odaberite UV unwrap Smart projection. UV Image editor bi sada trebao dobiti unwrap UV koordinata. U UV image editoru snimite sliku i nazovite je "normal-map.png" - obavezno u comboboxu na dnu odaberite PNG.

Oznacite lowpoly model i otvorite material menu (F5). U Textures izborniku dodajte novu teksturu, nazovite je "Normal map". U Map Input tabu oznacite UV, a u Map Output tabu selektirajte samo Normal.
Otvorimo Textures izbornik pritiskom na F6. U spisku tekstura nalazi se "Normal map", oznacite je i u combo boxu sastrane odaberite Image. Nas lowpoly model sada ima normal mapu dodijeljenu.

Sada je potrebno oba modela postaviti na isto fizicko mjesto: oznacite prvo jedan, pritisnite Shift-S i onda Object To center, pa onda drugi i isto.
Zatim oznacite prvo highpolymodel, onda low poly model, pritisnite F10 (scene menu), u Render sekciji potrazite Bake, ondje oznacite Normals i pritisnite bake dugme.
Ako je sve proteklo dobro, UV Image editor ce nakon kalkulacija prikazati sarenu normal mapu na mjestima gdje se nalazi UV unwrap. Snimite bakeanu mapu u isti file ("normal-map.png");

Sad malo kodiranja:
Novi termin su Sampleri. Sampler je u shaderu handle texturu koja se nalazi u texture unitu za trenutno procesirani model. Sampler se moze iskljucivo deklarirati kao uniform i mora biti postavljen iz OpenGL dijela. Sampler NE SADRZI teksturni ID, vec broj od 0-15 koji predstavlja ID teksturnog unita. Dakle, u fragment shaderu dopisimo:
uniform sampler2D normalmap;
a u OpenGL kodu se potrudimo da uniform normalmap bude postavljen na 0, nas prvi teksture unit.

S obzirom da cemo nase normale dobiti iz mape, kalkulacija normala iz vertex shadera je nepotrebna, pa se ove linije mogu ukloniti iz vertex shadera:
varying vec3 normal;
normal = gl_NormalMatrix * gl_Normal;

a ova iz fragment shadera:
varying vec3 normal;

To znaci da je ova linija u fragment shaderu postala greska:
vec3 N = normalize(normal);

Nasu normalu dobit cemo iz mape tako da prvo sempliramo mapu na tocnoj UV koordinati, a onda je normaliziramo (za svaki slucaj). Za ispravno sempliranje potrebno je intepolirati UV koordinate na odredjenom vertexu. To je, razumljivo, posao za vertex shader. Iskoristit cemo built-in varying array gl_TexCoord i uniform gl_MultiTexCoord0:
gl_TexCoord[0] = gl_MultiTexCoord0;

U fragment shaderu, sempliranje teksture izgleda ovako:
vec3 normal = vec3(texture2D(normalTex, gl_TexCoord[0].st));

I to je to!
Ova tehnika ima najocitiji dobitak u odnosu na ulozeno vrijeme procesiranja i trud, pogledajte samo razliku na jednakom modelu sa i bez normal mape:

Lowpoly model sa perpixel lambert/phong shaderom, bez normal mappinga Lowpoly model sa perpixel lambert/phong shaderom, bez normal mappinga
Lowpoly model sa perpixel lambert/phong shaderom sa normal mappingom Lowpoly model sa perpixel lambert/phong shaderom sa normal mappingom
Stay tuned tomorrow za kratki ambient occlusions mapping i diffuse mapping lekciju, koje ce modelu dati obaveznu difuznu teksturu i bolji osjecaj 'dubine'.

"Fans are clinging complaining dipshits who will never ever be happy for any concession you make. The sooner you shut up their shrilled tremolous voices, the happier are you going to be for it.&q
Poruka je uređivana zadnji put sri 29.7.2009 13:30 (Deus ex machina).
 
5 0 hvala 8
14 godina
protjeran
offline
GLSL + OpenGL shader tutorial

Ovo je predobro.

Svaka čast na trudu.

No, imam jedno pitanje.

Vidim ti si ovdje pisao GLSL turorijal (nisam citao sve) - dakle to je OpenGL jezik za shader (Graphics Libray Shading Language) jel tako?

Koliko se to razlikuje od DX-ovog HLSL?

trezan a za sebe neznam
 
0 0 hvala 0
15 godina
neaktivan
offline
GLSL + OpenGL shader tutorial

Tocno, GLSL je highlevel shading language iz OpenGL-a. HLSL nije puno razlicit, a oba jezika su DSL-ovi (Domain Specific Language), pa se brzo uce - nekolicina tipova podataka i nesto funkcija, usko orijentirani na domenu.

Ono sto je bitno shvatiti je tehnika, kasnije ju je lagano implementirati. Ima informacija o raznim tehnikama krcat internet, ali moram priznati da sam se za neke stvari pomucio potraziti, a neke nisam jos ni nasao.

Npr. nemam export tangencijalnih normal mapa u blenderu koje se u pravilu koriste, pa shader ovdje ne transformira tangencijalne normale u model space. Drawback je u tome sto trenutni normal mapping radi samo za staticnu geometriju, ali prepravak na tangencijalne mape je par linija koda.

"Fans are clinging complaining dipshits who will never ever be happy for any concession you make. The sooner you shut up their shrilled tremolous voices, the happier are you going to be for it.&q
Poruka je uređivana zadnji put pet 14.8.2009 0:06 (Deus ex machina).
 
0 0 hvala 4
14 godina
neaktivan
offline
GLSL + OpenGL shader tutorial

OpenGL ES + GLSL = ?

http://www.woodgamesfx.com
 
0 0 hvala 0
15 godina
neaktivan
offline
RE: GLSL + OpenGL shader tutorial
Čestitke na trudu,zaista pohvalno...
1
Nova poruka
E-mail:
Lozinka:
 
vrh stranice