Dasar-Dasar WebGL

Gregg Tavares
Gregg Tavares

Dasar-Dasar WebGL

WebGL memungkinkan Anda menampilkan grafik 3D realtime yang menakjubkan di browser Anda, tetapi yang tidak diketahui banyak orang adalah bahwa WebGL sebenarnya adalah API 2D, bukan API 3D. Izinkan saya menjelaskannya.

WebGL hanya memerlukan 2 hal. Koordinat ruang klip dalam 2D dan warna. Tugas Anda sebagai programmer yang menggunakan WebGL adalah menyediakan 2 hal tersebut untuk WebGL. Anda menyediakan 2 "shader" untuk melakukan ini. Shader Vertex yang menyediakan koordinat clipspace dan shader fragmen yang menyediakan warna. Koordinat Clipspace selalu dimulai dari -1 hingga +1, terlepas dari ukuran kanvas Anda. Berikut adalah contoh WebGL sederhana yang menampilkan WebGL dalam bentuk paling sederhana.

// Get A WebGL context
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("experimental-webgl");

// setup a GLSL program
var vertexShader = createShaderFromScriptElement(gl, "2d-vertex-shader");
var fragmentShader = createShaderFromScriptElement(gl, "2d-fragment-shader");
var program = createProgram(gl, [vertexShader, fragmentShader]);
gl.useProgram(program);

// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");

// Create a buffer and put a single clipspace rectangle in
// it (2 triangles)
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([
        -1.0, -1.0,
         1.0, -1.0,
        -1.0,  1.0,
        -1.0,  1.0,
         1.0, -1.0,
         1.0,  1.0]),
    gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

// draw
gl.drawArrays(gl.TRIANGLES, 0, 6);

Berikut adalah 2 shader tersebut

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

void main() {
  gl_Position = vec4(a_position, 0, 1);
}
</script>

<script id="2d-fragment-shader" type="x-shader/x-fragment">
void main() {
  gl_FragColor = vec4(0,1,0,1);  // green
}
</script>

Sekali lagi, koordinat clipspace selalu dimulai dari -1 hingga +1, terlepas dari ukuran kanvas. Dalam kasus di atas, Anda dapat melihat bahwa kita tidak melakukan apa pun selain meneruskan data posisi secara langsung. Karena data posisi sudah ada di clipspace, tidak ada pekerjaan yang perlu dilakukan. Jika Anda ingin 3D, terserah Anda untuk menyediakan shader yang dikonversi dari 3D ke 2D karena WebGL ADALAH API 2D! Untuk elemen 2D, Anda mungkin lebih suka bekerja dalam piksel daripada clipspace. Jadi, mari kita ubah shader sehingga kita dapat menyediakan persegi panjang dalam piksel dan mengonversinya menjadi clipspace. Berikut adalah shader verteks yang baru

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

uniform vec2 u_resolution;

void main() {
   // convert the rectangle from pixels to 0.0 to 1.0
   vec2 zeroToOne = a_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, 0, 1);
}
</script>

Sekarang kita dapat mengubah data dari clipspace ke {i>pixel<i}

// set the resolution
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);

// setup a rectangle from 10,20 to 80,30 in pixels
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    10, 20,
    80, 20,
    10, 30,
    10, 30,
    80, 20,
    80, 30]), gl.STATIC_DRAW);

Anda mungkin melihat persegi panjang berada di dekat bagian bawah area tersebut. WebGL menganggap sudut kiri bawah sebagai 0,0. Untuk membuatnya menjadi sudut kiri atas yang lebih tradisional yang digunakan untuk API grafis 2d, kami hanya perlu membalik koordinat y.

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

Mari kita membuat kode yang mendefinisikan persegi panjang menjadi sebuah fungsi sehingga kita bisa memanggilnya untuk persegi panjang dengan ukuran yang berbeda. Selagi menunggu, kita akan mengaturnya agar dapat disetel. Pertama, kita membuat shader fragmen mengambil input seragam warna.

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

uniform vec4 u_color;

void main() {
   gl_FragColor = u_color;
}
</script>

Dan inilah kode baru yang menggambar 50 persegi panjang di tempat acak dan warna acak.

