Transformasi WebGL

Gregg Tavares
Gregg Tavares

Terjemahan 2D WebGL

Sebelum kita beralih ke 3D, mari tetap gunakan 2D untuk waktu yang lebih lama. Tolong, saya tidak keberatan. Artikel ini mungkin terlihat sangat jelas bagi sebagian orang, tetapi saya akan menambahkan poin-poin ini ke beberapa artikel saja.

Artikel ini merupakan kelanjutan dari seri yang dimulai dengan Dasar-Dasar WebGL. Jika Anda belum membacanya, saya sarankan Anda membaca setidaknya bab pertama lalu kembali ke sini. Terjemahan adalah istilah matematika yang rumit yang pada dasarnya berarti "memindahkan" sesuatu. Saya kira memindahkan kalimat dari bahasa Inggris ke bahasa Jepang juga cocok, tetapi dalam kasus ini kita berbicara tentang memindahkan geometri. Dengan menggunakan kode contoh yang kita dapatkan di postingan pertama, Anda dapat dengan mudah menerjemahkan persegi panjang kita hanya dengan mengubah nilai yang diteruskan ke setRectangle, bukan? Berikut ini contoh berdasarkan contoh sebelumnya.

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

Sejauh ini tidak ada masalah. Tapi sekarang, bayangkan kita ingin melakukan hal yang sama dengan bentuk yang lebih rumit. Katakanlah kita ingin menggambar 'F' yang terdiri dari 6 segitiga seperti ini.

Huruf F

Nah, berikut ini adalah kode saat ini yang harus kita ubah {i>setRectangle<i} menjadi sesuatu yang lebih seperti ini.

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

Mudah-mudahan Anda melihat hal itu tidak akan diskalakan dengan baik. Jika ingin menggambar geometri yang sangat kompleks dengan ratusan atau ribuan baris, kita harus menulis beberapa kode yang cukup kompleks. Selain itu, setiap kali kita menggambar JavaScript harus memperbarui semua titiknya. Ada cara yang lebih sederhana. Cukup upload geometri dan lakukan terjemahan dalam shader. Berikut adalah shader barunya

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

dan kita akan sedikit merestrukturisasi {i>code<i}. Untuk satu, kita hanya perlu menetapkan geometri sekali.

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

Kemudian, kita hanya perlu memperbarui u_translation sebelum menggambar dengan terjemahan yang diinginkan.

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

Perhatikan bahwa setGeometry hanya dipanggil sekali. Objek tersebut tidak lagi berada di dalam drawScene.

Sekarang saat kita menggambar WebGL, melakukan hampir semua hal. Yang kami lakukan hanyalah menyetel terjemahan dan memintanya untuk menggambar. Bahkan jika geometri kita memiliki puluhan ribu titik, kode utamanya akan tetap sama.

Rotasi 2D WebGL

Saya akan mengakuinya di awal bahwa saya tidak tahu apakah bagaimana saya menjelaskan hal ini masuk akal, tetapi apa sih, mungkin juga mencoba.

Pertama-tama, kami ingin memperkenalkan Anda pada apa yang disebut dengan "lingkaran satuan". Jika Anda ingat matematika SMP Anda tadi (jangan tidurkan aku!) lingkaran memiliki radius. Radius lingkaran adalah jarak dari pusat lingkaran ke tepi. Lingkaran satuan adalah lingkaran dengan jari-jari 1,0.

Jika Anda ingat dari matematika dasar kelas 3 jika Anda mengalikan sesuatu dengan 1, hasilnya tetap sama. Jadi 123 * 1 = 123. Cukup mendasar, bukan? Nah, lingkaran satuan, lingkaran dengan jari-jari 1,0 juga merupakan bentuk 1. Nilainya adalah 1 yang berputar. Jadi Anda bisa mengalikan sesuatu dengan lingkaran satuan ini dan seperti mengalikan dengan 1 kecuali sihir terjadi dan benda-benda berputar. Kita akan mengambil nilai X dan Y tersebut dari titik mana pun di lingkaran unit dan kita akan mengalikan geometri dengannya dari contoh sebelumnya. Berikut adalah update untuk shader kami.

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

Kita juga memperbarui JavaScript agar dapat meneruskan kedua nilai tersebut.

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

Mengapa cara ini berhasil? Lihatlah perhitungannya.

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;

