Trasformazioni WebGL

Gregg Tavares
Gregg Tavares

Traduzione 2D WebGL

Prima di passare al 3D, rimaniamo in 2D per un po' più a lungo. Abbi pazienza. Questo articolo potrebbe sembrare estremamente ovvio per alcuni, ma entrerò fino a un punto in alcuni articoli.

Questo articolo è la continuazione di una serie che inizia con WebGL Fundamentals. Se non l'hai ancora letto, ti suggerisco di leggere almeno il primo capitolo, quindi di tornare qui. che consiste in un nome matematico fantasioso che in pratica significa "spostare" qualcosa. Suppongo che spostare una frase dall'inglese al giapponese sia appropriata, ma in questo caso stiamo parlando dello spostamento della geometria. Utilizzando il codice campione che abbiamo trovato nel primo post, potresti tradurre facilmente il nostro rettangolo semplicemente modificando i valori passati a setRettangolo, giusto? Ecco un esempio basato sul precedente.

  // First lets make some variables 
  // to hold the translation of the rectangle
  var translation = [0, 0];
  // then let's make a function to
  // re-draw everything. We can call this
  // function after we update the translation.
  // Draw the scene.
  function drawScene() {
     // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);
    // Setup a rectangle
    setRectangle(gl, translation[0], translation[1], width, height);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  }

Stai andando bene. Ma ora immagina di voler fare la stessa cosa con una forma più complicata. Supponiamo di voler disegnare una "F" composta da 6 triangoli come questo.

Lettera F

Di seguito è riportato il codice attuale che dovremmo cambiare setRettangolo in qualcosa di più simile a questo.

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl, x, y) {
  var width = 100;
  var height = 150;
  var thickness = 30;
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          // left column
          x, y,
          x + thickness, y,
          x, y + height,
          x, y + height,
          x + thickness, y,
          x + thickness, y + height,

          // top rung
          x + thickness, y,
          x + width, y,
          x + thickness, y + thickness,
          x + thickness, y + thickness,
          x + width, y,
          x + width, y + thickness,

          // middle rung
          x + thickness, y + thickness * 2,
          x + width * 2 / 3, y + thickness * 2,
          x + thickness, y + thickness * 3,
          x + thickness, y + thickness * 3,
          x + width * 2 / 3, y + thickness * 2,
          x + width * 2 / 3, y + thickness * 3]),
      gl.STATIC_DRAW);
}

Si spera che non si scaleranno bene. Se vogliamo tracciare una geometria molto complessa con centinaia o migliaia di righe, dobbiamo scrivere un codice piuttosto complesso. Inoltre, ogni volta che tracciamo JavaScript dobbiamo aggiornare tutti i punti. Esiste un modo più semplice. È sufficiente caricare la geometria ed eseguire la traduzione nello strumento di orientamento. Ecco il nuovo smoother

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform vec2 u_translation;

void main() {
   // Add in the translation.
   vec2 position = a_position + u_translation;

   // convert the rectangle from pixels to 0.0 to 1.0
   vec2 zeroToOne = position / u_resolution;
   ...

e riformuleremo il codice. Per uno, la geometria deve essere impostata una sola volta.

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          // left column
          0, 0,
          30, 0,
          0, 150,
          0, 150,
          30, 0,
          30, 150,

          // top rung
          30, 0,
          100, 0,
          30, 30,
          30, 30,
          100, 0,
          100, 30,

          // middle rung
          30, 60,
          67, 60,
          30, 90,
          30, 90,
          67, 60,
          67, 90]),
      gl.STATIC_DRAW);
}

Dobbiamo solo aggiornare u_translation prima di disegnare con la traduzione che vogliamo.

  ...
  var translationLocation = gl.getUniformLocation(
             program, "u_translation");
  ...
  // Set Geometry.
  setGeometry(gl);
  ..
  // Draw scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

Nota: setGeometry viene chiamato una sola volta. Non si trova più all'interno di disegnoScene.

