socialgekon.com
  • Tärkein
  • Projektinhallinta
  • Trendit
  • Työkalut Ja Oppaat
  • Web-Käyttöliittymä
Teknologia

3D-grafiikka: WebGL-opetusohjelma

3D-grafiikan maailma voi olla hyvin pelottava päästä. Halusitpa vain luoda interaktiivisen 3D-logon tai suunnitella täysimittaisen pelin, jos et tiedä 3D-renderoinnin periaatteita, olet jumissa kirjaston avulla, joka tiivistää paljon asioita.

Kirjaston käyttö voi olla juuri oikea työkalu ja JavaScript on hämmästyttävä avoimen lähdekoodin muodossa three.js . Valmiiden ratkaisujen käytöllä on kuitenkin joitain haittoja:

  • Niillä voi olla monia ominaisuuksia, joita et aio käyttää. Pienennettyjen three.js-perusominaisuuksien koko on noin 500 kt, ja mahdolliset lisäominaisuudet (todellisten mallitiedostojen lataaminen on yksi niistä) lisää hyötykuormaa entisestään. Tämän tiedon siirtäminen vain pyörivän logon näyttämiseksi verkkosivustollasi olisi tuhlaa.
  • Ylimääräinen abstraktiokerros voi tehdä muuten helppoja muokkauksia vaikeaksi tehdä. Luova tapasi varjostaa kohdetta ruudulla voi olla joko suoraviivainen toteuttaa tai vaatia kymmenien tuntien työn sisällyttämisen kirjaston abstraktioihin.
  • Vaikka kirjasto on optimoitu erittäin hyvin useimmissa tilanteissa, paljon kelloja ja pillejä voidaan leikata käyttötapauksellesi. Renderöijä voi aiheuttaa tiettyjen toimintojen suorittamisen miljoonia kertoja näytönohjaimella. Jokainen tällaisesta menettelystä poistettu käsky tarkoittaa, että heikompi näytönohjain pystyy käsittelemään sisältöäsi ongelmitta.

Vaikka päätät käyttää korkean tason grafiikkakirjastoa, sinulla on perustiedot hupun alla olevista asioista, jotta voit käyttää sitä tehokkaammin. Kirjastoissa voi olla myös lisäominaisuuksia, kuten ShaderMaterial sisään three.js Graafisen renderöinnin periaatteiden tunteminen antaa sinun käyttää tällaisia ​​ominaisuuksia.



Kuva 3D ApeeScape -logosta WebGL-kankaalla

Tavoitteenamme on antaa lyhyt esittely kaikista 3D-grafiikan renderoinnin ja WebGL: n käyttämisen keskeisistä käsitteistä niiden toteuttamiseksi. Näet tavallisimman tekemisen, joka on 3D-objektien näyttäminen ja siirtäminen tyhjässä tilassa.

lopullinen koodi on käytettävissäsi haarautumaan ja leikkimään.

Edustaa 3D-malleja

Ensinnäkin sinun on ymmärrettävä, miten 3D-mallit esitetään. Malli on valmistettu kolmioverkosta. Kutakin kolmiota edustaa kolme kärkipistettä kullekin kolmion kulmalle. Pisteisiin on kiinnitetty kolme yleisintä ominaisuutta.

Huipun sijainti

Sijainti on kärjen intuitiivisin ominaisuus. Se on sijainti 3D-tilassa, jota edustaa 3D-koordinaattivektori. Jos tiedät kolmen avaruuspisteen tarkat koordinaatit, sinulla on kaikki tarvitsemasi tiedot piirtämään yksinkertainen kolmio niiden välille. Jotta mallit näyttävät todella hyviltä renderoiduna, renderöijälle on toimitettava vielä muutama asia.

Vertex Normal

Pallot, joissa on sama lankakehys, joissa on tasainen ja sileä varjostus

Harkitse kahta yllä olevaa mallia. Ne koostuvat samoista kärkipisteistä, mutta näyttävät kuitenkin täysin erilaisilta renderoinnissa. Kuinka se on mahdollista?

Sen lisäksi, että renderöijälle kerrotaan, missä haluamme kärkipisteen sijainnin, voimme myös antaa sille vihjeen siitä, kuinka pinta on vinossa tarkassa asennossa. Vihje on pinnan normaalin muoto mallin tietyssä kohdassa, jota edustaa 3D-vektori. Seuraava kuva antaa sinulle kuvaavamman kuvan siitä, miten sitä käsitellään.

Normaalien vertailu tasaiselle ja tasaiselle varjostukselle

Vasen ja oikea pinta vastaavat edellisen kuvan vasenta ja oikeaa palloa. Punaiset nuolet edustavat kärkipisteelle määritettyjä normaaaleja, kun taas siniset nuolet edustavat renderöijän laskelmia siitä, kuinka normaalin tulisi etsiä kaikkia pisteiden välisiä pisteitä. Kuvassa on esittely 2D-avaruudesta, mutta sama periaate pätee myös 3D: ssä.

Normaali on vihje siitä, kuinka valot valaisevat pintaa. Mitä lähempänä valonsäteen suunta on normaalia, sitä kirkkaampi kohta on. Asteittaiset muutokset normaalisuuntaan aiheuttavat valon kaltevuuksia, kun taas äkilliset muutokset ilman muutoksia välillä aiheuttavat pintoja, joissa on jatkuvasti valaistusta, ja äkillisiä muutoksia valaistuksessa niiden välillä.

Rakenteen koordinaatit

Viimeinen merkittävä ominaisuus ovat tekstuurikoordinaatit, joita kutsutaan yleisesti UV-kartoitukseksi. Sinulla on malli ja rakenne, jota haluat käyttää siihen. Rakenteessa on useita alueita, jotka edustavat kuvia, joita haluamme soveltaa mallin eri osiin. On oltava tapa merkitä, mikä kolmio tulisi edustaa tekstuurin minkä osan kanssa. Siellä tulee tekstuurin kartoitus.

Jokaiselle kärjelle merkitään kaksi koordinaattia, U ja V. Nämä koordinaatit edustavat sijaintia tekstuurissa, jossa U edustaa vaaka-akselia ja V pystysuoraa akselia. Arvot eivät ole pikseleinä, vaan prosentuaalinen sijainti kuvassa. Kuvan vasemmassa alakulmassa on kaksi nollaa, kun taas oikeassa yläkulmassa kaksi.

Kolmio maalataan juuri ottamalla kunkin kolmiopisteen UV-koordinaatit ja soveltamalla näiden koordinaattien väliin kaapattu kuva tekstuuriin.

Osoitus UV-kartoituksesta, kun yksi laastari on korostettu ja saumat näkyvät mallissa

Yllä olevassa kuvassa näkyy UV-kartoituksen esittely. Pallomalli otettiin ja leikattiin osiin, jotka ovat tarpeeksi pieniä litistää 2D-pinnalle. Saumat, joissa leikkaukset tehtiin, on merkitty paksummilla viivoilla. Yksi laastareista on korostettu, joten näet hienosti, kuinka asiat sopivat yhteen. Voit myös nähdä, kuinka hymyn keskellä oleva sauma sijoittaa suun osat kahteen eri laastariin.

Lankakehykset eivät ole osa tekstuuria, vaan vain päällekkäin kuvan päällä, jotta näet kuinka asiat kartoittavat toisiaan.

OBJ-mallin lataaminen

Uskokaa tai älkää, tämä on kaikki mitä sinun tarvitsee tietää oman yksinkertaisen kuormaajamallin luomiseksi. OBJ-tiedostomuoto on tarpeeksi yksinkertainen jäsentimen toteuttamiseksi muutamassa rivissä koodia.

Tiedosto listaa pisteiden sijainnit v muodossa, valinnaisella neljännellä kellukkeella, jonka jätämme huomiotta, jotta asiat ovat yksinkertaisia. Vertex-normaali on edustettu samalla tavalla kuin vn Lopuksi tekstuurikoordinaatit esitetään vt : lla, valinnaisella kolmannella kellukkeella, jonka jätämme huomiotta. Kaikissa kolmessa tapauksessa kellukkeet edustavat vastaavia koordinaatteja. Nämä kolme ominaisuutta kerääntyvät kolmeen ryhmään.