Mari kita tetap memiliki persegi panjang dan Anda ingin memutarnya. Sebelum Anda mulai memutarnya, sudut kanan atas adalah 3,0, 9,0. Mari kita pilih titik pada lingkaran satuan 30 derajat searah jarum jam dari jam 12.

Rotasi 30deg

Posisi pada lingkaran ada 0,50 dan 0,87

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

Di situlah kita memerlukannya

Gambar rotasi

Hal yang sama untuk 60 derajat searah jarum jam

Rotasi 60deg

Posisi pada lingkaran ada 0,87 dan 0,50

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

Anda dapat melihat bahwa saat kita memutar titik tersebut searah jarum jam ke kanan, nilai X menjadi lebih besar dan Y menjadi lebih kecil. Jika terus melewati 90 derajat, X akan mulai mengecil lagi dan Y akan mulai bertambah besar. Pola itu memberi kita rotasi. Ada nama lain untuk titik-titik pada lingkaran unit. Mereka disebut sinus dan kosinus. Jadi untuk sudut apa pun, kita cukup mencari sinus dan kosinus seperti ini.

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

Jika Anda menyalin dan menempelkan kode ke konsol JavaScript dan mengetik printSineAndCosignForAngle(30), Anda akan melihatnya mencetak s = 0.49 c= 0.87 (catatan: Saya membulatkan angkanya.) Jika menggabungkan semuanya, Anda dapat memutar geometri ke sudut mana pun yang diinginkan. Cukup setel rotasi ke sinus dan kosinus dari sudut yang ingin Anda putar.

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

Semoga jawaban ini masuk akal. Selanjutnya adalah pertanyaan yang lebih sederhana. Skalakan.

Apa itu radian?

Radian adalah satuan pengukuran yang digunakan dengan lingkaran, rotasi, dan sudut. Sama seperti kita yang dapat mengukur jarak dalam inci, yard, meter, dll., kita dapat mengukur sudut dalam derajat atau radian.

Anda mungkin menyadari bahwa matematika dengan pengukuran metrik lebih mudah daripada matematika dengan pengukuran imperial. Untuk mengubah satuan inci ke kaki, kita bagi dengan 12. Untuk mengubah satuan inci ke yard, kita bagi dengan 36. Saya tidak tahu dengan Anda, tapi saya tidak bisa membagi 36 di kepala saya. Dengan metrik, itu menjadi jauh lebih mudah. Untuk mengubah dari milimeter ke sentimeter, kita bagi dengan 10. Dari milimeter ke meter, kita bagi dengan 1000. Saya dapat membagi dengan 1.000 di kepala saya.

Radian vs derajat serupa. Gelar membuat matematika sulit. Radian membuat perhitungan menjadi mudah. Lingkaran memiliki 360 derajat, tetapi hanya ada 2π radian. Jadi satu putaran penuh adalah 2π radian. Setengah putaran adalah π radian. 1/4 putaran, yaitu 90 degress adalah π/2 radian. Jadi, jika Anda ingin memutar sesuatu 90 derajat, cukup gunakan Math.PI * 0.5. Jika Anda ingin memutarnya 45 derajat, gunakan Math.PI * 0.25, dll.

Hampir semua matematika yang melibatkan sudut, lingkaran, atau rotasi bekerja sangat mudah jika Anda mulai berpikir dalam radian. Jadi cobalah. Gunakan radian, bukan derajat kecuali di tampilan UI.

Skala 2D WebGL

Penskalaan sama mudahnya dengan penerjemahan.

Kita mengalikan posisi dengan skala yang diinginkan. Berikut adalah perubahan dari contoh kami sebelumnya.

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

dan menambahkan JavaScript yang diperlukan untuk mengatur skala saat kita menggambar.

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

Satu hal yang perlu diperhatikan adalah penskalaan menurut nilai negatif akan membalikkan geometri. Saya harap tiga bab terakhir ini bermanfaat dalam memahami terjemahan, rotasi, dan skala. Selanjutnya kita akan membahas keajaiban yaitu matriks yang menggabungkan ketiganya ke dalam bentuk yang jauh lebih sederhana dan sering kali lebih berguna.

Mengapa huruf 'F'?