Ora, quando disegniamo WebGL, fa praticamente tutto. Tutto ciò che facciamo è impostare una traduzione e chiederle di disegnare. Anche se la nostra geometria avesse decine di migliaia di punti, il codice principale rimarrebbe invariato.

Rotazione 2D WebGL

Ammetterò fin da subito che non ho idea se il modo in cui lo spiego abbia senso, ma che diamine potresti anche provare.

Innanzitutto, vorrei presentarti un cosiddetto "cerchio di unità". Se ricordi la matematica alle superiori (non andare a dormire con me!), un cerchio ha un raggio. Il raggio di un cerchio è la distanza dal centro del cerchio al bordo. Un cerchio unitario è un cerchio con un raggio di 1,0.

Dalla matematica di base di 3° elementare, se moltiplichi qualcosa per 1, il tempo non cambia. Quindi 123 * 1 = 123. È piuttosto semplice, vero? Beh, un cerchio unitario, un cerchio con un raggio di 1,0 è anche una forma di 1. È un 1 a rotazione. Quindi puoi moltiplicare qualcosa per questa circonferenza unitaria e in un modo simile a moltiplicarlo per 1, solo che si verifica la magia e le cose ruotano. Prenderemo i valori X e Y da qualsiasi punto della circonferenza unitaria e moltiplicheremo la geometria per il valore ottenuto dal nostro campione precedente. Di seguito sono riportati gli aggiornamenti del nostro Shadr.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_rotation;

void main() {
  // Rotate the position
  vec2 rotatedPosition = vec2(
     a_position.x * u_rotation.y + a_position.y * u_rotation.x,
     a_position.y * u_rotation.y - a_position.x * u_rotation.x);

  // Add in the translation.
  vec2 position = rotatedPosition + u_translation;

Aggiorniamo quindi JavaScript in modo da poter passare questi 2 valori.

  ...
  var rotationLocation = gl.getUniformLocation(program, "u_rotation");
  ...
  var rotation = [0, 1];
  ..
  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);

    // Set the rotation.
    gl.uniform2fv(rotationLocation, rotation);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

Perché funziona? Guarda il calcolo.

rotatedX = a_position.x * u_rotation.y + a_position.y * u_rotation.x;
rotatedY = a_position.y * u_rotation.y - a_position.x * u_rotation.x;

Mantieni un rettangolo e vuoi ruotarlo. Prima di iniziare a ruotarlo, l'angolo in alto a destra è a 3,0, 9,0. Scegliamo un punto sul cerchio unitario di 30 gradi in senso orario a partire dalle ore 12.

Rotazione di 30 gradi

La posizione sul cerchio è 0,50 e 0,87

3.0 * 0.87 + 9.0 * 0.50 = 7.1
9.0 * 0.87 - 3.0 * 0.50 = 6.3

Ed è esattamente dove ci serve

Disegno rotatorio

La stessa operazione per 60 gradi in senso orario

Rotazione di 60°

La posizione sul cerchio è 0,87 e 0,50

3.0 * 0.50 + 9.0 * 0.87 = 9.3
9.0 * 0.50 - 3.0 * 0.87 = 1.9

Puoi notare che ruotando quel punto in senso orario verso destra il valore X aumenta e la Y diminuisce. Se continuassi a superare i 90 gradi X, inizierà a diminuire di nuovo e Y inizierà a diventare più grande. Questo schema ci permette di ruotare. Esiste un altro nome per i punti su un cerchio unitario. Si chiamano seno e coseno. Quindi per ogni angolo possiamo semplicemente cercare il seno e il coseno in questo modo.

function printSineAndCosineForAnAngle(angleInDegrees) {
  var angleInRadians = angleInDegrees * Math.PI / 180;
  var s = Math.sin(angleInRadians);
  var c = Math.cos(angleInRadians);
  console.log("s = " + s + " c = " + c);
}

