WebGL dönüşümleri

Gregg Tavares
Gregg Tavares

WebGL 2D Çeviri

3D'ye geçmeden önce, 2D'de biraz daha kalalım. Anlayışla karşılayacağını umarım. Bu makale bazılarına çok açık gelebilir, ancak birkaç makalede bir noktaya değineceğim.

Bu makale, WebGL Temelleri ile başlayan bir serinin devamıdır. Okumadıysanız en azından ilk bölümünü okumanızı ve sonra buraya geri dönmenizi öneririm. Çeviri, bir şeyi "taşımak" anlamına gelen gösterişli bir matematik adıdır. Bir cümleyi İngilizceden Japoncaya taşımanın da uygun olduğunu düşünüyorum, ancak bu örnekte, geometriyi hareket ettirmekten bahsediyoruz. İlk gönderide bulduğumuz örnek kodu kullanarak, setRectangle değerini doğru şekilde değiştirerek dikdörtgenimizi kolayca çevirebilirsiniz. Önceki örneğimize dayalı bir örneği burada bulabilirsiniz.

  // 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);
  }

Şu ana kadar her şey yolunda. Ama şimdi, aynı şeyi daha karmaşık bir şekille yapmak istediğimizi hayal edin. Buna benzer 6 üçgenden oluşan bir 'F' çizmek istediğimizi varsayalım.

F harfi

Şu anda setRectangle'ı buna benzer bir kodla değiştirmemiz gerekiyor.

// 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);
}

Bunun iyi ölçeklemeyeceğini görebilirsiniz. Yüzlerce veya binlerce çizgiden oluşan çok karmaşık bir geometri çizmek istiyorsak oldukça karmaşık bir kod yazmamız gerekirdi. Üstelik, her çizimde JavaScript'in tüm noktaları güncellemesi gerekir. Daha basit bir yolu var. Geometriyi yüklemeniz ve gölgelendiricide çeviriyi yapmanız yeterlidir. İşte yeni gölgelendirici

<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;
   ...

kodu biraz yeniden yapılandıracağız. Birincisi, geometriyi yalnızca bir kez ayarlamamız gerekir.

// 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);
}

Ardından, istediğimiz çeviriyi yapmadan önce u_translation uygulamasını güncellememiz gerekiyor.

  ...
  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);
  }

setGeometry bildirimi yalnızca bir kez çağrılır. Öğe artıkdrawScene'in içinde değildir.

Şimdi, WebGL çizildiğinde hemen hemen her şeyi yapıyor. Tüm yaptığımız bir çeviriyi ayarlayıp ondan çizim yapmasını istemektir. Geometrimizde on binlerce nokta olsa bile ana kod aynı kalacaktı.

WebGL 2D Döndürme

En baştan itiraf ediyorum, bunu nasıl açıklayacağımı nasıl açıklayacağımı hiçbir fikrim yok, ama ne denli denemeli de olabilir?

Öncelikle size "birim çemberi" denen bir kavramı tanıtmak istiyorum. Lisedeki matematiğinizi hatırlıyorsanız (benimle uyumayın!) bir dairenin yarıçapı vardır. Bir dairenin yarıçapı, dairenin merkezinden kenara olan mesafedir. Birim daire, yarıçapı 1,0 olan bir dairedir.

3. sınıf matematiğinden bir şeyi 1'le çarptığınızda sonuç aynı kalır. Dolayısıyla, 123 * 1 = 123 olur. Oldukça basit, değil mi? Birim daire, yarıçapı 1, 0 olan bir daire de 1'in biçimidir. Dönen 1'dir. Yani, bir şeyi bu birim çemberle, yani 1'le çarpmış gibi çarpabilirsiniz. Tek fark, sihirin gerçekleşir ve her şeyin değişmesidir. Birim çember üzerindeki herhangi bir noktadan X ve Y değerini alıp bir önceki örneğimizden geometrimizi bu öğelerle çarpacağız. Gölgelendiricimizle ilgili güncellemeleri burada bulabilirsiniz.

<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;

Bu 2 değeri aktarabilmek için JavaScript'i de güncelliyoruz.

  ...
  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);
  }

Neden işe yarar? Şimdi işin matematiğine bakalım.

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;

Diyelim ki bir dikdörtgeniniz var ve onu döndürmek istiyorsunuz. Döndürmeye başlamadan önce sağ üst köşe 3,0, 9,0 konumundadır. Birim çember üzerinde saat 12'den 30 derece uzaklaşan bir nokta seçelim.

