Noțiuni de bază în WebGL, Partea 4 Vizualizare și decupare WebGL

În părțile anterioare ale acestei serii, am învățat multe despre shadere, elementul canvas, contextele WebGL și modul în care browserul alfa-compozitează tamponul de culoare peste restul elementelor de pagină. 

În acest articol, continuăm să scriem codul de bare WebGL. Încă ne pregătim panza pentru desenul WebGL, de această dată luând în considerare ferestrele de vizualizare și primitive. 

Acest articol face parte din seria "Noțiuni de bază în WebGL". Dacă nu ați citit părțile anterioare, le recomand să le citiți mai întâi:

  1. Introducere în Shaders
  2. Elementul de canvas pentru primul nostru shader
  3. Contextul WebGL și clar

Recapitulare

  • În primul articol din această serie, am scris un shader simplu care atrage un gradient colorat și îl estompează ușor și ușor.
  • Î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.
  • În cel de-al treilea articol, am achiziționat contextul WebGL și l-am folosit pentru a șterge buffer-ul de culoare. De asemenea, am explicat modul în care pânza se amestecă cu celelalte elemente ale paginii.

În acest articol, continuăm de unde am plecat, de această dată învățând despre ferestrele de vizualizare WebGL și despre modul în care ele afectează tăierea primitivelor.

În continuare, în această serie - dacă Allah dorește - ne vom compila programul Shader, vom afla despre tampoanele WebGL, vom extrage primitive și de fapt vom rula programul de shader pe care l-am scris în primul articol. Aproape acolo!

Dimensiunea pânzei

Acesta este codul nostru până acum:

Rețineți că am restabilit culoarea de fundal CSS la negru și culoarea deschisă la culoarea opacă roșie.

Datorită CSS-ului nostru, avem o pânză care se întinde pentru a umple pagina noastră de web, dar tamponul de bază 1x1 nu este foarte util. Trebuie să setăm o dimensiune adecvată pentru tamponul nostru de desen. Dacă tamponul este mai mic decât panza, atunci nu utilizăm pe deplin rezoluția dispozitivului și suntem supuși unor artefacte scalabile (așa cum este discutat într-un articol anterior). Dacă tamponul este mai mare decât pânza, bine, calitatea beneficiază de fapt mult! Este din cauza super-eșantionarea anti-aliasing browser-ul se aplică la downscale tampon înainte de a fi predat la compozitor. 

Cu toate acestea, performanța are un succes bun. Dacă se dorește anti-aliasing, este mai bine realizat prin MSAA (filtrarea texturilor cu mai multe eșantioane) și filtrarea texturii. Pentru moment, ar trebui să urmărim un tampon de desen de aceeași mărime a pânzei noastre pentru a utiliza pe deplin rezoluția dispozitivului și pentru a evita scalarea cu totul.

Pentru a face asta, vom împrumuta adjustCanvasBitmapSize din partea 2 (cu unele modificări):

funcția adjustDrawingBufferSize () var canvas = glContext.canvas; var pixelRatio = window.devicePixelRatio? window.devicePixelRatio: 1.0; // Verificați lățimea și înălțimea individual pentru a evita două operațiuni de redimensionare dacă este necesar doar // unul. Deoarece această funcție a fost apelată, atunci cel puțin pe ele a fost // modificat, dacă (canvas.width! = Math.floor (canvas.clientWidth * pixelRatio)) canvas.width = pixelRatio * canvas.clientWidth; dacă (canvas.height! = Math.floor (canvas.clientHeight * pixelRatio)) canvas.height = pixelRatio * canvas.clientHeight; // Setați noile dimensiuni ale ferestrei de vizualizare, glContext.viewport (0, 0, glContext.drawingBufferWidth, glContext.drawingBufferHeight); 