Se copi e incolli il codice nella console JavaScript e digiti printSineAndCosignForAngle(30), viene visualizzata la dicitura s = 0.49 c= 0.87 (nota: ho arrotondato i numeri). Se metti tutto insieme, puoi ruotare la geometria fino all'angolazione che desideri. Imposta la rotazione sul seno e coseno dell'angolo desiderato.

  ...
  var angleInRadians = angleInDegrees * Math.PI / 180;
  rotation[0] = Math.sin(angleInRadians);
  rotation[1] = Math.cos(angleInRadians);

Spero che tutto abbia un senso. Ora passiamo a una più semplice. Scala.

Cosa sono i radianti?

I radianti sono un'unità di misura utilizzata con i cerchi, la rotazione e gli angoli. Così come possiamo misurare la distanza in pollici, iarde, metri e così via, possiamo misurare gli angoli in gradi o radianti.

Probabilmente sai già che la matematica con misurazioni metriche è più facile di quella con misurazioni imperiali. Per andare da pollici a piedi, dividiamo per 12. Per passare da pollici a iarde, dividiamo per 36. Non so tu, ma non posso dividere per 36 nella mia testa. Con le metriche è molto più facile. Per passare da millimetri a centimetri, dividiamo per 10. A da millimetri a metri dividiamo per 1000. Posso dividere per 1000 nella mia testa.

I radianti e i gradi sono simili. I gradi rendono la matematica difficile. I radianti semplificano i calcoli matematici. In un cerchio ci sono 360 gradi, ma ci sono solo 2p radianti. Perciò un turno completo è 2 {/5} radianti. Un mezzo giro corrisponde a β radianti. Un 1/4 di giro, vale a dire che 90 degress è uguale a p/2 radianti. Pertanto, se vuoi ruotare un elemento di 90 gradi, utilizza Math.PI * 0.5. Se vuoi ruotarla di 45 gradi, usa Math.PI * 0.25 e così via.

Quasi tutti i calcoli matematici relativi ad angoli, cerchi o rotazione funzionano molto semplicemente se inizi a pensare in radianti. Provalo. Utilizza i radianti, non i gradi, se non nelle visualizzazioni dell'interfaccia utente.

Scala 2D WebGL

La scalabilità è semplice quanto la traduzione.

moltiplichiamo la posizione per la scala desiderata. Ecco le modifiche rispetto all'esempio precedente.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_rotation;
uniform vec2 u_scale;

void main() {
  // Scale the positon
  vec2 scaledPosition = a_position * u_scale;

  // Rotate the position
  vec2 rotatedPosition = vec2(
     scaledPosition.x * u_rotation.y +
        scaledPosition.y * u_rotation.x,
     scaledPosition.y * u_rotation.y -
        scaledPosition.x * u_rotation.x);

  // Add in the translation.
  vec2 position = rotatedPosition + u_translation;

e aggiungiamo il codice JavaScript necessario per impostare la scala quando disegniamo.

  ...
  var scaleLocation = gl.getUniformLocation(program, "u_scale");
  ...
  var scale = [1, 1];
  ...
  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);

    // Set the rotation.
    gl.uniform2fv(rotationLocation, rotation);

    // Set the scale.
    gl.uniform2fv(scaleLocation, scale);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

Una cosa da notare è che il ridimensionamento di un valore negativo capovolge la nostra geometria. Spero che questi ultimi 3 capitoli ti siano stati utili per comprendere la traduzione, la rotazione e la scalabilità. Quindi, vedremo la magia delle matrici che combinano tutti e tre questi elementi in una forma molto più semplice e spesso più utile.

Perché una "F"?

La prima volta che ho visto qualcuno usare una "F" era su una texture. La "F" stessa non è importante. È importante poter distinguere l'orientamento da qualsiasi direzione. Ad esempio, se usassimo un cuore △ o un triangolo △, non riuscivamo a capire se fosse stato capovolto orizzontalmente. Una cerchia ○ sarebbe ancora peggiore. Un rettangolo colorato probabilmente funzionava con colori diversi per ogni angolo, ma poi bisognava ricordare quale fosse. L'orientamento della F è immediatamente riconoscibile.