Kasvot esitetään pisteiden ryhmillä. Kutakin kärkeä edustetaan kunkin ominaisuuden indeksillä, jolloin indeksit alkavat arvosta 1. Tätä voidaan esittää eri tavoin, mutta pidämme kiinni f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 muoto, joka vaatii kaikkien kolmen ominaisuuden antamisen ja rajoittamalla kasvoja kohti olevien pisteiden määrän kolmeksi. Kaikki nämä rajoitukset tehdään pitämään kuormaaja mahdollisimman yksinkertaisena, koska kaikki muut vaihtoehdot edellyttävät ylimääräistä triviaalia käsittelyä, ennen kuin ne ovat WebGL: n tykkäämässä muodossa.

Olemme asettaneet paljon vaatimuksia tiedostojen lataajalle. Tämä saattaa kuulostaa rajoittavalta, mutta 3D-mallinnussovellukset antavat sinulle mahdollisuuden asettaa nämä rajoitukset viedessäsi mallia OBJ-tiedostona.

Seuraava koodi jäsentää OBJ-tiedostoa edustavan merkkijonon ja luo mallin kasvojen joukoksi.

function Geometry (faces) [] // Parses an OBJ file, passed as a string Geometry.parseOBJ = function (src) { var POSITION = /^vs+([d.+-eE]+)s+([d.+-eE]+)s+([d.+-eE]+)/ var NORMAL = /^vns+([d.+-eE]+)s+([d.+-eE]+)s+([d.+-eE]+)/ var UV = /^vts+([d.+-eE]+)s+([d.+-eE]+)/ var FACE = /^fs+(-?d+)/(-?d+)/(-?d+)s+(-?d+)/(-?d+)/(-?d+)s+(-?d+)/(-?d+)/(-?d+)(?:s+(-?d+)/(-?d+)/(-?d+))?/ lines = src.split(' ') var positions = [] var uvs = [] var normals = [] var faces = [] lines.forEach(function (line) { // Match each line of the file against various RegEx-es var result if ((result = POSITION.exec(line)) != null) { // Add new vertex position positions.push(new Vector3(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3]))) } else if ((result = NORMAL.exec(line)) != null) { // Add new vertex normal normals.push(new Vector3(parseFloat(result[1]), parseFloat(result[2]), parseFloat(result[3]))) } else if ((result = UV.exec(line)) != null) { // Add new texture mapping point uvs.push(new Vector2(parseFloat(result[1]), 1 - parseFloat(result[2]))) } else if ((result = FACE.exec(line)) != null) { // Add new face var vertices = [] // Create three vertices from the passed one-indexed indices for (var i = 1; i <10; i += 3) { var part = result.slice(i, i + 3) var position = positions[parseInt(part[0]) - 1] var uv = uvs[parseInt(part[1]) - 1] var normal = normals[parseInt(part[2]) - 1] vertices.push(new Vertex(position, normal, uv)) } faces.push(new Face(vertices)) } }) return new Geometry(faces) } // Loads an OBJ file from the given URL, and returns it as a promise Geometry.loadOBJ = function (url) { return new Promise(function (resolve) { var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState == XMLHttpRequest.DONE) { resolve(Geometry.parseOBJ(xhr.responseText)) } } xhr.open('GET', url, true) xhr.send(null) }) } function Face (vertices) this.vertices = vertices function Vertex (position, normal, uv) new Vector3() this.normal = normal function Vector3 (x, y, z) function Vector2 (x, y) 0 this.y = Number(y)

Geometry rakenne sisältää tarkat tiedot, joita tarvitaan mallin lähettämiseen näytönohjaimelle käsiteltäväksi. Ennen kuin teet niin, haluat todennäköisesti pystyä siirtämään mallia näytöllä.

Suoritetaan avaruusmuunnoksia

Kaikki ladatun mallin pisteet ovat suhteessa sen koordinaattijärjestelmään. Jos haluamme kääntää, kääntää ja skaalata mallia, meidän on vain suoritettava kyseinen operaatio sen koordinaattijärjestelmässä. Koordinaattijärjestelmä A, suhteessa koordinaatistoon B, määritetään sen keskipisteen sijaintina vektorina p_ab ja vektorina kullekin sen akselille x_ab, y_ab ja z_ab, joka edustaa kyseisen akselin suuntaa. Joten jos piste liikkuu 10: llä x: lla koordinaattijärjestelmän A akseli, sitten - koordinaatistossa B - se liikkuu suuntaan x_ab kerrottuna 10: llä.

Kaikki nämä tiedot tallennetaan seuraavaan matriisimuotoon:

x_ab.x y_ab.x z_ab.x p_ab.x x_ab.y y_ab.y z_ab.y p_ab.y x_ab.z y_ab.z z_ab.z p_ab.z 0 0 0 1

Jos haluamme muuntaa 3D-vektorin q, meidän on vain kerrottava muunnosmatriisi vektorilla:

q.x q.y q.z 1

Tämä saa pisteen siirtymään q.x pitkin uutta x akselilla q.y pitkin uutta y akselilla ja q.z pitkin uutta z akseli. Lopuksi se saa pisteen liikkumaan lisäksi p vektori, mikä on syy siihen, miksi käytämme yhtä kertomisen viimeisenä elementtinä.

Näiden matriisien käytön suuri etu on se, että jos meillä on useita muunnoksia suoritettavaksi kärjessä, voimme yhdistää ne yhdeksi muunnokseksi kertomalla niiden matriisit, ennen kuin itse kärki muunnetaan.

On olemassa useita muunnoksia, jotka voidaan suorittaa, ja katsomme tärkeimpiä.

Ei muutosta

Jos muunnoksia ei tapahdu, p vektori on nollavektori, x vektori on [1, 0, 0], y on [0, 1, 0] ja z on [0, 0, 1]. Tästä eteenpäin viittaamme näihin arvoihin näiden vektorien oletusarvoiksi. Näiden arvojen soveltaminen antaa meille identiteettimatriisin:

1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1

Tämä on hyvä lähtökohta muunnosten ketjutukseen.

Käännös

Kehyksen muunnos käännettäväksi

Kun suoritamme käännöksen, kaikki vektorit paitsi p vektorilla on oletusarvot. Tuloksena on seuraava matriisi:

1 0 0 p.x 0 1 0 p.y 0 0 1 p.z 0 0 0 1

Skaalaus

Kehyksen muunnos skaalausta varten

Mallin skaalaus tarkoittaa sitä, että pienennetään määrää, jonka kukin koordinaatti vaikuttaa pisteen sijaintiin. Skaalauksen aiheuttama tasainen siirtymä ei ole, joten p vektori säilyttää oletusarvonsa. Oletusakselivektorit tulisi kertoa niiden vastaavilla skaalauskertoimilla, mikä johtaa seuraavaan matriisiin:

s_x 0 0 0 0 s_y 0 0 0 0 s_z 0 0 0 0 1

Täällä s_x, s_y ja s_z edustavat kullekin akselille sovellettua skaalausta.

Kierto

Kehyksen muunnos kiertämistä varten Z-akselin ympäri

Yllä olevassa kuvassa näkyy, mitä tapahtuu, kun kiertämme koordinaattikehystä Z-akselin ympäri.

Kierto ei aiheuta tasaista siirtymää, joten p vektori säilyttää oletusarvonsa. Nyt asiat muuttuvat hieman hankalammiksi. Kierrot saavat liikkeen tietyllä akselilla alkuperäisessä koordinaatistossa liikkumaan eri suuntaan. Joten jos kierrämme koordinaatistoa 45 astetta Z-akselin ympäri, liikkumalla x alkuperäisen koordinaattijärjestelmän akseli aiheuttaa liikkeen diagonaalisuunnassa x ja y akseli uudessa koordinaattijärjestelmässä.

Jotta asiat pysyisivät yksinkertaisina, näytämme vain, kuinka muunnosmatriisit etsivät pyörimistä pääakselien ympäri.