Schimbări:

  • Noi am folosit clientWidth și clientHeight in loc de offsetWidth și offsetHeight. Acestea din urmă includ granițele pânzei, astfel încât acestea să nu fie exact ceea ce căutăm. clientWidth și clientHeight sunt mai potrivite în acest scop. Greșeala mea!
  • adjustDrawingBufferSize este programat să ruleze numai dacă au avut loc schimbări. Prin urmare, nu este necesar să verificăm și să abandonăm în mod explicit dacă nimic nu sa schimbat.
  • Nu mai trebuie să sunăm drawScene de fiecare dată când dimensiunea se schimbă. Ne vom asigura că este chemat în mod regulat în altă parte.
  • A glContext.viewport a apărut! Ea are propria secțiune, așa că lăsați-l să treacă deocamdată!

De asemenea, vom împrumuta funcția de diminuare a evenimentelor de redimensionare, onWindowResize (și cu unele modificări):

function onCanvasResize () // Calculati dimensiunile in pixeli fizici, var canvas = glContext.canvas; var pixelRatio = window.devicePixelRatio? window.devicePixelRatio: 1.0; var physicalWidth = Math.floor (canvas.clientWidth * pixelRatio); var fizicHeight = Math.floor (canvas.clientHeight * pixelRatio); // Abort dacă nimic nu sa schimbat, dacă ((onCanvasResize.targetWidth == physicalWidth) && (onCanvasResize.targetHeight == physicalHeight)) return;  // Setați noile dimensiuni necesare, onCanvasResize.targetWidth = physicalWidth; onCanvasResize.targetHeight = physicalHeight; // Așteptați până la inundarea evenimentelor de redimensionare, dacă (onCanvasResize.timeoutId) window.clearTimeout (onCanvasResize.timeoutId); onCanvasResize.timeoutId = fereastră.setTimeout (adjustDrawingBufferSize, 600); 

Schimbări:

  • Ninge onCanvasResize in loc de onWindowResize. Este ok în exemplul nostru să presupunem că dimensiunea panzei se modifică numai atunci când dimensiunea ferestrei este schimbată, dar în lumea reală, pânza noastră poate face parte dintr-o pagină în care există alte elemente, elemente care pot fi redimensionate și care afectează dimensiunea panzei.
  • În loc să ascultați evenimentele legate de modificările dimensiunii canvasului, vom verifica modificările de fiecare dată când intenționăm să redesemnăm conținutul panzei. Cu alte cuvinte, onCanvasResize se știe dacă au apărut schimbări sau nu, astfel încât să se întrerupă atunci când nimic nu sa schimbat este necesar.

Acum, să sunăm onCanvasResize din drawScene:

funcția drawScene () // Schimbarea mărimii canelurilor, onCanvasResize (); // Ștergeți buffer-ul de culoare, glContext.clear (glContext.COLOR_BUFFER_BIT); 

Am spus că o să sunăm drawScene in mod regulat. Asta înseamnă că suntem redând continuu, nu numai atunci când apar schimbări (aka când este murdar). Desenul consumă în permanență mai multă putere decât desenul numai atunci când este murdar, dar ne salvează dificultățile de a urmări când trebuie să fie actualizate conținutul. 

Dar merită să vă gândiți dacă intenționați să faceți o aplicație care rulează pentru perioade lungi de timp, cum ar fi imagini de fundal și lansatoare (dar nu ați face aceste lucruri în WebGL pentru a începe, nu?). Prin urmare, pentru acest tutorial, vom redata continuu. Cea mai ușoară modalitate de a face acest lucru este prin programarea repetării drawScene din interior:

funcția drawScene () ... stuff ... // Solicitați din nou desenul următor, window.requestAnimationFrame (drawScene); 

Nu, nu am folosit-o setInterval sau setTimeout pentru asta. requestAnimationFrame spune browser-ului că doriți să efectuați o animație și solicită apelarea drawScene înainte de revopsirea următoare. Este cel mai potrivit pentru animații printre cei trei, deoarece:

  • Timpurile din setInterval și setTimeout adesea nu sunt onorate tocmai - acestea se bazează pe cele mai bune eforturi. Cu requestAnimationFrame, calendarul se va potrivi, în general, cu rata de reîmprospătare a afișajului.
  • Dacă codul programat conține modificări în aspectul conținutului paginii, setInterval și setTimeout ar putea provoca dispariția (dar asta nu este cazul nostru). requestAnimationFrame are grijă de asta și nu declanșează cicluri inutile de reflow și revopsire.
  • Utilizarea requestAnimationFrame permite browserului să decidă cât de des să apelăm funcția animație / desen. Acest lucru înseamnă că poate să-l accelerați dacă pagina / iframa devine ascunsă sau inactivă, ceea ce înseamnă mai multă durată de viață a bateriei pentru dispozitivele mobile. Acest lucru se întâmplă de asemenea cu setInterval și setTimeout în mai multe browsere (Firefox, Chrome) - doar pretindeți că nu știți!