30 derece döndürme

Çemberin konumu 0,50 ve 0,87'dir

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

Olması gereken nokta tam olarak

Döndürme çizimi

Saat yönünde 60 derece için aynı

60 derece döndürme

Çemberin konumu 0,87 ve 0,50'dir

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

Gördüğünüz gibi, bu noktayı saat yönünde sağa döndürdüğümüzde X değeri büyür ve Y değeri küçülür. 90 dereceyi geçmeye devam etseydi tekrar küçülecek, Y'nin ise büyüyecekti. Bu kalıp bize rotasyon sağlıyor. Birim daire üzerindeki noktalar için başka bir ad vardır. Bunlara sinüs ve kosinüs denir. Herhangi bir açı için bunun gibi sinüs ve kosinüs araması yapabiliriz.

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);
}

Kodu kopyalayıp JavaScript konsolunuza yapıştırırsanız ve printSineAndCosignForAngle(30) yazarsanız s = 0.49 c= 0.87 sonucunu görürsünüz (not: sayılar yuvarlanmıştır.) Hepsini bir araya getirirseniz geometrinizi istediğiniz açıya döndürebilirsiniz. Döndürmeyi, döndürmek istediğiniz açının sinüsü ve kosinüsüne ayarlamanız yeterlidir.

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

Bu bilgilerin işinize yarayacağını umuyorum. Sırada daha basit bir soru var. Ölçeklendirin.

Radyan nedir?

Radyan; daireler, dönüş ve açılarla kullanılan bir ölçü birimidir. Mesafeyi inç, metre, metre vb. cinsinden ölçebildiğimiz gibi, açıları derece ya da radyan cinsinden ölçebiliriz.

Metrik ölçülere göre matematiğin İngiliz ölçü birimi matematiğinden daha kolay olduğunu muhtemelen biliyorsunuzdur. inçten fite gitmek için 12'ye böleriz. İnçten yardaya gitmek için 36'ya böleriz. Sizi bilmem ama benim kafamda 36'ya bölemiyorum. Metrik ile bu çok daha kolay. Milimetreden santimetreye çıkmak için 10'a böleriz. Milimetre kaç metre olursa 1000'e böleriz. Kafamda 1000'e bölebilirim.

Radyan ve dereceler benzerdir. Dereceler, matematiği zorlaştırır. Radyanlar, hesaplamayı kolaylaştırır. Bir dairenin içinde 360 derece vardır, ancak yalnızca 2 sorusu radyan vardır. Yani tam dönüş, 2π radyandır. Yarım dönüş, π radyandır. 1/4 dönüş, ör. 90 derece, π/2 radyandır. Bir şeyi 90 derece döndürmek istiyorsanız Math.PI * 0.5 kullanmanız yeterlidir. 45 derece döndürmek isterseniz Math.PI * 0.25 vb. kullanın.

Açı, çember veya döndürmeyle ilgili neredeyse tüm matematiksel işlemler radyan cinsinden düşünmeye başladığınızda çok basit bir şekilde çalışır. Siz de deneyin. Kullanıcı arayüzü ekranları dışında derece değil radyan kullanın.

WebGL 2D Ölçeklendirme

Ölçeklendirmek de çeviri kadar kolaydır.

Konumu istediğimiz ölçekle çarparız. Bir önceki örneğimizde yapılan değişiklikleri burada görebilirsiniz.

<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;

ve çizim sırasında ölçeği ayarlamak için gereken JavaScript'i ekliyoruz.

  ...
  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);
  }

Dikkat edilmesi gereken bir nokta, negatif bir değere göre ölçeklendirmenin geometrimizi tersine çevirmesidir. Umarım bu son 3 bölüm çeviri, döndürme ve ölçekleme konularında yararlı olmuştur. Ardından, bunların üçünü de çok daha basit ve çoğu zaman daha kullanışlı bir formda bir araya getiren matrislerden bahsedeceğiz.

Neden "F"?

Birinin "F" harfini ilk kez bir doku üzerinde kullandığını gördüm. "F"nin kendisi önemli değildir. Önemli olan, yönünü istediğiniz yönden bildirebilmenizdir. Örneğin, kalp ♥ veya üçgen △ kullandığımızda yatay olarak çevrilmiş olup olmadığını anlayamıyorduk. Daire (○) daha da kötü olurdu. Renkli bir dikdörtgenin her köşesi muhtemelen farklı renklerde kullanılabilir, ancak o zaman hangi köşenin hangisi olduğunu hatırlamanız gerekir. F yönü anında tanınabilir.

