WebGL memungkinkan tampilan grafis 3D real-time yang luar biasa di browser Anda, tetapi banyak orang tidak tahu bahwa WebGL sebenarnya adalah API 2D, bukan API 3D. Berikut ini penjelasannya.
WebGL hanya mementingkan 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 melakukannya. Shader vertex yang menyediakan koordinat ruang klip dan shader fragmen yang menyediakan warna. Koordinat ruang klip selalu berkisar dari -1 hingga +1, berapa pun ukuran kanvas Anda. Berikut adalah contoh WebGL sederhana yang menunjukkan 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
<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 ruang klip selalu berkisar dari -1 hingga +1, berapa pun ukuran kanvasnya. Dalam kasus di atas, Anda dapat melihat bahwa kita hanya meneruskan data posisi secara langsung. Karena data posisi sudah ada di ruang klip, tidak ada pekerjaan yang harus dilakukan. Jika Anda menginginkan 3D, Anda harus menyediakan shader yang mengonversi dari 3D ke 2D karena WebGL ADALAH API 2D. Untuk hal-hal 2D, Anda mungkin lebih suka bekerja dalam piksel daripada clipspace, jadi mari kita ubah shader agar kita dapat menyediakan persegi panjang dalam piksel dan mengonversinya ke clipspace untuk kita. Berikut shader verteks 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 menjadi piksel
// 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, kita cukup membalik koordinat y.
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
Mari kita buat kode yang menentukan persegi panjang menjadi fungsi sehingga kita dapat memanggilnya untuk persegi panjang dengan ukuran berbeda. Sementara itu, kita akan membuat warna dapat disetel. Pertama, kita membuat shader fragmen mengambil input uniform 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>
Berikut 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);
}
Semoga Anda dapat melihat bahwa WebGL sebenarnya adalah API yang cukup sederhana. Meskipun melakukan 3D bisa menjadi lebih rumit, kerumitan tersebut ditambahkan oleh Anda, sang programmer, dalam bentuk shader yang lebih kompleks. WebGL API itu sendiri bersifat 2D dan cukup sederhana.
Apa yang dimaksud dengan type="x-shader/x-vertex" dan type="x-shader/x-fragment"?
Tag <script> secara default memiliki JavaScript di dalamnya. Anda dapat tidak memasukkan jenis atau Anda dapat memasukkan 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. Bahkan, kita dapat membuat jenis kita sendiri dan mencarinya di JavaScript untuk memutuskan apakah akan mengompilasi shader sebagai shader vertex atau shader fragmen.
Dalam hal ini, fungsi createShaderFromScriptElement mencari skrip dengan id yang ditentukan, lalu melihat type untuk memutuskan jenis shader yang akan dibuat.
Pemrosesan gambar WebGL
Pemrosesan gambar mudah dilakukan di WebGL. Seberapa mudah? Baca di bawah.
Untuk menggambar gambar di WebGL, kita perlu menggunakan tekstur. Sama seperti WebGL yang mengharapkan koordinat clipspace saat merender, bukan piksel, WebGL mengharapkan koordinat tekstur saat membaca tekstur. Koordinat tekstur berkisar dari 0,0 hingga 1,0, berapa pun dimensi teksturnya. Karena kita hanya menggambar satu persegi panjang (atau 2 segitiga), kita perlu memberi tahu WebGL tempat di tekstur yang sesuai dengan setiap titik di persegi panjang. Kita akan meneruskan informasi ini dari shader vertex ke shader fragmen menggunakan jenis variabel khusus yang disebut 'varying'. Disebut bervariasi karena bervariasi. WebGL akan menginterpolasi nilai yang kita berikan di shader verteks saat menggambar setiap piksel menggunakan shader fragmen. Dengan menggunakan shader vertex 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 perlu memuat gambar, membuat tekstur, dan menyalin gambar ke dalam tekstur. Karena kita berada di browser, gambar dimuat secara asinkron sehingga 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 tersebut. Bagaimana kalau hanya menukar warna merah dan biru?
...
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 dari 0,0 hingga 1,0, kita dapat menghitung seberapa banyak pergerakan untuk 1 piksel dengan matematika 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>
Kemudian, 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 kita mengetahui cara mereferensikan piksel lain, mari kita gunakan kernel konvolusi untuk melakukan berbagai pemrosesan gambar umum. Dalam hal ini, kita akan menggunakan kernel 3x3. Kernel konvolusi hanyalah matriks 3x3 dengan setiap entri dalam matriks mewakili seberapa banyak 8 piksel di sekitar piksel yang kita render harus dikalikan. Kemudian, kita membagi hasilnya dengan bobot kernel (jumlah semua nilai dalam kernel) atau 1,0, mana saja yang lebih besar. Berikut artikel yang cukup bagus tentang hal ini. Dan berikut artikel lain yang menunjukkan beberapa kode aktual jika Anda menulisnya secara manual di C++. Dalam kasus ini, kita akan melakukan pekerjaan tersebut di shader, jadi berikut 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>
Di 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);
...
Semoga ini meyakinkan Anda bahwa pemrosesan gambar di WebGL cukup sederhana. Selanjutnya, saya akan membahas cara menerapkan lebih dari satu efek ke gambar.
Apa arti awalan a, u, dan v_ di depan 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 varying yang merupakan nilai yang diteruskan dari shader verteks ke shader fragmen dan diinterpolasi (atau divariasikan) di antara verteks untuk setiap piksel yang digambar.
Menerapkan beberapa efek
Pertanyaan berikutnya yang paling jelas untuk pemrosesan gambar adalah bagaimana cara menerapkan beberapa efek?
Anda dapat mencoba membuat shader secara langsung. Sediakan UI yang memungkinkan pengguna memilih efek yang ingin digunakan, lalu buat shader yang melakukan semua efek tersebut. Hal itu mungkin tidak selalu memungkinkan, meskipun teknik tersebut sering digunakan untuk membuat efek untuk grafis real-time. Cara yang lebih fleksibel adalah menggunakan 2 tekstur lagi dan merender ke setiap tekstur secara bergantian, melakukan ping pong bolak-balik dan menerapkan efek berikutnya setiap kali.
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 melakukannya, kita perlu membuat framebuffer. Di WebGL dan OpenGL, Framebuffer sebenarnya adalah nama yang kurang tepat. Framebuffer WebGL/OpenGL sebenarnya hanyalah kumpulan status dan bukan buffer dalam bentuk apa pun. Namun, dengan melampirkan tekstur ke framebuffer, kita dapat merender ke dalam tekstur tersebut. Pertama, mari kita 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 memasangnya 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 sekumpulan kernel, lalu daftar kernel yang akan 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"
];
Terakhir, mari kita terapkan setiap tekstur, dengan bergantian merender tekstur yang kita gunakan
// 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.
WebGL harus mengonversi dari clipspace kembali ke piksel. Hal ini dilakukan berdasarkan setelan gl.viewport. Setelan gl.viewport ditetapkan secara default ke ukuran kanvas saat kita melakukan inisialisasi WebGL. Karena framebuffer yang kita render memiliki ukuran yang berbeda dengan kanvas, kita perlu menyetel area pandang dengan tepat.
Terakhir, dalam contoh dasar-dasar WebGL, kami membalik koordinat Y saat merender karena WebGL menampilkan kanvas dengan 0,0 sebagai sudut kiri bawah, bukan sudut kiri atas yang lebih umum untuk 2D. Hal ini tidak diperlukan saat merender ke framebuffer. Karena framebuffer tidak pernah ditampilkan, bagian mana yang berada di atas dan bawah tidak relevan. Yang penting adalah piksel 0,0 di framebuffer sesuai dengan 0,0 dalam penghitungan kita. Untuk mengatasinya, saya memungkinkan pengaturan apakah akan membalik atau tidak dengan menambahkan satu input lagi ke 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 dapat menetapkannya 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 satu program GLSL yang dapat mencapai beberapa efek. Jika ingin melakukan pemrosesan gambar secara penuh, Anda mungkin memerlukan banyak program GLSL. Program untuk penyesuaian hue, saturasi, dan luminans. Satu lagi untuk kecerahan dan kontras. Satu untuk inversi, satu lagi untuk menyesuaikan level, dll. Anda perlu mengubah kode untuk beralih program GLSL dan memperbarui parameter untuk program tersebut. Saya telah 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 refaktorisasi besar untuk mencegah semuanya menjadi berantakan. Semoga contoh ini dan contoh sebelumnya membuat WebGL tampak sedikit lebih mudah dipahami dan semoga memulai dengan 2D membantu membuat WebGL sedikit lebih mudah dipahami. Jika sempat, saya akan mencoba menulis beberapa artikel lagi tentang cara membuat 3D serta detail selengkapnya tentang cara kerja WebGL di balik layar.
WebGL dan alfa
Saya melihat beberapa developer OpenGL mengalami masalah terkait cara WebGL memperlakukan alfa di backbuffer (yaitu, kanvas), jadi saya rasa sebaiknya kita membahas beberapa perbedaan antara WebGL dan OpenGL terkait alfa.
Perbedaan terbesar antara OpenGL dan WebGL adalah OpenGL merender ke backbuffer yang tidak dikomposisikan dengan apa pun, atau secara efektif tidak dikomposisikan dengan apa pun oleh pengelola jendela OS, sehingga tidak masalah apa pun alfa Anda.
WebGL dikomposisikan oleh browser dengan halaman web dan defaultnya adalah menggunakan alfa yang telah dikalikan sebelumnya sama seperti tag <img> .png dengan transparansi dan tag kanvas 2d.
WebGL memiliki beberapa cara untuk membuat hal ini lebih mirip dengan OpenGL.
#1. Memberi tahu WebGL bahwa Anda ingin menggabungkannya dengan alfa yang tidak dipramultiplikasi
gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});
Nilai defaultnya adalah benar (true). Tentu saja, hasilnya akan tetap digabungkan di atas halaman dengan warna latar belakang apa pun yang akhirnya berada di bawah kanvas (warna latar belakang kanvas, warna latar belakang penampung kanvas, warna latar belakang halaman, elemen di belakang kanvas jika kanvas memiliki z-index > 0, dll.) dengan kata lain, warna yang ditentukan CSS untuk area halaman web tersebut. Cara yang sangat baik 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 yang Anda miliki.
#2. Beri tahu WebGL bahwa Anda tidak menginginkan alfa di backbuffer
gl = canvas.getContext("experimental-webgl", {alpha: false});
Tindakan 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 dikomposisikan. Tentu saja, ini juga berarti bahwa sebenarnya tidak akan ada alfa di backbuffer, jadi jika Anda menggunakan alfa di backbuffer untuk beberapa tujuan, hal ini mungkin tidak akan berfungsi. Hanya sedikit aplikasi yang kami ketahui menggunakan alfa di backbuffer. Menurut kami, ini seharusnya menjadi setelan default.
#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);
Penghapusan umumnya sangat cepat karena ada kasus khusus untuk hal ini di sebagian besar hardware. Saya melakukan ini di sebagian besar demo saya. Jika saya cerdas, saya akan beralih ke metode #2 di atas. Mungkin saya akan melakukannya setelah memposting ini. Sepertinya sebagian besar library WebGL harus ditetapkan secara default ke metode ini. Beberapa developer yang benar-benar menggunakan alfa untuk efek pengomposisian dapat memintanya. Sisanya akan mendapatkan performa terbaik dan kejutan paling sedikit.
#4. Hapus alfa sekali, lalu jangan render 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 Anda beralih ke rendering ke kanvas.
#5. Menangani gambar
Selain itu, jika Anda memuat file PNG dengan alfa ke dalam tekstur, secara default alfa-nya akan dikalikan sebelumnya, yang umumnya BUKAN cara yang dilakukan sebagian besar game. Jika Anda ingin mencegah perilaku tersebut, Anda harus memberi tahu WebGL dengan
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
#6. Menggunakan persamaan penggabungan yang berfungsi dengan alfa yang telah dikalikan sebelumnya
Hampir semua aplikasi OpenGL yang saya tulis atau kerjakan menggunakan
gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);
Cara ini berfungsi untuk tekstur alfa yang tidak dipra-kalikan. Jika Anda benar-benar ingin menggunakan tekstur alfa yang telah dikalikan sebelumnya, Anda mungkin ingin
gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);
Itulah metode yang saya ketahui. Jika Anda mengetahui lebih banyak, posting di bawah.