Pertama kali aku melihat seseorang menggunakan 'F' pada tekstur. 'F' itu sendiri tidak penting. Yang penting adalah Anda dapat mengetahui orientasinya dari segala arah. Misalnya, jika kita menggunakan simbol hati ♥ atau segitiga △, kita tidak dapat mengetahui apakah simbol tersebut dibalik secara horizontal. Lingkaran ○ bisa lebih buruk lagi. Persegi panjang berwarna bisa dibilang bekerja dengan warna yang berbeda di setiap sudut, tetapi kemudian Anda harus mengingat sudut mana yang mana. Orientasi F langsung dapat dikenali.

Orientasi F

Bentuk apa pun yang bisa Anda anggap orientasinya akan berfungsi, saya baru saja menggunakan 'F' sejak saya 'pertama kali diperkenalkan dengan ide ini.

Matriks 2D WebGL

Dalam 3 bab terakhir, kita telah membahas cara menerjemahkan geometri, memutar geometri, dan geometri skala. Translasi, rotasi, dan skala masing-masing dianggap sebagai jenis 'transformasi'. Setiap transformasi ini memerlukan perubahan pada shader, dan masing-masing dari 3 transformasi tersebut bergantung pada urutan.

Sebagai contoh di sini adalah skala 2, 1, rotasi 30%, dan terjemahan 100, 0.

Rotasi dan translasi F

Dan di sini adalah terjemahan dari 100,0, rotasi 30% dan skala 2, 1

Rotasi dan skala F

Hasilnya benar-benar berbeda. Lebih buruk lagi, jika kita membutuhkan contoh kedua, kita harus menulis shader lain yang menerapkan terjemahan, rotasi, dan skala dalam urutan baru yang diinginkan. Nah, beberapa orang jauh lebih pintar, mereka tahu bahwa Anda dapat melakukan semua hal yang sama dengan matematika matriks. Untuk 2d, kita menggunakan matriks 3x3. Matriks 3x3 berbentuk seperti {i>grid<i} dengan 9 kotak.

1.0 2.0 3.0
4.0 5,0 6.0
7,0 8.0 9.0

Untuk melakukan perhitungan, kita mengalikan posisi ke bawah kolom matriks dan menjumlahkan hasilnya. Posisi kita hanya memiliki 2 nilai, x dan y, tetapi untuk melakukan perhitungan ini, kita perlu 3 nilai jadi kita akan menggunakan 1 untuk nilai ketiga. Dalam hal ini hasil kita adalah

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

Anda mungkin melihat hal itu dan berpikir "APA POINNYA". Mari asumsikan kita memiliki terjemahan. Kita sebut jumlah tx dan ty yang ingin diterjemahkan. Mari kita buat matriks seperti ini

1.00.00.0
0.01.00.0
txty1.0

Dan sekarang periksa

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

Jika Anda ingat aljabar, kita bisa menghapus tempat mana pun yang dikalikan dengan nol. Mengalikan dengan 1 secara efektif tidak melakukan apa pun, jadi mari kita sederhanakan untuk melihat apa yang terjadi

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

Dan kami tidak begitu penting. Hal itu terlihat mengejutkan seperti kode terjemahan dari contoh terjemahan kita. Demikian pula, mari kita lakukan rotasi. Seperti yang kami tunjukkan di postingan rotasi, kita hanya memerlukan sinus dan kosinus dari sudut tempat kita ingin memutarnya.

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

Dan kita membuat matriks seperti ini

c-s0.0
dc0.0
0.00.01.0

Dengan menerapkan matriks, kita mendapatkan

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

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

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

Menggelapkan semua kalikan dengan 0 dan 1s kita dapatkan

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

Yang persis seperti apa yang kita miliki di sampel rotasi. Dan yang terakhir, penskalaan. Kita akan memanggil 2 faktor skala sx dan sy Dan kita membuat matriks seperti ini

sx0.00.0
0.0sy0.0
0.00.01.0

Dengan menerapkan matriks, kita mendapatkan

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

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

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

yang benar-benar

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

