WebGL Temelleri

Gregg Tavares
Gregg Tavares

WebGL'nin Temel Özellikleri

WebGL, tarayıcınızda gerçek zamanlı olarak muhteşem 3D grafikler görüntülemenizi sağlar. Ancak birçok kullanıcı, WebGL'nin aslında 3D API değil 2D API olduğunu bilmez. Bunu şöyle açıklayabiliriz.

WebGL yalnızca iki şeye önem verir. 2D olarak ve renklerde klip alanı koordinatları. WebGL kullanan bir programcı olarak işiniz, WebGL'ye bu iki şeyi sağlamaktır. Bunu yapmak için 2 "gölgelendirici" sağlarsınız. Kırpma alanı koordinatlarını sağlayan bir tepe noktası gölgelendirici ve rengi sağlayan bir parçacık gölgelendirici. Tuvalinizin boyutu ne olursa olsun, kırpma alanı koordinatları her zaman -1 ile +1 arasındadır. Aşağıda, WebGL'i en basit haliyle gösteren basit bir WebGL örneği verilmiştir.

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

İşte 2 gölgelendirici

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

Yine, tuval boyutu ne olursa olsun kırpma alanı koordinatları her zaman -1'den +1'e gider. Yukarıdaki örnekte, konum verilerimizi doğrudan aktarmaktan başka bir şey yaptığımızı görebilirsiniz. Konum verileri zaten kırpma alanında olduğu için herhangi bir işlem yapmanız gerekmez. 3D istiyorsanız WebGL 2D API OLDUĞU İÇİN 3D'den 2D'ye dönüştüren gölgelendiricileri sağlamanız gerekir. 2D öğeler için büyük olasılıkla klip alanı yerine piksel olarak çalışmayı tercih edersiniz. Bu nedenle gölgelendiriciyi değiştirerek dikdörtgenleri piksel cinsinden tedarik edip kırpma alanına dönüştürmesini yapalım. Yeni köşe düğümü gölgelendirici

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

Artık verileri klip alanından piksele değiştirebiliriz

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

Dikdörtgenin bu alanın alt kısmına yakın olduğunu fark edebilirsiniz. WebGL, sol alt köşeyi 0,0 olarak kabul eder. 2D grafik API'leri için kullanılan daha geleneksel sol üst köşeyi elde etmek üzere y koordinatını ters çeviririz.

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

Dikdörtgeni tanımlayan kodu fonksiyona dönüştürelim. Böylece, farklı boyutlardaki dikdörtgenler için bu kodu çağırabiliriz. Bu arada rengi ayarlanabilir hale getireceğiz. Öncelikle, parçacık gölgelendiricinin tekdüze bir renk girişi almasını sağlarız.

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

uniform vec4 u_color;

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

Aşağıda, rastgele yerlerde ve rastgele renklerde 50 dikdörtgen çizen yeni kod verilmiştir.

...

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

WebGL'in aslında oldukça basit bir API olduğunu anlayabildiğinizi umuyorum. 3D yapmak daha karmaşık olabilir ancak bu karmaşıklık, programcı olarak daha karmaşık gölgelendiriciler şeklinde sizin tarafınızdan eklenir. WebGL API'si 2D'dir ve oldukça basittir.

type="x-shader/x-vertex" ve type="x-shader/x-fragment" ne anlama gelir?

<script> etiketlerinde varsayılan olarak JavaScript bulunur. Tür belirtmeyebilir veya type="javascript" ya da type="text/javascript" yazabilirsiniz. Bu durumda tarayıcı, içeriği JavaScript olarak yorumlar. Başka bir şey girerseniz tarayıcı, komut dosyası etiketinin içeriğini yoksayar.

Bu özelliği, gölgelendiricileri komut dosyası etiketlerinde depolamak için kullanabiliriz. Daha da iyisi, kendi türümüzü oluşturabilir ve JavaScript'imizde bu türü arayabilir, böylece gölgelendiriciyi bir köşe gölgelendiricisi mi yoksa bir parça gölgelendiricisi mi derleyeceğimize karar verebiliriz.

Bu durumda createShaderFromScriptElement işlevi, belirtilen id içeren bir komut dosyası arar ve ardından ne tür bir gölgelendirici oluşturulacağına karar vermek için type değerine bakar.

WebGL Görüntü İşleme

WebGL'de resim işleme kolaydır. Ne kadar kolay? Aşağıdakileri okuyun.

