Concetti fondamentali di WebGL
WebGL consente di visualizzare straordinarie immagini 3D in tempo reale nel browser, ma molti non sanno che in realtà è un'API 2D, non 3D. Mi spiego meglio.
WebGL si occupa solo di due cose. Coordinate dello spazio di clip in 2D e colori. Il tuo compito come programmatore che utilizza WebGL è fornire a WebGL queste due cose. A tal fine, fornisci due "shader". Uno shader vertex che fornisce le coordinate dello spazio clip e uno shader fragment che fornisce il colore. Le coordinate dello spazio clip vanno sempre da -1 a +1, indipendentemente dalle dimensioni della tela. Ecco un semplice esempio di WebGL che mostra WebGL nella sua forma più semplice.
// 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);
Ecco i due 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>
Anche in questo caso, le coordinate dello spazio di clip vanno sempre da -1 a +1, indipendentemente dalle dimensioni della tela. Nel caso riportato sopra, non facciamo altro che trasmettere direttamente i dati sulla posizione. Poiché i dati sulla posizione sono già nello spazio clip, non devi fare nulla. Se vuoi il 3D, devi fornire gli shader che convertono dal 3D al 2D perché WebGL È UN'API 2D. Per gli elementi 2D probabilmente preferisci lavorare in pixel anziché in clipspace, quindi modifichiamo lo shader in modo da poter fornire rettangoli in pixel e farli convertire in clipspace. Ecco il nuovo shader vertex
<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>
Ora possiamo cambiare i dati da clipspace a pixel
// 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);
Potresti notare che il rettangolo si trova nella parte inferiore dell'area. WebGL considera l'angolo in basso a sinistra pari a 0,0. Per ottenere l'angolo in alto a sinistra più tradizionale utilizzato per le API di grafica 2D, basta capovolgere la coordinata y.
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
Trasformiamo il codice che definisce un rettangolo in una funzione in modo da poterlo chiamare per rettangoli di dimensioni diverse. A proposito, renderemo il colore impostabile. Innanzitutto, facciamo in modo che lo shader frammento riceva un input uniforme di colore.
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
</script>
Ed ecco il nuovo codice che disegna 50 rettangoli in posizioni e colori casuali.
...
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);
}
Spero che tu abbia capito che WebGL è in realtà un'API piuttosto semplice. Anche se può essere più complicato fare 3D, questa complicazione viene aggiunta da te, il programmatore, sotto forma di shader più complessi. L'API WebGL stessa è 2D e abbastanza semplice.
Che cosa significano type="x-shader/x-vertex" e type="x-shader/x-fragment"?
Per impostazione predefinita, i tag <script>
contengono JavaScript. Puoi non inserire alcun tipo o inserire type="javascript"
o type="text/javascript"
e il browser interpreterà i contenuti come JavaScript. Se inserisci altro, il browser ignora i contenuti del tag script.
Possiamo utilizzare questa funzionalità per memorizzare gli shader nei tag script. Ancora meglio, possiamo creare il nostro tipo e cercarlo nel codice JavaScript per decidere se compilare lo shader come shader vertex o shader fragment.
In questo caso, la funzione createShaderFromScriptElement
cerca uno script con id
specificato e poi esamina type
per decidere quale tipo di shader creare.
Elaborazione di immagini WebGL
L'elaborazione delle immagini è facile in WebGL. Quanto è facile? Leggi di seguito.
Per disegnare immagini in WebGL, dobbiamo utilizzare le texture. Analogamente al modo in cui WebGL si aspetta coordinate dello spazio di clip durante il rendering anziché pixel, WebGL si aspetta coordinate texture durante la lettura di una texture. Le coordinate texture vanno da 0,0 a 1,0 indipendentemente dalle dimensioni della texture. Poiché stiamo disegnando un solo rettangolo (o meglio, 2 triangoli), dobbiamo dire a WebGL a quale posizione della texture corrisponde ogni punto del rettangolo. Trasferiremo queste informazioni dallo shader vertex allo shader fragment utilizzando un tipo speciale di variabile chiamato "varying". Si chiama variabile perché varia. WebGL interpolerà i valori forniti nello shader vertex mentre disegna ogni pixel utilizzando lo shader fragment. Utilizzando lo shader vertex della fine della sezione precedente, dobbiamo aggiungere un attributo per passare le coordinate texture e poi trasmetterle allo shader fragment.
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;
}
Poi forniamo un frammento shader per cercare i colori dalla texture.
<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>
Infine, dobbiamo caricare un'immagine, creare una trama e copiare l'immagine nella trama. Poiché siamo in un browser, le immagini vengono caricate in modo asincrono, quindi dobbiamo riorganizzare un po' il codice per attendere il caricamento della trama. Una volta caricato, lo disegneremo.
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);
...
}
Non è molto interessante, quindi manipoliamo l'immagine. Che ne dici di scambiare il rosso e il blu?
...
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
...
E se volessimo eseguire un'elaborazione delle immagini che esamini altri pixel? Poiché WebGL fa riferimento alle texture nelle coordinate texture che vanno da 0,0 a 1,0, possiamo calcolare quanto spostarci per 1 pixel con la semplice matematica onePixel = 1.0 / textureSize
.
Ecco un frammento shader che calcola la media dei pixel sinistro e destro di ogni pixel della trama.
<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>
Poi dobbiamo passare le dimensioni della trama da JavaScript.
...
var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
...
// set the size of the image
gl.uniform2f(textureSizeLocation, image.width, image.height);
...
Ora che sappiamo come fare riferimento ad altri pixel, utilizziamo un kernel di convoluzione per eseguire una serie di operazioni di elaborazione delle immagini comuni. In questo caso utilizzeremo un kernel 3x3. Un kernel di convoluzione è solo una matrice 3x3 in cui ogni voce della matrice rappresenta il fattore di moltiplicazione degli 8 pixel intorno al pixel di cui stiamo eseguendo il rendering. Dividiamo quindi il risultato per il peso del kernel (la somma di tutti i valori del kernel) o per 1,0, a seconda del valore maggiore. Ecco un articolo abbastanza buono al riguardo. Ecco un altro articolo che mostra del codice effettivo se dovessi scriverlo a mano in C++. Nel nostro caso, eseguiremo questo lavoro nello shader, quindi ecco il nuovo shader di frammento.
<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>
In JavaScript dobbiamo fornire un kernel di convoluzione.
...
var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
...
var edgeDetectKernel = [
-1, -1, -1,
-1, 8, -1,
-1, -1, -1
];
gl.uniform1fv(kernelLocation, edgeDetectKernel);
...
Spero che questo ti abbia convinto che l'elaborazione delle immagini in WebGL è piuttosto semplice. Adesso ti mostrerò come applicare più di un effetto all'immagine.
Che cosa significano i prefissi a, u e v_ davanti alle variabili in GLSL?
Si tratta solo di una convenzione di denominazione. a_
per gli attributi, ovvero i dati forniti dai buffer. u_
per le uniformi, ovvero gli input degli shader, v_
per le varianti, ovvero i valori passati da un vertex shader a un fragment shader e interpolati (o modificati) tra i vertici per ogni pixel disegnato.
Applicazione di più effetti
La domanda più ovvia successiva per l'elaborazione delle immagini è come applicare più effetti.
Potresti provare a generare shader dinamicamente. Fornisci un'interfaccia utente che consenta all'utente di selezionare gli effetti che vuole utilizzare, quindi genera uno shader che applichi tutti gli effetti. Sebbene questa tecnica venga spesso utilizzata per creare effetti per le grafica in tempo reale, potrebbe non essere sempre possibile. Un modo più flessibile è utilizzare altre due texture e eseguire il rendering in ciascuna texture a turno, passando da una all'altra e applicando ogni volta l'effetto successivo.
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
Per farlo, dobbiamo creare framebuffer. In WebGL e OpenGL, Framebuffer è in realtà un nome improprio. Un framebuffer WebGL/OpenGL è in realtà solo una raccolta di stati e non un buffer di alcun tipo. Tuttavia, se colleghiamo una texture a un framebuffer, possiamo eseguire il rendering in quella texture. Per prima cosa, trasformiamo il vecchio codice di creazione della trama in una funzione
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);
Ora utilizziamo questa funzione per creare altre due texture e associarle a due 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);
}
Ora creiamo un insieme di kernel e un elenco da applicare.
// 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"
];
Infine, applichiamo ogni texture, passando da una all'altra.
// 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);
}
Ci sono alcune cose che devo esaminare.
La chiamata a gl.bindFramebuffer
con null
indica a WebGL che vuoi eseguire il rendering nella tela anziché in uno dei tuoi frame buffer.
WebGL deve convertire lo spazio clip in pixel. Lo fa in base alle impostazioni di gl.viewport
. Le impostazioni di gl.viewport
sono predefinite in base alle dimensioni della tela quando viene inizializzato WebGL. Poiché i framebuffer in cui eseguiamo il rendering hanno dimensioni diverse rispetto alla tela, dobbiamo impostare l'area visibile in modo appropriato.
Infine, negli esempi di base di WebGL abbiamo invertito la coordinata Y durante il rendering perché WebGL mostra la tela con 0,0 nell'angolo in basso a sinistra anziché in alto a sinistra, come è più tradizionale per 2D. Non è necessario quando si esegue il rendering in un framebuffer. Poiché il framebuffer non viene mai visualizzato, la parte superiore e inferiore è irrilevante. L'importante è che il pixel 0,0 nel framebuffer corrisponda a 0,0 nei nostri calcoli. Per risolvere il problema, ho reso possibile impostare l'opzione di capovolgimento aggiungendo un altro input allo 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>
e poi possiamo impostarlo durante il rendering con
...
var flipYLocation = gl.getUniformLocation(program, "u_flipY");
...
// don't flip
gl.uniform1f(flipYLocation, 1);
...
// flip
gl.uniform1f(flipYLocation, -1);
Ho mantenuto questo esempio semplice utilizzando un singolo programma GLSL che può ottenere più effetti. Se vuoi eseguire un'elaborazione completa delle immagini, probabilmente avrai bisogno di molti programmi GLSL. Un programma per la regolazione di tonalità, saturazione e luminanza. Un altro per luminosità e contrasto. Uno per l'inversione, un altro per la regolazione dei livelli e così via. Dovrai modificare il codice per cambiare i programmi GLSL e aggiornare i parametri per quel determinato programma. Avevo pensato di scrivere questo esempio, ma è un esercizio meglio lasciato al lettore perché più programmi GLSL, ciascuno con i propri requisiti di parametro, probabilmente richiedono un refactoring importante per evitare che tutto diventi un grande pasticcio di spaghetti. Spero che questo e gli esempi precedenti abbiano reso WebGL un po' più accessibile e che iniziare con 2D aiuti a comprendere meglio WebGL. Se trovo il tempo, cercherò di scrivere altri articoli su come creare contenuti 3D e su ulteriori dettagli su cosa fa realmente WebGL sotto il cofano.
WebGL e alpha
Ho notato che alcuni sviluppatori OpenGL hanno problemi con il modo in cui WebGL tratta l'alpha nel backbuffer (ovvero la tela), quindi ho pensato che sarebbe stato utile esaminare alcune delle differenze tra WebGL e OpenGL relative all'alpha.
La differenza più grande tra OpenGL e WebGL è che OpenGL esegue il rendering in un backbuffer che non viene composto con nulla o, in pratica, non viene composto con nulla dal gestore delle finestre del sistema operativo, quindi non importa quale sia il valore alpha.
WebGL viene composto dal browser con la pagina web e per impostazione predefinita viene utilizzato l'alpha premoltiplicato come i tag <img>
.png con trasparenza e i tag canvas 2D.
WebGL offre diversi modi per rendere l'esperienza più simile a OpenGL.
1) Indica a WebGL che vuoi che il compositing venga eseguito con un'alpha non premoltiplicata
gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});
Il valore predefinito è true. Ovviamente, il risultato verrà comunque composto sulla pagina con il colore di sfondo che si trova sotto la tela (il colore di sfondo della tela, il colore di sfondo del contenitore della tela, il colore di sfondo della pagina, gli elementi dietro la tela se la tela ha un indice z > 0 e così via), in altre parole, il colore definito dal CSS per quell'area della pagina web. Un ottimo modo per scoprire se hai problemi con l'alpha è impostare lo sfondo della tela su un colore brillante come il rosso. Vedrai immediatamente cosa sta succedendo.
<canvas style="background: red;"></canvas>
Puoi anche impostarlo su nero per nascondere eventuali problemi di trasparenza.
2) Indica a WebGL che non vuoi l'alpha nel backbuffer
gl = canvas.getContext("experimental-webgl", {alpha: false});
In questo modo, il backbuffer avrà solo RGB e il comportamento sarà più simile a quello di OpenGL. Probabilmente è l'opzione migliore perché un buon browser potrebbe rilevare che non hai alpha e ottimizzare il modo in cui viene composto WebGL. Ciò significa anche che non avrà l'alpha nel backbuffer, quindi se lo utilizzi per qualche scopo, questa opzione potrebbe non essere adatta a te. Poche app che conosco utilizzano l'alpha nel backbuffer. A mio avviso, questa avrebbe dovuto essere l'impostazione predefinita.
3) Cancella l'alpha al termine del 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);
Lo svuotamento è in genere molto veloce perché esiste un caso speciale per la maggior parte dell'hardware. L'ho fatto nella maggior parte delle mie demo. Se fossi furbo, passerei al metodo 2 sopra indicato. Forse lo farò subito dopo aver pubblicato questo messaggio. Sembra che la maggior parte delle librerie WebGL debba utilizzare per impostazione predefinita questo metodo. I pochi sviluppatori che utilizzano effettivamente l'alpha per gli effetti di composizione possono richiederlo. Il resto avrà solo le migliori prestazioni e le sorprese minori.
4) Cancella l'alpha una volta e non eseguirne più il rendering
// 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);
Ovviamente, se esegui il rendering nei tuoi framebuffer, potrebbe essere necessario riattivare il rendering in alpha e disattivarlo di nuovo quando passi al rendering nella tela.
5) Gestione delle immagini
Inoltre, se carichi file PNG con alpha nelle texture, per impostazione predefinita l'alpha è premoltiplicato, il che in genere NON è il modo in cui funziona la maggior parte dei giochi. Se vuoi impedire questo comportamento, devi indicare a WebGL di
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
6) Utilizzo di un'equazione di miscelazione che funzioni con l'alpha premoltiplicato
Quasi tutte le app OpenGL che ho scritto o su cui ho lavorato utilizzano
gl.blendFunc(gl.SRC_ALPHA, gl_ONE_MINUS_SRC_ALPHA);
Questo funziona per le texture alpha non premoltiplicate. Se vuoi lavorare con texture alpha pre-moltiplicate, probabilmente ti consigliamo di
gl.blendFunc(gl.ONE, gl_ONE_MINUS_SRC_ALPHA);
Questi sono i metodi di cui sono a conoscenza. Se ne conosci altri, pubblicali di seguito.