Around X: 1 0 0 0 0 cos(phi) sin(phi) 0 0 -sin(phi) cos(phi) 0 0 0 0 1 Around Y: cos(phi) 0 sin(phi) 0 0 1 0 0 -sin(phi) 0 cos(phi) 0 0 0 0 1 Around Z: cos(phi) -sin(phi) 0 0 sin(phi) cos(phi) 0 0 0 0 1 0 0 0 0 1

Toteutus

Kaikki tämä voidaan toteuttaa luokkana, joka tallentaa 16 numeroa ja tallentaa matriisit a: han sarake-pääjärjestys .

function Transformation () { // Create an identity transformation this.fields = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } // Multiply matrices, to chain transformations Transformation.prototype.mult = function (t) { var output = new Transformation() for (var row = 0; row <4; ++row) { for (var col = 0; col < 4; ++col) { var sum = 0 for (var k = 0; k < 4; ++k) { sum += this.fields[k * 4 + row] * t.fields[col * 4 + k] } output.fields[col * 4 + row] = sum } } return output } // Multiply by translation matrix Transformation.prototype.translate = function (x, y, z) // Multiply by scaling matrix Transformation.prototype.scale = function (x, y, z) // Multiply by rotation matrix around X axis Transformation.prototype.rotateX = function (angle) // Multiply by rotation matrix around Y axis Transformation.prototype.rotateY = function (angle) // Multiply by rotation matrix around Z axis Transformation.prototype.rotateZ = function (angle)

Kameran läpi katsominen

Tässä on avainosa esineiden esittämisestä näytöllä: kamera. Kamerassa on kaksi avainkomponenttia; nimittäin sen sijainti ja miten se heijastaa havaittuja esineitä näytölle.

Kameran sijainti hoidetaan yhdellä yksinkertaisella temppulla. Kameran siirtämisellä metriä eteenpäin ja koko maailman metrillä taaksepäin ei ole visuaalista eroa. Joten luonnollisesti teemme jälkimmäisen soveltamalla matriisin käänteistä muunnokseksi.

Toinen keskeinen komponentti on tapa, jolla havaitut kohteet heijastetaan linssiin. WebGL: ssä kaikki ruudulla näkyvät paikat ovat laatikossa. Laatikko on välillä -1 ja 1 kullakin akselilla. Kaikki näkyvä on siinä laatikossa. Voimme käyttää samaa muunnosmatriisien lähestymistapaa projektiomatriisin luomiseen.

Ortografinen projektio

Suorakulmainen tila muuttuu oikeaksi kehyspuskurin mitoiksi käyttämällä ortografista projektiota

Yksinkertaisin projektio on ortografinen projektio . Otat avaruuteen ruudun, joka merkitsee leveyttä, korkeutta ja syvyyttä olettaen, että sen keskusta on nolla-asennossa. Sitten projektio muuttaa kotelon kokoa sopivaksi aiemmin kuvattuun ruutuun, jossa WebGL havaitsee objekteja. Koska haluamme muuttaa kunkin ulottuvuuden kokoa kahdeksi, skaalataan jokainen akseli 2/size: lla, jolloin size on vastaavan akselin mitta. Pieni huomautus on se, että kerrotaan Z-akseli negatiivisella. Tämä tehdään, koska haluamme kääntää tuon ulottuvuuden suunnan. Lopullisella matriisilla on seuraava muoto:

2/width 0 0 0 0 2/height 0 0 0 0 -2/depth 0 0 0 0 1

Perspektiivinen projektio

Frustum muuttuu oikeiksi kehyspuskurin mitoiksi perspektiiviprojektion avulla

Emme käy läpi yksityiskohtia siitä, miten tämä projektio suunnitellaan, vaan käytämme vain lopullinen kaava , joka on nyt melkein vakio. Voimme yksinkertaistaa sitä sijoittamalla projektio nolla-asemaan x- ja y-akselille, jolloin oikea / vasen ja ylä / alaraja ovat yhtä suuret kuin width/2 ja height/2 vastaavasti. Parametrit n ja f edustavat near ja far leikkaustasot, jotka ovat pienin ja suurin etäisyys pisteestä, voidaan kaapata kameralla. Niitä edustavat frustum yllä olevassa kuvassa.

Perspektiivinen projektio esitetään yleensä a: lla näkökenttä (käytämme pystysuoraa), kuvasuhde sekä lähi- ja kaukotasotasot. Näitä tietoja voidaan käyttää laskemaan width ja height, ja sitten matriisi voidaan luoda seuraavasta mallista:

2*n/width 0 0 0 0 2*n/height 0 0 0 0 (f+n)/(n-f) 2*f*n/(n-f) 0 0 -1 0

Leveyden ja korkeuden laskemiseksi voidaan käyttää seuraavia kaavoja:

height = 2 * near * Math.tan(fov * Math.PI / 360) width = aspectRatio * height

FOV (näkökenttä) edustaa pystykulmaa, jonka kamera ottaa objektiivillaan. Kuvasuhde edustaa kuvan leveyden ja korkeuden välistä suhdetta, ja se perustuu renderöidyn näytön mittoihin.

Toteutus

Nyt voimme edustaa kameraa luokana, joka tallentaa kameran sijainnin ja projektiomatriisin. Meidän on myös osattava laskea käänteismuunnokset. Yleisten matriisiversioiden ratkaiseminen voi olla ongelmallista, mutta on olemassa yksinkertaistettu lähestymistapa erityistapauksemme.

function Camera () { this.position = new Transformation() this.projection = new Transformation() } Camera.prototype.setOrthographic = function (width, height, depth) { this.projection = new Transformation() this.projection.fields[0] = 2 / width this.projection.fields[5] = 2 / height this.projection.fields[10] = -2 / depth } Camera.prototype.setPerspective = function (verticalFov, aspectRatio, near, far) { var height_div_2n = Math.tan(verticalFov * Math.PI / 360) var width_div_2n = aspectRatio * height_div_2n this.projection = new Transformation() this.projection.fields[0] = 1 / height_div_2n this.projection.fields[5] = 1 / width_div_2n this.projection.fields[10] = (far + near) / (near - far) this.projection.fields[10] = -1 this.projection.fields[14] = 2 * far * near / (near - far) this.projection.fields[15] = 0 } Camera.prototype.getInversePosition = function () { var orig = this.position.fields var dest = new Transformation() var x = orig[12] var y = orig[13] var z = orig[14] // Transpose the rotation matrix for (var i = 0; i <3; ++i) { for (var j = 0; j < 3; ++j) { dest.fields[i * 4 + j] = orig[i + j * 4] } } // Translation by -p will apply R^T, which is equal to R^-1 return dest.translate(-x, -y, -z) }

Tämä on viimeinen tarvittava kappale, ennen kuin voimme aloittaa piirtämisen asioita ruudulle.

Objektin piirtäminen WebGL-grafiikan putkilinjalla

Yksinkertaisin pinta, jonka voit piirtää, on kolmio. Itse asiassa suurin osa 3D-avaruudessa piirtämistäsi asioista koostuu suuresta määrästä kolmioita.

Perustiedot siitä, mitä grafiikkaputken vaiheita tehdään

Ensimmäinen asia, joka sinun on ymmärrettävä, on se, miten näyttö on esitetty WebGL: ssä. Se on 3D-tila, joka ulottuu välillä -1 ja 1 x , Y ja kanssa akseli. Oletuksena tämä kanssa akselia ei käytetä, mutta olet kiinnostunut 3D-grafiikasta, joten sinun kannattaa ottaa se käyttöön heti.

Ottaen huomioon tämän seuraavat kolme vaihetta vaaditaan kolmion piirtämiseksi tälle pinnalle.

Voit määrittää kolme kärkeä, jotka edustavat piirrettävää kolmiota. Sarjaat nämä tiedot ja lähetät ne GPU: lle (grafiikkaprosessoriyksikölle). Kun käytettävissä on koko malli, voit tehdä sen kaikille mallin kolmioille. Antamasi kärjen sijainnit ovat lataamasi mallin paikallisessa koordinaatistossa. Yksinkertaisesti sanottuna antamasi sijainnit ovat tarkkoja tiedostosta eivätkä ne, jotka saat matriisimuunnosten suorittamisen jälkeen.

Nyt kun olet antanut pisteet GPU: lle, kerrot GPU: lle, mitä logiikkaa tulee käyttää, kun laitat pisteet näytölle. Tätä vaihetta käytetään matriisimuunnosten soveltamiseen. GPU on erittäin hyvä kertomaan paljon 4x4-matriiseja, joten käytämme tätä kykyä hyvään käyttöön.

Viimeisessä vaiheessa GPU rasteroi kyseisen kolmion. Rasterointi on prosessi, jossa otetaan vektorigrafiikka ja määritetään mitkä näytön pikselit on maalattava, jotta kyseinen vektorigrafiikkaobjekti voidaan näyttää. Meidän tapauksessamme GPU yrittää selvittää, mitkä pikselit sijaitsevat kussakin kolmiossa. Jokaisen pikselin kohdalla grafiikkasuoritin kysyy, minkä värin haluat sen maalattavan.

Nämä ovat neljä elementtiä, joita tarvitaan piirtämään haluamasi, ja ne ovat yksinkertaisin esimerkki a: sta grafiikkaputki . Seuraavassa on tarkastelu jokaiseen niistä ja yksinkertainen toteutus.

Oletuskehyspuskuri

Tärkein osa WebGL-sovellusta on WebGL-konteksti. Voit käyttää sitä painamalla gl = canvas.getContext('webgl') tai käyttämällä 'experimental-webgl' varalla, jos tällä hetkellä käytetty selain ei vielä tue kaikkia WebGL-ominaisuuksia. canvas viittasi on DOM-elementti kankaasta, johon haluamme piirtää. Konteksti sisältää monia asioita, joiden joukossa on oletuskehyspuskuri.

Voit kuvata kehystyspuskuria löyhästi mistä tahansa puskurista (objektista), johon voit piirtää. Oletusarvoisesti kehyspuskuri tallentaa värin jokaiselle kankaan pikselille, johon WebGL-konteksti on sidottu. Kuten edellisessä osassa on kuvattu, kun piirrämme kehyspuskuriin, kukin pikseli sijaitsee -1 ja 1 välillä x ja Y akseli. Mainitsimme myös sen, että WebGL ei oletusarvoisesti käytä kanssa akseli. Tämä toiminto voidaan ottaa käyttöön suorittamalla gl.enable(gl.DEPTH_TEST). Hienoa, mutta mikä on syvyystesti?

Syvyystestin käyttöönotto sallii pikselin tallentaa sekä värit että syvyyden. Syvyys on kanssa kyseisen pikselin koordinaatti. Kun olet piirtänyt pikseliin tietyllä syvyydellä kanssa , päivittääksesi kyseisen pikselin värin, sinun täytyy piirtää kohtaan a kanssa asentoa, joka on lähempänä kameraa. Muussa tapauksessa arvontayritys jätetään huomioimatta. Tämä sallii 3D-illuusion, koska muiden objektien takana olevien esineiden piirtäminen aiheuttaa niiden edessä olevien esineiden tukkeutumisen.

Kaikki tekemäsi piirrokset pysyvät näytöllä, kunnes käsket heitä puhdistumaan. Voit tehdä tämän soittamalla gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT). Tämä tyhjentää sekä väri- että syvyyspuskurin. Valitse väri, jolle tyhjennetyt pikselit on asetettu, käyttämällä gl.clearColor(red, green, blue, alpha).

Luodaan renderöijä, joka käyttää kangasta ja tyhjentää sen pyynnöstä:

function Renderer (canvas) Renderer.prototype.setClearColor = function (red, green, blue) { gl.clearColor(red / 255, green / 255, blue / 255, 1) } Renderer.prototype.getContext = function () { return this.gl } Renderer.prototype.render = function () gl.DEPTH_BUFFER_BIT) var renderer = new Renderer(document.getElementById('webgl-canvas')) renderer.setClearColor(100, 149, 237) loop() function loop () { renderer.render() requestAnimationFrame(loop) }