F yönü

Yönünü anlayabileceğiniz tüm şekiller işe yarayacaktır. Bu fikirle ilk tanıştığımdan beri "F" harfini kullandım.

WebGL 2D Matrisleri

Son 3 bölümde, geometriyi çevirme, geometriyi döndürme ve geometriyi ölçeklendirme konularından bahsettik. Hareket ettirme, döndürme ve ölçeklemenin her biri birer "dönüşüm" türü olarak kabul edilir. Bu dönüşümlerin her biri, gölgelendiricide değişiklik yapılmasını gerektiriyordu ve 3 dönüşümün her biri sıraya bağlıydı.

Örneğin burada 2, 1 ölçeği, %30 döndürme ve 100, 0 çevirme gösterilmektedir.

F döndürme ve çevirme

Burada da 100,0 çevirisi, %30 döndürme ve 2, 1 ölçeği

F döndürme ve ölçek

Sonuçlar tamamen farklıdır. Daha da kötüsü, ikinci örneğe ihtiyacımız olsaydı çevirmeyi, döndürmeyi ve ölçeği istenen yeni sırada uygulayan farklı bir gölgelendirici yazmamız gerekecekti. Benden çok daha akıllı insanlar var. Aynı şeyleri matris matematiğiyle de yapabileceğini fark ettiler. 2d için 3x3'lük bir matris kullanırız. 3x3'lük bir matris, 9 kutulu bir ızgara gibidir.

1.0 2,0 3,0
4.0 5,0 6.0
7,0 8.0 9.0

Matematiği yapmak için konumu matrisin sütunlarından aşağı doğru çarpıp sonuçları toplarız. Konumlarımız yalnızca 2 değere (x ve y) sahiptir. Ancak bunun için 3 değere ihtiyacımız vardır. Dolayısıyla, üçüncü değer için 1'i kullanacağız. Bu örnekte sonucumuz

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

Muhtemelen buna bakıyor ve "Amacı NE VAR?" diye düşünüyorsunuz. Diyelim ki bir çevirimiz var. Tx ve ty'nin çevrilmesini istediğimiz tutarı arayacağız. Şöyle bir matris oluşturalım:

1.00,00,0
0,01.00,0
txt1.0

Şimdi göz atın

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

Cebirinizi hatırlıyorsanız sıfırla çarpan her yeri silebiliriz. 1'le çarpma işlemi etkili bir sonuç vermez. Bu nedenle, neler olduğunu görmek için basit bir işlem yapalım.

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

Bunlarla pek ilgilenmediğimiz şeyler var. Bu şaşırtıcı bir şekilde, çeviri örneğimizdeki çeviri koduna benziyor. Benzer şekilde rotasyon yapalım. Rotasyon gönderisinde belirttiğimiz gibi, yalnızca döndürmek istediğimiz açının sinüsü ve kosinüsüne ihtiyacımız var.

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

Şöyle bir matris oluşturuyoruz:

c-sn.0,0
sn.c0,0
0,00,01.0

Matris uygulandığında şu sonucu elde ederiz:

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

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

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

Hepsi karartıldığında 0 ve 1 ile çarpılır

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

Rotasyon örneğimizde tam olarak bunu gördük. Son olarak da ölçeklendirme yapın. 2 ölçek faktörümüze sx ve sy adını veriyoruz. Şöyle bir matris oluşturuyoruz.

seks0,00,0
0,0sn0,0
0,00,01.0

Matris uygulandığında şu sonucu elde ederiz:

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

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

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

Bu da aslında

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

Bu, ölçeklendirme örneğimizle aynıdır. Hala düşünüyor olabileceğinizden eminim. Peki bu benim ne işime yarayacak? Bunun anlamı ne. Zaten aynısını yapmaya devam etmek için çok fazla emek istiyormuş gibi duruyor musunuz? İşte işin sırrı tam bu noktada devreye giriyor. Matrisleri birlikte çarpıp tüm dönüşümleri aynı anda uygulayabiliriz. İki matrisi alan matrixMultiply fonksiyonumuz olduğunu, iki matrisi çarpıp sonucu döndürdüğümüzü varsayalım. Konuyu daha netleştirmek için, çevirme, döndürme ve ölçekleme için matrisler oluşturacak fonksiyonlar oluşturalım.

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
  ];
}