...

  var colorLocation = gl.getUniformLocation(program, "u_color");
  ...
  // Create a buffer
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

  // draw 50 random rectangles in random colors
  for (var ii = 0; ii < 50; ++ii) {
    // Setup a random rectangle
    setRectangle(
        gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));

    // Set a random color.
    gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1);

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

// Returns a random integer from 0 to range - 1.
function randomInt(range) {
  return Math.floor(Math.random() * range);
}

// Fills the buffer with the values that define a rectangle.
function setRectangle(gl, x, y, width, height) {
  var x1 = x;
  var x2 = x + width;
  var y1 = y;
  var y2 = y + height;
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
     x1, y1,
     x2, y1,
     x1, y2,
     x1, y2,
     x2, y1,
     x2, y2]), gl.STATIC_DRAW);
}

Saya harap Anda dapat melihat bahwa WebGL adalah API yang cukup sederhana. Meskipun bisa menjadi lebih rumit melakukan 3D, detail yang ditambahkan oleh Anda, sebagai programmer, dalam bentuk shader yang lebih kompleks. WebGL API sendiri bersifat 2D dan cukup sederhana.

Apa arti type="x-shader/x-vertex" dan type="x-shader/x-fragment"?

Tag <script> secara default berisi JavaScript. Anda tidak dapat menempatkan jenis atau Anda dapat menempatkan type="javascript" atau type="text/javascript" dan browser akan menafsirkan konten sebagai JavaScript. Jika Anda memasukkan hal lain, browser akan mengabaikan konten tag skrip.

Kita dapat menggunakan fitur ini untuk menyimpan shader dalam tag skrip. Lebih baik lagi, kita bisa membuat jenis kita sendiri dan dalam javascript mencarinya untuk memutuskan apakah akan mengompilasi shader sebagai shader verteks atau shader fragmen.

Dalam hal ini, fungsi createShaderFromScriptElement mencari skrip dengan id yang ditentukan, lalu melihat type untuk menentukan jenis shader yang akan dibuat.

Pemrosesan Gambar WebGL

Pemrosesan gambar mudah di WebGL. Seberapa mudahnya? Baca penjelasan di bawah.

Untuk menggambar gambar di WebGL, kita perlu menggunakan tekstur. Demikian pula dengan cara WebGL mengharapkan koordinat clipspace saat merender, bukan piksel, WebGL mengharapkan koordinat tekstur saat membaca tekstur. Koordinat tekstur berkisar dari 0,0 menjadi 1,0, tidak peduli dimensi tekstur. Karena kita hanya menggambar satu persegi panjang (yah, 2 segitiga), kita perlu memberi tahu WebGL di mana dalam tekstur yang sesuai dengan setiap titik dalam persegi panjang. Kita akan meneruskan informasi ini dari shader verteks ke shader fragmen menggunakan jenis variabel khusus yang disebut 'varying'. Fungsi ini disebut {i>variety<i} karena sangat bervariasi. WebGL akan menginterpolasi nilai yang kami berikan dalam shader verteks saat menggambar setiap piksel menggunakan shader fragmen. Dengan menggunakan shader verteks dari akhir bagian sebelumnya, kita perlu menambahkan atribut untuk meneruskan koordinat tekstur, lalu meneruskannya ke shader fragmen.

attribute vec2 a_texCoord;
...
varying vec2 v_texCoord;

void main() {
   ...
   // pass the texCoord to the fragment shader
   // The GPU will interpolate this value between points
   v_texCoord = a_texCoord;
}

Kemudian, kita menyediakan shader fragmen untuk mencari warna dari tekstur.

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   // Look up a color from the texture.
   gl_FragColor = texture2D(u_image, v_texCoord);
}
</script>

Terakhir, kita harus memuat gambar, membuat tekstur, dan menyalin gambar ke dalam tekstur. Karena kita berada di gambar browser yang dimuat secara asinkron, kita harus sedikit mengatur ulang kode untuk menunggu tekstur dimuat. Setelah dimuat, kita akan menggambarnya.

function main() {
  var image = new Image();
  image.src = "http://someimage/on/our/server";  // MUST BE SAME DOMAIN!!!
  image.onload = function() {
    render(image);
  }
}