Yang sama dengan contoh penskalaan kita. Sekarang saya yakin Anda mungkin masih berpikir. Lalu apa? Apa gunanya. Sepertinya banyak pekerjaan yang harus dilakukan untuk melakukan hal yang sama seperti yang sudah kita lakukan? Di sinilah keajaiban muncul. Ternyata kita bisa mengalikan beberapa matriks dan menerapkan semua transformasi sekaligus. Anggaplah kita memiliki fungsi, matrixMultiply, yang mengambil dua matriks, mengalikannya dan menampilkan hasilnya. Untuk membuat segalanya lebih jelas, mari kita buat fungsi untuk membuat matriks untuk translasi, rotasi, dan skala.

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

Sekarang, mari kita ubah shader. Shader lama terlihat seperti ini

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

Shader baru kami akan jauh lebih sederhana.

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

Dan inilah cara menggunakannya

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

Namun, Anda mungkin bertanya, lalu apa? Sepertinya hal itu tidak banyak bermanfaat . Namun, sekarang jika ingin mengubah urutan, kita tidak perlu menulis shader baru. Kita cukup mengubah perhitungannya.

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

Kemampuan untuk menerapkan matriks seperti ini sangat penting untuk animasi hierarkis seperti lengan di tubuh, bulan di planet di sekitar matahari, atau cabang di pohon. Untuk contoh sederhana animasi hierarkis memungkinkan kita menggambar 'F' sebanyak 5 kali, tetapi setiap kali mari kita mulai dengan matriks dari 'F' sebelumnya.

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

Untuk melakukannya, kami telah memperkenalkan fungsi, makeIdentity, yang membuat matriks identitas. Matriks identitas adalah matriks yang secara efektif mewakili 1,0 sehingga jika Anda mengalikan dengan identitas, tidak akan terjadi apa-apa. Sama seperti

X * 1 = X

begitu juga

matrixX * identity = matrixX

Berikut adalah kode untuk membuat matriks identitas.

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

Satu contoh lagi, Dalam setiap sampel sejauh ini, 'F' berputar di sekitar sudut kiri atas. Ini karena matematika yang kita gunakan selalu berputar di sekitar titik asal dan sudut kiri atas 'F' berada di titik asal, (0, 0) Namun sekarang, karena kita bisa melakukan perhitungan matriks dan kita bisa memilih urutan penerapan transformasi, kita bisa memindahkan titik asal sebelum transformasi lainnya diterapkan.

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

Dengan menggunakan teknik itu, Anda dapat memutar atau menskalakan dari titik mana pun. Sekarang Anda sudah tahu bagaimana Photoshop atau Flash memungkinkan Anda memindahkan titik rotasi. Ayo kita lebih gila. Jika kembali ke artikel pertama tentang dasar-dasar WebGL, Anda mungkin ingat bahwa kita memiliki kode dalam shader untuk mengonversi dari piksel ke clipspace yang terlihat seperti ini.

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

Jika Anda melihat setiap langkah tersebut secara bergantian, langkah pertama, "mengonversi dari {i>pixel<i} menjadi 0,0 ke 1,0", adalah benar-benar sebuah operasi skala. Yang kedua juga adalah operasi skala. Berikutnya adalah terjemahan dan skala terakhir Y dengan -1. Kita sebenarnya bisa melakukan itu semua dalam matriks yang kita teruskan ke shader. Kita dapat membuat 2 matriks skala, satu untuk menskalakan 1,0/resolusi, satu lagi untuk menskalakan 2,0, yang ke-3 untuk diterjemahkan dengan -1,0, -1,0 dan ke-4 untuk menskalakan Y dengan -1 lalu mengalikan semuanya bersama-sama tetapi sebagai gantinya, karena matematikanya sederhana, kita hanya akan membuat fungsi yang membuat matriks 'proyeksi' untuk resolusi yang diberikan secara langsung.

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

Sekarang kita dapat lebih menyederhanakan shader. Inilah seluruh shader verteks yang baru.

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

Di JavaScript, kita perlu mengalikan dengan matriks proyeksi

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

Kami juga menghapus kode yang menetapkan resolusi. Dengan langkah terakhir ini, kita telah beralih dari shader yang agak rumit dengan 6-7 langkah menjadi shader yang sangat sederhana dengan hanya 1 langkah yang dilakukan untuk keajaiban matriks matematika.

Saya harap artikel ini dapat membantu menjelaskan matematika matriks. Selanjutnya saya akan beralih ke 3D. Dalam matriks 3D, matematika mengikuti prinsip dan penggunaan yang sama. Saya mulai dengan 2D agar mudah dipahami.