Elementi essenziali di WebGL parte I

WebGL è un visualizzatore 3D in-browser basato su OpenGL, che consente di visualizzare i contenuti 3D direttamente in una pagina HTML5. In questo tutorial coprirò tutti gli elementi essenziali di cui hai bisogno per iniziare a utilizzare questo framework.


introduzione

Ci sono un paio di cose che dovresti sapere prima di iniziare. WebGL è un'API JavaScript che esegue il rendering di contenuti 3D su un canvas HTML5. Lo fa usando due script conosciuti nel "mondo 3D" come shaders. I due shader sono:

  • Il vertex shader
  • Lo shader del frammento

Ora non innervosirti quando senti questi nomi; è solo un modo elegante per dire "calcolatrice di posizione" e "selettore di colori" rispettivamente. Lo shader di frammenti è più facile da capire; semplicemente dice a WebGL di che colore deve essere un determinato punto sul modello. Il vertex shader è un po 'più tecnico, ma in pratica converte i punti nei tuoi modelli 3D in coordinate 2D. Poiché tutti i monitor per computer sono superfici 2D piatte e quando vedete oggetti 3D sullo schermo, sono semplicemente un'illusione di prospettiva.

Se vuoi sapere esattamente come funziona questo calcolo, dovresti chiedere a un matematico, perché utilizza moltiplicazioni di matrice 4 x 4 avanzate, che sono un po 'oltre il tutorial "Essentials". Fortunatamente, non devi sapere come funziona perché WebGL si prenderà cura di gran parte di esso. Quindi iniziamo.


Passaggio 1: impostazione di WebGL

WebGL ha un sacco di piccole impostazioni che devi configurare quasi ogni volta che disegni qualcosa sullo schermo. Per risparmiare tempo e rendere il tuo codice ordinato, creerò un oggetto JavaScript che conterrà tutte le cose "dietro le quinte" in un file separato. Per iniziare, crea un nuovo file chiamato "WebGL.js" e inserisci il seguente codice al suo interno:

funzione WebGL (CID, FSID, VSID) var canvas = document.getElementById (CID); se (! canvas.getContext ("webgl") &&! canvas.getContext ("experimental-webgl")) alert ("Il tuo browser non supporta WebGL"); else this.GL = (canvas.getContext ("webgl"))? canvas.getContext ("webgl"): canvas.getContext ("experimental-webgl"); this.GL.clearColor (1.0, 1.0, 1.0, 1.0); // questo è il colore this.GL.enable (this.GL.DEPTH_TEST); // Abilita Depth Test this.GL.depthFunc (this.GL.LEQUAL); // Imposta prospettiva Visualizza this.AspectRatio = canvas.width / canvas.height; // Carica qui gli shader

Questa funzione di costruzione prende gli ID della tela e i due oggetti shader. Per prima cosa, otteniamo l'elemento canvas e ci assicuriamo che supporti WebGL. In caso affermativo, assegniamo il contesto WebGL a una variabile locale chiamata "GL". Il colore chiaro è semplicemente il colore di sfondo, e vale la pena notare che in WebGL la maggior parte dei parametri va da 0.0 a 1.0 quindi dovresti dividere i tuoi valori rgb di 255. Quindi nel nostro esempio 1.0, 1.0, 1.0, 1.0 significa uno sfondo bianco con visibilità al 100% (nessuna trasparenza). Le prossime due righe indicano a WebGL di calcolare la profondità e la prospettiva in modo che un oggetto più vicino a voi blocchi gli oggetti dietro di esso. Infine, impostiamo il rapporto aspetto che viene calcolato dividendo la larghezza della tela per la sua altezza.

Prima di continuare e caricare i due shader, scriviamoli. Scriverò questi nel file HTML dove inseriremo l'elemento canvas. Crea un file HTML e posiziona i seguenti due elementi di script subito prima del tag body di chiusura:

 

Il vertex shader viene creato per primo e definiamo due attributi:

  • la posizione del vertice, che è la posizione nelle coordinate x, yez del vertice corrente (Punto nel modello)
  • la coordinata della trama; la posizione nell'immagine della trama che dovrebbe essere assegnata a questo punto