Înapoi la pagina noastră. Acum, mecanismul nostru de redimensionare este complet:

  • drawScene este numit în mod regulat și se cheamă onCanvasResize de fiecare data.
  • onCanvasResize verifică dimensiunea pânzei și dacă au avut loc schimbări, programați o adjustDrawingBufferSize apelați sau amânați dacă era deja programată.
  • adjustDrawingBufferSize modifică de fapt dimensiunea buffer-ului de desenare și stabilește dimensiunile noi ale ferestrei de vizualizare în timp.

Punând totul împreună:

Am adăugat o alertă care apare de fiecare dată când tamponul de tragere este redimensionat. Poate doriți să deschideți mostra de mai sus într-o filă nouă și să redimensionați fereastra sau să schimbați orientarea dispozitivului pentru ao testa. Observați că redimensionează numai când ați oprit redimensionarea timp de 0,6 secunde (ca și cum ați măsura asta!).

O ultimă remarcă înainte de a termina acest lucru de redimensionare a tamponului. Există limite la cât de mare poate fi o tampon de desen. Acestea depind de hardware-ul și browserul în uz. Dacă se întâmplă să fii:

  • utilizând un smartphone sau
  • un ecran ridicol de înaltă rezoluție sau
  • au mai multe monitoare / spații de lucru / desktopuri virtuale setate, sau
  • utilizează un smartphone sau
  • vizualizează pagina dvs. într - un cadru iframe foarte mare (care este cel mai simplu mod de a testa acest lucru), sau
  • folosesc un smartphone

există șansa ca panza să fie redimensionată la mai mult decât limitele posibile. În acest caz, lățimea și înălțimea pânzei nu vor prezenta obiecții, însă dimensiunea efectivă a tamponului va fi fixată la maximul posibil. Puteți obține dimensiunea efectivă a tamponului utilizând membrii numai pentru citire glContext.drawingBufferWidth și glContext.drawingBufferHeight, pe care am folosit-o pentru a construi alerta. 

În afară de asta, totul ar trebui să funcționeze bine ... cu excepția faptului că pe anumite browsere, anumite părți din ceea ce desenați (sau toate acestea) nu pot ajunge niciodată pe ecran! În acest caz, adăugând aceste două linii adjustDrawingBufferSize după redimensionare ar putea fi utilă:

dacă (canvas.width! = glContext.drawingBufferWidth) canvas.width = glContext.drawingBufferWidth; dacă (canvas.height! = glContext.drawingBufferHeight) canvas.height = glContext.drawingBufferHeight;

Acum ne întoarcem unde lucrurile au sens. Dar rețineți că fixarea la drawingBufferWidth și drawingBufferHeight poate nu este cea mai bună acțiune. S-ar putea să doriți să luați în considerare menținerea unui anumit raport de aspect.

Acum să facem niște desene!

Vedere și foarfece 

// Setați noile dimensiuni ale ferestrei de vizualizare, glContext.viewport (0, 0, glContext.drawingBufferWidth, glContext.drawingBufferHeight);

Amintiți-vă în primul articol din această serie când am menționat că în interiorul shaderului, WebGL folosește coordonatele (-1, -1) pentru a reprezenta colțul din stânga jos al portului de vizualizare și (1, 1) pentru a reprezenta colțul din dreapta sus? Asta e. viewport spune WebGL care dreptunghi în tampon de desen ar trebui să fie mapate la (-1, -1) și (1, 1). E doar o transformare, nimic mai mult. Nu afectează tampoanele sau nimic.