Tämän komentosarjan liittäminen seuraavaan HTML-koodiin antaa sinulle kirkkaan sinisen suorakulmion näytöllä

requestAnimationFrame

N kutsu aiheuttaa silmukan kutsumisen uudelleen heti, kun edellinen kehys on tehty ja kaikki tapahtumien käsittely on valmis.

Vertex-puskurin objektit

Ensimmäinen asia, joka sinun on tehtävä, on määritellä piikit, jotka haluat piirtää. Voit tehdä sen kuvaamalla ne 3D-avaruudessa olevien vektorien kautta. Sen jälkeen haluat siirtää nämä tiedot GPU: n RAM-muistiin luomalla uuden Vertex -puskurin objekti (Helmikuu).

TO Puskurikohde yleensä on objekti, joka tallentaa joukon muistinpaloja GPU: lle. Se on VBO tarkoittaa vain sitä, mihin GPU voi käyttää muistia. Suurimman osan ajasta luomasi puskurikohteet ovat VBO: ita.

Voit täyttää VBO: n ottamalla kaikki 3N pisteet, jotka meillä on, ja luodaan joukko kellukkeita 2N: lla elementit kärjen sijainnille ja kärjen normaalille VBO: lle ja Geometry tekstuurikoordinaateille VBO. Kukin kolmen uimurin tai kahden uimurin UV-koordinaattien ryhmä edustaa kärkipisteen yksittäisiä koordinaatteja. Sitten välitämme nämä taulukot GPU: lle, ja pisteemme ovat valmiita loppuputkelle.

Koska tiedot ovat nyt GPU: n RAM-muistissa, voit poistaa ne yleiskäyttöisestä RAM-muistista. Eli, ellet halua myöhemmin muokata sitä ja ladata sen uudelleen. Jokaista muutosta on seurattava lataus, koska JS-ryhmien muutokset eivät koske todellisen GPU-muistin VBO-levyjä.

Alla on koodiesimerkki, joka tarjoaa kaikki kuvatut toiminnot. Tärkeä huomautus on se, että GPU: lle tallennetut muuttujat eivät ole roskia. Tämä tarkoittaa, että meidän on poistettava ne manuaalisesti, kun emme halua enää käyttää niitä. Annamme sinulle vain esimerkin siitä, miten se tehdään täällä, emmekä keskity tähän käsitteeseen. Muuttujien poistaminen GPU: sta on tarpeen vain, jos aiot lopettaa tietyn geometrian käytön koko ohjelmassa.

Lisäsimme myös sarjallisuuden Geometry.prototype.vertexCount = function () { return this.faces.length * 3 } Geometry.prototype.positions = function () { var answer = [] this.faces.forEach(function (face) { face.vertices.forEach(function (vertex) { var v = vertex.position answer.push(v.x, v.y, v.z) }) }) return answer } Geometry.prototype.normals = function () { var answer = [] this.faces.forEach(function (face) { face.vertices.forEach(function (vertex) { var v = vertex.normal answer.push(v.x, v.y, v.z) }) }) return answer } Geometry.prototype.uvs = function () { var answer = [] this.faces.forEach(function (face) { face.vertices.forEach(function (vertex) { var v = vertex.uv answer.push(v.x, v.y) }) }) return answer } //////////////////////////////// function VBO (gl, data, count) { // Creates buffer object in GPU RAM where we can store anything var bufferObject = gl.createBuffer() // Tell which buffer object we want to operate on as a VBO gl.bindBuffer(gl.ARRAY_BUFFER, bufferObject) // Write the data, and set the flag to optimize // for rare changes to the data we're writing gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW) this.gl = gl this.size = data.length / count this.count = count this.data = bufferObject } VBO.prototype.destroy = function () { // Free memory that is occupied by our buffer object this.gl.deleteBuffer(this.data) } luokka ja sen elementit.

VBO

gl tietotyyppi tuottaa VBO: n välitetyssä WebGL-kontekstissa toisen parametrina välitetyn taulukon perusteella.