Successivamente, creiamo le variabili per le matrici di trasformazione e prospettiva. Questi sono usati per convertire il modello 3D in un'immagine 2D. La riga successiva crea una variabile condivisa allo shader di frammenti e nella funzione principale calcoliamo la gl_Position (la posizione 2D finale). Assegniamo quindi la "coordinata della trama corrente" alla variabile condivisa.

Nello shader dei frammenti prendiamo semplicemente le coordinate che abbiamo definito nel vertex shader e "campioniamo" la texture a quella coordinata. Fondamentalmente stiamo ottenendo solo il colore nella texture che corrisponde al punto corrente della nostra geometria.

Ora che abbiamo scritto gli shader, possiamo tornare a caricarli nel nostro file JS. Quindi sostituire "// Carica shader qui" con il seguente codice:

var FShader = document.getElementById (FSID); var VShader = document.getElementById (VSID); se (! FShader ||! VShader) avvisa ("Errore, impossibile trovare gli shader"); else // Carica e compila frammenti Shader var Code = LoadShader (FShader); FShader = this.GL.createShader (this.GL.FRAGMENT_SHADER); this.GL.shaderSource (FShader, Code); this.GL.compileShader (FShader); // Carica e compila Codice shader Vertex = LoadShader (VShader); VShader = this.GL.createShader (this.GL.VERTEX_SHADER); this.GL.shaderSource (VShader, Code); this.GL.compileShader (VShader); // Crea il programma Shader this.ShaderProgram = this.GL.createProgram (); this.GL.attachShader (this.ShaderProgram, FShader); this.GL.attachShader (this.ShaderProgram, VShader); this.GL.linkProgram (this.ShaderProgram); this.GL.useProgram (this.ShaderProgram); // Collega l'attributo Posizione vertice da Shader this.VertexPosition = this.GL.getAttribLocation (this.ShaderProgram, "VertexPosition"); this.GL.enableVertexAttribArray (this.VertexPosition); // Collega l'attributo Coordinate Texture da Shader this.VertexTexture = this.GL.getAttribLocation (this.ShaderProgram, "TextureCoord"); this.GL.enableVertexAttribArray (this.VertexTexture); 

Le tue trame devono avere dimensioni pari in byte o si otterrà un errore ... come 2x2, 4x4, 16x16, 32x32 ...

Prima assicuriamo che esistano gli shader e poi passiamo a caricarli uno alla volta. In pratica il processo ottiene il codice sorgente dello shader, lo compila e lo allega al programma dello shader centrale. C'è una funzione, chiamata LoadShader, che ottiene il codice shader dal file HTML; ci arriveremo in un secondo. Usiamo il "programma shader" per collegare insieme i due shader e ci dà accesso alle loro variabili. Memorizziamo i due attributi che abbiamo definito negli shader; in modo che possiamo inserire la nostra geometria in loro più tardi.

Ora diamo un'occhiata alla funzione LoadShader, dovresti metterla al di fuori della funzione WebGL:

function LoadShader (Script) var Code = ""; var CurrentChild = Script.firstChild; while (CurrentChild) if (CurrentChild.nodeType == CurrentChild.TEXT_NODE) ​​Codice + = CurrentChild.textContent; CurrentChild = CurrentChild.nextSibling;  codice di ritorno; 

Fondamentalmente passa semplicemente attraverso lo shader e raccoglie il codice sorgente.


Passaggio 2: il cubo "semplice"

Per disegnare oggetti in WebGL avrai bisogno dei seguenti tre array:

  • vertici; i punti che compongono i tuoi oggetti
  • triangoli; dice a WebGL come connettere i vertici alle superfici
  • coordinate della trama; definisce come i vertici sono mappati sull'immagine della trama

Si parla di mappatura UV. Per il nostro esempio creiamo un cubo di base. Dividerò il cubo in 4 vertici per lato che si collegano in due triangoli. facciamo una variabile che manterrà gli array di un cubo.

