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 2 şeyi önemser. 2D'de kırpma alanı koordinatları ve renkler. 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 de, klipping alanı koordinatları, tuvalin boyutundan bağımsız olarak her zaman -1 ile +1 arasındadır. Yukarıdaki örnekte, konum verilerini doğrudan iletmekten başka bir şey yapmadığı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 muhtemelen kırpma alanından ziyade piksel olarak çalışmak istersiniz. Bu nedenle, piksel olarak dikdörtgenler sağlayabilmemiz ve bunların bizim için kırpma alanına dönüştürülmesini sağlayabilmemiz için gölgelendiriciyi değiştirelim. 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ı tersine çeviririz.
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
Farklı boyutlarda dikdörtgenler için çağırabilmemiz amacıyla, dikdörtgeni tanımlayan kodu bir işleve dönüştürelim. 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>
etiketleri varsayılan olarak JavaScript içerir. Tür belirtmeyebilir veya type="javascript"
ya da type="text/javascript"
yazabilirsiniz. Tarayıcınız 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ölgelendiricinin köşe gölgelendirici mi yoksa parça gölgelendirici mi derleneceğine 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 Resim İş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 klip 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 (aslında 2 üçgen) çizdiğimiz için WebGL'ye, dikdörtgendeki her 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ü kullanarak köşe üstü gölgelendiriciden parçacık gölgelendiriciye iletiriz. Değişken olarak adlandırılmasının nedeni, değişken olmasıdı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, dokunun 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 maviyi değiştirmeye ne dersiniz?
...
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
...
Peki diğer piksellere bakan bir görüntü işleme işlemi 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, yaygın olarak kullanılan bir dizi görüntü işleme işlemi yapmak için bir örtüşme çekirdeği kullanalım. Bu durumda 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 işlemi C++'da manuel olarak yapacak olsanız kullanabileceğiniz gerçek kodu gösteren başka bir makale de burada. Bizim durumumuzda bu işlemi gölgelendiricide yapacağız. İşte yeni parçacık gölgelendirici.
<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ğunu anlamanıza yardımcı olduğunu umuyoruz. Ardından, resme birden fazla efektin nasıl uygulanacağını anlatacağım.
GLSL'de değişkenlerin ö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 fazla efekt uygulama
Görüntü 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 yöntem, 2 tane daha doku kullanmak ve her dokuya sırayla oluşturma işlemi uygulamak, ileri geri gidip 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 çekirdek grubu ve ardından uygulanacak ç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);
}
Ele almamız gereken bazı konular var.
gl.bindFramebuffer
işlevini null
ile çağırmak, WebGL'ye framebuffer'larınızdan biri yerine tuvale oluşturma işlemi 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. WebGL'yi başlattığımızda gl.viewport
ayarlarının varsayılan değeri tuvalin boyutudur. 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şleminde Y koordinatını ters çevirdik. Bu, bir çerçeve önbelleği oluşturmak için 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 gidermek 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. Birisi ters çevirmek, diğeri seviyeleri ayarlamak için vb. GLSL programlarını değiştirmek için kodu değiştirmeniz ve ilgili programın parametrelerini güncellemeniz 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'in arka tamponda (ör. tuval) alfa değerini işleme şekliyle ilgili sorunlar yaşadığını fark ettim. Bu nedenle, WebGL ile OpenGL arasındaki alfa ile ilgili bazı farklılıkları incelemenin faydalı 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ç, tuvalin altında kalan arka plan rengiyle (tuvalin arka plan rengi, tuvalin kapsayıcı arka plan rengi, sayfanın arka plan rengi, tuvalin z-dizini 0'dan büyükse tuvalin 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ı öğ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 tamponda alfa istemediğinizi bildirin
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. Diğer kullanıcılar ise en iyi performansı ve en az sürprizi elde eder.
#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 çerçeve önbelleğinize oluşturma işlemi yapıyorsanız alfa oluşturma işlemini tekrar açmanız ve tuvale oluşturmaya geçtiğinizde tekrar kapatmanız gerekebilir.
#5) Resimleri işleme
Ayrıca, alfa içeren PNG dosyalarını dokulara yüklüyorsanız varsayılan olarak alfaları önceden çarpılır. Bu, çoğu oyunda uygulanan bir yöntem DEĞİLDİR. Bu davranışı önlemek istiyorsanız WebGL'ye
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 de ö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.