WebGL Temelleri

Gregg Tavares
Gregg Tavares

WebGL Temelleri

WebGL, tarayıcınızda harika gerçek zamanlı 3D grafikler görüntüleyebilmenizi sağlar, ancak birçok kişi WebGL'nin aslında bir 3D API değil, 2D API olduğunu bilmemektedir. Bunu şöyle açıklayabiliriz.

WebGL yalnızca iki şeye önem verir. 2D ve renklerde klip alanı koordinatları. WebGL kullanan bir programcı olarak göreviniz, WebGL'yi bu iki özelliği sunmaktır. Bunun için 2 "gölgelendirici" sağlarsınız. Kırpma alanı koordinatlarını sağlayan Köşe gölgelendirici ve rengi sağlayan parça gölgelendirici Tuvalinizin boyutu ne olursa olsun, klip alanı koordinatları her zaman -1'den +1'e çıkar. Aşağıda, WebGL'yi en basit biçimde 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);

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>

Kırpma alanı koordinatları, zeminin boyutundan bağımsız olarak her zaman -1 ile +1 aralığındadır. Yukarıdaki örnekte, konum verilerimizi doğrudan iletmekten başka bir şey yaptığımızı görebilirsiniz. Konum verileri zaten klip alanında olduğundan yapılacak bir şey yoktur. 3D istiyorsanız, WebGL 2D API' olduğu için 3D'den 2D'ye dönüşen gölgelendiricileri sağlamak size kalmıştır! 2D öğeler için büyük olasılıkla klip alanı yerine piksel cinsinden çalışmayı tercih edersiniz, bu nedenle gölgelendiriciyi piksel cinsinden değiştirerek dikdörtgenler sağlayıp bizim için klip alanına dönüştürmesini sağlayalım. Yeni köşe 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 verilerimizi klip alanından piksel alanına 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 bir yerde olduğunu fark edebilirsiniz. WebGL, sol alt köşeyi 0,0 olarak kabul eder. 2D grafik API'lerinde kullanılan daha geleneksel sol üst köşe olmasını sağlamak için y koordinatını çevirmemiz yeterli.

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

Dikdörtgeni tanımlayan kodu farklı boyutlardaki dikdörtgenler için çağırabilmemiz amacıyla bir fonksiyona dönüştürelim. Hazır üzerinde çalışırken rengi ayarlanabilir hale getireceğiz. İlk olarak, parça gölgelendiricinin tek tip 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>

İşte rastgele yerlerde ve rastgele renklerde 50 dikdörtgen çizen yeni kod.

...

  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'nin aslında oldukça basit bir API olduğunu anladığınızı umuyoruz. 3D yapmak daha karmaşık bir hale gelebilse de, komplikasyonları siz programcı olarak eklersiniz ve bunları daha karmaşık gölgelendiriciler şeklinde görürsünüz. WebGL API'sının kendisi 2D 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. Hiç tür yazamaz veya type="javascript" ya da type="text/javascript" değerlerini girebilirsiniz. Tarayıcı, içeriği JavaScript olarak yorumlar. Başka bir şey eklerseniz tarayıcı komut dosyası etiketinin içeriğini yoksayar.

Bu özelliği, gölgelendiricileri komut dosyası etiketlerinde saklamak için kullanabiliriz. Daha da iyisi, kendi türümüzü oluşturabilir ve gölgelendiricinin köşe gölgelendiricisi veya parça gölgelendirici olarak derlenip derleneceğine karar vermek için JavaScript'imizde bunu arayabiliriz.

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

WebGL Görüntü İşleme

WebGL'de resim işleme kolaydır. Ne kadar kolaydı? Aşağıdaki bölümü okuyun.