Orientamento F

Qualsiasi forma di cui riesci a capire l'orientamento avrebbe funzionato. Ho appena usato "F" da quando mi è stato "Se prima ho introdotto l'idea".

Matrici WebGL 2D

Negli ultimi tre capitoli abbiamo esaminato come tradurre la geometria, ruotare la geometria e scalare la geometria. Ciascuna traslazione, rotazione e scala sono considerate un tipo di "trasformazione". Ognuna di queste trasformazioni richiedeva modifiche allo strumento di ombra e ciascuna delle tre trasformazioni dipendeva dall'ordine.

Ad esempio, ecco una scala di 2, 1, una rotazione del 30% e una traduzione di 100, 0.

Rotazione F e traslazione

Ed ecco una traduzione di 100,0, una rotazione del 30% e una scala di 2, 1

Rotazione F e scala

I risultati sono completamente diversi. Peggio ancora, se avessimo bisogno del secondo esempio, avremmo dovuto scrivere uno shardr diverso che applicasse la traduzione, la rotazione e la scala nel nuovo ordine desiderato. Beh, alcune persone molto più intelligenti di me hanno capito che si possono fare tutte le stesse cose con la matematica matriciale. Per il 2D usiamo una matrice 3x3. Una matrice 3x3 è come una griglia con 9 caselle.

1,0 2.0 3,0
4.0 5,0 6.0
7,0 8.0 9.0

Per calcolare i calcoli matematici, moltiplichiamo la posizione tra le colonne della matrice e sottraiamo i risultati. Le nostre posizioni hanno solo 2 valori, x e y, ma per fare questi calcoli abbiamo bisogno di 3 valori, quindi useremo 1 per il terzo valore. In questo caso, il nostro risultato sarà

newX = x * 1.0 + y * 4.0 + 1 * 7.0

newY = x * 2.0 + y * 5.0 + 1 * 8.0

extra = x * 3.0 + y * 6.0 + 1 * 9.0

Probabilmente lo stai guardando e stai pensando "COSA È IL PUNTO". Beh, supponiamo di avere una traduzione. Chiameremo l'importo da tradurre tx e ty. Creiamo una matrice come questa

1,00.00.0
0.01,00.0
txty 1,0

E ora dai un'occhiata

newX = x * 1.0 + y * 0.0 + 1 * tx

newY = x * 0.0 + y * 1.0 + 1 * ty

extra = x * 0.0 + y * 0.0 + 1 * 1

Se ricordi la tua algebra, possiamo eliminare qualsiasi luogo che moltiplica per zero. Moltiplicare per 1 in modo efficace non fa nulla, quindi semplifichiamo per vedere cosa succede

newX = x + tx;
newY = y + ty;

E in più non ci interessa davvero. Sembra sorprendentemente come il codice di traduzione del nostro esempio di traduzione. Analogamente, adottiamo la rotazione. Come abbiamo sottolineato nel post sulla rotazione, abbiamo solo bisogno del seno e del coseno dell'angolo a cui vogliamo ruotare.

s = Math.sin(angleToRotateInRadians);
c = Math.cos(angleToRotateInRadians);

E creiamo una matrice come questa

C-s0.0
sC0.0
0.00.01,0

Applicando la matrice otteniamo questo

newX = x * c + y * s + 1 * 0

newY = x * -s + y * c + 1 * 0

extra = x * 0.0 + y * 0.0 + 1 * 1

Oscurando tutti, moltiplichiamo per 0 e 1

newX = x *  c + y * s;
newY = x * -s + y * c;

Che è esattamente ciò che avevamo nel nostro campione di rotazione. E infine la scalabilità. Chiameremo i nostri due fattori di scala sx e sy E creiamo una matrice come questa

s0.00.0
0.0si0.0
0.00.01,0

Applicando la matrice otteniamo questo

newX = x * sx + y * 0 + 1 * 0

newY = x * 0 + y * sy + 1 * 0

extra = x * 0.0 + y * 0.0 + 1 * 1