Näet kolme puhelua createBuffer() yhteydessä. bindBuffer() puhelu luo puskurin. ARRAY_BUFFER call käskee WebGL-tilakonetta käyttämään tätä erityistä muistia nykyisenä VBO: na (bufferData()) kaikissa tulevissa toiminnoissa, kunnes toisin sanotaan. Sen jälkeen asetamme nykyisen VBO: n arvon toimitettuihin tietoihin deleteBuffer() .

Tarjoamme myös tuhoamismenetelmän, joka poistaa puskurikohteemme GPU: n RAM-muistista käyttämällä function Mesh (gl, geometry) { var vertexCount = geometry.vertexCount() this.positions = new VBO(gl, geometry.positions(), vertexCount) this.normals = new VBO(gl, geometry.normals(), vertexCount) this.uvs = new VBO(gl, geometry.uvs(), vertexCount) this.vertexCount = vertexCount this.position = new Transformation() this.gl = gl } Mesh.prototype.destroy = function () { this.positions.destroy() this.normals.destroy() this.uvs.destroy() } .

Voit käyttää kolmea VBO: ta ja muunnosta kuvaamaan verkon kaikkia ominaisuuksia ja sen sijaintia.

Geometry.loadOBJ('/assets/model.obj').then(function (geometry) { var mesh = new Mesh(gl, geometry) console.log(mesh) mesh.destroy() })

Esimerkiksi, miten voimme ladata mallin, tallentaa sen ominaisuudet verkkoon ja sitten tuhota sen:

attribute

Varjostimet

Seuraavassa on aiemmin kuvattu kaksivaiheinen prosessi, jolla pisteet siirretään haluttuihin paikkoihin ja kaikki yksittäiset pikselit maalataan. Tätä varten kirjoitamme ohjelman, joka suoritetaan näytönohjaimella useita kertoja. Tämä ohjelma koostuu tyypillisesti vähintään kahdesta osasta. Ensimmäinen osa on a Vertex Shader , joka suoritetaan kullekin kärjelle, ja antaa tulosteet, joihin meidän tulisi sijoittaa kärki näytölle, muun muassa. Toinen osa on Fragment Shader , joka suoritetaan jokaiselle pikselille, jonka kolmio peittää näytöllä, ja tuottaa värin, johon pikseli tulisi maalata.

Vertex Shaders

Oletetaan, että haluat mallin, joka liikkuu vasemmalla ja oikealla ruudulla. Naiivisessa lähestymistavassa voit päivittää jokaisen kärjen sijainnin ja lähettää sen uudelleen GPU: lle. Tämä prosessi on kallista ja hidasta. Vaihtoehtoisesti annat ohjelman, jonka GPU suorittaa jokaiselle kärjelle, ja teet kaikki nämä toiminnot rinnakkain prosessorin kanssa, joka on rakennettu juuri tämän työn suorittamiseen. Se on a vertex shader .

Kärkipistevarjostin on renderöintiputken osa, joka käsittelee yksittäisiä pisteitä. Kutsu kärkipaikan varjostimeen vastaanottaa yhden kärkipisteen ja antaa yhden kärkipisteen, kun kaikki mahdolliset pisteeseen tehdyt muunnokset on toteutettu.

Varjostimet on kirjoitettu GLSL: llä. Tällä kielellä on paljon ainutlaatuisia elementtejä, mutta suurin osa syntaksista on hyvin C-tyyppinen, joten sen pitäisi olla ymmärrettävää useimmille ihmisille.

Pisteiden varjostimesta tulee sisään ja ulos kolmea muuttujatyyppiä, ja ne kaikki palvelevat tiettyä käyttöä:

  • uniform - Nämä ovat syötteitä, joilla on kärkipisteen tietyt ominaisuudet. Aikaisemmin kuvailimme kärjen sijaintia attribuuttina kolmielementtisen vektorin muodossa. Voit tarkastella attribuutteja arvoina, jotka kuvaavat yhtä kärkeä.
  • uniform - Nämä ovat tuloja, jotka ovat samat kaikille saman renderointikutsun kullekin kärjelle. Oletetaan, että haluamme pystyä siirtämään malliamme määrittelemällä muunnosmatriisin. Voit käyttää varying muuttuja sen kuvaamiseksi. Voit osoittaa myös GPU: n resursseja, kuten tekstuureja. Voit tarkastella univormuja arvona, joka kuvaa mallia tai mallin osaa.
  • attribute vec3 position; attribute vec3 normal; attribute vec2 uv; uniform mat4 model; uniform mat4 view; uniform mat4 projection; varying vec3 vNormal; varying vec2 vUv; void main() { vUv = uv; vNormal = (model * vec4(normal, 0.)).xyz; gl_Position = projection * view * model * vec4(position, 1.); } - Nämä ovat ulostuloja, jotka välitämme fragmentin varjostimelle. Koska pisteiden kolmion kohdalla on mahdollisesti tuhansia pikseleitä, kukin pikseli saa tälle muuttujalle interpoloidun arvon sijainnista riippuen. Joten jos yksi kärki lähettää 500 ulostulona ja toinen 100, niiden keskellä oleva pikseli saa 300 muuttujan tulona. Voit tarkastella vaihteluja arvoina, jotka kuvaavat pisteiden välisiä pintoja.

Oletetaan, että haluat luoda kärkipisteiden varjostimen, joka vastaanottaa kullekin kärjelle sijainnin, normaalin ja uv-koordinaatin sekä kullekin renderoidulle objektille sijainnin, näkymän (kameran käänteisen sijainnin) ja projektiomatriisin. Oletetaan, että haluat myös maalata yksittäisiä pikseleitä niiden uv-koordinaattien ja normaalien perusteella. 'Kuinka tuo koodi näyttäisi?' saatat kysyä.

main

Suurimman osan elementeistä tässä pitäisi olla itsestään selviä. Tärkeintä on huomata, että varying: ssa ei ole palautusarvoja toiminto. Kaikki arvot, jotka haluaisimme palauttaa, määritetään joko ryhmille gl_Position muuttujiin tai erityisiin muuttujiin. Tässä osoitetaan vec4, joka on nelidimensionaalinen vektori, jolloin viimeinen ulottuvuus tulisi aina asettaa yhdeksi. Toinen outo asia, jonka saatat huomata, on tapa, jolla rakennamme vec4 pois asemavektorista. Voit luoda float käyttämällä neljää vec2 s, kahta varying s tai mitä tahansa muuta yhdistelmää, josta saadaan neljä elementtiä. On paljon näennäisesti oudon tyyppisiä valukappaleita, jotka ovat täysin järkeviä, kun olet tutustunut muunnosmatriiseihin.

Voit myös nähdä, että täällä voimme suorittaa matriisimuunnoksia erittäin helposti. GLSL on erityisesti suunniteltu tällaista työtä varten. Lähtöasento lasketaan kertomalla projektio, näkymä ja mallimatriisi ja soveltamalla se sijaintiin. Normaali lähtö muunnetaan juuri maailmatilaan. Selitämme myöhemmin, miksi olemme pysähtyneet normaalien muutosten kanssa.

Toistaiseksi pidämme sen yksinkertaisena ja siirrymme yksittäisten pikselien maalaamiseen.

Fragment Shaders

TO fragmentti varjostin on askel rasteroinnin jälkeen grafiikkaputkessa. Se tuottaa väriä, syvyyttä ja muuta tietoa jokaiselle maalattavan kohteen pikselille.

Fragmenttivarjostimien toteuttamisen periaatteet ovat hyvin samankaltaisia ​​kuin kärkipisteet. On kuitenkin kolme suurta eroa:

  • Ei enää ole attribute lähdöt ja varying tulot on korvattu gl_FragColor tulot. Olemme juuri siirtyneet eteenpäin putkistossamme, ja asiat, jotka ovat kärkipisteiden varjostimen ulostuloja, ovat nyt panoksia fragmentti-varjostimessa.
  • Ainoa tuotoksemme on nyt vec4, joka on #ifdef GL_ES precision highp float; #endif varying vec3 vNormal; varying vec2 vUv; void main() { vec2 clampedUv = clamp(vUv, 0., 1.); gl_FragColor = vec4(clampedUv, 1., 1.); } . Elementit edustavat punaista, vihreää, sinistä ja alfaa (RGBA) vastaavasti muuttujien ollessa alueella 0-1. Sinun tulisi pitää alfa arvossa 1, ellet tee läpinäkyvyyttä. Läpinäkyvyys on kuitenkin melko edistynyt käsite, joten pidämme kiinni läpinäkymättömistä esineistä.
  • Fragmenttivarjostimen alussa sinun on asetettava kellutarkkuus, mikä on tärkeää interpoloinnille. Pidä melkein kaikissa tapauksissa vain seuraavan varjostimen viivoja.

