În cheia pentru ziua 2 din // Build 2014 (vezi 2: 24-2: 28), evangheliștii Microsoft Steven Guggenheimer și John Shewchuk au demonstrat cum a fost adăugat sprijinul lui Oculus Rift la Babylon.js. Și unul dintre lucrurile cheie pentru acest demo a fost lucrarea pe care am făcut-o pe un shader specific pentru simularea lentilelor, după cum puteți vedea în această imagine:
De asemenea, am prezentat o sesiune cu Frank Olivier și Ben Constable despre grafica pe IE și Babylon.js.
Acest lucru mă duce la una din întrebările pe care oamenii mă întreabă frecvent despre Babylon.js: "Ce vrei să spui prin shader?"Deci, în acest post, vă voi explica cum funcționează shaders și dați câteva exemple de tipuri comune de shaders.
Înainte de a începe experimentarea, trebuie să vedem mai întâi cum funcționează lucrurile intern.
Când ne confruntăm cu 3D accelerat hardware, discutăm două CPU-uri: CPU principal și GPU. GPU-ul este un fel de CPU extrem de specializat.
GPU-ul este o mașină de stat pe care o configurați utilizând CPU-ul. De exemplu, CPU-ul va configura GPU-ul pentru a face linii în loc de triunghiuri. Sau va defini că transparența este activă și așa mai departe.
Odată ce toate stările sunt setate, CPU-ul va defini ce trebuie făcut - geometria, care este compusă dintr-o listă de puncte (numită noduri și stocate într-o matrice numită vertex tampon) și o listă a indexurilor (fețele sau triunghiurile stocate într-un matrice numită index tampon).
Ultimul pas pentru CPU este definirea modului de redare a geometriei, iar pentru această sarcină specifică, CPU-ul va defini shader pentru GPU. Shaderele sunt o bucată de cod pe care GPU-ul o va executa pentru fiecare dintre vârfurile și pixelii pe care trebuie să le facă.
În primul rând, un anumit vocabular: gândiți-vă la un vertex (vârfuri când există mai multe dintre ele) ca un "punct" într-un mediu 3D (spre deosebire de un punct într-un mediu 2D).
Există două tipuri de shadere: shadere de vârf și shadere pixel (sau fragmente).
Înainte să sapi în shadere, hai să facem un pas înapoi. Pentru a face pixeli, GPU-ul va lua geometria definită de CPU și va face următoarele:
Folosind tamponul index, trei noduri sunt adunate pentru a defini un triunghi: tamponul index conține o listă de indexuri de vârfuri. Aceasta înseamnă că fiecare intrare din indexul tampon este numărul unui vertex din tamponul de vârf. Acest lucru este foarte util pentru a evita suprapunerea nodurilor.
De exemplu, următoarea tampon index este o listă cu două fețe: [1 2 3 1 3 4]
. Prima față conține vertex 1, vertex 2 și vertex 3. A doua față conține vertex 1, vertex 3 și vertex 4. Deci, există patru vârfuri în această geometrie:
Shader-ul de vârfuri este aplicat pe fiecare vârf al triunghiului. Scopul principal al shader-ului de vertex este de a produce un pixel pentru fiecare vertex (proiecția pe ecranul 2D al vertexului 3D):
Folosind acești trei pixeli (care definesc un triunghi 2D pe ecran) GPU va interpola toate valorile atașate la pixel (cel puțin poziția sa), iar pixelul shader va fi aplicat pe fiecare pixel inclus în triunghiul 2D pentru a generați o culoare pentru fiecare pixel:
Acest proces se face pentru fiecare față definită de tampon index.
Evident, datorita naturii sale paralele, GPU-ul este capabil sa proceseze acest pas pentru o multime de fete simultan, si astfel sa obtina o performanta foarte buna.
Tocmai am văzut că pentru a face triunghiuri, GPU are nevoie de două shadere: shader-ul de vârfuri și shader-ul pixelilor. Aceste shadere sunt scrise folosind o limbă numită GLSL (Graphics Library Shader Language). Se pare că C.
Pentru Internet Explorer 11, am dezvoltat un compilator pentru a transforma GLSL în HLSL (High Level Shader Language), care este limbajul shader al DirectX 11. Aceasta permite IE11 să se asigure că codul shader este sigur (nu doriți să utilizați WebGL pentru a reseta calculatorul!):
Iată o mostră a unui shader de vârf comun:
precizie highp float; // Atribute atribut vec3 position; atribut vec2 uv; Uniforme uniforme mat4 worldViewProjection; // variază variabilă vec2 vUV; void principal (void) gl_Pozi = lumeViewProjecție * vec4 (poziție, 1.0); vUV = uv;
Un shader de vertex conține următoarele:
vector3: x, y, z
). Dar, ca dezvoltator, puteți decide să adăugați mai multe informații. De exemplu, în fostul shader, există a vector2
numit uv
(coordonatele texturilor care ne permit să aplicăm o textură 2D pe un obiect 3D).(x, y, z)
pe ecran (X y)
.VUV
(o copie simplă a lui uv
) pentru pixel shader. Aceasta înseamnă că aici este definit un pixel cu coordonate de poziție și textură. Aceste valori vor fi interpolate de unitatea de procesare grafică și vor fi utilizate de shaderul pixelilor. principal()
este codul executat de GPU pentru fiecare vârf și trebuie să producă cel puțin o valoare pentru gl_position
(poziția de pe ecran a vârfului curent). Putem vedea în eșantionul nostru că shaderul de vertex este destul de simplu. Acesta generează o variabilă de sistem (începând cu gl_
) numit gl_position
pentru a defini poziția pixelului asociat și stabilește o variabilă variabilă numită VUV
.
În shaderul nostru avem o matrice numită worldViewProjection
. Utilizăm această matrice pentru a proiecta poziția vârfului la gl_position
variabil. Este cool, dar cum obținem valoarea acestei matrice? Este o uniformă, deci trebuie să o definim pe partea CPU (folosind JavaScript).
Aceasta este una dintre părțile complexe de a face 3D. Trebuie să înțelegeți matematica complexă (sau va trebui să utilizați un motor 3D, cum ar fi Babylon.js, pe care o vom vedea mai târziu).
worldViewProjection
matricea este combinația a trei matrici diferite:
Folosind matricea rezultată ne permite să putem transforma vârfurile 3D în pixeli 2D luând în considerare punctul de vedere și tot ceea ce se referă la poziția / scala / rotația obiectului curent.
Aceasta este responsabilitatea dvs. ca dezvoltator 3D: să creați și să păstrați această matrice actualizată.
Odată ce shaderul de vârfuri este executat pe fiecare vertex (de trei ori, atunci) avem trei pixeli cu o valoare corectă gl_position
și a VUV
valoare. GPU-ul va interpola aceste valori pe fiecare pixel conținut în triunghiul produs de acești pixeli.
Apoi, pentru fiecare pixel, acesta va executa pixel shader:
precizie highp float; variabile vec2 vUV; uniform sampler2D textureSampler; void principal (void) gl_FragColor = textură2D (texturăSampler, vUV);
Structura unui shader pixel este similară cu un shader de vârf:
VUV
valoare de la shaderul de vârfuri. principal
este codul executat de GPU pentru fiecare pixel și trebuie să producă cel puțin o valoare pentru gl_FragColor
(culoarea pixelului curent). Acest shader pixel este destul de simplu: citește culoarea din textura folosind coordonatele de textură din shaderul de vârfuri (care la rândul său a ajuns de la vârf).
Vrei să vezi rezultatul unui astfel de shader? Aici este:
Acest lucru este redat în timp real; puteți glisa sfera cu mouse-ul.Pentru a obține acest rezultat, va trebui să faceți față unui rezultat mult din codul WebGL. Într-adevăr, WebGL este un API foarte puternic, dar într-adevăr la un nivel scăzut, și trebuie să faci totul singur, de la crearea buffer-urilor la definirea structurilor de vârfuri. De asemenea, trebuie să faceți tot matematica și să setați toate stările și să vă ocupați de încărcarea texturilor și așa mai departe ...
Știu ce vă gândiți: shaderele sunt foarte cool, dar nu vreau să mă deranjez cu sistemele sanitare interne WebGL sau chiar cu matematica.
Și e bine! Aceasta este o întrebare perfect legitimă, și tocmai de aceea am creat Babylon.js.
Permiteți-mi să vă prezint codul utilizat de demonstrația sferică precedentă. În primul rând, veți avea nevoie de o pagină web simplă:
Babylon.js
Veți observa că shaderele sunt definite de >