function render(image) {
  ...
  // all the code we had before.
  ...
  // look up where the texture coordinates need to go.
  var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");

  // provide texture coordinates for the rectangle.
  var texCoordBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
      0.0,  0.0,
      1.0,  0.0,
      0.0,  1.0,
      0.0,  1.0,
      1.0,  0.0,
      1.0,  1.0]), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(texCoordLocation);
  gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

  // Create a texture.
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set the parameters so we can render any size image.
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  // Upload the image into the texture.
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  ...
}

Tidak terlalu menarik, jadi mari kita manipulasi gambar itu. Bagaimana kalau kita mengganti warna merah dan biru saja?

...
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
...

Bagaimana jika kita ingin melakukan pemrosesan gambar yang benar-benar melihat piksel lain? Karena WebGL mereferensikan tekstur dalam koordinat tekstur yang berkisar antara 0,0 hingga 1,0, maka kita dapat menghitung seberapa banyak gerakan untuk 1 piksel dengan perhitungan sederhana onePixel = 1.0 / textureSize. Berikut adalah shader fragmen yang menghitung rata-rata piksel kiri dan kanan dari setiap piksel dalam tekstur.

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   // compute 1 pixel in texture coordinates.
   vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;

   // average the left, middle, and right pixels.
   gl_FragColor = (
       texture2D(u_image, v_texCoord) +
       texture2D(u_image, v_texCoord + vec2(onePixel.x, 0.0)) +
       texture2D(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
}
</script>

Selanjutnya, kita perlu meneruskan ukuran tekstur dari JavaScript.

...
var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
...
// set the size of the image
gl.uniform2f(textureSizeLocation, image.width, image.height);
...

Setelah mengetahui cara mereferensikan piksel lain, mari kita gunakan kernel konvolusi untuk melakukan banyak pemrosesan gambar umum. Dalam hal ini kita akan menggunakan {i>kernel<i} 3x3. Kernel konvolusi adalah matriks 3x3 di mana setiap entri dalam matriks mewakili berapa banyak untuk mengalikan 8 piksel di sekitar piksel yang kita render. Kemudian kita membagi hasilnya dengan bobot {i>kernel<i} (jumlah semua nilai dalam {i>kernel<i}) atau 1,0, mana pun yang lebih besar. Berikut artikel yang cukup bagus tentang hal tersebut. Dan berikut artikel lain yang menunjukkan beberapa kode aktual jika Anda menulis ini secara manual di C++. Dalam kasus ini, kita akan melakukan pekerjaan itu dalam shader, jadi inilah shader fragmen yang baru.

<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;

// our texture
uniform sampler2D u_image;
uniform vec2 u_textureSize;
uniform float u_kernel[9];

// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;

void main() {
   vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
   vec4 colorSum =
     texture2D(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
     texture2D(u_image, v_texCoord + onePixel * vec2(-1,  0)) * u_kernel[3] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 0,  0)) * u_kernel[4] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 1,  0)) * u_kernel[5] +
     texture2D(u_image, v_texCoord + onePixel * vec2(-1,  1)) * u_kernel[6] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 0,  1)) * u_kernel[7] +
     texture2D(u_image, v_texCoord + onePixel * vec2( 1,  1)) * u_kernel[8] ;
   float kernelWeight =
     u_kernel[0] +
     u_kernel[1] +
     u_kernel[2] +
     u_kernel[3] +
     u_kernel[4] +
     u_kernel[5] +
     u_kernel[6] +
     u_kernel[7] +
     u_kernel[8] ;

   if (kernelWeight <= 0.0) {
     kernelWeight = 1.0;
   }

   // Divide the sum by the weight but just use rgb
   // we'll set alpha to 1.0
   gl_FragColor = vec4((colorSum / kernelWeight).rgb, 1.0);
}
</script>

Dalam JavaScript, kita perlu menyediakan kernel konvolusi.

...
var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
...
var edgeDetectKernel = [
    -1, -1, -1,
    -1,  8, -1,
    -1, -1, -1
];
gl.uniform1fv(kernelLocation, edgeDetectKernel);
...

Saya harap hal ini meyakinkan Anda bahwa pemrosesan gambar di WebGL cukup sederhana. Selanjutnya, saya akan membahas cara menerapkan lebih dari satu efek pada gambar.

Ada apa dengan awalan a, u, dan v_ dari variabel dalam GLSL?