De asemenea, am spus că orice aspect din afara dimensiunilor ferestrei de vedere este omis și nu este desenat cu totul. Asta e aproape în întregime adevărat, dar are o întorsătură. Trucul se află în cuvintele "trase" și "în afara". Ceea ce contează cu adevărat ca desen sau ca afară?

// Restrânge desenul la jumătatea stângă a panzei, glContext.viewport (0, 0, glContext.drawingBufferWidth / 2, glContext.drawingBufferHeight);

Această linie limitează dreptunghiul din fereastra de vizualizare la jumătatea stângă a pânzei. Am adăugat-o la drawScene funcţie. De obicei, nu trebuie să sunăm viewport cu excepția cazului în care dimensiunea pânzei se schimbă și de fapt am făcut-o acolo. Puteți șterge unul din funcția de redimensionare, dar voi lăsa să fie. În practică, încercați să micșorați cât mai mult apelurile dvs. WebGL. Să vedem ce face această linie:

Oh, clar (glContext.COLOR_BUFFER_BIT) au ignorat total setările noastre de vizualizare! Asta face, duh! viewport nu are niciun efect asupra apelurilor clare. Ceea ce afectează dimensiunile ferestrei de vizualizare este tăierea primitivelor. Amintiți-vă în primul articol, am spus că nu putem desena decât puncte, linii și triunghiuri în WebGL. Acestea vor fi tăiate pe dimensiunile ferestrei de vedere așa cum credeți că sunt ... cu excepția punctelor. 

puncte

Un punct este tras în întregime dacă centrul său se află în dimensiunile ferestrei de vedere și va fi omis în întregime dacă centrul său se află în afara acestora. Dacă un punct este suficient de gras, centrul său poate fi încă în interiorul ferestrei de vizualizare, în timp ce o parte din ea se extinde în afară. Această parte extinsă ar trebui să fie desenată. Asa ar trebui sa fie, dar acest lucru nu este neaparat cazul in practica:

Ar trebui să vedeți ceva care seamănă cu acest lucru dacă browserul, dispozitivul și driverele dvs. respectă standardul (în această privință):

Dimensiunea punctelor depinde de rezoluția reală a dispozitivului dvs., deci nu vă supărați diferența de dimensiune. Doar acordați atenție cât de multe puncte apar. În exemplul de mai sus, am setat zona de vizualizare la secțiunea mijlocie a pânzei (zona cu gradient), dar din moment ce centrele de puncte sunt încă în interiorul ferestrei de vizualizare, acestea trebuie să fie desenate în întregime (lucrurile verzi). Dacă acest lucru este cazul în browser-ul dvs., atunci minunat! Dar nu toți utilizatorii sunt norocoși. Unii utilizatori vor vedea părțile exterioare tăiate, ceva de genul:

De cele mai multe ori, nu are nicio importanță. Dacă portul de vizualizare va acoperi întreaga pânză, atunci nu ne pasă dacă vor fi tăiate sau nu. Dar ar avea importanță dacă aceste puncte se mișcau ușor în fața pânzei și apoi au dispărut dintr-o dată, deoarece centrele lor au ieșit afară:

(Presa Rezultat pentru a reporni animația.)

Din nou, acest comportament nu este neapărat ceea ce vedeți. Potrivit istoriei, dispozitivele Nvidia nu vor fixa punctele când centrele lor vor ieși afară, ci vor tăia piesele care ies în afară. Pe computerul meu (folosind un dispozitiv AMD), Chrome, Firefox și Edge se comportă la fel în cazul în care rulează pe Windows. Cu toate acestea, pe aceeași mașină, Chrome și Firefox vor închide punctele și nu le vor tăia atunci când se execută pe Linux. Pe telefonul meu Android, Chrome și Firefox vor ambelor clipuri și tăierea punctelor!

forfecare