var Cube = Vertices: [// X, Y, Z Coordinates // Front 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, // Indietro 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, // Right 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0 , -1.0, 1.0, -1.0, -1.0, // Left -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, // Superiore 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, // Bottom 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0 , -1.0, -1.0, -1.0, -1.0, -1.0], Triangoli: [// Anche in gruppi di tre per definire i tre punti di ogni triangolo // I numeri qui sono i numeri di indice nell'array dei vertici // Front 0, 1, 2, 1, 2, 3, // Back 4, 5, 6, 5, 6, 7, // Right 8, 9, 10, 9, 10, 11, // Left 12, 13, 14, 13, 14, 15, // Top 16, 17, 18, 17, 18, 19, // Bottom 20, 21, 22, 21, 22, 23], Texture: [// Questo array è in gruppi di due, le coordinate xey (anche U, V) nella texture // I numeri vanno da 0.0 a 1.0, una coppia per ogni vertice // Front 1.0, 1.0, 1.0, 0.0 , 0.0, 1.0, 0.0, 0.0, // Indietro 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, // Right 1.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // Left 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, // Superiore 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, // Bottom 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0];

Potrebbe sembrare un sacco di dati per un cubo semplice, tuttavia, nella seconda parte di questo tutorial, creerò uno script che importerà i tuoi modelli 3D in modo da non doverti preoccupare di calcolarli.

Ci si potrebbe anche chiedere perché ho fatto 24 punti (4 per ogni lato), quando ci sono davvero solo otto punti unici su un cubo? L'ho fatto perché puoi assegnare solo una coordinata della trama per vertice; quindi, se inserissimo solo gli 8 punti, l'intero cubo dovrebbe apparire uguale perché avvolge la trama attorno a tutti i lati toccati dal vertice. Ma in questo modo, ogni lato ha i propri punti in modo da poter mettere una parte diversa della trama su ciascun lato.

Ora abbiamo questa variabile cubica e siamo pronti per iniziare a disegnarla. Torniamo al metodo WebGL e aggiungiamo a Disegnare funzione.


Passaggio 3: la funzione Disegna

La procedura per disegnare oggetti in WebGL ha molti passaggi; quindi, è una buona idea creare una funzione per semplificare il processo. L'idea di base è di caricare i tre array in buffer WebGL. Quindi colleghiamo questi buffer agli attributi che abbiamo definito negli shader insieme alle matrici di trasformazione e prospettiva. Successivamente, dobbiamo caricare la trama in memoria e, infine, possiamo chiamare il disegnare comando. Quindi iniziamo.

Il seguente codice va all'interno della funzione WebGL:

this.Draw = function (Object, Texture) var VertexBuffer = this.GL.createBuffer (); // Crea un nuovo buffer // Collegalo come il buffer corrente this.GL.bindBuffer (this.GL.ARRAY_BUFFER, VertexBuffer); // Compilalo con i dati this.GL.bufferData (this.GL.ARRAY_BUFFER, new Float32Array (Object.Vertices), this.GL.STATIC_DRAW); // Connetti l'attributo Buffer To Shader this.GL.vertexAttribPointer (this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0); // Ripeti per il prossimo due Var TextureBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ARRAY_BUFFER, TextureBuffer); this.GL.bufferData (this.GL.ARRAY_BUFFER, nuovo Float32Array (Object.Texture), this.GL.STATIC_DRAW); this.GL.vertexAttribPointer (this.VertexTexture, 2, this.GL.FLOAT, false, 0, 0);
 var TriangleBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ELEMENT_ARRAY_BUFFER, TriangleBuffer); // Genera The Perspective Matrix var PerspectiveMatrix = MakePerspective (45, this.AspectRatio, 1, 10000.0); var TransformMatrix = MakeTransform (Object); // Imposta lo slot 0 come Texture attiva this.GL.activeTexture (this.GL.TEXTURE0); // Carica nella texture alla memoria this.GL.bindTexture (this.GL.TEXTURE_2D, Texture); // Aggiorna The Texture Sampler nel framment shader per usare lo slot 0 this.GL.uniform1i (this.GL.getUniformLocation (this.ShaderProgram, "uSampler"), 0); // Imposta le matrici Prospettiva e Trasformazione var pmatrix = this.GL.getUniformLocation (this.ShaderProgram, "PerspectiveMatrix"); this.GL.uniformMatrix4fv (pmatrix, false, new Float32Array (PerspectiveMatrix)); var tmatrix = this.GL.getUniformLocation (this.ShaderProgram, "TransformationMatrix"); this.GL.uniformMatrix4fv (tmatrix, false, new Float32Array (TransformMatrix)); // Disegna i triangoli this.GL.drawElements (this.GL.TRIANGLES, Object.Trinagles.length, this.GL.UNSIGNED_SHORT, 0); ;

