WebGL consente di visualizzare una straordinaria grafica 3D in tempo reale nel browser, ma ciò che molti non sanno è che WebGL è in realtà un'API 2D, non un'API 3D. Mi spiego meglio.
WebGL si occupa solo di due cose: Coordinate clipspace in 2D e colori. Il tuo compito come programmatore che utilizza WebGL è fornire a WebGL questi due elementi. Per farlo, devi fornire due "shader". Un vertex shader che fornisce le coordinate dello spazio di ritaglio e un fragment shader che fornisce il colore. Le coordinate dello spazio di ritaglio vanno sempre da -1 a +1, indipendentemente dalle dimensioni del canvas. 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 ritaglio vanno sempre da -1 a +1, indipendentemente dalle dimensioni del canvas. Nel caso precedente, puoi vedere che non facciamo altro che trasmettere direttamente i dati sulla posizione. Poiché i dati sulla posizione sono già nello spazio di ritaglio, non c'è nulla da fare. Se vuoi il 3D, devi fornire shader che convertono da 3D a 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 convertirli in clipspace. Ecco il nuovo vertex shader
<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 convertire 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 vicino alla parte inferiore di quell'area. WebGL considera l'angolo inferiore sinistro come 0,0. Per ottenere l'angolo in alto a sinistra più tradizionale utilizzato per le API di grafica 2D, basta invertire 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. Già che ci siamo, renderemo il colore impostabile. Innanzitutto, facciamo in modo che il fragment shader accetti 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 possa vedere che WebGL è in realtà un'API piuttosto semplice. Sebbene la creazione di contenuti 3D possa diventare più complicata, questa complicazione viene aggiunta da te, il programmatore, sotto forma di shader più complessi. L'API WebGL è in 2D e piuttosto semplice.
What do type="x-shader/x-vertex" and type="x-shader/x-fragment" mean?
I tag <script> contengono JavaScript per impostazione predefinita. Puoi non inserire alcun tipo o inserire type="javascript" o type="text/javascript" e il browser interpreterà i contenuti come JavaScript. Se inserisci qualsiasi altro elemento, il browser ignora i contenuti del tag di script.
Possiamo utilizzare questa funzionalità per archiviare gli shader nei tag di script. Ancora meglio, possiamo creare il nostro tipo e nel nostro JavaScript cercarlo per decidere se compilare lo shader come vertex shader o fragment shader.
In questo caso, la funzione createShaderFromScriptElement cerca uno script con l'attributo id specificato e poi esamina l'attributo type per decidere quale tipo di shader creare.
Elaborazione delle immagini WebGL
L'elaborazione delle immagini è semplice in WebGL. Quanto è facile? Leggi di seguito.
Per disegnare immagini in WebGL dobbiamo utilizzare le texture. Analogamente al modo in cui WebGL prevede le coordinate dello spazio di ritaglio durante il rendering anziché i pixel, WebGL prevede le coordinate della texture durante la lettura di una texture. Le coordinate della texture vanno da 0,0 a 1,0, indipendentemente dalle dimensioni della texture. Poiché stiamo disegnando un solo rettangolo (beh, 2 triangoli), dobbiamo indicare a WebGL a quale punto della texture corrisponde ogni punto del rettangolo. Passeremo queste informazioni dal vertex shader al fragment shader utilizzando un tipo speciale di variabile chiamata "varying". Si chiama variabile perché varia. WebGL interpolerà i valori forniti nello shader dei vertici mentre disegna ogni pixel utilizzando lo shader dei frammenti. Utilizzando il vertex shader della fine della sezione precedente, dobbiamo aggiungere un attributo per passare le coordinate della texture e poi passarle al fragment shader.
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 fragment 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 texture e copiare l'immagine nella texture. Poiché siamo in un browser, le immagini vengono caricate in modo asincrono, quindi dobbiamo riorganizzare un po' il nostro codice per attendere il caricamento della texture. Una volta caricata, la 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 entusiasmante, quindi manipoliamo l'immagine. Che ne dici di scambiare semplicemente il rosso e il blu?
...
gl_FragColor = texture2D(u_image, v_texCoord).bgra;
...
E se volessimo eseguire un'elaborazione delle immagini che esamini effettivamente altri pixel? Poiché WebGL fa riferimento alle texture in coordinate di texture che vanno da 0,0 a 1,0, possiamo calcolare quanto spostare per 1 pixel con la semplice operazione matematica onePixel = 1.0 / textureSize.
Ecco un fragment shader che calcola la media dei pixel sinistro e destro di ogni pixel nella texture.
<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>
Dobbiamo quindi passare le dimensioni della texture 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 elaborazioni di immagini comuni. In questo caso utilizzeremo un kernel 3x3. Un kernel di convoluzione è semplicemente una matrice 3x3 in cui ogni voce rappresenta il valore per cui moltiplicare gli 8 pixel intorno al pixel di cui stiamo eseguendo il rendering. Poi dividiamo il risultato per il peso del kernel (la somma di tutti i valori nel kernel) o per 1,0, a seconda di quale sia il valore maggiore. Ecco un articolo piuttosto interessante sull'argomento. E ecco un altro articolo che mostra un codice reale se dovessi scriverlo a mano in C++. Nel nostro caso faremo questo lavoro nello shader, quindi ecco il nuovo fragment shader.
<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 di averti convinto che l'elaborazione delle immagini in WebGL è piuttosto semplice. Ora ti spiegherò come applicare più di un effetto all'immagine.
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 uniform, che sono input per gli shader, v_ per le variabili, che sono valori passati da un vertex shader a un fragment shader e interpolati (o variati) tra i vertici per ogni pixel disegnato.
Applicazione di più effetti
La domanda successiva più ovvia per l'elaborazione delle immagini è: come si applicano più effetti?
Beh, potresti provare a generare gli shader al volo. Fornisci una UI che consenta all'utente di selezionare gli effetti che vuole utilizzare, quindi genera uno shader che esegua tutti gli effetti. Tuttavia, questo potrebbe non essere sempre possibile, anche se questa tecnica viene spesso utilizzata per creare effetti per la grafica in tempo reale. Un modo più flessibile è utilizzare altre due texture ed eseguire il rendering su ciascuna texture a turno, alternandole e applicando l'effetto successivo ogni volta.
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 dei framebuffer. In WebGL e OpenGL, il nome Framebuffer non è molto appropriato. Un framebuffer WebGL/OpenGL è in realtà solo una raccolta di stati e non un buffer di alcun tipo. Tuttavia, collegando una texture a un framebuffer, possiamo eseguire il rendering in quella texture. Per prima cosa, trasformiamo il vecchio codice di creazione delle texture 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 collegarle 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 poi un elenco di kernel 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 filtro, alternando la texture di cui eseguiamo il rendering.
// 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);
}
Alcune cose che dovrei ripassare.
Chiamando gl.bindFramebuffer con null si indica a WebGL che si desidera eseguire il rendering sulla tela invece che su uno dei framebuffer.
WebGL deve convertire lo spazio di ritaglio in pixel. Lo fa in base alle impostazioni di gl.viewport. Le impostazioni di gl.viewport vengono impostate per impostazione predefinita sulle dimensioni del canvas quando inizializziamo WebGL. Poiché i framebuffer in cui stiamo eseguendo il rendering hanno dimensioni diverse rispetto al canvas, dobbiamo impostare l'area visibile in modo appropriato.
Infine, negli esempi di WebGL Fundamentals abbiamo invertito la coordinata Y durante il rendering perché WebGL visualizza il canvas con 0,0 nell'angolo in basso a sinistra anziché nell'angolo in alto a sinistra, più tradizionale per il 2D. Questo non è necessario quando si esegue il rendering in un framebuffer. Poiché il framebuffer non viene mai visualizzato, la parte superiore e quella inferiore sono irrilevanti. L'unica cosa che conta è che il pixel 0,0 nel framebuffer corrisponda a 0,0 nei nostri calcoli. Per risolvere questo problema, ho reso possibile impostare se capovolgere o meno l'immagine 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>
Poi possiamo impostarlo quando eseguiamo 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 volessi eseguire un'elaborazione completa delle immagini, probabilmente avresti bisogno di molti programmi GLSL. Un programma per la regolazione di tonalità, saturazione e luminanza. Un altro per la luminosità e il contrasto. Uno per l'inversione, un altro per la regolazione dei livelli e così via. Dovresti modificare il codice per cambiare i programmi GLSL e aggiornare i parametri per quel particolare programma. Avevo pensato di scrivere questo esempio, ma è un esercizio che è meglio lasciare al lettore, perché più programmi GLSL, ciascuno con le proprie esigenze di parametri, probabilmente significano un importante refactoring per evitare che tutto diventi un gran pasticcio. Spero che questo e gli esempi precedenti abbiano reso WebGL un po' più accessibile e che iniziare con il 2D aiuti a rendere WebGL un po' più facile da capire. Se trovo il tempo, cercherò di scrivere altri articoli su come realizzare il 3D, oltre a fornire maggiori dettagli su ciò che WebGL sta effettivamente facendo dietro le quinte.
WebGL e alpha
Ho notato che alcuni sviluppatori OpenGL hanno problemi con il modo in cui WebGL tratta l'alpha nel backbuffer (ovvero il canvas), quindi ho pensato che potrebbe essere utile esaminare alcune delle differenze tra WebGL e OpenGL relative all'alpha.
La differenza principale tra OpenGL e WebGL è che OpenGL esegue il rendering in un backbuffer che non viene composto con nulla, o almeno non viene composto con nulla dal gestore delle finestre del sistema operativo, quindi il valore alfa non ha importanza.
WebGL viene composto dal browser con la pagina web e l'impostazione predefinita prevede l'utilizzo di alfa premoltiplicato, come i tag <img> .png con trasparenza e i tag canvas 2d.
WebGL offre diversi modi per renderlo più simile a OpenGL.
1. Indica a WebGL che vuoi che venga composto con alfa non premoltiplicato
gl = canvas.getContext("experimental-webgl", {premultipliedAlpha: false});
Il valore predefinito è true. Naturalmente, il risultato verrà comunque composto sulla pagina con il colore di sfondo che si trova sotto il canvas (il colore di sfondo del canvas, il colore di sfondo del contenitore del canvas, il colore di sfondo della pagina, gli elementi dietro il canvas se il canvas ha un z-index > 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 del canvas su un colore brillante come il rosso. Vedrai immediatamente cosa sta succedendo.
<canvas style="background: red;"></canvas>
Puoi anche impostarlo su nero, in modo da nascondere eventuali problemi di alfa.
2. Indica a WebGL che non vuoi l'alpha nel backbuffer
gl = canvas.getContext("experimental-webgl", {alpha: false});
In questo modo, il comportamento sarà più simile a quello di OpenGL, poiché il backbuffer avrà solo RGB. Questa è probabilmente l'opzione migliore perché un buon browser potrebbe rilevare che non hai un canale alfa e ottimizzare effettivamente il modo in cui WebGL viene composto. Naturalmente, questo significa anche che non avrà l'alpha nel backbuffer, quindi se utilizzi l'alpha nel backbuffer per qualche scopo, potrebbe non funzionare. Poche app che conosco utilizzano l'alpha nel backbuffer. Penso che probabilmente questa avrebbe dovuto essere l'impostazione predefinita.
3. Cancella l'alpha alla fine 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);
La cancellazione è in genere molto rapida, poiché esiste un caso speciale per questa operazione nella maggior parte dei componenti hardware. L'ho fatto nella maggior parte delle mie demo. Se fossi intelligente, passerei al metodo n. 2 descritto sopra. Forse lo farò subito dopo aver pubblicato questo post. Sembra che la maggior parte delle librerie WebGL debba utilizzare questo metodo per impostazione predefinita. I pochi sviluppatori che utilizzano effettivamente l'alpha per gli effetti di compositing possono richiederlo. Il resto otterrà semplicemente le migliori prestazioni e il minor numero di sorprese.
4. Cancella l'alpha una volta, quindi non eseguire 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);
Naturalmente, se esegui il rendering nei tuoi framebuffer, potresti dover riattivare il rendering in alfa e poi disattivarlo di nuovo quando passi al rendering nel canvas.
5. Gestione delle immagini
Inoltre, se carichi file PNG con canale alfa nelle texture, per impostazione predefinita il canale alfa viene premoltiplicato, il che generalmente NON è il modo in cui la maggior parte dei giochi esegue le operazioni. Se vuoi impedire questo comportamento, devi indicarlo a WebGL con
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
6. Utilizzo di un'equazione di combinazione che funziona 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 alfa non premoltiplicate. Se vuoi effettivamente lavorare con texture alfa premoltiplicate, probabilmente vorrai
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.