Se pare că punctele de tragere sunt deranjante. De ce chiar ai grija? Deoarece punctele nu trebuie să fie circulare. Acestea sunt regiuni dreptunghiulare aliniate pe axe. Este fragmentul de shader care decide cum să le deseneze. Ele pot fi texturate, caz în care sunt cunoscute ca point-sprite. Acestea pot fi folosite pentru a face o mulțime de lucruri, cum ar fi hărți de țigle și efecte de particule, în care sunt cu adevărat la îndemână, deoarece trebuie doar să treci un vertex pe sprite (centrul), în loc de patru în cazul unei benzi triunghiulare . Reducerea cantității de date transferate de la procesor la unitatea de procesare grafică poate fi cu adevărat plătită în scenele complexe. În WebGL 2, putem folosi configurarea geometriei (care are propriile capturi), dar nu suntem încă acolo.

Deci, cum ne ocupăm de tăierea punctelor? Pentru a scoate părțile exterioare, folosim forfecare:

funcția initializeState () ... // Activați scissoring, glContext.enable (glContext.SCISSOR_TEST); 

Scissoring este acum activat, deci iată cum să setați regiunea ascuțită:

function adjustDrawingBufferSize () ... // Setați noua cutie pentru foarfece, glContext.scissor (xInPixels, yInPixels, widthInPixels, heightInPixels); 

În timp ce pozițiile primitivilor sunt relativ la dimensiunile ferestrei de vedere, dimensiunile cutiei de foarfece nu sunt. Acestea specifică un dreptunghi brut în tamponul de desen, nefiind minte cât de mult se suprapune cu orizontul de vizualizare (sau nu). În următorul exemplu, am setat fereastra de vizualizare și cutia de foarfece în secțiunea mijlocie a pânzei:

(Presa Rezultat pentru a reporni animația.)

Rețineți că testul cu foarfece este o operație per eșantion care elimină fragmentele care se află în afara casetei de testare. Nu are nimic de-a face cu ceea ce este tras; doar aruncă fragmentele care ies din afară. Chiar clar respectă testul foarfeca! De aceea, culoarea albastră (culoarea clară) este legată de cutia de foarfece. Tot ce rămâne rămâne să împiedicăți dispariția punctelor atunci când centrele lor se află afară. Pentru a face acest lucru, mă asigur că portul de vizualizare este mai mare decât cutia de foarfece, cu o margine care permite punctelor să fie desenate până când sunt complet în afara cutiei de foarfece:

(Presa Rezultat pentru a reporni animația.)

Ura! Acest lucru ar trebui să funcționeze frumos pretutindeni. Dar în codul de mai sus, am folosit doar o parte din panza pentru a face desenul. Dacă am vrea să ocupăm întreaga pânză? Nu are nici o importanță. Portalul de vizualizare poate fi mai mare decât tamponul de desen fără probleme (ignorați-l pe Firefox în legătură cu ieșirea din consola):

function adjustDrawingBufferSize () ... // Setați noile dimensiuni ale ferestrei de vizualizare, var pointSize = 150; glContext.viewport (-0.5 * punctSize, -0.5 * punctSize, glContext.drawingBufferWidth + punctSize, glContext.drawingBufferHeight + punctSize); // Setați noua cutie de foarfece, glContext.scissor (0, 0, glContext.drawingBufferWidth, glContext.drawingBufferHeight); 

Vedea:

Aveți însă grijă de mărimea ferestrei de vedere. Chiar dacă fereastra de vizualizare nu este decât o transformare care nu vă costă resurse, nu doriți să vă bazați doar pe tăierea individuală. Luați în considerare modificarea portului de vizualizare numai atunci când este necesar și restaurați-o pentru restul desenului. Și rețineți că fereastra de vizualizare afectează poziția primitivelor de pe ecran, deci luați în considerare și acest lucru.

Atât deocamdată! Data viitoare, să lăsăm întreaga dimensiune, fereastră de vizualizare și să tăiem lucrurile în spatele nostru. La desenarea unor triunghiuri! Vă mulțumim pentru că ați citit până acum și sper că a fost de ajutor.

Referințe

  • Operațiile care scriu în tamponul de desen în specificația WebGL
  • Modul în care browserul măsoară tamponul de extragere pe MDN
  • Anti-modele de vizualizare WebGL pe Fundamentele WebGL
  • Timeri în HTML
  • requestAnimationFrame pe MDN
  • WebGL foarfece de testare foarfece