Il vertex shader posiziona, ruota e ridimensiona l'oggetto in base alle matrici di trasformazione e prospettiva. Andremo più in profondità nelle trasformazioni nella seconda parte di questa serie.

Ho aggiunto due funzioni: MakePerspective () e MakeTransform (). Questi generano solo le matrici 4x4 necessarie per WebGL. Il MakePerspective () la funzione accetta il campo visivo verticale, le proporzioni e i punti più vicini e più lontani come argomenti. Tutto ciò che è più vicino di 1 unità e più lontano di 10000 unità non verrà visualizzato, ma puoi modificare questi valori per ottenere l'effetto che stai cercando. Ora diamo un'occhiata a queste due funzioni:

function MakePerspective (FOV, AspectRatio, Closest, Farest) var YLimit = Closest * Math.tan (FOV * Math.PI / 360); var A = - (Farest + Closest) / (Farest - Closest); var B = -2 * Farest * Closest / (Farest - Closest); var C = (2 * Closest) / ((YLimit * AspectRatio) * 2); var D = (2 * Closest) / (YLimit * 2); return [C, 0, 0, 0, 0, D, 0, 0, 0, 0, A, -1, 0, 0, B, 0];  function MakeTransform (Object) return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, -6, 1]; 

Entrambe queste matrici influenzano l'aspetto finale dei tuoi oggetti, ma la matrice prospettica modifica il tuo "mondo 3D" come il campo visivo e gli oggetti visibili mentre la matrice di trasformazione modifica i singoli oggetti come la loro scala e posizione di rotazione. Fatto questo, siamo quasi pronti per disegnare, tutto ciò che rimane è una funzione per convertire un'immagine in una trama WebGL.


Passaggio 4: caricamento di trame

Il caricamento di una trama è un processo in due fasi. Per prima cosa dobbiamo caricare un'immagine come faresti in un'applicazione JavaScript standard, e quindi dobbiamo convertirla in una trama WebGL. Quindi iniziamo con la seconda parte dato che siamo già nel file JS. Aggiungi quanto segue nella parte inferiore della funzione WebGL subito dopo il comando Draw:

this.LoadTexture = function (Img) // Crea una nuova Texture e assegna a quella attiva var TempTex = this.GL.createTexture (); this.GL.bindTexture (this.GL.TEXTURE_2D, TempTex); // Flip Positive Y (Opzionale) this.GL.pixelStorei (this.GL.UNPACK_FLIP_Y_WEBGL, true); // Carica in Image this.GL.texImage2D (this.GL.TEXTURE_2D, 0, this.GL.RGBA, this.GL.RGBA, this.GL.UNSIGNED_BYTE, Img); // Imposta le proprietà di scalabilità this.GL.texParameteri (this.GL.TEXTURE_2D, this.GL.TEXTURE_MAG_FILTER, this.GL.LINEAR); this.GL.texParameteri (this.GL.TEXTURE_2D, this.GL.TEXTURE_MIN_FILTER, this.GL.LINEAR_MIPMAP_NEAREST); this.GL.generateMipmap (this.GL.TEXTURE_2D); // Separa la texture e la restituisce. this.GL.bindTexture (this.GL.TEXTURE_2D, null); return TempTex; ;