WebGL'de resim çizmek için doku kullanmamız gerekir. WebGL, oluşturma işleminde piksel yerine kırpma alanı koordinatlarını beklediği gibi, doku okuma işleminde de doku koordinatlarını bekler. Doku koordinatları, dokunun boyutlarından bağımsız olarak 0,0 ile 1,0 arasındadır. Yalnızca tek bir dikdörtgen (2 üçgen) çizdiğimiz için WebGL'ye dikdörtgendeki her bir noktanın dokudaki hangi yerin karşılık geldiğini bildirmemiz gerekir. Bu bilgiyi, "değişken" adı verilen özel bir değişken türünü kullanarak köşe gölgelendiricisinden parça gölgelendiriciye iletiriz. Değişken olduğu için değişken olarak adlandırılır. WebGL, her pikseli parçacık gölgelendiriciyi kullanarak çizerken köşe gölgelendiricide sağladığımız değerleri enterpolasyon yapar. Önceki bölümün sonundaki köşe düğümü gölgelendiriciyi kullanarak, doku koordinatlarını iletecek ve ardından bunları parça gölgelendiriciye iletecek bir özellik eklememiz gerekir.

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

Ardından, dokudaki renkleri aramak için bir parça gölgelendirici sağlarız.

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

Son olarak bir resim yüklememiz, bir doku oluşturmamız ve resmi dokuya kopyalamamız gerekir. Bir tarayıcıda olduğumuz için resimler eşzamansız olarak yüklenir. Bu nedenle, dokuların yüklenmesini beklemek için kodumuzu biraz yeniden düzenlememiz gerekir. Yüklendikten sonra çizeceğiz.

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

Çok heyecan verici değil. Bu resmi değiştirelim. Kırmızı ve mavinin yerini değiştirmeye ne dersiniz?

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

Peki diğer piksellere bakarak görüntü işleme yapmak istersek ne olur? WebGL, doku koordinatlarında 0,0 ile 1,0 arasında değişen dokulara referans verdiğinden, 1 piksel için ne kadar hareket edileceğini basit bir matematik işlemiyle onePixel = 1.0 / textureSize hesaplayabiliriz. Aşağıda, dokudaki her pikselin sol ve sağ piksellerinin ortalamasını alan bir kırıntı gölgelendirici verilmiştir.

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

Ardından, JavaScript'den doku boyutunu iletmemiz gerekir.

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

Diğer piksellere nasıl referans vereceğimizi öğrendiğimize göre, bir dizi yaygın görüntü işleme işlemi yapmak için bir örtüşme çekirdeği kullanalım. Bu örnekte 3x3 boyutunda bir çekirdek kullanacağız. Devrimsel çekirdek, matristeki her girişin oluşturduğumuz pikselin etrafındaki 8 pikseli ne kadar çarpacağını temsil ettiği 3x3 boyutunda bir matristir. Ardından sonucu, çekirdeğin ağırlığına (çekirdekteki tüm değerlerin toplamı) veya 1,0'a (hangisi daha büyükse) böleriz. Bu konuyla ilgili oldukça iyi bir makaleyi aşağıda bulabilirsiniz. Bu makale, C++ ile elle yazacaksanız gerçek bir kod gösteren başka bir makaleyi de burada bulabilirsiniz. Bizim durumumuzda bu işlemi gölgelendiricide yapacağız, dolayısıyla yeni parça gölgelendirici burada verilmiştir.

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

JavaScript'te bir toplayıcı çekirdeği sağlamamız gerekir.

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

Bu makalenin, WebGL'de görüntü işlemenin oldukça basit olduğuna sizi ikna ettiğini umuyoruz. Ardından, resme birden fazla efektin nasıl uygulanacağını anlatacağım.

GLSL'deki değişkenler önündeki a, u ve v_ ön eklerinin ne anlama geldiği?

Bu, yalnızca bir adlandırma kuralı. Tamponlar tarafından sağlanan veriler olan özellikler için a_. u_, gölgelendiricilere giriş olan üniformalar için, v_ ise bir köşe gölgelendiricisinden bir parça gölgelendiriciye iletilen ve çizilen her piksel için köşeler arasında ara değer olarak kullanılan (veya değiştirilen) değerler olan varyantlar içindir.

Birden çok efekt uygulama

Resim işlemeyle ilgili en belirgin sorulardan biri, birden fazla efekt nasıl uygulanır? sorusudur.