WebGL'de görüntü çizmek için dokuları kullanmamız gerekir. WebGL, oluşturma sırasında piksel yerine klip alanı koordinatlarını beklediği gibi, WebGL bir doku okurken doku koordinatlarını bekler. Doku koordinatları, dokunun boyutları ne olursa olsun 0,0 ile 1,0 arasında değişir. Yalnızca tek bir dikdörtgen (2 üçgen) çizdiğimiz için WebGL'ye, dokudaki her bir noktanın dokuda hangi yere karşılık geldiğini söylememiz gerekir. Bu bilgileri, "değişken" adı verilen özel bir değişken türünü kullanarak tepe gölgelendiricisinden parça gölgelendiricisine iletiriz. Değişkenlik gösterdiği için değişken olarak adlandırılır. WebGL, her bir pikseli parça gölgelendiricisini kullanarak çizerken, köşe gölgelendiricisinde sağladığımız değerleri ara değer olarak hesaplar. Önceki bölümün sonundaki köşe gölgelendiricisini kullanarak, doku koordinatlarını geçirmek için bir özellik eklememiz ve ardından, bunları parça gölgelendiriciye iletmemiz 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;
}

Daha sonra, dokudaki renkleri aramak için bir parça gölgelendiriciyi 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, doku oluşturmamız ve resmi dokuya kopyalamamız gerekir. Bir tarayıcı içinde olduğumuzdan görüntüler eşzamansız olarak yüklendiğinden, dokunun yüklenmesini beklemek için kodumuzu biraz yeniden düzenlememiz gerekiyor. Yükledikten 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 nedenle resmi değiştirelim. Kırmızı ve mavi renkleri birbiriyle değiştirmeye ne dersiniz?

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

Peki ya gerçekten diğer piksellere bakan bir resim işleme yapmak istersek? WebGL, doku koordinatlarında 0,0 ile 1,0 arasındaki dokulara başvurduğundan, onePixel = 1.0 / textureSize basit matematiğiyle 1 piksel için ne kadar hareket edeceğini hesaplayabiliriz. Burada, dokudaki her bir pikselin sol ve sağ piksellerinin ortalamasını alan bir parça gölgelendirici gösterilmektedir.

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

Daha sonra, dokunun boyutunu JavaScript'ten aktarmamız gerekir.

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

Artık diğer piksellere nasıl referans verileceğini öğrendiğimize göre, pek çok yaygın görüntü işleme işlemi için bir konvolüsyon çekirdeğini kullanalım. Bu örnekte, 3x3 boyutunda bir çekirdek kullanacağız. Konvolüsyon çekirdeği 3x3 boyutunda bir matristir. Bu matristeki her bir giriş, oluşturduğumuz pikselin etrafındaki 8 pikselin ne kadar çarpılacağını temsil eder. Daha sonra, sonuç, çekirdeğin ağırlığına (çekirdekteki tüm değerlerin toplamı) veya bu değerden büyük olan 1.0'a böleriz. Bu konuyla ilgili oldukça iyi bir makaleyi burada bulabilirsiniz. Burada da, C++'ta bunu elle yazacak olsanız gerçek bir kodu gösteren başka bir makaleyi bulabilirsiniz. Örneğimizde bu işi gölgelendiricide yapacağımızdan, yeni parça gölgelendiriciyi aşağıda görebilirsiniz.

<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 konvolüsyon ç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);
...

Umarım bu bilgiler sizi WebGL'de görüntü işlemenin oldukça basit olduğuna ikna etmiştir. Şimdi de resme birden fazla efektin nasıl uygulanacağından bahsedeceğim.

GLSL'de değişkenlerden gelen a, u ve v_ ön ekleri ne anlama gelir?

Bu sadece bir adlandırma kuralı. Tamponlar tarafından sağlanan veriler için a_. Gölgelendiricilere girdi olarak üniformalar için u_, köşe gölgelendiricisinden bir parça gölgelendiriciye geçirilen ve çizilen her pikselin köşeleri arasında interpolasyon yapılan (veya değişen) değerler olan varyasyonlar için v_.

Birden çok efekt uygulama

Resim işlemeyle ilgili en bariz diğer soru, birden fazla efektin nasıl uygulanacağıdır.

