Bine ați revenit la cea de-a treia și ultima tranșă din mini-seria WebGL Essentials. În această lecție, vom examina iluminarea și adăugarea obiectelor 2D în scenă. Sunt multe informații noi aici, așa că să ne aruncăm în direct!
Lumina poate fi aspectul cel mai tehnic și mai dificil al unei aplicații 3D de înțeles. O înțelegere fermă a iluminatului este absolut esențială.
Înainte de a intra în diferitele tehnici de lumină și codificare, este important să știm cum funcționează lumina în lumea reală. Fiecare sursă de lumină (de exemplu: un bec, soarele etc.) generează particule numite fotoni. Acești fotoni izbesc în jurul obiectelor până când ajung în cele din urmă în ochii noștri. Ochii noștri convertesc fotonii pentru a produce o "imagine" vizuală. Acesta este modul în care vedem. Lumina este de asemenea aditivă, ceea ce înseamnă că un obiect cu mai multă culoare este mai strălucitor decât un obiect fără culoare (negru). Negrul este absența completă a culorilor, în timp ce albul conține toate culorile. Aceasta este o distincție importantă atunci când lucrați cu lumini foarte luminoase sau cu "saturare".
Luminozitatea este doar un principiu care are mai multe stări. Reflecția, de exemplu, poate avea o varietate de nivele diferite. Un obiect, ca o oglindă, poate fi complet reflexiv, în timp ce alte obiecte pot avea o suprafață mată. Transparența determină modul în care obiectele îndoaie lumina și provoacă refracția; un obiect poate fi complet transparent, în timp ce altele pot fi opace (sau orice etapă între ele).
Lista continuă, dar cred că puteți vedea deja că lumina nu este simplă.
Dacă ați dori chiar și o mică scenă pentru a simula lumina reală, ar merge la ceva de genul 4 cadre pe oră, și asta e pe un computer de mare putere. Pentru a rezolva această problemă, programatorii folosesc trucuri și tehnici pentru a simula iluminarea semi-realistă la o rată rezonabilă de cadre. Trebuie să veniți cu o anumită formă de compromis între realism și viteză. Să aruncăm o privire la câteva dintre aceste tehnici.
Înainte de a începe să lucrez la diferite tehnici, aș dori să vă dau o mică notă de responsabilitate. Există o mulțime de controverse cu privire la numele exacte pentru diferitele tehnici de iluminare, iar diferiți oameni vă vor da explicații diferite cu privire la ceea ce este "Casting Ray" sau "Light Mapping". Deci, înainte de a începe să primesc poșta de ură, aș vrea să spun că voi folosi numele pe care le-am învățat; unii oameni ar putea să nu fie de acord cu privire la titlurile mele exacte. În orice caz, importantul lucru de știut este ceea ce sunt diferitele tehnici. Așa că, fără să mai vorbim, să începem.
Trebuie să veniți cu o anumită formă de compromis între realism și viteză.
Trasarea cu raze este una dintre cele mai realiste tehnici de iluminare, dar este și una dintre cele mai costisitoare. Trasarea Ray trasează lumina reală; emite "fotoni" sau "raze" de la sursa de lumină și le bate în jurul valorii de. În cele mai multe implementări de urmărire a razei, razele aparțin "camerei" și se strecoară pe scena în direcția opusă. Această tehnică este de obicei folosită în filme sau scene care pot fi redate înainte. Acest lucru nu înseamnă că nu puteți utiliza urmărirea razei într-o aplicație în timp real, dar acest lucru vă forțează să toniflați alte lucruri în scenă. De exemplu, s-ar putea să trebuiască să reduceți cantitatea de "bounces" pe care ar trebui să o efectueze razele sau să vă asigurați că nu există obiecte care să aibă suprafețe reflexive sau refractive. Trasarea Ray poate fi, de asemenea, o opțiune viabilă dacă aplicația dvs. are foarte puține lumini și obiecte.
Dacă aveți o aplicație în timp real, este posibil să puteți precompila părți din scenă.
Dacă luminile din aplicația dvs. nu se mișcă sau se deplasează numai într-o zonă mică, puteți precompila iluminarea printr-un algoritm de urmărire a radiațiilor foarte avansat și puteți recalcula o mică zonă în jurul sursei luminoase în mișcare. De exemplu, dacă faceți un joc în care luminile nu se mișcă, puteți precompila lumea cu toate luminile și efectele dorite. Apoi, puteți adăuga o umbră în jurul personajului dvs. atunci când se mișcă. Acest aspect produce o foarte bună calitate, cu o cantitate minimă de procesare.
Radiația cu raze este foarte asemănătoare cu trasarea cu raze, dar "fotonii" nu sare de pe obiecte sau nu interacționează cu diferite materiale. Într-o aplicație tipică, veți începe cu o scenă întunecată și apoi veți desena linii de la sursa de lumină. Orice sunt aprinse lumina; totul rămâne întunecat. Această tehnică este semnificativ mai rapidă decât trasarea razei, oferindu-vă totuși un efect realist de umbră. Dar problema cu turnarea razei este restrictivitatea ei; nu aveți prea mult spațiu de lucru când încercați să adăugați efecte precum reflecțiile. De obicei, trebuie să veniți cu un fel de compromis între turnarea razei și trasarea razei, echilibrarea între viteză și efecte vizuale.
Problema majoră cu ambele tehnici este că WebGL nu vă oferă acces la niciun punct, cu excepția celui activ în prezent.
Aceasta înseamnă că trebuie fie să efectuați totul pe CPU (așa cum este indicat pe placa grafică), fie că ați făcut un al doilea shader care calculează toate luminile și stochează informațiile într-o textura falsă. Apoi, va trebui să decomprimați datele de textură înapoi în informațiile de iluminare și să o mapați la vârfuri. Deci, practic versiunea curentă a WebGL nu este foarte potrivită pentru acest lucru. Nu spun că nu se poate face, spun doar că WebGL nu te va ajuta.
Trasarea Ray poate fi, de asemenea, o opțiune viabilă dacă aplicația dvs. are foarte puține lumini și obiecte.
O alternativă mult mai bună pentru distribuirea razei în WebGL se numește maparea umbrelor. Vă dă același efect ca și turnarea cu raze, dar folosiți o abordare diferită. Aplicația Shadow Mapping nu va rezolva toate problemele dvs., dar WebGL este semi-optimizat pentru aceasta. Puteți să vă gândiți la acest lucru ca la un fel de hack, dar maparea umbrelor este folosită în aplicațiile reale de PC și console.
Deci, ce vă cereți?
Trebuie să înțelegeți cum face WebGL scenele pentru a răspunde la această întrebare. WebGL împinge toate vârfurile în shader-ul de vârf, care calculează coordonatele finale pentru fiecare vârf după aplicarea transformărilor. Apoi, pentru a economisi timp, WebGL elimină nodurile care sunt ascunse în spatele altor obiecte și desenează doar obiectele esențiale. Dacă vă aduceți aminte cum funcționează turnarea prin raze, aceasta aruncă raze luminoase pe obiectele vizibile. Așa că am stabilit "camera" scenei noastre la coordonatele sursei de lumină și am indicat-o în direcția în care vrem să se confrunte lumina. Apoi, WebGL elimină automat toate nodurile care nu țin cont de lumină. Putem apoi să salvăm aceste date și să le folosim atunci când redați scena pentru a afla care dintre nodurile sunt aprinse.
Această tehnică sună bine pe hârtie, dar are câteva dezavantaje:
Toate aceste tehnici necesită o cantitate suficientă de tinkering cu WebGL. Dar vă voi arăta o tehnică de bază pentru a produce o lumină difuză pentru a da o mică personalitate obiectelor voastre. Nu aș numi-o lumină realistă, dar vă dă definiția obiectelor. Această tehnică utilizează matricea obișnuită a obiectului pentru a calcula unghiul luminii în raport cu suprafața obiectului. Este rapid, eficient și nu necesită hacking cu WebGL. Să începem.
Să începem prin actualizarea shaderelor pentru a încorpora iluminarea. Trebuie să adăugăm un boolean care determină dacă obiectul trebuie sau nu să fie aprins. Apoi, avem nevoie de vertexul normal normale și îl transformăm astfel încât să se alinieze cu modelul. În cele din urmă, trebuie să facem o variabilă pentru a transmite rezultatul final șaraderului de fragmente. Acesta este noul shader de vârfuri:
Dacă nu folosim luminile, atunci trecem doar un vertex gol la shaderul de fragmente, iar culoarea lui rămâne aceeași. Atunci când luminile sunt aprinse, se calculează unghiul dintre direcția luminii și suprafața obiectului folosind funcția punct pe normal și multiplicăm rezultatul cu culoarea luminii ca un fel de mască pentru a suprapune pe obiect.
Imagine a normalelor de suprafață de Oleg Alexandrov.
Aceasta funcționează deoarece normalele sunt deja perpendiculare pe suprafața obiectului, iar funcția punct dă un număr bazat pe unghiul luminii față de normal. Dacă lumina normală și lumina sunt aproape paralele, atunci funcția punct returnează un număr pozitiv, adică lumina se îndreaptă spre suprafață. Atunci când normalul și lumina sunt perpendiculare, suprafața este paralelă cu lumina, iar funcția revine la zero. Orice valoare mai mare de 90 de grade între lumină și cea normală are un număr negativ, dar filtrăm cu funcția "max zero".
Acum, permiteți-mi să vă arăt fragmentul de shader:
Acest shader este cam la fel din părțile anterioare ale seriei. Singura diferență este că noi înmulțim culoarea texturii cu nivelul de lumină. Acest lucru luminează sau întunecă diferite părți ale obiectului, dându-i o anumită adâncime.
Asta e tot pentru shadere, acum să mergem la WebGL.js
fișierul și modificarea celor două clase.
Să începem cu GLObject
clasă. Trebuie să adăugăm o variabilă pentru matricea normale. Iată ce este partea superioară a dvs. GLObject
Ar trebui să arate acum:
funcția GLObject (VertexArr, TriangleArr, TextureArr, ImageSrc, NormalsArr) this.Pos = X: 0, Y: 0, Z: 0; acest Scalare = X: 1.0, Y: 1.0, Z: 1.0; această rotire = X: 0, Y: 0, Z: 0; this.Vertices = VertexArr; // Array pentru a ține datele normale this.Normals = NormalsArr; // Restul de GLObject continuă aici
Acest cod este destul de drept înainte. Acum, să revenim la fișierul HTML și să adăugăm matricea normale obiectului nostru.
În Gata()
funcția în care încărcăm modelul nostru 3D, trebuie să adăugăm parametrul pentru matricea normale. O matrice goală înseamnă că modelul nu conține date normale și va trebui să desenați obiectul fără lumină. În cazul în care matricea normale conține date, le vom trece doar pe GLObject
obiect.
De asemenea, trebuie să actualizăm WebGL
clasă. Trebuie să conectăm variabilele la shadere imediat după încărcarea shaderelor. Să adăugăm vertexul normalelor; codul dvs. ar trebui să arate astfel:
// Link Atributul de poziție a vârfului din Shader this.VertexPosition = this.GL.getAttribLocation (acest.ShaderProgram, "VertexPosition"); this.GL.enableVertexAttribArray (this.VertexPosition); // Atributul de coordonate al texturii de legătură din Shader this.VertexTexture = this.GL.getAttribLocation (acest.ShaderProgram, "TextureCoord"); this.GL.enableVertexAttribArray (this.VertexTexture); // Acesta este noul atribut Normals array this.VertexNormal = this.GL.getAttribLocation (acest.ShaderProgram, "VertexNormal"); this.GL.enableVertexAttribArray (this.VertexNormal);
Apoi, să actualizăm PrepareModel ()
și adăugați un cod pentru a tampona datele normale atunci când este disponibil. Adăugați noul cod chiar înainte de Model.Ready
declarație în partea de jos:
dacă (false! == Model.Normals) Buffer = this.GL.createBuffer (); this.GL.bindBuffer (acest.GL.ARRAY_BUFFER, Buffer); this.GL.bufferData (acest.GL.ARRAY_BUFFER, noul Float32Array (Model.Normals), this.GL.STATIC_DRAW); Model.Normals = Tampon; Model.Ready = adevărat;
Nu în ultimul rând, actualizați actualul A desena
să includă toate aceste modificări. Sunt câteva schimbări aici, deci purtați-mă cu mine. Voi merge bucata cu bucata in intreaga functie:
this.Draw = funcție (Model) if (Model.Image.ReadyState == true && Model.Ready == false) this.PrepareModel (Model); dacă (Model.Ready) this.GL.bindBuffer (this.GL.ARRAY_BUFFER, Model.Vertices); this.GL.vertexAttribPointer (acest.VertexPosition, 3, this.GL.FLOAT, false, 0, 0); this.GL.bindBuffer (acest.GL.ARRAY_BUFFER, Model.TextureMap); this.GL.vertexAttribPointer (acest.VertexTexture, 2, this.GL.FLOAT, false, 0, 0);
Până aici este la fel ca înainte. Acum vine partea normală:
// Check for Normals dacă (false! == Model.Normals) // Conectați tamponul normale la Shader this.GL.bindBuffer (this.GL.ARRAY_BUFFER, Model.Normals); acest.GL.vertexAttribPointer (acest.VertexNormal, 3, this.GL.FLOAT, false, 0, 0); // Spuneți shader-ul pentru a utiliza iluminatul var UseLights = acest.GL.getUniformLocation (acest.ShaderProgram, "UseLights"); this.GL.uniform1i (UtilizareLights, true); altfel // Chiar dacă obiectul nostru nu are date normale, trebuie să trecem încă ceva. Deci, trec în Vertices în loc de this.GL.bindBuffer (this.GL.ARRAY_BUFFER, Model.Vertices); acest.GL.vertexAttribPointer (acest.VertexNormal, 3, this.GL.FLOAT, false, 0, 0); // Spuneți shader-ul pentru a utiliza iluminatul var UseLights = acest.GL.getUniformLocation (acest.ShaderProgram, "UseLights"); this.GL.uniform1i (Utilizați luminile, false);
Verificăm dacă modelul are date normale. Dacă da, se conectează buffer-ul și setează booleanul. Dacă nu, shader-ul are nevoie de un fel de date sau vă va da o eroare. Deci, în schimb, am trecut tamponul de vârfuri și am setat UseLight
boolean la fals
. Ați putea obține acest lucru prin utilizarea mai multor shadere, dar am crezut că acest lucru ar fi mai simplu pentru ceea ce încercăm să facem.
this.GL.bindBuffer (acest.GL.ELEMENT_ARRAY_BUFFER, Model.Triangles); // Generați Perspectiva Matrix var PerspectiveMatrix = MakePerspective (45, this.AspectRatio, 1, 1000.0); var TransformMatrix = Model.GetTransforms ();
Din nou, această parte a funcției este în continuare aceeași.
var NormalsMatrix = MatrixTranspose (InverseMatrix (TransformMatrix));
Aici calculam matricea de transformare a normalelor. Voi discuta MatrixTranspose ()
și InverseMatrix ()
funcționează într-un minut. Pentru a calcula matricea de transformare pentru matricea normale, trebuie să transpuneți matricea inversă a matricei de transformare obișnuită a obiectului. Mai multe despre asta mai târziu.
// Setați slotul 0 ca textura activă this.GL.activeTexture (this.GL.TEXTURE0); // Încărcați în textură în memorie this.GL.bindTexture (this.GL.TEXTURE_2D, Model.Image); // Actualizați proba de textură în shaderul de fragmente pentru a utiliza slotul 0 this.GL.uniform1i (acest.GL.getUniformLocation (acest.ShaderProgram, "uSampler"), 0); // Setați matricea de perspectivă și transformare var pmatrix = this.GL.getUniformLocation (acest.ShaderProgram, "PerspectiveMatrix"); this.GL.uniformMatrix4fv (pmatrix, false, noul Float32Array (PerspectiveMatrix)); var tmatrix = acest.GL.getUniformLocation (acest.ShaderProgram, "TransformationMatrix"); this.GL.uniformMatrix4fv (tmatrix, false, noul Float32Array (TransformMatrix)); var nmatrix = acest.GL.getUniformLocation (acest.ShaderProgram, "NormalTransformation"); this.GL.uniformMatrix4fv (nmatrix, false, nou Float32Array (NormalsMatrix)); // Desenați triunghiurile this.GL.drawElements (this.GL.TRIANGLES, Model.TriangleCount, this.GL.UNSIGNED_SHORT, 0); ;
Puteți vedea cu ușurință sursa oricărei aplicații WebGL pentru a afla mai multe.
Acesta este restul A desena()
funcţie. Este aproape la fel ca înainte, dar există codul adăugat care leagă matricea normalelor de shadere. Acum, să ne întoarcem la cele două funcții pe care le obișnuisem să obțin matricea de transformări normale.
InverseMatrix ()
funcția acceptă o matrice și returnează matricea inversă. O matrice inversă este o matrice care, înmulțită cu matricea originală, returnează o matrice de identitate. Să aruncăm o privire la un exemplu de algebră de bază pentru a clarifica acest lucru. Inversul numărului 4 este de 1/4 deoarece atunci când 1/4 x 4 = 1
. Echivalentul "unul" în matrice este o matrice de identitate. De aceea InverseMatrix ()
funcția returnează matricea identității pentru argument. Iată această funcție:
funcția InverseMatrix (A) var s0 = A [0] * A [5] - A [4] * A [1]; var s1 = A [0] * A [6] - A [4] * A [2]; var s2 = A [0] * A [7] - A [4] * A [3]; var s3 = A [1] * A [6] - A [5] * A [2]; var s4 = A [1] * A [7] - A [5] * A [3]; var s5 = A [2] * A [7] - A [6] * A [3]; var c5 = A [10] * A [15] - A [14] * A [11]; var c4 = A [9] * A [15] - A [13] * A [11]; var c3 = A [9] * A [14] - A [13] * A [10]; var c2 = A [8] * A [15] - A [12] * A [11]; var c1 = A [8] * A [14] - A [12] * A [10]; var c0 = A [8] * A [13] - A [12] * A [9]; var invadet = 1.0 / (s0 * c5 - s1 * c4 + s2 * c3 + s3 * c2 - s4 * c1 + s5 * c0); var B = []; B [0] = (A [5] * c5 - A [6] * c4 + A [7] * c3) invadet; B [1] = (-A [1] * c5 + A [2] * c4-A [3] * c3) invadet; B [2] = (A [13] * s5 - A [14] * s4 + A [15] * s3) invdet; B [3] = (-A [9] * s5 + A [10] * s4 - A [11] * s3) invadet; B [4] = (-A [4] * c5 + A [6] * c2-A [7] * c1) invadet; B [5] = (A [0] * c5 - A [2] * c2 + A [3] * c1) invadet; B [6] = (-A [12] * s5 + A [14] * s2 - A [15] * s1) invadet; B [7] = (A [8] * s5 - A [10] * s2 + A [11] * s1) invadet; B [8] = (A [4] * c4 - A [5] * c2 + A [7] * c0) * invadet; B [9] = (-A [0] * c4 + A [1] * c2 - A [3] * c0) * invadet; B [10] = (A [12] * s4 - A [13] * s2 + A [15] * s0) * invadet; B [11] = (-A [8] * s4 + A [9] * s2 - A [11] * s0) invadet; B [12] = (-A [4] * c3 + A [5] * c1 - A [6] * c0) * invadet; B [13] = (A [0] * c3 - A [1] * c1 + A [2] * c0) * invadet; B [14] = (-A [12] * s3 + A [13] * s1 - A [14] * s0) invadet; B [15] = (A [8] * s3 - A [9] * s1 + A [10] * s0) invadet; retur B;
Această funcție este destul de complicată și, pentru a vă spune adevărul, nu înțeleg pe deplin de ce lucrează matematica. Dar i-am explicat deja esența de mai sus. Nu am venit cu această funcție; a fost scris în ActionScript de Robin Hilliard.
Următoarea funcție, MatrixTranspose ()
, este mult mai simplu de înțeles. Aceasta returnează versiunea "transpusă" a matricei sale de intrare. Pe scurt, doar roteste matricea pe partea ei. Iată codul:
Funcția MatrixTranspose (A) return [A [0], A [4], A [8], A [12], A [ 2], A [6], A [10], A [14], A [3], A [7], A [11], A [15]];
În locul rândurilor orizontale (adică A [0], A [1], A [2] ...), această funcție coboară vertical (A [0], A [.
Esti bine sa mergi dupa ce ai adaugat aceste doua functii la tine WebGL.js
fișierul și orice model care conține datele normale trebuie să fie umbrite. Puteți juca cu direcția și culoarea luminii în shaderul de vârfuri pentru a obține efecte diferite.
Există un ultim subiect pe care doresc să-l acordez, și asta înseamnă adăugarea conținutului 2D la scena noastră. Adăugarea de componente 2D pe o scenă 3D poate avea multe beneficii. De exemplu, acesta poate fi utilizat pentru a afișa informații despre coordonate, o hartă mini, instrucțiuni pentru aplicația dvs. și lista continuă. Acest proces nu este la fel de clar cum credeți, așa că haideți să-l verificăm.
HTML nu vă va permite să utilizați API-ul WebGL și API-ul 2D din aceeași pânză.
S-ar putea să te gândești: "De ce să nu folosești doar panza construită în API HTML 2D?" Ei bine, problema este că HTML nu vă va permite să utilizați API-ul WebGL și API-ul 2D din aceeași pânză. Odată ce atribuiți contextul panzei la WebGL, nu îl puteți utiliza cu API-ul 2D. HTML5 revine pur și simplu nul
când încercați să obțineți contextul 2D. Deci, atunci cum te pricepi la asta? Îți dau două opțiuni.
2.5D, pentru cei care nu știu, este atunci când puneți obiecte 2D (obiecte fără adâncime) într-o scenă 3D. Adăugarea de text la o scenă este un exemplu de 2.5D. Puteți prelua textul dintr-o imagine și aplica-l ca textură unui plan 3D sau puteți obține un model 3D pentru text și renderul acestuia pe ecran.
Beneficiile acestei abordări sunt că nu aveți nevoie de două pânze și ar fi mai rapid să trageți dacă ați folosit doar forme simple în aplicația dvs..
Dar pentru a face lucruri ca textul, trebuie fie să aveți poze cu tot ceea ce doriți să scrieți, fie un model 3D pentru fiecare literă (puțin peste partea de sus, după părerea mea).
Alternativa este să creați oa doua pânză și să o suprapuneți pe panza 3D. Prefer această abordare deoarece pare mai bine echipat pentru a desena conținut 2D. Nu voi începe să fac un nou cadru 2D, ci să creăm doar un exemplu simplu în care afișăm coordonatele modelului împreună cu rotația curentă. Să adăugăm oa doua pânză la fișierul HTML imediat după panza WebGL. Iată noua pânză, împreună cu cea actuală:
De asemenea, am adăugat câteva CSS inline pentru a suprapune cea de-a doua pânză deasupra primului. Următorul pas este să creați o variabilă pentru panza 2D și să obțineți contextul. Am de gând să fac asta în Gata()
funcţie. Codul dvs. actualizat ar trebui să arate astfel:
var GL; var Building; var Canvas2D; funcția Ready () // Declarația Gl și funcția modelului Load aici Canvas2D = document.getElementById ("2DCanvas"). getContext ("2d"); Canvas2D.fillStyle = "# 000";
În partea de sus, puteți vedea că am adăugat o variabilă globală pentru panza 2D. Apoi, am adăugat două linii în partea de jos a Gata()
funcţie. Prima linie nouă primește contextul 2D, iar a doua linie nouă stabilește culoarea neagră.
Ultimul pas este de a desena textul în interiorul Actualizați()
funcţie:
funcția Update () Building.Rotation.Y + = 0.3 // Eliminați Canvasul de la tragerea precedentă Canvas2D.clearRect (0, 0, 600, 400); // Titlu text Canvas2D.font = "25px sans-serif"; Canvas2D.fillText ("Building", 20, 30); // Proprietățile obiectului Canvas2D.font = "16px sans-serif"; Canvas2D.fillText ("X:" + Building.Pos.X, 20, 55); Canvas2D.fillText ("Y:" + Building.Pos.Y, 20, 75); Canvas2D.fillText ("Z:" + Building.Pos.Z, 20, 95); Canvas2D.fillText ("Rotire:" + Math.floor (Building.Rotation.Y), 20, 115); GL.GL.clear (16384 | 256); GL.Draw (Clădire);
Începem prin rotirea modelului pe axa Y, apoi ștergem panza 2D a oricărui conținut anterior. Apoi, setăm dimensiunea fontului și tragem un text pentru fiecare axă. fillText ()
metoda acceptă trei parametri: textul de tras, coordonata x și coordonata y.
Simplitatea vorbește de la sine. Acest lucru poate fi un pic de overkill pentru a trage un text simplu; ați putea fi ușor de scris doar într-un text poziționat sau
element. Dar dacă faci ceva cum ar fi desenarea unor forme, sprite, un bar de sănătate etc., atunci este probabil cea mai bună opțiune.
În cadrul ultimelor trei tutoriale, am creat un motor 3D destul de frumos, deși simplu. În ciuda naturii sale primitive, aceasta vă oferă o bază solidă pentru a lucra în afara. Mergând înainte, vă sugerăm să vă uitați la alte cadre, cum ar fi three.js sau glge, pentru a obține o idee despre ceea ce este posibil. În plus, WebGL rulează în browser și puteți vedea cu ușurință sursa oricărei aplicații WebGL pentru a afla mai multe.
Sper că v-ați bucurat de această serie de tutorial și, ca întotdeauna, lăsați-vă comentariile și întrebările în secțiunea de comentarii de mai jos.