Tässä mielessä voit helposti kirjoittaa varjostimen, joka maalaa punaisen kanavan U-sijainnin perusteella, vihreä kanavan V-sijainnin perusteella ja asettaa sinisen kanavan maksimiin.

clamp

Toiminto function ShaderProgram (gl, vertSrc, fragSrc) { var vert = gl.createShader(gl.VERTEX_SHADER) gl.shaderSource(vert, vertSrc) gl.compileShader(vert) if (!gl.getShaderParameter(vert, gl.COMPILE_STATUS)) { console.error(gl.getShaderInfoLog(vert)) throw new Error('Failed to compile shader') } var frag = gl.createShader(gl.FRAGMENT_SHADER) gl.shaderSource(frag, fragSrc) gl.compileShader(frag) if (!gl.getShaderParameter(frag, gl.COMPILE_STATUS)) { console.error(gl.getShaderInfoLog(frag)) throw new Error('Failed to compile shader') } var program = gl.createProgram() gl.attachShader(program, vert) gl.attachShader(program, frag) gl.linkProgram(program) if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error(gl.getProgramInfoLog(program)) throw new Error('Failed to link program') } this.gl = gl this.position = gl.getAttribLocation(program, 'position') this.normal = gl.getAttribLocation(program, 'normal') this.uv = gl.getAttribLocation(program, 'uv') this.model = gl.getUniformLocation(program, 'model') this.view = gl.getUniformLocation(program, 'view') this.projection = gl.getUniformLocation(program, 'projection') this.vert = vert this.frag = frag this.program = program } // Loads shader files from the given URLs, and returns a program as a promise ShaderProgram.load = function (gl, vertUrl, fragUrl) { return Promise.all([loadFile(vertUrl), loadFile(fragUrl)]).then(function (files) { return new ShaderProgram(gl, files[0], files[1]) }) function loadFile (url) { return new Promise(function (resolve) { var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { if (xhr.readyState == XMLHttpRequest.DONE) { resolve(xhr.responseText) } } xhr.open('GET', url, true) xhr.send(null) }) } } vain rajoittaa kohteen kaikki kelluvat olevan annettujen rajojen sisällä. Lopun koodin tulisi olla melko suoraviivaista.

Tämän kaiken mielessä jäljellä on vain toteuttaa tämä WebGL: ssä.

Shadersin yhdistäminen ohjelmaksi

Seuraava vaihe on yhdistää varjostimet ohjelmaksi:

ShaderProgram.prototype.use = function () { this.gl.useProgram(this.program) }

Ei ole paljon sanottavaa siitä, mitä täällä tapahtuu. Jokaiselle varjostimelle määritetään merkkijono lähdekoodiksi ja se käännetään, minkä jälkeen tarkistamme onko koontivirheitä. Sitten luomme ohjelman linkittämällä nämä kaksi varjostinta. Lopuksi säilytämme viitteitä kaikkiin asiaankuuluviin ominaisuuksiin ja univormuihin jälkipolville.

Itse mallin piirtäminen

Viimeisenä, mutta ei vähäisimpänä, piirrät mallin.

Ensin valitset varjostimen, jota haluat käyttää.

Transformation.prototype.sendToGpu = function (gl, uniform, transpose) gl.uniformMatrix4fv(uniform, transpose Camera.prototype.use = function (shaderProgram) { this.projection.sendToGpu(shaderProgram.gl, shaderProgram.projection) this.getInversePosition().sendToGpu(shaderProgram.gl, shaderProgram.view) }

Sitten lähetät kaikki kameraan liittyvät univormut GPU: lle. Nämä univormut vaihtuvat vain kerran kameran vaihdon tai liikkeen aikana.

VBO.prototype.bindToAttribute = function (attribute) { var gl = this.gl // Tell which buffer object we want to operate on as a VBO gl.bindBuffer(gl.ARRAY_BUFFER, this.data) // Enable this attribute in the shader gl.enableVertexAttribArray(attribute) // Define format of the attribute array. Must match parameters in shader gl.vertexAttribPointer(attribute, this.size, gl.FLOAT, false, 0, 0) }

Lopuksi otat muunnokset ja VBO: t ja määrität ne vastaavasti univormuihin ja määritteisiin. Koska tämä on tehtävä jokaiselle VBO: lle, voit luoda sen datan sidonnan menetelmänä.

drawArrays()

Sitten määrität pukuun kolme kelluketta. Jokaisella yhtenäisellä tyypillä on erilainen allekirjoitus, joten dokumentointi ja enemmän dokumentointi ovat ystäväsi täällä. Lopuksi piirrät kolmion taulukon näytölle. Kerrot piirtopuhelun TRIANGLES mistä kärjestä aloittaa ja kuinka monta kärkeä piirtää. Ensimmäinen annettu parametri kertoo WebGL: lle, kuinka sen tulee tulkita pisteiden taulukko. Käyttämällä POINTS ottaa kolme kolmesta kärjestä ja piirtää kolmion jokaiselle tripletille. Käyttämällä Mesh.prototype.draw = function (shaderProgram) { this.positions.bindToAttribute(shaderProgram.position) this.normals.bindToAttribute(shaderProgram.normal) this.uvs.bindToAttribute(shaderProgram.uv) this.position.sendToGpu(this.gl, shaderProgram.model) this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertexCount) } piirtäisi vain pisteen jokaiselle ohitetulle kärjelle. Vaihtoehtoja on paljon enemmän, mutta ei tarvitse löytää kaikkea kerralla. Alla on koodi objektin piirtämistä varten:

Renderer.prototype.setShader = function (shader) { this.shader = shader } Renderer.prototype.render = function (camera, objects) { this.gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) var shader = this.shader if (!shader) { return } shader.use() camera.use(shader) objects.forEach(function (mesh) { mesh.draw(shader) }) }

Renderöintijärjestelmää on laajennettava hieman, jotta siihen mahtuu kaikki käsiteltävät ylimääräiset elementit. Varjostusohjelman tulisi olla mahdollista liittää ja tehdä joukko esineitä kameran nykyisen sijainnin perusteella.

var renderer = new Renderer(document.getElementById('webgl-canvas')) renderer.setClearColor(100, 149, 237) var gl = renderer.getContext() var objects = [] Geometry.loadOBJ('/assets/sphere.obj').then(function (data) { objects.push(new Mesh(gl, data)) }) ShaderProgram.load(gl, '/shaders/basic.vert', '/shaders/basic.frag') .then(function (shader) { renderer.setShader(shader) }) var camera = new Camera() camera.setOrthographic(16, 10, 10) loop() function loop () { renderer.render(camera, objects) requestAnimationFrame(loop) }

Voimme yhdistää kaikki elementit, jotka joudumme lopulta piirtämään jotain näytölle:

#ifdef GL_ES precision highp float; #endif varying vec3 vNormal; varying vec2 vUv; void main() { vec3 brown = vec3(.54, .27, .07); gl_FragColor = vec4(brown, 1.); }

Kankaalle piirretty esine, jonka värit riippuvat UV-koordinaateista

Tämä näyttää hieman satunnaiselta, mutta voit nähdä pallon eri laastarit sen perusteella, missä ne ovat UV-kartalla. Voit muuttaa varjostimen maalaamaan kohteen ruskeaksi. Aseta vain kunkin pikselin väri ruskean RGBA: ksi:

#ifdef GL_ES precision highp float; #endif varying vec3 vNormal; varying vec2 vUv; void main() { vec3 brown = vec3(.54, .27, .07); vec3 sunlightDirection = vec3(-1., -1., -1.); float lightness = -clamp(dot(normalize(vNormal), normalize(sunlightDirection)), -1., 0.); gl_FragColor = vec4(brown * lightness, 1.); }