Gölgelendiricileri anında oluşturmayı deneyebilirsiniz. Kullanıcının kullanmak istediği efektleri seçmesine ve ardından tüm efektleri yapan bir gölgelendirici oluşturmasına olanak tanıyan bir kullanıcı arayüzü sağlayın. Bu teknik genellikle gerçek zamanlı grafikler için efekt oluşturmak amacıyla kullanılır. Ancak bu her zaman mümkün olmayabilir. Daha esnek bir yöntem ise 2 doku daha kullanıp sırayla her doku için oluşturmak, ileri geri pinglemek 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 çerçeve arabellekleri oluşturmamız gerekir. WebGL ve OpenGL'de, Framebuffer aslında kötü bir addır. WebGL/OpenGL Framebuffer yalnızca bir durum koleksiyonudur ve herhangi bir tür tampon değildir. Ancak, çerçeve arabelleğine bir doku ekleyerek bu dokuyu oluşturabiliriz. Önce eski doku oluşturma kodunu bir fonksiyona 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 yapalım ve bunları 2 framebuffer'a ekleyelim.

// 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 de çekirdek kümesi oluşturalım ve uygulanacak bunların listesini yapalı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 da her birini uygulayalım, hangi dokuyu oluşturacağımızı pingle

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

Anlamam gereken bazı şeyler var.

gl.bindFramebuffer hizmetini null ile çağırmak, WebGL'yi, çerçeve arabelleklerinizden biri yerine tuvale oluşturmak istediğinizi bildirir. WebGL'nin klip alanından tekrar piksele dönüştürmesi gerekmektedir. Bu işlem, gl.viewport ayarlarına göre yapılır. WebGL'yi ilk kullanıma sunduğumuzda gl.viewport ayarları varsayılan olarak tuvalin boyutuna ayarlanır. Oluşturduğumuz çerçeve arabellekleri farklı boyutta olduğundan görüntü alanını uygun şekilde ayarlamamız gerekir. Son olarak, WebGL'nin temel ilkelerinde, oluşturma sırasında Y koordinatını çevirdik. Bunun nedeni, WebGL'nin tuvali, 2D sol üst için daha geleneksel olan yerine 0,0 sol alt köşe olacak şekilde göstermesidir. Bir çerçeve arabelleğine oluştururken buna gerek yoktur. Çerçeve arabelleği hiçbir zaman gösterilmediği için hangi bölümün üst ve alt kısım olduğu önemli değildir. Önemli olan tek şey, çerçeve arabelleğindeki 0,0 pikselinin, hesaplamalarımızda 0,0'a karşılık gelmesidir. Bununla başa çıkmak için, gölgelendiriciye bir giriş daha ekleyerek çevirip çevirmeyeceğinizi ayarlamayı mümkün hale getirdim.

<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, özel bir resimle oluşturulan

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

Birden çok etki oluşturabilen tek bir GLSL programı kullanarak bu örneği basit tuttuk. Görüntü işlemeyi eksiksiz olarak gerçekleştirmek istiyorsanız, büyük olasılıkla birçok GLSL programına ihtiyacınız olacaktır. Ton, doygunluk ve parlaklık ayarı için program. Bir diğeri parlaklık ve kontrast. Bunlardan biri ters çevirme, diğeri düzeyleri ayarlama vb. için. GLSL programlarını değiştirmek için kodu değiştirmeniz ve söz konusu program için parametreleri güncellemeniz gerekir. Bu örneği yazmayı düşünmüştüm, ancak bu, okuyucuya bırakılabilecek bir alıştırmadır çünkü her biri kendi parametresi olan birden fazla GLSL programı, her şeyin büyük bir spagetti karmaşasına dönüşmemesi için büyük bir yeniden düzenleme gerektirebilir. Umarım bu ve önceki örnekler WebGL'yi biraz daha ulaşılabilir hale getirmiştir. Umarım 2D ile başlamanın WebGL'yi biraz daha kolay anlaşılır hale getirdiğini umuyorum. Zaman bulursam 3D'nin nasıl yapılacağıyla ilgili birkaç makale daha yazmaya çalışacağım ve WebGL'nin aslında ne yaptığıyla ilgili daha ayrıntılı bilgi edinmeye çalışacağım.