Itu hanyalah konvensi penamaan. a_ untuk atribut yang merupakan data yang disediakan oleh buffer. u_ untuk uniform yang merupakan input ke shader, v_ untuk variasi yang merupakan nilai yang diteruskan dari shader verteks ke shader fragmen dan interpolasi (atau bervariasi) antara verteks untuk setiap piksel yang digambar.

Menerapkan beberapa efek

Pertanyaan paling jelas berikutnya untuk pemrosesan gambar adalah bagaimana cara menerapkan banyak efek?

Nah, Anda bisa mencoba membuat shader dengan cepat. Sediakan UI yang memungkinkan pengguna memilih efek yang ingin digunakan, lalu hasilkan shader yang melakukan semua efek tersebut. Hal ini mungkin tidak selalu dapat dilakukan, meskipun teknik ini sering digunakan untuk membuat efek untuk grafik real time. Cara yang lebih fleksibel adalah dengan menggunakan 2 tekstur lainnya dan merender ke setiap tekstur secara bergantian, melakukan ping ke depan dan ke belakang, serta menerapkan efek berikutnya setiap kalinya.

Original Image -> [Blur]        -> Texture 1
Texture 1      -> [Sharpen]     -> Texture 2
Texture 2      -> [Edge Detect] -> Texture 1
Texture 1      -> [Blur]        -> Texture 2
Texture 2      -> [Normal]      -> Canvas

Untuk melakukan ini, kita perlu membuat framebuffer. Di WebGL dan OpenGL, Framebuffer sebenarnya adalah nama yang buruk. WebGL/OpenGL Framebuffer sebenarnya hanyalah kumpulan status dan bukan buffer apa pun. Namun, dengan melampirkan tekstur ke framebuffer, kita dapat merender ke dalam tekstur tersebut. Pertama, mari ubah kode pembuatan tekstur lama menjadi fungsi

function createAndSetupTexture(gl) {
  var texture = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // Set up texture so we can render any size image and so we are
  // working with pixels.
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

  return texture;
}

// Create a texture and put the image in it.
var originalImageTexture = createAndSetupTexture(gl);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

Sekarang, mari kita gunakan fungsi tersebut untuk membuat 2 tekstur lagi dan melampirkannya ke 2 framebuffer.