Gölgelendiricileri anında oluşturmayı deneyebilirsiniz. Kullanıcının kullanmak istediği efektleri seçmesine olanak tanıyan bir kullanıcı arayüzü sağlayın ve ardından tüm efektleri uygulayan bir gölgelendirici oluşturun. Bu teknik genellikle gerçek zamanlı grafikler için efekt oluşturmak amacıyla kullanılsa da her zaman mümkün olmayabilir. Daha esnek bir yol, 2 doku daha kullanmak ve her doku için sırayla oluşturmak, ping pong'u yapmak ve her seferinde bir sonraki efekti uygulamaktır.

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

Bunun için framebuffer oluşturmamız gerekir. WebGL ve OpenGL'de Framebuffer aslında kötü bir ad. WebGL/OpenGL Framebuffer, aslında herhangi bir türde bir arabellek değil, yalnızca bir durum koleksiyonudur. Ancak bir çerçeve önbelleğiye doku ekleyerek bu dokuda oluşturma işlemi yapabiliriz. Öncelikle eski doku oluşturma kodunu bir işleve dönüştürelim.

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

Şimdi bu işlevi kullanarak 2 doku daha oluşturup bunları 2 çerçeve önbelleğiyle bağdaştıralım.

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

Şimdi bir dizi çekirdek oluşturalım ve uygulanacak olan çekirdeklerin listesini oluşturalım.

// 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"
];

Son olarak, her birini uygulayalım ve hangi dokuyu oluşturduğumuzu da değiştirelim.

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

Bahsetmem gereken şeyler var.

gl.bindFramebuffer işlevini null ile çağırmak, WebGL'ye çerçeve mülklerinizden biri yerine tuvalde oluşturma yapmak istediğinizi bildirir. WebGL'nin, kırpma alanından tekrar piksele dönüştürmesi gerekir. Bu işlemi gl.viewport ayarlarına göre yapar. gl.viewport ayarları, WebGL ilk kullanıma hazırlanırken varsayılan olarak tuvalin boyutuna ayarlanır. Oluşturduğumuz framebuffer'lar tuvalden farklı bir boyuta sahip olduğundan görüntü alanını uygun şekilde ayarlamamız gerekir. Son olarak, WebGL kanvası 2D için daha geleneksel olan sol üst yerine 0,0 ile sol alt köşede gösterdiğinden, WebGL temelleri örneklerindeki oluşturma işlemi sırasında Y koordinatını ters çevirdik. Çerçeve arabelleğine oluşturma sırasında bu gerekli değildir. Görüntü çerçevesi hiçbir zaman gösterilmediğinden, hangi kısmın üstte ve hangi kısmın altta olduğu önemli değildir. Önemli olan, çerçeve önbelleğindeki 0,0 pikselin hesaplamalarımızdaki 0,0'a karşılık gelmesidir. Bu sorunu çözmek için gölgelendiriciye bir giriş daha ekleyerek resmin ters çevrilip çevrilmeyeceğini ayarlama olanağı sağladım.

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

Ardından, oluşturma işlemini gerçekleştirirken bunu ayarlayabiliriz.

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

Birden fazla efekt elde edebilecek tek bir GLSL programı kullanarak bu örneği basit tuttum. Tam resim işleme yapmak istiyorsanız muhtemelen birçok GLSL programına ihtiyacınız olacaktır. Ton, doygunluk ve parlaklık ayarlama programı. Parlaklık ve kontrast için başka bir tane. Biri ters çevirmek, diğeri düzeyleri ayarlamak vb. için kullanılır. GLSL programlarını değiştirmek ve o programın parametrelerini güncellemek için kodu değiştirmeniz gerekir. Bu örneği yazmak istedim ancak her biri kendi parametre ihtiyaçlarına sahip birden fazla GLSL programının olması, büyük olasılıkla her şeyin büyük bir karmaşaya dönüşmesini önlemek için önemli bir yeniden yapılandırmayı gerektireceğinden, bu alıştırmayı okuyucuya bırakmayı tercih ettim. Bu ve önceki örneklerin WebGL'i biraz daha erişilebilir hâle getirdiğini ve 2D ile başlamanın WebGL'in anlaşılmasını biraz daha kolaylaştırdığını umuyoruz. Vakit bulursam 3D'nin nasıl yapılacağıyla ilgili birkaç makale daha yazmaya ve WebGL'in aslında ne yaptığıyla ilgili daha fazla ayrıntı sunmaya çalışacağım.

WebGL ve alfa