Şimdi gölgelendiricimizi değiştirelim. Eski gölgelendirici böyle görünüyordu

<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;
  ...

Yeni gölgelendiricimiz çok daha basit olacak.

<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;
  ...

Şimdi, bunu nasıl kullandığımız

  // 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);
  }

"Peki ne?" diye soruyor olabilirsiniz. Bu pek büyük bir avantaj gibi görünmüyor . Ama şimdi sırayı değiştirmek istersek yeni bir gölgelendirici yazmamız gerekmiyor. Şimdi hesabı değiştirebiliriz.

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

Vücuttaki kollar, bir güneşin etrafındaki gezegende yer alan aylar veya ağaç dalları gibi hiyerarşik animasyonlarda bunun gibi matrisleri uygulayabilmek özellikle önemlidir. Basit bir hiyerarşik animasyon örneği olarak, "F" harfimizi 5 kez çizelim, ancak her defasında önceki "F" matrisiyle başlayalım.

  // 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);
    }
  }

Bunun için, kimlik matrisi oluşturan makeIdentity işlevini kullanıma sunduk. Özdeşlik matrisi, 1,0'ı etkin bir şekilde temsil eden bir matristir.Dolayısıyla, kimlikle çarptığınızda hiçbir şey olmaz. Aynı

X * 1 = X

öyle de

matrixX * identity = matrixX

Aşağıda, kimlik matrisi oluşturmak için gereken kod verilmiştir.

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

Bir örnek daha vermek isteriz. Şimdiye kadarki her örnekte 'F' harfi sol üst köşesi etrafında dönüyor. Bunun nedeni, kullandığımız matematiğin her zaman başlangıç noktası çevresinde dönmesi ve "F" harfimizin sol üst köşesinin başlangıç noktasında (0, 0) olmasıdır. Ancak artık, matris matematiği yapabildiğimiz ve dönüşümlerin uygulanma sırasını seçebildiğimiz için, geri kalan dönüşümler uygulanmadan önce başlangıç noktasını taşıyabiliriz.

    // 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);

Bu tekniği kullanarak herhangi bir noktadan döndürebilir veya ölçeklendirebilirsiniz. Artık Photoshop veya Flash'ın döndürme noktasını taşımanıza nasıl izin verdiğini biliyorsunuz. Haydi daha da çılgın olalım. WebGL ile ilgili temel bilgiler hakkındaki ilk makaleye dönerseniz, gölgelendiricide pikselleri klip alanına dönüştürmek için kullanabileceğimiz kod olduğunu hatırlayabilirsiniz.

  ...
  // 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);

Bu adımların her birine sırayla bakarsanız "pikselden 0,0'dan 1,0'a dönüştürme" adlı ilk adımın aslında bir ölçek işlemi olduğunu görürsünüz. İkincisi aynı zamanda bir ölçek işlemidir. Bir sonraki kısım bir çeviridir. Sonuncusu ise Y x -1 olarak ölçeklendirilir. Aslında bunu, gölgelendiriciye aktardığımız matriste yapabiliriz. Biri 1,0/çözünürlük değerine, diğeri 2,0 ile ölçeklendirmek üzere 2 ölçek matrisi yapabiliriz, 3'üncüsü -1,0, -1,0 ile çevirmek için 4'üncüsü de -1 ile çarparak hepsini bir araya getirebiliriz. Bunun yerine, matematik basit olduğu için belirli bir çözünürlük için doğrudan bir "projeksiyon" matrisi oluşturan bir fonksiyon yaparız.

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
  ];
}

Artık gölgelendiriciyi daha da basitleştirebiliriz. Yeni köşe gölgelendiricinin tamamı burada.

<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>

JavaScript'te ise kendimizi projeksiyon matrisi ile

  // 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);
    ...
  }

Çözünürlüğü belirleyen kodu da kaldırdık. Bu son adımla, 6-7 adımlık oldukça karmaşık bir gölgelendiriciden, matris matematiğinin büyüsünde yalnızca 1 adımdan oluşan çok basit bir gölgelendiriciye geçtik.

Bu makalenin, matris matematiğinin anlaşılmasına yardımcı olduğunu umuyorum. Şimdi 3D'ye geçeceğim. 3D matriste matematikte aynı ilkeler ve kullanımlar izlenir. Anlaşılması basit olmasını umuyorum, 2D ile başladım.