// create 2 textures and attach them to framebuffers.
var textures = [];
var framebuffers = [];
for (var ii = 0; ii < 2; ++ii) {
  var texture = createAndSetupTexture(gl);
  textures.push(texture);

  // make the texture the same size as the image
  gl.texImage2D(
      gl.TEXTURE_2D, 0, gl.RGBA, image.width, image.height, 0,
      gl.RGBA, gl.UNSIGNED_BYTE, null);

  // Create a framebuffer
  var fbo = gl.createFramebuffer();
  framebuffers.push(fbo);
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

  // Attach a texture to it.
  gl.framebufferTexture2D(
      gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
}

Sekarang, mari kita buat satu set {i>kernel<i} dan kemudian daftarnya untuk diterapkan.

// Define several convolution kernels
var kernels = {
  normal: [
    0, 0, 0,
    0, 1, 0,
    0, 0, 0
  ],
  gaussianBlur: [
    0.045, 0.122, 0.045,
    0.122, 0.332, 0.122,
    0.045, 0.122, 0.045
  ],
  unsharpen: [
    -1, -1, -1,
    -1,  9, -1,
    -1, -1, -1
  ],
  emboss: [
     -2, -1,  0,
     -1,  1,  1,
      0,  1,  2
  ]
};

// List of effects to apply.
var effectsToApply = [
  "gaussianBlur",
  "emboss",
  "gaussianBlur",
  "unsharpen"
];

Dan akhirnya, mari terapkan satu per satu, dengan melakukan ping pada tekstur yang juga sedang kita render

// start with the original image
gl.bindTexture(gl.TEXTURE_2D, originalImageTexture);

// don't y flip images while drawing to the textures
gl.uniform1f(flipYLocation, 1);

// loop through each effect we want to apply.
for (var ii = 0; ii < effectsToApply.length; ++ii) {
  // Setup to draw into one of the framebuffers.
  setFramebuffer(framebuffers[ii % 2], image.width, image.height);

  drawWithKernel(effectsToApply[ii]);

  // for the next draw, use the texture we just rendered to.
  gl.bindTexture(gl.TEXTURE_2D, textures[ii % 2]);
}

// finally draw the result to the canvas.
gl.uniform1f(flipYLocation, -1);  // need to y flip for canvas
setFramebuffer(null, canvas.width, canvas.height);
drawWithKernel("normal");

function setFramebuffer(fbo, width, height) {
  // make this the framebuffer we are rendering to.
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

  // Tell the shader the resolution of the framebuffer.
  gl.uniform2f(resolutionLocation, width, height);

  // Tell webgl the viewport setting needed for framebuffer.
  gl.viewport(0, 0, width, height);
}

function drawWithKernel(name) {
  // set the kernel
  gl.uniform1fv(kernelLocation, kernels[name]);

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

Beberapa hal yang harus saya bahas.

Memanggil gl.bindFramebuffer dengan null akan memberi tahu WebGL bahwa Anda ingin merender ke kanvas, bukan ke salah satu framebuffer Anda. WebGL harus mengonversi dari clipspace kembali menjadi piksel. Hal ini dilakukan berdasarkan setelan gl.viewport. Setelan gl.viewport secara default disetel ke ukuran kanvas saat kita melakukan inisialisasi WebGL. Karena framebuffer yang kita render memiliki ukuran yang berbeda, kanvas yang perlu kita setel area pandang dengan tepat. Terakhir, dalam contoh dasar WebGL, kami membalik koordinat Y saat merender karena WebGL menampilkan kanvas dengan 0,0 menjadi sudut kiri bawah, bukan yang lebih tradisional untuk 2D kiri atas. Hal itu tidak diperlukan saat merender ke framebuffer. Karena framebuffer tidak pernah ditampilkan, bagian mana yang atas dan bawah tidak relevan. Yang penting adalah bahwa piksel 0,0 dalam framebuffer sesuai dengan 0,0 dalam perhitungan kita. Untuk mengatasi hal ini, saya memungkinkan untuk mengatur apakah akan membalik atau tidak dengan menambahkan satu input lagi ke dalam shader.

<script id="2d-vertex-shader" type="x-shader/x-vertex">
...
uniform float u_flipY;
...

void main() {
   ...
   gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
   ...
}
</script>

Lalu, kita bisa menyetelnya saat merender dengan

...
var flipYLocation = gl.getUniformLocation(program, "u_flipY");
...
// don't flip
gl.uniform1f(flipYLocation, 1);
...
// flip
gl.uniform1f(flipYLocation, -1);

Saya membuat contoh ini tetap sederhana dengan menggunakan program GLSL tunggal yang dapat mencapai banyak efek. Jika Anda ingin melakukan pemrosesan gambar secara utuh, Anda mungkin memerlukan banyak program GLSL. Program untuk penyesuaian hue, saturasi, dan luminans. Satu lagi untuk kecerahan dan kontras. Satu untuk pembalikan, satu lagi untuk menyesuaikan level, dll. Anda harus mengubah kode untuk mengalihkan program GLSL dan memperbarui parameter untuk program tertentu. Saya akan mempertimbangkan untuk menulis contoh tersebut, tetapi ini adalah latihan yang sebaiknya diserahkan kepada pembaca karena beberapa program GLSL yang masing-masing memiliki kebutuhan parameternya sendiri mungkin berarti beberapa pemfaktoran ulang besar agar semuanya tidak menjadi berantakan. Saya harap hal ini dan contoh sebelumnya telah membuat WebGL sedikit lebih mudah dipahami dan saya harap memulai dengan 2D membantu membuat WebGL sedikit lebih mudah dipahami. Jika saya menemukan waktunya, saya akan mencoba menulis beberapa artikel lagi tentang cara melakukan 3D serta detail lebih lanjut tentang apa yang sebenarnya dilakukan WebGL di balik layar.

WebGL dan Alfa

Saya memperhatikan beberapa developer OpenGL yang mengalami masalah dengan cara WebGL memperlakukan alfa di backbuffer (yakni kanvas), jadi saya pikir mungkin lebih baik untuk membahas beberapa perbedaan antara WebGL dan OpenGL yang terkait dengan alfa.

Perbedaan terbesar antara OpenGL dan WebGL adalah bahwa OpenGL merender ke backbuffer yang tidak dikomposisikan dengan apa pun, atau secara efektif tidak digabungkan dengan apa pun oleh pengelola jendela OS, jadi tidak masalah apa alfa Anda. WebGL digabungkan oleh browser dengan halaman web dan default adalah menggunakan tag alfa pra-dikalikan sama dengan tag <img> .png2. WebGL memiliki beberapa cara untuk membuatnya seperti OpenGL.

#1) Memberi tahu WebGL bahwa Anda ingin WebGL dikomposisi dengan alfa yang tidak diperbanyak

gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});

