În articolele anterioare, am învățat cum să scriem niște shadere simple de vârfuri și fragmente, să facem o pagină web simplă și să pregătim o pânză pentru desen. În acest articol, vom începe să lucrăm la codul nostru de bootplate WebGL.
Vom obține un context WebGL și îl vom folosi pentru a șterge panza cu culoarea aleasă de noi. Woo hoo! Acest lucru poate fi la fel de puțin ca trei linii de cod, dar vă promit că nu voi face așa de ușor! Ca de obicei, vom încerca să explicăm conceptele JavaScript greșite pe măsură ce le vom întâlni și vă vom oferi toate detaliile de care aveți nevoie pentru a înțelege și prezice comportamentul WebGL corespunzător.
Acest articol face parte din seria "Noțiuni de bază în WebGL". Dacă nu ați citit părțile anterioare, vă recomandăm să le citiți mai întâi:
În primul articol din această serie, am scris un shader simplu care atrage un gradient colorat și îl estompează ușor și ușor. Iată umbra pe care am scris-o:
În cel de-al doilea articol din această serie, am început să lucrăm spre utilizarea acestui shader într-o pagină Web. Luând pași mici, am explicat fundalul necesar al elementului de panza. Noi:
Iată ce am făcut până acum:
În acest articol, vom împrumuta câteva bucăți de cod din articolul precedent și vom adapta experiența noastră la WebGL în locul desenului 2D. În următorul articol, dacă Allah dorește, voi acoperi manipularea cu vedere la vedere și tăierea primitivelor. Durează ceva, dar sper că veți găsi întreaga serie foarte utilă!
Să construim pagina noastră WebGL. Vom folosi același HTML pe care l-am folosit pentru exemplul de desen 2D:
... cu o modificare foarte mică. Aici numim panza glCanvas
în loc de doar pânză
(MEH!).
De asemenea, vom folosi același CSS:
html, corp înălțime: 100%; corp margine: 0; canvas display: block; lățime: 100%; înălțime: 100%; fundal: # 000;
Cu excepția culorii de fundal, care este acum negru.
Nu vom folosi același cod JavaScript. Vom începe fără cod JavaScript, și adăugați funcții bit-by-bit pentru a evita confuzia. Iată configurația noastră de până acum:
Acum să scriem un cod!
Primul lucru pe care ar trebui să-l facem este să obțineți un context WebGL pentru pânză. La fel cum am făcut și când am obținut un context de desen 2D, folosim funcția membru getContext
:
glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ( "experimental-webgl");
Această linie conține două getContext
apeluri. În mod normal, nu ar trebui să avem nevoie de al doilea apel. Dar doar în cazul în care utilizatorul utilizează un browser vechi în care implementarea WebGL este încă experimentală (sau Microsoft Edge), am adăugat al doilea.
Lucru minunat despre ||
operator (sau operator) Este că nu trebuie să evalueze întreaga expresie dacă sa descoperit primul operand Adevărat
. Cu alte cuvinte, într-o expresie a || b
, dacă A
evaluează pentru Adevărat
, atunci dacă b
este Adevărat
sau fals
nu afectează deloc rezultatul. Astfel, nu este necesar să evaluăm b
și a fost omisă în întregime. Aceasta se numește Evaluarea circuitului scurt.
În cazul nostru, getContext ( "experimental-webgl")
vor fi executate numai dacă getContext ( "webgl")
eșuează (întoarce nul
, care evaluează fals
într-o expresie logică).
Am folosit, de asemenea, o altă caracteristică a sau
operator. Rezultatul orangerii nu este nici unul Adevărat
nici fals
. În schimb, este primul obiect care evaluează Adevărat
. Dacă niciunul dintre obiecte nu evaluează Adevărat
, or-return cel mai drept obiect din expresie. Aceasta înseamnă că, după executarea liniei de mai sus, glContext
va conține fie un obiect context, fie nul
, dar nu Adevărat
sau fals
.
Notă: dacă browserul acceptă ambele moduri (WebGL
și experimental-webgl
), atunci ele sunt tratate ca aliasuri. Nu ar fi absolut nici o diferență între ei.
Punctând linia de mai sus unde îi aparține:
var glContext; funcția initialize () // Obține contextul WebGL, var glCanvas = document.getElementById ("glCanvas"); glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ( "experimental-webgl"); if (! glContext) alert ("Nu a reușit să dobândești un context WebGL. Îmi pare rău!"); return false; return true;
Voila! Avem pe noi inițializa
(da, păstrați-vă vise!).
Observați că nu am folosit-o încerca
și captură
a detecta getContext
așa cum am făcut în articolul precedent. Este pentru că WebGL are propriile mecanisme de raportare a erorilor. Aceasta nu aruncă o excepție când crearea de context nu reușește. În schimb, se declanșează a webglcontextcreationerror
eveniment. Dacă suntem interesați de mesajul de eroare, atunci ar trebui să facem acest lucru:
// ascultător de eroare de creare a contextelor, var errorMessage = "Nu s-a putut crea un context WebGL"; funcția onContextCreationError (eveniment) if (event.statusMessage) errorMessage = event.statusMessage; glCanvas.addEventListener ("webglcontextcreationerror", peContextCreationError, false);
Luând aceste linii în afară:
glCanvas.addEventListener ("webglcontextcreationerror", peContextCreationError, false);
La fel ca atunci când am adăugat un ascultător la evenimentul de încărcare a ferestrei din articolul precedent, am adăugat un ascultător pe pânză webglcontextcreationerror
eveniment. fals
argumentul este opțional; Doar îl includ pe completare (din exemplul de specificație WebGL). Este de obicei inclusă pentru compatibilitate înapoi. Inseamna useCapture
. Cand Adevărat
, înseamnă că ascultătorul va fi chemat în captură a propagării evenimentului. Dacă fals
, va fi chemat în stadiul de barbotare in schimb. Verificați acest articol pentru mai multe detalii despre propagarea evenimentelor.
Acum, la ascultător în sine:
var errorMessage = "Nu s-a putut crea un context WebGL"; funcția onContextCreationError (eveniment) if (event.statusMessage) errorMessage = event.statusMessage;
În acest ascultător păstrăm o copie a mesajului de eroare, dacă există. Da, având un mesaj de eroare este absolut opțional:
dacă (event.statusMessage) errorMessage = event.statusMessage;
Ceea ce am făcut aici este destul de interesant. ERRORMESSAGE
a fost declarată în afara funcției, dar am folosit-o înăuntru. Acest lucru este posibil în JavaScript și se numește închiderile. Ceea ce interesează închiderile este durata lor de viață. In timp ce ERRORMESSAGE
este locală la inițializa
deoarece a fost folosită în interior onContextCreationError
, nu va fi nimicită decât dacă nu onContextCreationError
ea însăși nu mai este menționată.
Cu alte cuvinte, atâta timp cât un identificator este încă accesibil, nu poate fi colectat gunoi. În situația noastră:
ERRORMESSAGE
trăiește pentru că onContextCreationError
se referă la acesta.onContextCreationError
trăiește pentru că este menționat undeva printre ascultătorii evenimentului de pânză. Deci, chiar dacă inițializa
se termină, onContextCreationError
este încă menționată undeva în obiectul panza. Doar atunci când se eliberează poate ERRORMESSAGE
să fie colectat de gunoi. În plus, apelurile ulterioare ale inițializa
nu va afecta precedentul ERRORMESSAGE
. Fiecare inițializa
funcția de apel va avea propriile sale ERRORMESSAGE
și onContextCreationError
.
Dar nu vrem cu adevărat onContextCreationError
pentru a trăi dincolo inițializa
terminare. Nu vrem să ascultăm alte încercări de a obține contexte WebGL în altă parte a codului. Asa de:
glCanvas.removeEventListener ("webglcontextcreationerror", peContextCreationError, false);
Punând totul împreună:
Pentru a verifica dacă am creat cu succes contextul, am adăugat o simplă alerta
:
alertă ("Contextul WebGL a fost creat cu succes!");
Acum treceți la Rezultat
pentru a rula codul.
Și nu funcționează! Evident, pentru că inițializa
nu a fost chemat niciodată. Trebuie să sunăm imediat după încărcarea paginii. Pentru aceasta, vom adăuga următoarele rânduri:
window.addEventListener ('load', function () initialize ();, false);
Hai sa incercam din nou:
Functioneaza! Vreau să spun, ar fi trebuit să nu se creeze un context! Dacă nu, asigurați-vă că vedeți acest articol dintr-un dispozitiv / browser compatibil WebGL.
Observați că am făcut un alt lucru interesant aici. Noi am folosit inițializa
în a noastră sarcină
ascultător înainte de a fi declarat chiar. Acest lucru este posibil din cauza JavaScript ridicare. Ridicarea înseamnă că toate declarațiile sunt mutate în partea de sus a domeniului lor de aplicare, în timp ce inițializările lor rămân în locurile lor.
Acum, nu ar fi bine să testați dacă mecanismul nostru de raportare a erorilor funcționează? Avem nevoie getContext
a esua. O modalitate ușoară de a face acest lucru este de a obține un tip diferit de context pentru panza înainte de a încerca să creați contextul WebGL (amintiți-vă când am spus că primul succes getContext
schimba permanent modul de pânză?). Vom adăuga această linie chiar înainte de a obține contextul WebGL:
glCanvas.getContext ( "2d");
Și:
Grozav! Acum, dacă mesajul pe care l-ați văzut a fost "Nu s-a putut crea un context WebGL
"sau ceva de genul"Canvasul are un context existent de alt tip
"depinde de compatibilitatea browserului webglcontextcreationerror
sau nu. La momentul redactării acestui articol, Edge și Firefox nu îl suportă (a fost planificat pentru Firefox 49, dar încă nu funcționează pe Firefox 50.1). În acest caz, ascultătorul evenimentului nu va fi chemat și ERRORMESSAGE
va rămâne setat la "Nu s-a putut crea un context WebGL
". Din fericire, getContext
încă se întoarce nul
, așa că știm că nu am putut crea contextul. Nu avem mesajul de eroare detaliat.
Lucrul cu mesajele de eroare WebGL este că ... nu există mesaje de eroare WebGL! WebGL returnează numere care indică stări de eroare, nu mesaje de eroare. Și când se întâmplă să permită mesajele de eroare, acestea sunt dependente de driver. Textul exact al mesajelor de eroare nu este furnizat în caietul de sarcini - este de datoria dezvoltatorilor de drivere să decidă cum ar trebui să o pună. Deci, așteptați să vedeți aceeași eroare formulată diferit pe diferite dispozitive.
Bine atunci. Deoarece ne-am asigurat că funcționează mecanismul nostru de raportare a erorilor,creat cu succes
"alertă și getContext ( "2d")
nu mai sunt necesare. O să le omorăm.
Înapoi la veneratul nostru getContext
funcţie:
glContext = glCanvas.getContext ("webgl");
Există mai mult pentru ea decât pentru ochi. getContext
poate lua opțional un argument suplimentar: un dicționar care conține un set de atribute de context și valorile acestora. Dacă nu este furnizat niciunul, valorile implicite sunt utilizate:
dicționar WebGLContextAttributes GLboolean alpha = true; Adâncime GLboolean = adevărat; Stencil GLboolean = fals; Antialias GLboolean = adevărat; GLboolean premultipliedAlpha = adevărat; Păstrarea GLbooleanDrawingBuffer = false; GLboolean preferLowPowerToHighPerformance = false; GLboolean failIfMajorPerformanceCaveat = false; ;
Voi explica unele dintre aceste atribute pe măsură ce le folosim. Puteți găsi mai multe despre ele în secțiunea Atribute de context WebGL din specificația WebGL. Pentru moment, nu avem nevoie de a tampon adâncime pentru shaderul nostru simplu (mai multe despre el mai târziu). Și pentru a evita să-i explicăm, vom dezactiva și noi premultiplied-alfa! Este nevoie de un articol propriu pentru a explica în mod corespunzător rațiunea în spatele ei. Astfel, ale noastre getContext
line devine:
var contextAttributes = adâncime: false, premultipliedAlpha: false; glContext = glCanvas.getContext ("webgl", contextAttributes) || glCanvas.getContext ("experimental-webgl", contextAttributes);
Notă: adâncime
, șablon
și AntiAlias
atribute, atunci când este setat la Adevărat
, sunt cereri, nu cerințe. Browserul ar trebui să încerce să facă tot posibilul pentru a le satisface, dar nu este garantat. Totuși, când sunt setați la fals
, browserul trebuie să respecte.
Pe de altă parte, alfa
, premultipliedAlpha
și preserveDrawingBuffer
atributele sunt cerințe care trebuie îndeplinite de browser.
Acum că avem contextul WebGL, este momentul să îl folosim de fapt! Una dintre operațiile de bază în desenul WebGL este curățarea tamponului de culoare (sau pur și simplu panza în situația noastră). Ștergerea panzei se face în două etape:
Apelurile OpenGL / WebGL sunt scumpe, iar driverele dispozitivelor nu sunt garantate ca fiind extrem de inteligente și evită munca inutilă. Prin urmare, ca regulă generală, dacă putem evita utilizarea API, ar trebui să evităm utilizarea acestuia.
Deci, dacă nu trebuie să schimbăm culoarea clară la fiecare cadru sau la desenul mediu, trebuie să scriem codul setându-l într-o funcție de inițializare în locul unei desene. În acest fel, se numește o singură dată la început și nu la fiecare cadru. Deoarece culoarea clară nu este singura variabila de stat că vom iniționa, vom face o funcție separată pentru inițializarea statului:
funcția initializeState () ...
Și vom numi această funcție din interiorul inițializa
funcţie:
funcția initialize () ... // Dacă nu a reușit, dacă (! glContext) alert (errorMessage); return false; initializeState (); return true;
Frumos! Modularitatea ne va păstra cel mai puțin curat și mai ușor de citit codul nostru. Acum, pentru a popula initializeState
funcţie:
funcția initializeState () // Setează culoarea clară în roșu, glContext.clearColor (1.0, 0.0, 0.0, 1.0);
clearColor
are patru parametri: roșu, verde, albastru și alfa. Patru flotoare, ale căror valori sunt prins la intervalul [0, 1]. Cu alte cuvinte, orice valoare mai mică de 0 devine 0, orice valoare mai mare de 1 devine 1, iar orice valoare între ele rămâne neschimbată. Inițial, culoarea clară este setată la toate zerouri. Deci, dacă negrul transparent ar fi în regulă cu noi, am fi putut omite acest lucru cu totul.
După ce ați setat culoarea clară, rămâneți să eliminați pânza. Dar nu putem ajuta decât să punem o întrebare, trebuie să clarificăm pânza?
Începând cu vremurile vechi, jocurile de redare pe ecran complet nu au nevoie de ștergerea ecranului în fiecare cadru (încercați să tastați idclip
în DOOM 2 și du-te undeva nu trebuie să fii!). Noul conținut ar înlocui doar pe cele vechi și am salva operațiunea clară non-banală.
La hardware-ul modern, curățarea tampoanelor este extrem de rapidă. În plus, curățarea tampoanelor poate îmbunătăți performanța! Pur și simplu, dacă conținutul tampon nu a fost șters, GPU-ul ar putea fi nevoit să preia conținuturile anterioare înainte de a le suprascrie. Dacă au fost șterse, atunci nu este nevoie să le recuperați din memoria relativ mai lentă.
Dar dacă nu doriți să înlocuiți întregul ecran, ci să adăugați în mod incremental la acesta? La fel ca atunci când faci un program de pictură. Vreți să desenați numai loviturile noi, păstrând în același timp cele anterioare. Nu are sens acum actul de a părăsi pânza fără a avea o compensare?
Răspunsul este încă nu. Pe cele mai multe platforme pe care le folosiți dublu-tampon. Aceasta înseamnă că tot desenul pe care îl executăm se face pe un a back buffer în timp ce monitorul își recuperează conținutul de la a tampon frontal. În timpul retragerii verticale, aceste tampoane sunt schimbate. Spatele devine din față, iar partea din față devine partea din spate. În acest fel evităm scrierea în aceeași memorie care este în prezent citită de monitor și afișată, evitând astfel artefactele datorate desenului sau desenului incomplet prea repede (având desenate mai multe cadre în timp ce monitorul încă trasează un singur).
Astfel, următorul cadru nu suprascrie cadrul actual, deoarece nu este scris în același buffer. În schimb, acesta suprascrie cel care a fost în tamponul frontal înainte de a schimba. Acesta este ultimul cadru. Și tot ceea ce am tras în acest cadru nu va apărea în următorul. Va apărea în următoarea. Această neconcordanță între tampoane provoacă pâlpâirea care este, în mod normal, nedorită.
Dar acest lucru ar fi funcționat dacă vom folosi o configurație unică tamponată. În OpenGL pe majoritatea platformelor, avem un control explicit asupra tamponării și schimbării tampoanelor, dar nu și în WebGL. Este de până la browser să se ocupe singur.
Umm ... Poate că nu este cel mai bun moment, dar este un lucru în ceea ce privește curățarea tamponului de desen pe care nu l-am menționat mai devreme. Dacă nu o clarificăm în mod explicit, ar fi clarificată implicit pentru noi!
Există doar trei funcții de desen în WebGL 1.0: clar
, drawArrays
, și drawElements
. Numai dacă numim unul dintre acestea pe tamponul activ de desen (sau dacă tocmai am creat contextul sau am redimensionat panza), acesta trebuie prezentat compozitorului de pagini HTML la începutul următoarei operații de compunere.
După compoziție, tampoanele de desen sunt șterse automat. Browserul este permis să fie inteligent și să evite ștergerea automată a tampoanelor dacă le-am eliminat. Dar rezultatul final este același; tampoanele vor fi eliminate oricum.
Vestea bună este că există încă o modalitate de a vă face programul de vopsea să funcționeze. Dacă insistați să faceți desen incremental, putem seta preserveDrawingBuffer
atributul context la dobândirea contextului:
glContext = glCanvas.getContext ("webgl", preserveDrawingBuffer: true);
Acest lucru împiedică ștergerea automată a pânzei după compoziție și simulează o configurație cu un singur buffer. O modalitate de a face acest lucru este prin copierea conținutului buffer-ului frontal la tamponul din spate după schimbare. Desenarea unui tampon de spate este încă necesară pentru a evita extragerea de artefacte, deci nu poate fi ajutată. Acest lucru, desigur, vine cu un preț. Poate afecta performanța. Deci, dacă este posibil, utilizați alte abordări pentru a păstra conținutul tamponului de desen, cum ar fi desenul la cadru tampon obiect (care este dincolo de scopul acestui tutorial).
Îmbrățișați-vă, vom șterge panza acum! Din nou, pentru modularitate, să scriem codul care atrage scena în fiecare cadru într-o funcție separată:
funcția drawScene () // Șterge tamponul de culoare, glContext.clear (glContext.COLOR_BUFFER_BIT);
Acum am făcut-o! clar
ia un parametru, un câmp de biți care indică ce tampoane trebuie să fie șterse. Se pare că avem de obicei nevoie de mai mult decât un tampon de culoare pentru a desena lucruri 3D. De exemplu, un tampon de adâncime este folosit pentru a urmări adâncimile fiecărui pixel desenat. Folosind acest tampon, atunci când GPU-ul este pe cale să deseneze un nou pixel, acesta poate decide cu ușurință dacă acest pixel se ocolește sau este ocolit de pixelul anterior care stă în locul său.
Ea merge astfel:
Am folosit "mai aproape" în loc de "mai mic" pentru că avem un control explicit asupra adâncime (care operatorul de utilizat în comparație). Putem decide dacă o valoare mai mare înseamnă un pixel mai aproape (sistem de coordonate drepte) sau invers (cu stânga).
Noțiunea de drepte sau drepte se referă la direcția degetului mare (axa z), pe măsură ce vă curgeți degetele de la axa x la axa y. Sunt rău la desen, deci, uitați-vă la acest articol în Windows Dev Center. WebGL este stânga în mod prestabilit, dar puteți să o faceți cu mâna dreaptă prin schimbarea funcției de adâncime, atâta timp cât luați în considerare intervalul de adâncime și transformările necesare.
Din moment ce am ales să nu avem un tampon de adâncime atunci când am creat contextul nostru, singurul tampon care trebuie să fie șters este bufferul de culoare. Astfel, am setat COLOR_BUFFER_BIT
. Dacă am avea un tampon de adâncime, am fi făcut-o în schimb:
glContext.clear (glContext.COLOR_BUFFER_BIT | glContext.GL_DEPTH_BUFFER_BIT);
Singurul lucru rămas este să sunați drawScene
. Să o facem imediat după inițializare:
window.addEventListener ('load', function () // Initializeaza totul, initialize (); // Start drawing, drawScene ();, false);
Treceți la Rezultat
pentru a vedea culoarea noastră frumoasă roșie!
Unul dintre faptele importante despre clar
este că nu aplică nici o alfa-compoziție. Chiar dacă folosim în mod explicit o valoare pentru alfa care o face transparentă, culoarea clară ar fi scrisă în tampon fără nici o compoziție, înlocuind tot ce a fost tras înainte. Astfel, dacă aveți o scenă desenată pe pânză și apoi ștergeți cu o culoare transparentă, scena va fi complet ștersă.
Cu toate acestea, browserul face încă alfa-compoziție pentru întreaga pânză și utilizează valoarea alfa prezentă în buffer-ul de culoare, care ar fi putut fi setată în timp ce se șterge. Să adăugăm un text sub pânză și apoi să ștergem cu o culoare roșie jumătate transparentă pentru ao vedea în acțiune. HTML-ul nostru ar fi:
Shhh, mă ascund în spatele pânzei, ca să nu mă poți vedea.
si clar
line devine:
// Setează culoarea deschisă la roșu transparent, glContext.clearColor (1.0, 0.0, 0.0, 0.5);
Și acum, revelația:
Uită-te atent. Sus acolo în colțul din stânga sus ... nu există absolut nimic! Desigur, nu puteți vedea textul! Aceasta deoarece, în CSS, am specificat # 000
ca fundal panza. Fundalul acționează ca un strat suplimentar sub panza, astfel încât browser-ul alfa-compozitează tamponul de culoare împotriva acestuia în timp ce ascunde complet textul. Pentru a face acest lucru mai clar, vom schimba fondul verde și vom vedea ce se întâmplă:
fundal: # 0f0;
Și rezultatul:
Arată rezonabil. Culoarea pare să fie rgb (128, 127, 0)
, care poate fi considerată rezultatul amestecării roșu și verde cu alfa egal cu 0,5 (cu excepția cazului în care utilizați Microsoft Edge, în care culoarea ar trebui să fie rgb (255, 127, 0)
deoarece pentru moment nu susține alpha premultiplied). Tot nu putem vedea textul, dar cel puțin știm cum culoarea de fundal afectează desenul nostru.
Rezultatul însă atrage curiozitatea. De ce a fost redus la jumătate 128
, în timp ce verde a fost redus la jumătate 127
? Nu ar trebui să fie ambele 128
sau 127
, în funcție de rotunjirea punctului plutitor? Singura diferență dintre acestea este că culoarea roșie a fost setată ca o culoare clară în codul WebGL, în timp ce culoarea verde a fost setată în CSS. Sincer, nu știu de ce se întâmplă acest lucru, dar am o teorie. Probabil din cauza funcție de amestecare folosit pentru a îmbina cele două culori.
Când desenați ceva transparent peste altceva, funcția de amestecare intră. Definește modul în care culoarea finală a pixelilor (OUT
) trebuie să fie calculat din stratul de sus (stratul sursă, SRC
) și stratul de mai jos (stratul de destinație, DST
). Când desenați utilizând WebGL, avem multe funcții de amestecare pentru a alege. Dar când browserul alfa-compozitează panza cu celelalte straturi, avem doar două moduri (pentru moment): premultiplied-alfa și nu premultiplied-alpha (să o numim normal mod).
Modul alfa normal merge ca:
OUT = SRC + DST (1 - SRC) OUT = = SRC ʙ ʙ R R R R ᴀ ᴀ +
În modul alpha premultiplied, se presupune că valorile RGB sunt deja înmulțite cu valorile lor alfa corespunzătoare (de aici numele pre-multiplicate). În acest caz, ecuațiile sunt reduse la:
OUT = SRC + DST (1 - SRC) OUT = DRC = SRC + CRD (1 - SRC)
Deoarece nu utilizăm alfa premultiplied, ne bazăm pe primul set de ecuații. Aceste ecuații presupun că componentele de culoare sunt valori în virgulă variabilă care variază de la 0
la 1
. Dar acest lucru nu este modul în care acestea sunt de fapt stocate în memorie. În schimb, acestea sunt valori întregi de la 0
la 255
. Asa de srcAlpha
(0.5
) devine 127
(sau 128
, bazat pe modul în care îl rotunji) și 1 - srcAlpha
(1 - 0,5
) devine 128
(sau 127
). Este pentru că jumătate 255
(care este 127,5
) nu este un intreg, deci ajungem cu unul din straturi sa pierdem a 0.5
iar cealaltă câștigă a 0.5
în valorile lor alfa. Caz inchis!
Notă: alfa-compoziția nu trebuie confundată cu modurile de amestecare CSS. Se efectuează mai întâi alfa-compoziția, iar culoarea calculată este amestecată cu stratul de destinație utilizând modurile de amestecare.
Înapoi la textul nostru ascuns. Să încercăm să transformăm fundalul într-un mediu transparent:
fundal: rgba (0, 255, 0, 0,5);
In cele din urma:
Ar trebui să puteți vedea textul acum! Din cauza modului în care aceste straturi sunt pictate una peste alta:
Dură, nu? Din fericire, nu trebuie să ne ocupăm de toate acestea dacă nu vrem ca panza noastră să fie transparentă!
var contextAttributes = adâncime: false, alfa: false; glContext = glCanvas.getContext ("webgl", contextAttributes) || glCanvas.getContext ("experimental-webgl", contextAttributes);
Acum, tamponul nostru de culori nu va avea un canal alfa pentru a începe! Dar nu ne-ar împiedica să facem lucruri transparente?
Raspunsul este nu. Anterior, am menționat ceva despre WebGL având funcții de amestecare flexibile care sunt independente de modul în care browserul îmbină panza cu alte elemente de pagină. Dacă folosim o funcție de amestecare care are ca rezultat o combinare premultiplied-alpha, atunci nu avem nevoie absolut de canalul alfa-tampon de desen:
OUT = SRC + DST (1 - SRC) OUT = DRC = SRC + CRD (1 - SRC)
Dacă doar ignorăm outAlpha
în totalitate, nu pierdem nimic. Cu toate acestea, tot ceea ce facem are încă nevoie de un canal alfa pentru a fi transparent. Numai tamponul de desen nu are unul.
Premultiplied-alpha joacă bine cu filtrarea texturilor și alte chestii, dar nu și cu cele mai multe instrumente de manipulare a imaginii (nu am discutat încă despre texturi - presupunem că sunt imagini pe care trebuie să le desenăm). Editarea unei imagini care este stocată în modul alfa premultiplied nu este convenabilă deoarece acumulează erori de rotunjire. Aceasta înseamnă că dorim să nu păstrăm texturile noastre premultipli, atâta timp cât încă lucrăm la ele. Când este timpul să testăm sau să ne eliberăm, trebuie:
glContext.pixelStorei (glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
Notă: pixelStorei
nu are efect asupra texturilor comprimate (Umm ... mai târziu!).
Toate aceste opțiuni pot fi puțin incomode. Din fericire, putem continua să obținem transparență fără a avea un canal alfa și fără să folosim alpha premultiplied:
OUT = = SRC ʙ ʙ ʙ R R R ᴀ ᴀ ᴀ ᴀ ᴀ ᴀ ʙ ʙ ʙ ʙ ʙ ʙ ʙ ʙ ʙ ʙ ʙ
Ignora outAlpha
complet și eliminați dstAlpha
de la outRGB
ecuaţie. Functioneaza! Dacă vă obișnuiți să o utilizați, puteți începe să întrebați motivul dstAlpha
a fost inclusă vreodată în ecuația inițială!
Deoarece nu am desenat primitive în acest tutorial (am folosit doar clar
, care nu utilizează alfa-blending) nu avem nevoie să scriem niciun amestec de alfa în cauză pentru codul WebGL. Dar doar pentru referință, iată pașii necesari pentru a permite amestecarea alfa de mai sus în WebGL:
funcția initializeState