Bazı OpenGL geliştiricilerinin WebGL'nin arka arabellekteki (ör. tuval) alfayı işleme şekli konusunda sorun yaşadığını fark ettim. Bu nedenle, alfayla ilgili olarak WebGL ve OpenGL arasındaki bazı farkların üzerinden geçmenin iyi olabileceğini düşündüm.

OpenGL ile WebGL arasındaki en büyük fark, OpenGL'in hiçbir şeyle birleştirilmeyen veya işletim sisteminin pencere yöneticisi tarafından hiçbir şeyle etkili bir şekilde birleştirilmeyen bir arka tamponda oluşturulmasıdır. Bu nedenle, alfa değerinizin ne olduğu önemli değildir. WebGL, tarayıcı tarafından web sayfasıyla birleştirilir ve varsayılan olarak, şeffaflık ve 2D kanvas etiketleri içeren .png <img> etiketleriyle aynı şekilde önceden çarpılmış alfa kullanılır. WebGL'de bunu OpenGL'e daha benzer hale getirmenin birkaç yolu vardır.

#1) WebGL'ye, önceden çarpılmamış alfa ile birleştirilmesini istediğinizi söyleyin

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

Varsayılan değer true'dur. Elbette sonuç, kanvasın altında kalan arka plan rengiyle (kanvasın arka plan rengi, kanvasın kapsayıcı arka plan rengi, sayfanın arka plan rengi, kanvasın z-dizini 0'dan büyükse kanvasın arkasındaki öğeler vb.) sayfa üzerinde birleştirilir. Diğer bir deyişle, CSS'nin web sayfasının bu alanı için tanımladığı renk kullanılır. Alfa sorunu olup olmadığınızı öğrenmenin en iyi yolu, kanvasın arka planını kırmızı gibi parlak bir renge ayarlamaktır. Ne olduğunu hemen görürsünüz.

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

Ayrıca siyah olarak ayarlayarak alfa sorunlarını gizleyebilirsiniz.

2) WebGL'ye arka arabellekte alfa olmasını istemediğinizi söyleyin

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

Arka tamponda yalnızca RGB olacağından bu, OpenGL gibi davranmasını sağlar. İyi bir tarayıcı, alfa değerinizin olmadığını görebilir ve WebGL'nin birleştirilme şeklini optimize edebilir. Bu nedenle, bu seçenek muhtemelen en iyisidir. Elbette bu, arka tamponda alfa olmadığı anlamına da gelir. Dolayısıyla, arka tamponda alfa'yı bir amaç için kullanıyorsanız bu sizin için işe yaramayabilir. Arka tamponda alfa kullanan çok az uygulama olduğunu biliyorum. Bunun varsayılan seçenek olması gerektiğini düşünüyorum.

#3) Oluşturma işleminizin sonunda alfayı temizleyin

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

Çoğu donanımda bunun için özel bir yer olduğundan temizleme işlemi genellikle çok hızlıdır. Bunu çoğu demomda yaptım. Akıllı olsaydım yukarıdaki 2. yönteme geçerdim. Bunu bu e-postayı gönderdikten hemen sonra yapabilirim. Çoğu WebGL kitaplığının varsayılan olarak bu yöntemi kullandığı anlaşılıyor. Gerçekten de efektleri birleştirmek için alfa kullanan az sayıda geliştirici bunu isteyebilir. Geri kalanlar yalnızca en iyi ve en az sürprizleri alır.

#4) Alfayı bir kez temizleyin ve artık ona oluşturma işlemi uygulamayın

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

Elbette kendi iFrame arabelleklerinizde oluşturma yapıyorsanız oluşturmayı alfa olarak yeniden açmanız ve tuvalde oluşturmaya geçtiğinizde tekrar kapatmanız gerekebilir.

#5) Resimleri işleme

Ayrıca, alfa içeren PNG dosyalarını dokular halinde yüklüyorsanız varsayılan olarak bunların alfaları önceden çarpılır. Bu da genellikle çoğu oyunun yaptığı gibi DEĞİLDİR. Bu davranışı önlemek istiyorsanız WebGL'ye şunu söylemeniz gerekir:

gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);

#6) Önceden çarpılmış alfa ile çalışan bir karışım denklemesi kullanma

Yazdığım veya üzerinde çalıştığım neredeyse tüm OpenGL uygulamaları

gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);

Bu, önceden çarpılmamış alfa dokuları için işe yarar. Gerçekten önceden çarpılmış alfa dokularıyla çalışmak istiyorsanız muhtemelen

gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);

Bildiğim yöntemler bunlar. Daha fazla bilginiz varsa lütfen aşağıdan paylaşın.