Nilai defaultnya adalah benar (true). Tentu saja hasilnya akan tetap dikomposisi di atas halaman dengan warna latar belakang apa pun yang berakhir di bawah kanvas (warna latar belakang kanvas, warna latar belakang container kanvas, warna latar belakang halaman, hal di belakang kanvas jika kanvas memiliki z-index > 0, dll...) dengan kata lain, warna yang didefinisikan oleh CSS untuk area halaman web tersebut. Cara yang bagus untuk mengetahui apakah Anda memiliki masalah alfa adalah dengan menyetel latar belakang kanvas ke warna cerah seperti merah. Anda akan langsung melihat apa yang terjadi.

<canvas style="background: red;"></canvas>

Anda juga dapat menyetelnya ke hitam yang akan menyembunyikan masalah alfa apa pun yang Anda miliki.

#2) Memberi tahu WebGL bahwa Anda tidak menginginkan alfa di backbuffer

gl = canvas.getContext("experimental-webgl", {alpha: false});

Hal ini akan membuatnya berfungsi lebih seperti OpenGL karena backbuffer hanya akan memiliki RGB. Ini mungkin opsi terbaik karena browser yang baik dapat melihat bahwa Anda tidak memiliki alfa dan benar-benar mengoptimalkan cara WebGL disusun. Tentu saja itu juga berarti bahwa ia tidak akan memiliki alfa di backbuffer, jadi jika Anda menggunakan alfa di backbuffer untuk beberapa tujuan, hal itu mungkin tidak berhasil untuk Anda. Beberapa aplikasi yang saya ketahui menggunakan alfa di backbuffer. Saya pikir mungkin ini seharusnya menjadi {i>default<i}.

3) Menghapus alfa di akhir rendering

..
renderScene();
..
// Set the backbuffer's alpha to 1.0
gl.clearColor(1, 1, 1, 1);
gl.colorMask(false, false, false, true);
gl.clear(gl.COLOR_BUFFER_BIT);

Proses penghapusan umumnya sangat cepat karena ada kasus khusus untuk hal ini di sebagian besar hardware. Saya melakukannya di sebagian besar demo saya. Kalau saya pintar, saya akan beralih ke metode #2 di atas. Mungkin saya akan melakukannya setelah memposting ini. Sepertinya sebagian besar library WebGL ditetapkan secara default ke metode ini. Beberapa developer yang benar-benar menggunakan alfa untuk pengomposisian efek dapat memintanya. Sisanya hanya akan mendapatkan performa terbaik dan sedikit kejutan.

#4) Menghapus alfa sekali, lalu tidak merendernya lagi

// At init time. Clear the back buffer.
gl.clearColor(1,1,1,1);
gl.clear(gl.COLOR_BUFFER_BIT);

// Turn off rendering to alpha
gl.colorMask(true, true, true, false);

Tentu saja jika Anda merender ke framebuffer Anda sendiri, Anda mungkin perlu mengaktifkan kembali rendering ke alfa lalu menonaktifkannya lagi saat beralih ke rendering ke kanvas.

5. Menangani Gambar

Selain itu, jika Anda memuat file PNG dengan alfa ke dalam tekstur, defaultnya adalah alfa-nya dikali sebelumnya yang umumnya BUKAN cara sebagian besar game melakukan sesuatu. Jika Anda ingin mencegah perilaku itu, Anda perlu memberi tahu WebGL dengan

gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);

#6) Menggunakan persamaan pencampuran yang dapat digunakan dengan alfa pra-perkalian

Hampir semua aplikasi OpenGL yang telah saya tulis atau gunakan

gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);

Ini berfungsi untuk tekstur alfa yang tidak dikalikan sebelumnya. Jika Anda ingin menggunakan tekstur alfa pra-kalimat, maka Anda mungkin ingin

gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);

Itu metode yang saya ketahui. Jika Anda mengetahui lebih banyak, silakan posting di bawah ini.