WebGL ve Alfa

Bazı OpenGL geliştiricilerinin WebGL'nin arka arabelleğe (yani tuvalde) alfayı işleme biçiminde sorunlar yaşadığını fark ettim; bu yüzden, alfa ile ilgili WebGL ve OpenGL arasındaki bazı farkları ele almanın iyi olabileceğini düşündüm.

GL<img> WebGL, bunu daha çok OpenGL ile benzer hale getirmek için çeşitli yöntemler sunar.

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

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

Varsayılan, doğru değeridir. Sonuç, tuvalin altındaki arka plan rengiyle (tuvalin arka plan rengi, tuvalin kapsayıcı arka plan rengi, sayfanın arka plan rengi, tuvalin z-endeksi > 0 vb. varsa vb.) sayfa üzerinde birleştirilir. Başka bir deyişle, renk CSS'si web sayfasının söz konusu alanını tanımlar. Alfa ile ilgili sorun yaşayıp yaşamadığını öğrenmenin gerçekten iyi bir yolu, kanvasın arka planını kırmızı gibi parlak bir renge ayarlamaktır. Neler olduğunu hemen göreceksiniz.

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

Parmağınızı siyah olarak da ayarlayabilirsiniz. Böylece alfa sorunlarını gizlemiş olursunuz.

#2) WebGL'ye, geri arabelleğe alfa sürümünde alfa testi istemediğinizi söyleyin

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

Geri arabellek yalnızca RGB'ye sahip olacağı için bu, daha çok OpenGL gibi çalışmasını sağlar. İyi bir tarayıcı alfanızın olmadığını görüp WebGL'nin birleştirilme şeklini optimize edebileceği için muhtemelen en iyi seçenek budur. Elbette bu durum, arka arabellekte alfa olmayacağı anlamına da gelir. Bu nedenle, sizin için işe yaramayabilecek bir amaçla arka arabellekte alfa kullanıyorsanız. Arka arabellekte alfa kullandığını bildiğim az sayıda uygulama var. Muhtemelen varsayılan ayar bu olmalıydı.

3) Oluşturma işleminin 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);

Temizleme işlemi, çoğu donanımda özel bir durum olduğundan genellikle çok hızlıdır. Demolarımın çoğunda bunu yaptım. Akıllı olsaydım yukarıdaki 2. yönteme geçerdim. Belki bunu yayınladıktan hemen sonra yaparım. Çoğu WebGL kitaplığının varsayılan olarak bu yöntemi kullanması gerektiği anlaşılıyor. Birleştirme efektleri için gerçekten alfa kullanan az sayıda geliştirici bunu isteyebilir. Gerisi en iyi performansı ve en az sürprizi alır.

4) Alfayı bir kez temizleyip bir daha oluşturmayı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 kare arabelleklerinizi oluşturuyorsanız oluşturmayı tekrar alfa moduna almanız ve kanvasta oluşturmaya geçtiğinizde tekrar kapatmanız gerekebilir.

5) Resimleri Kullanma

Ayrıca, alfa içeren PNG dosyalarını dokulara yüklüyorsanız varsayılan değer, alfaların önceden çarpılmasıdır. Bu, genellikle çoğu oyunun yaptığı gibi DEĞİLDİR. Bu davranışı önlemek istiyorsanız WebGL'yi

gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);

6) Önceden çarpılmış alfa ile çalışan bir harmanlama denklemi kullanma

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

gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);

Bu yöntem, önceden çoğaltılmamış alfa dokularında işe yarar. Önceden çoğaltılmış alfa dokularla çalışmak istiyorsanız, büyük olasılıkla

gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);

Bildiğim yöntemler bunlar. Başka bilgi de varsa lütfen aşağıda yayınlayın.