Vale la pena notare che le tue trame devono avere dimensioni pari in byte, altrimenti riceverai un errore; quindi devono essere dimensioni, come 2x2, 4x4, 16x16, 32x32 e così via. Ho aggiunto la linea per capovolgere le coordinate Y semplicemente perché le coordinate Y della mia applicazione 3D erano indietro, ma dipendono da quello che stai usando. Ciò è dovuto ad alcuni programmi che fanno 0 nell'asse Y nell'angolo in alto a sinistra e alcune applicazioni lo rendono nell'angolo in basso a sinistra. Le proprietà di ridimensionamento impostate indicano semplicemente a WebGL come l'immagine deve essere ingrandita e ridimensionata. Puoi giocare con diverse opzioni per ottenere effetti diversi, ma ho pensato che funzionassero meglio.

Ora che abbiamo finito con il file JS, torniamo al file HTML e implementiamo tutto questo.


Passaggio 5: avvolgendolo

Come accennato in precedenza, WebGL esegue il rendering su un elemento canvas. Questo è tutto ciò di cui abbiamo bisogno nella sezione del corpo. Dopo aver aggiunto l'elemento canvas, la pagina html dovrebbe essere simile alla seguente:

        Il tuo browser non supporta la tela di HTML5.     

È una pagina piuttosto semplice. Nell'area head ho collegato al nostro file JS. Ora implementiamo la nostra funzione Ready, che viene chiamata quando viene caricata la pagina:

// Questo manterrà la nostra variabile WebGL var GL; // La nostra texture finita var Texture; // Ciò manterrà l'immagine di texture var TextureImage; funzione Pronto () GL = nuovo WebGL ("GLCanvas", "FragmentShader", "VertexShader"); TextureImage = new Image (); TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); GL.Draw (Cube, Texture); ; TextureImage.src = "Texture.png"; 

Quindi creiamo un nuovo oggetto WebGL e passiamo gli ID per canvas e shader. Successivamente, carichiamo l'immagine della trama. Una volta caricato, chiamiamo il Disegnare() metodo con il cubo e la trama. Se lo hai seguito, lo schermo dovrebbe avere un cubo statico con una texture su di esso.

Ora, anche se ho detto che copriremo le trasformazioni la prossima volta, non posso lasciarti con un quadrato statico; non è abbastanza 3D. Torniamo indietro e aggiungiamo una piccola rotazione. Nel file HTML, cambia il onload funzione per assomigliare così:

TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); setInterval (Aggiornamento, 33); ;

Questo chiamerà una funzione chiamata Aggiornare() ogni 33 millisecondi che ci daranno un frame rate di circa 30 fps. Ecco la funzione di aggiornamento:

function Update () GL.GL.clear (16384 | 256); GL.Draw (GL.Cube, Texture); 

Questa è una funzione abbastanza semplice; cancella lo schermo e quindi disegna il cubo aggiornato. Ora, andiamo al file JS per aggiungere il codice di rotazione.


Passaggio 6: aggiunta di alcuni spin

Non ho intenzione di implementare completamente le trasformazioni, perché lo sto salvando per la prossima volta, ma aggiungiamo una rotazione attorno all'asse Y. La prima cosa da fare è aggiungere una variabile Rotation al nostro oggetto Cube. Ciò manterrà traccia dell'angolo corrente e ci consentirà di continuare a incrementare la rotazione. Quindi la parte superiore della variabile Cube dovrebbe apparire così:

var Cube = Rotation: 0, // The Other Three Arrays;

Ora aggiorniamo il MakeTransform () funzione per incorporare la rotazione:

function MakeTransform (Object) var y = Object.Rotation * (Math.PI / 180.0); var A = Math.cos (y); var B = -1 * Math.sin (y); var C = Math.sin (y); var D = Math.cos (y); Object.Rotation + = .3; return [A, 0, B, 0, 0, 1, 0, 0, C, 0, D, 0, 0, 0, -6, 1]; 

Conclusione

E questo è tutto! Nel prossimo tutorial, tratteremo di caricare i modelli e di eseguire le trasformazioni. Spero che questo tutorial ti sia piaciuto; sentiti libero di lasciare qualsiasi domanda o commento che potresti avere sotto.