che è davvero

newX = x * sx;
newY = y * sy;

Che è uguale al nostro campione di scalabilità. Ora sono sicura che potresti ancora pensare. E quindi? Lo scopo? Ci sembra che ci sia molto lavoro per fare la stessa cosa che stavamo già facendo. È qui che entra la magia. Abbiamo scoperto che possiamo moltiplicare le matrici e applicare tutte le trasformazioni contemporaneamente. Supponiamo di avere una funzione, matrixMultiply, che prende due matrici, le moltiplica e restituisce il risultato. Per rendere le cose più chiare, creiamo funzioni per creare le matrici di traslazione, rotazione e scala.

function makeTranslation(tx, ty) {
  return [
    1, 0, 0,
    0, 1, 0,
    tx, ty, 1
  ];
}

function makeRotation(angleInRadians) {
  var c = Math.cos(angleInRadians);
  var s = Math.sin(angleInRadians);
  return [
    c,-s, 0,
    s, c, 0,
    0, 0, 1
  ];
}

function makeScale(sx, sy) {
  return [
    sx, 0, 0,
    0, sy, 0,
    0, 0, 1
  ];
}

Ora cambiamo il nostro ombreggiatore. Il vecchio shabby era così

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_rotation;
uniform vec2 u_scale;

void main() {
  // Scale the positon
  vec2 scaledPosition = a_position * u_scale;

  // Rotate the position
  vec2 rotatedPosition = vec2(
     scaledPosition.x * u_rotation.y + scaledPosition.y * u_rotation.x,
     scaledPosition.y * u_rotation.y - scaledPosition.x * u_rotation.x);

  // Add in the translation.
  vec2 position = rotatedPosition + u_translation;
  ...

Il nostro nuovo smoother sarà molto più semplice.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
uniform mat3 u_matrix;

void main() {
  // Multiply the position by the matrix.
  vec2 position = (u_matrix * vec3(a_position, 1)).xy;
  ...

Ecco come lo usiamo

  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Compute the matrices
    var translationMatrix =
       makeTranslation(translation[0], translation[1]);
    var rotationMatrix = makeRotation(angleInRadians);
    var scaleMatrix = makeScale(scale[0], scale[1]);

    // Multiply the matrices.
    var matrix = matrixMultiply(scaleMatrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);

    // Set the matrix.
    gl.uniformMatrix3fv(matrixLocation, false, matrix);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

Forse ti starai chiedendo comunque cosa sono. Non mi sembra un vantaggio importante . Se, però, vogliamo cambiare l'ordine, non c'è bisogno di scrivere un nuovo smoother. Possiamo solo cambiare il calcolo.

    ...
    // Multiply the matrices.
    var matrix = matrixMultiply(translationMatrix, rotationMatrix);
    matrix = matrixMultiply(matrix, scaleMatrix);
    ...

La capacità di applicare matrici come questa è particolarmente importante per un'animazione gerarchica come braccia su un corpo, lune su un pianeta intorno a un sole o rami su un albero. Come semplice esempio di animazione gerarchica, possiamo disegnare la nostra "F" 5 volte, ma ogni volta iniziamo con la matrice della "F" precedente.

  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Compute the matrices
    var translationMatrix = makeTranslation(translation[0], translation[1]);
    var rotationMatrix = makeRotation(angleInRadians);
    var scaleMatrix = makeScale(scale[0], scale[1]);

    // Starting Matrix.
    var matrix = makeIdentity();

    for (var i = 0; i < 5; ++i) {
      // Multiply the matrices.
      matrix = matrixMultiply(matrix, scaleMatrix);
      matrix = matrixMultiply(matrix, rotationMatrix);
      matrix = matrixMultiply(matrix, translationMatrix);

      // Set the matrix.
      gl.uniformMatrix3fv(matrixLocation, false, matrix);

      // Draw the geometry.
      gl.drawArrays(gl.TRIANGLES, 0, 18);
    }
  }

Per farlo, abbiamo introdotto la funzione makeIdentity, che crea una matrice identità. Una matrice identità è una matrice che rappresenta effettivamente 1,0 in modo che se si moltiplica per l'identità non accade nulla. Proprio come

X * 1 = X

così anche

matrixX * identity = matrixX

Ecco il codice per creare una matrice identità.

function makeIdentity() {
  return [
    1, 0, 0,
    0, 1, 0,
    0, 0, 1
  ];
}

Un altro esempio: in ogni campione finora la nostra "F" ruota intorno all'angolo in alto a sinistra. Questo perché la matematica che stiamo usando ruota sempre intorno all'origine e l'angolo in alto a sinistra della "F" si trova nell'origine, (0, 0). Ora, poiché possiamo eseguire calcoli matriciali e possiamo scegliere l'ordine di applicazione delle trasformazioni, possiamo spostare l'origine prima che vengano applicate le altre trasformazioni.

    // make a matrix that will move the origin of the 'F' to
    // its center.
    var moveOriginMatrix = makeTranslation(-50, -75);
    ...

    // Multiply the matrices.
    var matrix = matrixMultiply(moveOriginMatrix, scaleMatrix);
    matrix = matrixMultiply(matrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);

Utilizzando questa tecnica, puoi ruotare o ridimensionare da qualsiasi punto. Ora sai in che modo Photoshop o Flash ti permettono di spostare il punto di rotazione. Impazziamo ancora. Se ritorni al primo articolo sui concetti fondamentali di WebGL, potresti ricordare che abbiamo un codice nello strumento per convertire i pixel in spazi clip simili a questo.

  ...
  // convert the rectangle from pixels to 0.0 to 1.0
  vec2 zeroToOne = position / u_resolution;

  // convert from 0->1 to 0->2
  vec2 zeroToTwo = zeroToOne * 2.0;

  // convert from 0->2 to -1->+1 (clipspace)
  vec2 clipSpace = zeroToTwo - 1.0;

  gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

Se guardi ciascuno di questi passaggi uno alla volta, il primo passaggio, "converti da pixel a 0,0 in 1,0", è davvero un'operazione di scalabilità. La seconda è anche un'operazione di scalabilità. Poi c'è una traslazione e l'ultimo scala Y per -1. Possiamo farlo nella matrice che passiamo allo Shadr. Potremmo fare 2 matrici di scala, una per scalare di 1,0/risoluzione, un'altra per scalare di 2,0, una terza per tradurre di -1,0, -1,0 e una quarta per scalare Y per -1 e poi moltiplicarle tutte insieme, ma poiché la matematica è semplice, creeremo direttamente una funzione che crea una matrice di "proiezione" per una data risoluzione direttamente.

function make2DProjection(width, height) {
  // Note: This matrix flips the Y axis so that 0 is at the top.
  return [
    2 / width, 0, 0,
    0, -2 / height, 0,
    -1, 1, 1
  ];
}

Ora possiamo semplificare ancora di più lo smoothr. Ecco il nuovo Vertex Shader.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform mat3 u_matrix;

void main() {
  // Multiply the position by the matrix.
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
</script>

In JavaScript dobbiamo moltiplicare per la matrice di proiezione

  // Draw the scene.
  function drawScene() {
    ...
    // Compute the matrices
    var projectionMatrix =
       make2DProjection(canvas.width, canvas.height);
    ...

    // Multiply the matrices.
    var matrix = matrixMultiply(scaleMatrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);
    matrix = matrixMultiply(matrix, projectionMatrix);
    ...
  }

Abbiamo anche rimosso il codice che impostava la risoluzione. Con quest'ultimo passo siamo passati da uno smoother piuttosto complicato con 6-7 passi a uno smoother semplicissimo con un solo passo, tutti per fare la magia della matematica matrix.

Spero che questo articolo ti sia stato utile per comprendere meglio la matematica. Passerò al 3D. Nella matrice 3D, la matematica segue gli stessi principi e utilizzi. Ho iniziato con il 2D per renderlo semplice da capire.