Kankaalle piirretty ruskea esine

Se ei näytä kovin vakuuttavalta. Näyttää siltä, ​​että kohtaus tarvitsee joitain varjostustehosteita.

Valon lisääminen

Valot ja varjot ovat työkaluja, joiden avulla voimme havaita esineiden muodon. Valoja on useita muotoja ja kokoja: valonheittimiä, jotka loistavat yhdessä kartiossa, hehkulamput, jotka levittävät valoa kaikkiin suuntiin, ja mikä mielenkiintoisinta, aurinko, joka on niin kaukana, että kaikki sen meille säteilevä valo säteilee kaikin tavoin ja tarkoituksiin samaan suuntaan.

Auringonvalo kuulostaa siltä, ​​että se on yksinkertaisin toteuttaa, koska sinun tarvitsee vain antaa suunta, johon kaikki säteet leviävät. Jokaisen näytöllä piirtämäsi pikselin kohdalla tarkistat kulman, jonka alla valo osuu kohteeseen. Täällä sisään tulevat pinnan normaalit.

Valonsäteiden ja pintanormaalien välisten kulmien osoittaminen sekä tasaiselle että sileälle varjostukselle

Voit nähdä kaikki valonsäteet, jotka virtaavat samaan suuntaan ja osuvat pintaan eri kulmien alla, jotka perustuvat valonsäteen ja pinnan normaalin väliseen kulmaan. Mitä enemmän ne yhtyvät, sitä voimakkaampi valo on.

Jos suoritat pistetulon valonsäteen normalisoitujen vektorien ja pinnan normaalin välillä, saat -1, jos säde osuu pintaan täydellisesti kohtisuorassa, 0, jos säde on yhdensuuntainen pinnan kanssa, ja 1, jos se valaisee sen vastakkaisella puolella. Joten mikään 0: n ja 1: n välillä ei saa lisätä valoa, kun taas numeroiden välillä 0 ja -1 pitäisi vähitellen lisätä esineeseen osuvan valon määrää. Voit testata tämän lisäämällä kiinteän valon varjostimen koodiin.

#ifdef GL_ES precision highp float; #endif varying vec3 vNormal; varying vec2 vUv; void main() { vec3 brown = vec3(.54, .27, .07); vec3 sunlightDirection = vec3(-1., -1., -1.); float lightness = -clamp(dot(normalize(vNormal), normalize(sunlightDirection)), -1., 0.); float ambientLight = 0.3; lightness = ambientLight + (1. - ambientLight) * lightness; gl_FragColor = vec4(brown * lightness, 1.); }

Ruskea esine auringonvalolla

Annamme auringon paistaa eteenpäin vasemmalta alaspäin. Näet kuinka sävy on tasainen, vaikka malli onkin hyvin rosoinen. Voit myös huomata, kuinka tumma vasen alakulma on. Voimme lisätä ympäröivän valon tason, mikä tekee varjossa olevasta alueesta kirkkaamman.

#ifdef GL_ES precision highp float; #endif uniform vec3 lightDirection; uniform float ambientLight; varying vec3 vNormal; varying vec2 vUv; void main() { vec3 brown = vec3(.54, .27, .07); float lightness = -clamp(dot(normalize(vNormal), normalize(lightDirection)), -1., 0.); lightness = ambientLight + (1. - ambientLight) * lightness; gl_FragColor = vec4(brown * lightness, 1.); }

Ruskea esine, jossa on auringonvaloa ja ympäröivää valoa

Voit saavuttaa saman vaikutuksen ottamalla käyttöön valoluokan, joka tallentaa valonsuunnan ja ympäröivän valon voimakkuuden. Sitten voit muuttaa fragmentin varjostimen vastaamaan tätä lisäystä.

Nyt varjostimesta tulee:

function Light () { this.lightDirection = new Vector3(-1, -1, -1) this.ambientLight = 0.3 } Light.prototype.use = function (shaderProgram) { var dir = this.lightDirection var gl = shaderProgram.gl gl.uniform3f(shaderProgram.lightDirection, dir.x, dir.y, dir.z) gl.uniform1f(shaderProgram.ambientLight, this.ambientLight) }

Sitten voit määrittää valon:

this.ambientLight = gl.getUniformLocation(program, 'ambientLight') this.lightDirection = gl.getUniformLocation(program, 'lightDirection')

Lisää tarvittavat univormut shader-ohjelmaluokkaan:

Renderer.prototype.render = function (camera, light, objects) { this.gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) var shader = this.shader if (!shader) { return } shader.use() light.use(shader) camera.use(shader) objects.forEach(function (mesh) { mesh.draw(shader) }) }

Lisää ohjelmassa puhelu rendererin uuteen valoon:

var light = new Light() loop() function loop () { renderer.render(camera, light, objects) requestAnimationFrame(loop) }

Silmukka muuttuu sitten hieman:

sampler2D

Jos olet tehnyt kaiken oikein, renderoidun kuvan on oltava sama kuin viimeisessä kuvassa.

Viimeinen harkittava vaihe on todellisen tekstuurin lisääminen malliin. Tehdään se nyt.

Tekstuurien lisääminen

HTML5 tukee suuresti kuvien lataamista, joten kuvien jäsentämistä ei tarvitse tehdä. Kuvat välitetään GLSL: lle nimellä sampler2D kertomalla varjostimelle, mitkä sidotuista tekstuureista näytetään. On olemassa rajoitettu määrä tekstuureja, jotka voidaan sitoa, ja raja perustuu käytettyyn laitteistoon. A #ifdef GL_ES precision highp float; #endif uniform vec3 lightDirection; uniform float ambientLight; uniform sampler2D diffuse; varying vec3 vNormal; varying vec2 vUv; void main() { float lightness = -clamp(dot(normalize(vNormal), normalize(lightDirection)), -1., 0.); lightness = ambientLight + (1. - ambientLight) * lightness; gl_FragColor = vec4(texture2D(diffuse, vUv).rgb * lightness, 1.); } voidaan kysyä väreistä tietyissä paikoissa. Täältä tulevat UV-koordinaatit. Tässä on esimerkki, jossa ruskea korvattiin näytteillä.

this.diffuse = gl.getUniformLocation(program, 'diffuse')

Uusi univormu on lisättävä shader-ohjelman luetteloon:

function Texture (gl, image) { var texture = gl.createTexture() // Set the newly created texture context as active texture gl.bindTexture(gl.TEXTURE_2D, texture) // Set texture parameters, and pass the image that the texture is based on gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image) // Set filtering methods // Very often shaders will query the texture value between pixels, // and this is instructing how that value shall be calculated gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) this.data = texture this.gl = gl } Texture.prototype.use = function (uniform, binding) binding = Number(binding) Texture.load = function (gl, url) { return new Promise(function (resolve) { var image = new Image() image.onload = function () { resolve(new Texture(gl, image)) } image.src = url }) }

Lopuksi toteutamme tekstuurin lataamisen. Kuten aiemmin mainittiin, HTML5 tarjoaa tilat kuvien lataamiseen. Meidän tarvitsee vain lähettää kuva GPU: lle:

sampler2D

Prosessi ei ole paljon erilainen kuin prosessi, jota käytetään VBO: iden lataamiseen ja sitomiseen. Tärkein ero on, että emme enää sido attribuuttia, vaan pikemminkin sidomme tekstuurin indeksin yhtenäiseen kokonaislukuun. Mesh type ei ole muuta kuin osoittimen siirtymä tekstuurille.

Nyt sinun tarvitsee vain laajentaa function Mesh (gl, geometry, texture) { // added texture var vertexCount = geometry.vertexCount() this.positions = new VBO(gl, geometry.positions(), vertexCount) this.normals = new VBO(gl, geometry.normals(), vertexCount) this.uvs = new VBO(gl, geometry.uvs(), vertexCount) this.texture = texture // new this.vertexCount = vertexCount this.position = new Transformation() this.gl = gl } Mesh.prototype.destroy = function () { this.positions.destroy() this.normals.destroy() this.uvs.destroy() } Mesh.prototype.draw = function (shaderProgram) { this.positions.bindToAttribute(shaderProgram.position) this.normals.bindToAttribute(shaderProgram.normal) this.uvs.bindToAttribute(shaderProgram.uv) this.position.sendToGpu(this.gl, shaderProgram.model) this.texture.use(shaderProgram.diffuse, 0) // new this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertexCount) } Mesh.load = function (gl, modelUrl, textureUrl) { // new var geometry = Geometry.loadOBJ(modelUrl) var texture = Texture.load(gl, textureUrl) return Promise.all([geometry, texture]).then(function (params) { return new Mesh(gl, params[0], params[1]) }) } luokka käsittelemään myös tekstuureja:

var renderer = new Renderer(document.getElementById('webgl-canvas')) renderer.setClearColor(100, 149, 237) var gl = renderer.getContext() var objects = [] Mesh.load(gl, '/assets/sphere.obj', '/assets/diffuse.png') .then(function (mesh) { objects.push(mesh) }) ShaderProgram.load(gl, '/shaders/basic.vert', '/shaders/basic.frag') .then(function (shader) { renderer.setShader(shader) }) var camera = new Camera() camera.setOrthographic(16, 10, 10) var light = new Light() loop() function loop () { renderer.render(camera, light, objects) requestAnimationFrame(loop) }

Ja lopullinen pääkäsikirja näyttäisi seuraavalta:

function loop () { renderer.render(camera, light, objects) camera.position = camera.position.rotateY(Math.PI / 120) requestAnimationFrame(loop) }

Kuvioitu esine valotehosteilla

Jopa animaatio on tässä vaiheessa helppoa. Jos haluat, että kamera pyörii kohteen ympärillä, voit tehdä sen lisäämällä vain yhden koodirivin:

void main() { float lightness = -clamp(dot(normalize(vNormal), normalize(lightDirection)), -1., 0.); lightness = lightness > 0.1 ? 1. : 0.; // new lightness = ambientLight + (1. - ambientLight) * lightness; gl_FragColor = vec4(texture2D(diffuse, vUv).rgb * lightness, 1.); }

Kierretty pää kameran animaation aikana

Voit vapaasti leikkiä varjostajien kanssa. Yhden koodirivin lisääminen tekee tästä realistisesta valaistuksesta jotain sarjakuvamaista.

|_+_|

Se on niin yksinkertaista kuin käsketään valaistusta menemään äärimmäisyyksiin sen perusteella, ylittikö se asetetun kynnyksen.

Pää, jossa on sarjakuva-valaistus

Minne mennä seuraavaksi

On olemassa monia tietolähteitä kaikkien WebGL: n temppujen ja monimutkaisuuksien oppimiseksi. Ja parasta on, että jos et löydä vastausta, joka liittyy WebGL: ään, voit etsiä sitä OpenGL: stä, koska WebGL perustuu melko paljon OpenGL: n osajoukkoon, ja joitain nimiä muutetaan.

Ei missään erityisessä järjestyksessä, tässä on hienoja lähteitä tarkempaan tietoon, sekä WebGL: stä että OpenGL: stä.

  • WebGL-perusteet
  • Oppiminen WebGL
  • Erittäin yksityiskohtainen OpenGL-opetusohjelma opastaa sinut läpi kaikki tässä kuvatut perusperiaatteet hyvin hitaasti ja yksityiskohtaisesti.
  • Ja niitä on monet , monet muut sivustot, jotka on omistettu tietokonegrafiikan periaatteiden opettamiselle.
  • MDN-dokumentaatio WebGL: lle
  • Khronos WebGL 1.0 -määritys jos olet kiinnostunut ymmärtämään WebGL-sovellusliittymän teknisiä yksityiskohtia kaikissa reunatapauksissa.

Perustietojen ymmärtäminen

Mikä on WebGL?

WebGL on JavaScript-sovellusliittymä, joka lisää alkuperäisen tuen 3D-grafiikan renderoinnille yhteensopivissa verkkoselaimissa OpenGL: n kaltaisen sovellusliittymän kautta.

Kuinka 3D-mallit esitetään muistissa?

Yleisin tapa edustaa 3D-malleja on joukko pisteitä, joilla kullakin on määritelty sijainti avaruudessa, pinnan normaali pinta, johon kärjen tulisi olla osa, ja koordinaatit mallin maalaamiseen käytetyssä tekstuurissa. Nämä kärjet laitetaan sitten kolmen ryhmän muodostamaan kolmiot.

Mikä on kärkipiste?

Kärkipistevarjostin on renderöintiputken osa, joka käsittelee yksittäisiä pisteitä. Kutsu kärkipaikan varjostimeen vastaanottaa yhden kärkipisteen ja antaa yhden kärkipisteen, kun kaikki mahdolliset pisteeseen tehdyt muunnokset on toteutettu. Tämä mahdollistaa liikkeen ja muodonmuutosten kohdistamisen kokonaisiin esineisiin.

Mikä on fragmentin varjostin?

Fragmenttivarjostin on renderöintiputken osa, joka vie objektin pikselin näytölle yhdessä kohteen ominaisuuksien kanssa siinä pikselipaikassa ja voi tuottaa sille väriä, syvyyttä ja muuta dataa.

Kuinka esineet liikkuvat 3D-näkymässä?

Objektin kaikki kärkipisteet ovat suhteessa sen paikalliseen koordinaatistoon, jota edustaa 4x4-identiteettimatriisi. Jos siirrämme kyseistä koordinaattijärjestelmää, kertomalla se muunnosmatriiseilla kohteen kärkipisteet liikkuvat sen mukana.

Sivunopeus 101: Säätiö mobiilikäyttöliittymäsuunnittelijoille

Mobiilisuunnittelu

Sivunopeus 101: Säätiö mobiilikäyttöliittymäsuunnittelijoille
Suunnittelu ihmisen käyttäytymiseen: Aineettoman määritteleminen

Suunnittelu ihmisen käyttäytymiseen: Aineettoman määritteleminen

Ux-Suunnittelu

Suosittu Viestiä
Onko Yhdysvaltain oman pääoman joukkorahoitusmarkkinat eläneet odotuksia?
Onko Yhdysvaltain oman pääoman joukkorahoitusmarkkinat eläneet odotuksia?
Fintech-teollisuuden tila (infografiikan kanssa)
Fintech-teollisuuden tila (infografiikan kanssa)
10 Yleisimmät verkkoturvan haavoittuvuudet
10 Yleisimmät verkkoturvan haavoittuvuudet
Kuka tiesi Adobe CC: n voivan kehystää?
Kuka tiesi Adobe CC: n voivan kehystää?
Opas UTF-8-koodaukseen PHP: ssä ja MySQL: ssä
Opas UTF-8-koodaukseen PHP: ssä ja MySQL: ssä
 
Fintech ja pankit: Kuinka pankkisektori voi reagoida häiriöiden uhkaan?
Fintech ja pankit: Kuinka pankkisektori voi reagoida häiriöiden uhkaan?
Shopify-suunnitteluvinkit ja UX: n parhaat käytännöt
Shopify-suunnitteluvinkit ja UX: n parhaat käytännöt
Suorita matematiikka: Mikropalvelusovellusten skaalaus orkesterin kanssa
Suorita matematiikka: Mikropalvelusovellusten skaalaus orkesterin kanssa
Aloittelijan opas vedenalaiseen valokuvaukseen iPhonella
Aloittelijan opas vedenalaiseen valokuvaukseen iPhonella
Motivaation säännöt: Tarina epäonnistuneiden myyntikannustinjärjestelmien korjaamisesta
Motivaation säännöt: Tarina epäonnistuneiden myyntikannustinjärjestelmien korjaamisesta
Luokat
Tuotemerkin SuunnitteluWeb-KäyttöliittymäVarastointiKetterä KykyLähettäminenIhmiset Ja JoukkueetSijoittajat Ja RahoitusTuotteen ElinkaariProsessi Ja TyökalutLiikevaihdon Kasvu

© 2023 | Kaikki Oikeudet Pidätetään

socialgekon.com