Mise en route dans WebGL, partie 3 contexte WebGL et effacement

Dans les articles précédents, nous avons appris à écrire de simples shaders de vertex et de fragment, à créer une page Web simple et à préparer un canevas pour le dessin. Dans cet article, nous allons commencer à travailler sur notre code de plate-forme WebGL. 

Nous allons obtenir un contexte WebGL et l'utiliser pour effacer le canevas avec la couleur de notre choix. Woohoo! Cela peut ne comporter que trois lignes de code, mais je vous promets que je ne le rendrai pas aussi facile! Comme d'habitude, j'essaierai d'expliquer les concepts JavaScript complexes lorsque nous les rencontrerons et de vous fournir tous les détails nécessaires pour comprendre et prévoir le comportement de WebGL correspondant..

Cet article fait partie de la série "Mise en route dans WebGL". Si vous n'avez pas lu les parties précédentes, je vous recommande de les lire en premier:

  1. Introduction aux Shaders
  2. L'élément de toile

résumer

Dans le premier article de cette série, nous avons écrit un shader simple qui dessine un dégradé coloré et le fade légèrement. Voici le shader que nous avons écrit:

Dans le deuxième article de cette série, nous avons commencé à travailler à l’utilisation de ce shader dans une page Web. En prenant de petits pas, nous avons expliqué le fond nécessaire de l’élément canvas. Nous:

  • fait une simple page
  • ajouté un élément de toile
  • acquis un contexte 2D à rendre à la toile
  • utilisé le contexte 2D pour dessiner une ligne
  • gestion des problèmes de redimensionnement de page
  • gestion des problèmes de densité de pixels

Voici ce que nous avons fait jusqu'à présent:

Dans cet article, nous empruntons des morceaux de code de l’article précédent et adaptons notre expérience à WebGL au lieu du dessin 2D. Dans le prochain article, si Allah le veut bien, je traiterai du traitement des fenêtres et de la suppression des primitives. Ça prend du temps, mais j'espère que vous trouverez la série complète très utile!

La configuration initiale

Construisons notre page WebGL. Nous utiliserons le même code HTML que celui utilisé pour l'exemple de dessin 2D:

        

… Avec une très petite modification. Ici on appelle la toile glCanvas au lieu de juste Toile (meh!).

Nous allons également utiliser le même CSS:

html, corps hauteur: 100%;  body margin: 0;  canvas display: block; largeur: 100%; hauteur: 100%; arrière-plan: # 000; 

Sauf pour la couleur de fond, qui est maintenant noire.

Nous n'utiliserons pas le même code JavaScript. Nous allons commencer avec pas de code JavaScript du tout, et ajouter des fonctionnalités peu à peu pour éviter toute confusion. Voici notre configuration jusqu'à présent:

Maintenant écrivons du code!

Contexte WebGL

La première chose à faire est d’obtenir un contexte WebGL pour le canevas. Comme nous l’avons fait lorsque nous avons obtenu un contexte de dessin 2D, nous utilisons la fonction membre getContext:

glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ("experimental-webgl");

Cette ligne contient deux getContext appels. Normalement, nous ne devrions pas avoir besoin du deuxième appel. Mais au cas où l’utilisateur utilise un ancien navigateur dans lequel l’implémentation WebGL est encore expérimentale (ou Microsoft Edge), nous avons ajouté le second. 

La bonne chose à propos de la || opérateur (ou opérateur) Est qu'il n'a pas à évaluer l'expression entière si le premier opérande s'est avéré être vrai. En d'autres termes, dans une expression a || b, si une évalue à vrai, alors si b est vrai ou faux n'affecte pas le résultat du tout. Ainsi, nous n'avons pas besoin d'évaluer b et c'est complètement ignoré. C'est appelé Évaluation de court-circuit.

Dans notre cas, getContext ("experimental-webgl") ne sera exécuté que si getContext ("webgl") échoue (retourne nul, qui évalue à faux dans une expression logique). 

Nous avons également utilisé une autre fonctionnalité du ou opérateur. Le résultat de or-ing n'est ni vrai ni faux. Au lieu de cela, c'est le premier objet qui évalue à vrai. Si aucun des objets n'est évalué à vrai, les retours l'objet le plus à droite dans l'expression. Cela signifie, après avoir exécuté la ligne ci-dessus, glContext contiendra soit un objet de contexte ou nul, mais non vrai ou faux.

Remarque: si le navigateur prend en charge les deux modes (webgl et expérimental-webgl) puis ils sont traités comme des alias. Il n'y aurait absolument aucune différence entre eux.

Mettre la ligne ci-dessus à laquelle elle appartient:

var glContext; function initialize () // Récupère le contexte WebGL, var glCanvas = document.getElementById ("glCanvas"); glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ("experimental-webgl"); if (! glContext) alert ("Impossible d'acquérir un contexte WebGL. Désolé!"); retourne faux;  return true; 

Voila! Nous avons notre initialiser fonction (ouais, continue à rêver!).

Traitement des erreurs getContext

Notez que nous n'avons pas utilisé essayer et capture détecter getContext comme nous l’avions fait dans l’article précédent. C'est parce que WebGL a ses propres mécanismes de rapport d'erreur. Il ne lève pas d'exception lorsque la création de contexte échoue. Au lieu de cela, il tire un webglcontextcreationerror un événement. Si le message d'erreur nous intéresse, nous devrions probablement procéder ainsi:

// écouteur d'erreur de création de contexte, var errorMessage = "Impossible de créer un contexte WebGL"; function onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage;  glCanvas.addEventListener ("webglcontextcreationerror", onContextCreationError, false);

En séparant ces lignes:

glCanvas.addEventListener ("webglcontextcreationerror", onContextCreationError, false);

Tout comme lorsque nous avons ajouté un écouteur à l'événement de chargement de fenêtre dans l'article précédent, nous avons ajouté un écouteur à la zone de dessin. webglcontextcreationerror un événement. le faux l'argument est facultatif; Je ne fais que l'inclure par souci d'exhaustivité (comme le montre l'exemple de spécification WebGL). Il est généralement inclus pour la compatibilité ascendante. Ça signifie useCapture. Quand vrai, cela signifie que l'auditeur va être appelé dans le phase de capture de la propagation de l'événement. Si faux, ça va être appelé dans le phase bouillonnante au lieu. Consultez cet article pour plus de détails sur la propagation d'événements.

Passons maintenant à l'auditeur lui-même:

var errorMessage = "Impossible de créer un contexte WebGL"; function onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage; 

Dans cet écouteur, nous conservons une copie du message d'erreur, le cas échéant. Oui, avoir un message d'erreur est totalement optionnel:

if (event.statusMessage) errorMessage = event.statusMessage;

Ce que nous avons fait ici est assez intéressant. Message d'erreur a été déclaré en dehors de la fonction, mais nous l'avons utilisé à l'intérieur. Ceci est possible en JavaScript et s'appelle fermetures. Ce qui est intéressant avec les fermetures, c'est leur durée de vie. Tandis que Message d'erreur est local à la initialiser fonction, car il a été utilisé à l'intérieur onContextCreationError, il ne sera pas détruit sauf onContextCreationError lui-même n'est plus référencé.

En d'autres termes, tant qu'un identifiant est toujours accessible, il ne peut pas être récupéré. Dans notre situation:

  • Message d'erreur vit parce que onContextCreationError le référence.
  • onContextCreationError vit parce qu'il est référencé quelque part parmi les auditeurs d'événement de toile. 

Donc, même si initialiser se termine, onContextCreationError est toujours référencé quelque part dans l'objet canvas. Seulement quand il sortira peut Message d'erreur être ramassé. En outre, les appels ultérieurs de initialiser n'affectera pas le précédent Message d'erreur. Chaque initialiser appel de fonction aura son propre Message d'erreur et onContextCreationError.

Mais nous ne voulons pas vraiment onContextCreationError vivre au-delà initialiser Résiliation. Nous ne voulons pas écouter d'autres tentatives visant à obtenir des contextes WebGL ailleurs dans le code. Alors:

glCanvas.removeEventListener ("webglcontextcreationerror", onContextCreationError, false);

Mettre tous ensemble:

Pour vérifier que le contexte a été créé avec succès, j'ai ajouté un simple alerte:

alert ("contexte WebGL créé avec succès!");

Passons maintenant au Résultat onglet pour exécuter le code.

Et ça ne marche pas! Évidemment, parce que initialiser n'a jamais été appelé. Nous devons l'appeler juste après le chargement de la page. Pour cela, nous allons ajouter ces lignes au-dessus:

window.addEventListener ('load', function () initialize ();, false);

Essayons encore:

Ça marche! Je veux dire, ça devrait l'être à moins qu'un contexte ne puisse être créé! Si ce n'est pas le cas, assurez-vous de consulter cet article à partir d'un périphérique / navigateur compatible WebGL..

Notez que nous avons fait une autre chose intéressante ici. Nous avons utilisé initialiser dans notre charge auditeur avant même d'être déclaré. Ceci est possible en JavaScript à cause de levage. Levée signifie que toutes les déclarations sont déplacées vers le haut de leur portée, alors que leurs initialisations restent à leur place.

Ne serait-il pas intéressant de vérifier si notre mécanisme de signalement des erreurs fonctionne réellement? Nous avons besoin getContext échouer. Un moyen simple de le faire est d’obtenir un type de contexte différent pour le canevas avant de tenter de créer le contexte WebGL (rappelez-vous quand nous avons dit que le premier getContext change le mode de travail de façon permanente?). Nous allons ajouter cette ligne juste avant d'obtenir le contexte WebGL:

glCanvas.getContext ("2d");

Et:

Génial! Maintenant si le message que vous avez vu était "Impossible de créer un contexte WebGL"ou quelque chose comme"Canvas a un contexte existant d'un type différent"Cela dépend si votre navigateur supporte webglcontextcreationerror ou pas. Au moment de la rédaction de cet article, Edge et Firefox ne le supportaient pas (cela était prévu pour Firefox 49, mais ne fonctionne toujours pas sous Firefox 50.1). Dans ce cas, l'écouteur d'événement ne sera pas appelé et Message d'erreur restera réglé sur "Impossible de créer un contexte WebGL". Heureusement, getContext revient toujours nul, Nous savons donc que nous ne pouvions pas créer le contexte. Nous n'avons tout simplement pas le message d'erreur détaillé.

Le problème avec les messages d'erreur WebGL est qu'il n'y a pas de message d'erreur WebGL! WebGL renvoie des nombres indiquant les états d'erreur, pas les messages d'erreur. Et quand il arrive d'autoriser des messages d'erreur, ils dépendent du pilote. La formulation exacte des messages d'erreur n'est pas fournie dans la spécification. Il appartient aux développeurs de pilotes de décider comment ils doivent le formuler. Attendez-vous donc à voir la même erreur libellée différemment sur différents appareils.

Alors ok. Depuis que nous nous sommes assurés que notre mécanisme de signalement des erreurs fonctionne, le "créé avec succès"alerte et getContext ("2d") ne sont plus nécessaires. Nous les omettrons.

Attributs de contexte

Retour à notre vénéré getContext une fonction:

glContext = glCanvas.getContext ("webgl");

Il y a des choses que l'œil ne peut pas voir. getContext peut éventuellement prendre un autre argument: un dictionnaire contenant un ensemble d'attributs de contexte et leurs valeurs. Si aucun n'est fourni, les valeurs par défaut sont utilisées:

dictionnaire WebGLContextAttributes GLboolean alpha = true; GLooléen profondeur = vrai; Pochoir GLooléen = faux; Antialias GLooléen = vrai; GLboolean premultipliedAlpha = true; GLboolean preserveDrawingBuffer = false; GLboolean preferLowPowerToHighPerformance = false; GLboolean failIfMajorPerformanceCaveat = false; ;

Je vais expliquer certains de ces attributs lorsque nous les utilisons. Pour en savoir plus à leur sujet, référez-vous à la section Attributs de contexte WebGL de la spécification WebGL. Pour l'instant, nous n'avons pas besoin d'un tampon de profondeur pour notre shader simple (plus à ce sujet plus tard). Et pour éviter de devoir l'expliquer, nous allons également désactiver alpha prémultiplié! Il faut un article en soi pour bien expliquer la raison qui le sous-tend. Ainsi, notre getContext la ligne devient:

var contextAttributes = profondeur: false, premultipliedAlpha: false; glContext = glCanvas.getContext ("webgl", contextAttributes) || glCanvas.getContext ("experimental-webgl", contextAttributes);

Remarque: profondeur, pochoir et antialias attributs, quand mis à vrai, sont des demandes, pas des exigences. Le navigateur doit faire de son mieux pour les satisfaire, mais ce n’est pas garanti. Cependant, quand ils sont configurés pour faux, le navigateur doit rester.

D'autre part, le alpha, prémultiplié Alpha et preserveDrawingBuffer les attributs sont des exigences qui doivent être remplies par le navigateur.

clearColor

Maintenant que nous avons notre contexte WebGL, il est temps de l'utiliser! L'une des opérations de base du dessin WebGL consiste à effacer le tampon de couleur (ou simplement le canevas dans notre cas). Le nettoyage de la toile se fait en deux étapes:

  1. Réglage de la couleur claire (peut être fait qu'une seule fois).
  2. En train de nettoyer la toile.

Les appels OpenGL / WebGL sont coûteux et les pilotes de périphérique ne sont pas forcément intelligents et évitent un travail inutile. Par conséquent, en règle générale, si nous pouvons éviter d’utiliser l’API, nous devons éviter de l’utiliser.. 

Donc, à moins que nous ayons besoin de changer la couleur claire à chaque image ou au milieu du dessin, nous devrions écrire le code en le définissant dans une fonction d’initialisation au lieu d’une fonction de dessin. De cette façon, il n’est appelé qu’une fois au début et pas à chaque image. Depuis la couleur claire n'est pas le seul état variable que nous allons initialiser, nous allons créer une fonction séparée pour l’initialisation d’état:

fonction initializeState () …

Et nous appellerons cette fonction depuis le initialiser une fonction:

function initialize () … // En cas d'échec, if (! glContext) alert (errorMessage); retourne faux;  initializeState (); retourne vrai; 

Belle! La modularité gardera notre code pas si court plus propre et plus lisible. Maintenant pour peupler le initializeState une fonction:

function initializeState () // Définissez clear-color sur red, glContext.clearColor (1.0, 0.0, 0.0, 1.0); 

clearColor prend quatre paramètres: rouge, vert, bleu et alpha. Quatre flotteurs, dont les valeurs sont serré à l'intervalle [0, 1]. En d'autres termes, toute valeur inférieure à 0 devient 0, toute valeur supérieure à 1 devient 1 et toute valeur intermédiaire reste inchangée. Initialement, la couleur claire est définie sur tous les zéros. Donc, si le noir transparent nous convenait, nous aurions pu l'omettre complètement.

Effacement de la mémoire tampon de dessin

Une fois la couleur claire définie, il ne reste plus qu’à nettoyer la zone de travail. Mais on ne peut s'empêcher de poser une question, devons-nous nettoyer la toile du tout??

À l’époque, les jeux offrant un rendu plein écran n’avaient pas besoin de vider l’écran à chaque image (essayez de taper idclip dans DOOM 2 et allez quelque part, vous n'êtes pas censé être!). Le nouveau contenu écraserait simplement les anciens et nous économiserions l'opération claire non triviale. 

Sur le matériel moderne, la suppression des mémoires tampons est extrêmement rapide. De plus, effacer les tampons peut réellement améliorer les performances! En termes simples, si le contenu de la mémoire tampon n’est pas effacé, le GPU devra peut-être extraire le contenu précédent avant de le remplacer. Si elles sont effacées, il n'est pas nécessaire de les récupérer à partir de la mémoire relativement lente.

Mais que se passe-t-il si vous ne voulez pas écraser tout l'écran, mais y ajouter progressivement? Comme quand on fait un programme de peinture. Vous voulez dessiner les nouveaux traits uniquement, tout en conservant les précédents. Le fait de quitter la toile sans nettoyer n'a-t-il pas de sens maintenant?

La réponse est toujours non. Sur la plupart des plateformes, vous utiliseriez double tampon. Cela signifie que tout le dessin que nous effectuons est fait sur une tampon de retour tandis que le moniteur récupère son contenu à partir d'un tampon avant. Pendant le retour vertical, ces tampons sont échangés. L'arrière devient l'avant et l'avant devient l'arrière. De cette façon, nous évitons d’écrire dans la même mémoire en cours de lecture et d’affichage par le moniteur, évitant ainsi les artefacts dus à un dessin incomplet ou trop rapide (avoir dessiné plusieurs images alors que le moniteur en traçait un seul).

Ainsi, l'image suivante ne remplace pas l'image actuelle, car elle n'est pas écrite dans le même tampon. Au lieu de cela, il écrase celui qui était dans la mémoire tampon avant l'échange. C'est la dernière image. Et tout ce que nous avons dessiné dans ce cadre n'apparaîtra pas dans le prochain. Cela apparaîtra dans le prochain, le suivant. Cette incohérence entre les tampons provoque un scintillement qui est normalement indésirable.

Mais cela aurait fonctionné si nous utilisions une seule configuration en mémoire tampon. Dans OpenGL sur la plupart des plateformes, nous avons un contrôle explicite sur la mise en mémoire tampon et l’échange des tampons, mais pas dans WebGL. C'est au navigateur de le gérer tout seul. 

Umm… Peut-être que ce n'est pas le meilleur moment, mais il y a une chose à propos de la suppression du tampon de dessin que je n'ai pas mentionnée auparavant. Si nous ne le clarifions pas explicitement, il le serait implicitement pour nous.! 

WebGL 1.0 ne comporte que trois fonctions de dessin: clair, drawArrays, et drawElements. Seulement si nous appelons l'un de ceux-ci sur le tampon de dessin actif (ou si nous venons de créer le contexte ou de redimensionner le canevas), il doit être présenté au compositeur de page HTML au début de la prochaine opération de composition.. 

Après la composition, les tampons de dessin sont effacé automatiquement. Le navigateur est autorisé à être intelligent et à éviter de vider les tampons automatiquement si nous les avons effacés nous-mêmes. Mais le résultat final est le même. les tampons vont être effacés quand même.

La bonne nouvelle est qu’il existe toujours un moyen de faire fonctionner votre programme de peinture. Si vous insistez pour faire du dessin incrémental, nous pouvons définir la preserveDrawingBuffer attribut context lors de l'acquisition du contexte:

glContext = glCanvas.getContext ("webgl", preserveDrawingBuffer: true);

Cela évite d'effacer automatiquement le canevas après la composition et simule une seule configuration en mémoire tampon. Une façon de procéder consiste à copier le contenu de la mémoire tampon avant dans la mémoire tampon arrière après la permutation. Dessiner dans une mémoire tampon est toujours nécessaire pour éviter de dessiner des artefacts. Il ne peut donc pas être aidé. Ceci, bien sûr, a un prix. Cela peut affecter les performances. Donc, si possible, utilisez d’autres approches pour conserver le contenu du tampon de dessin, comme dessiner vers un objet frame buffer (ce qui dépasse le cadre de ce tutoriel).

clair

Préparez-vous, nous allons nettoyer la toile à tout moment maintenant! Encore une fois, pour la modularité, écrivons le code qui dessine la scène à chaque image dans une fonction distincte:

function drawScene () // Efface le tampon de couleur, glContext.clear (glContext.COLOR_BUFFER_BIT); 

Maintenant nous l'avons fait! clair prend un paramètre, un champ de bits qui indique quels tampons doivent être effacés. Il s'avère que nous avons généralement besoin de plus que simplement un tampon de couleur pour dessiner des éléments 3D. Par exemple, un tampon de profondeur est utilisé pour suivre les profondeurs de chaque pixel dessiné. À l'aide de ce tampon, lorsque le GPU est sur le point de dessiner un nouveau pixel, il peut facilement décider si ce pixel est fermé ou est fermé par le pixel précédent qui se trouve à sa place.. 

Ça va comme ça:

  1. Calculer la profondeur du nouveau pixel.
  2. Lire la profondeur de l'ancien pixel à partir du tampon de profondeur.
  3. Si la profondeur du nouveau pixel est plus proche que la profondeur de l'ancien pixel, écrasez la couleur du pixel (ou fusionnez-la) et définissez sa profondeur sur la nouvelle profondeur. Sinon, jetez le nouveau pixel.

J'ai utilisé "plus près" au lieu de "plus petit" parce que nous avons un contrôle explicite sur le fonction de profondeur (quel opérateur utiliser en comparaison). Nous devons décider si une valeur plus grande signifie un pixel plus proche (système de coordonnées à droite) ou inversement (à gauche). 

La notion de droitier ou de gaucher fait référence à la direction de votre pouce (axe des z) lorsque vous enroulez vos doigts de l'axe des x à l'axe des y. Je suis un mauvais dessin, alors consultez cet article du Centre de développement Windows. WebGL est gaucher par défaut, mais vous pouvez le faire à droite en modifiant la fonction de profondeur, tant que vous prenez en compte la plage de profondeur et les transformations nécessaires..

Comme nous avons choisi de ne pas utiliser de tampon de profondeur lors de la création de notre contexte, le seul tampon à effacer est le tampon de couleur. Ainsi, nous définissons le COLOR_BUFFER_BIT. Si nous avions un tampon de profondeur, nous aurions fait ceci à la place:

glContext.clear (glContext.COLOR_BUFFER_BIT | glContext.GL_DEPTH_BUFFER_BIT);

Il ne reste plus qu'à appeler drawScene. Faisons-le juste après l'initialisation:

window.addEventListener ('load', function () // Tout initialiser, initialiser (); // Commencer à dessiner, drawScene ();, false);

Basculer vers le Résultat onglet pour voir notre belle couleur claire rouge!

Canvas Alpha-Compositing

Un des faits importants à propos de clair c'est qu'il n'applique pas d'alpha-compositing. Même si nous utilisons explicitement une valeur pour alpha qui la rend transparente, la couleur claire serait simplement écrite dans la mémoire tampon sans aucune composition, remplaçant ainsi tout ce qui avait été dessiné auparavant. Ainsi, si vous avez une scène dessinée sur la toile et que vous effacez ensuite avec une couleur transparente, la scène sera complètement effacée.. 

Cependant, le navigateur utilise toujours la composition alpha pour l'intégralité de la trame et utilise la valeur alpha présente dans le tampon chromatique, qui aurait pu être définie lors de l'effacement. Ajoutons du texte sous la toile, puis effacez-le avec une couleur rouge semi-transparente pour le voir en action. Notre HTML serait:

 

Chut, je me cache derrière la toile pour que tu ne puisses pas me voir.

et le clair la ligne devient:

// Définit la couleur transparente sur le rouge transparent, glContext.clearColor (1.0, 0.0, 0.0, 0.5);

Et maintenant, la révélation:

Regarder attentivement. Là-haut dans le coin supérieur gauche… il n'y a absolument rien! Bien sûr, vous ne pouvez pas voir le texte! C'est parce que dans notre CSS, nous avons spécifié # 000 comme fond de toile. L'arrière-plan fait office de calque supplémentaire sous la grille, de sorte que le navigateur alpha-compose le tampon de couleur contre celui-ci tout en masquant complètement le texte. Pour que cela soit plus clair, nous allons changer le fond en vert et voir ce qui se passe:

arrière-plan: # 0f0;

Et le résultat:

Semble raisonnable. Cette couleur semble être rgb (128, 127, 0), peut être considéré comme le résultat de la fusion du rouge et du vert avec un alpha égal à 0,5 (sauf si vous utilisez Microsoft Edge, dans lequel la couleur doit être rgb (255, 127, 0) car il ne supporte pas premultiplied-alpha pour le moment). Nous ne pouvons toujours pas voir le texte, mais au moins nous savons comment la couleur de fond affecte notre dessin.

Mélange Alpha

Le résultat invite la curiosité, cependant. Pourquoi le rouge a été réduit de moitié 128, tandis que le vert a été réduit de moitié à 127? Ne devraient-ils pas être tous les deux 128 ou 127, en fonction de l'arrondi en virgule flottante? La seule différence entre eux est que la couleur rouge a été définie en tant que couleur claire dans le code WebGL, tandis que la couleur verte a été définie dans le CSS. Honnêtement, je ne sais pas pourquoi cela se produit, mais j'ai une théorie. C'est probablement à cause de la fonction de mélange utilisé pour fusionner les deux couleurs.

Lorsque vous dessinez quelque chose de transparent au-dessus d’autre chose, la fonction de fusion s’active. Elle définit la couleur finale du pixel (EN DEHORS) doit être calculée à partir de la couche supérieure (couche source, SRC) et le calque inférieur (calque de destination, Heure d'été). Lorsque vous dessinez avec WebGL, vous avez le choix entre plusieurs fonctions de fusion. Mais lorsque le navigateur alpha compose le canevas avec les autres calques, nous n’avons que deux modes (pour l’instant): alpha prémultiplié et non premultiplied-alpha (appelons cela Ordinaire mode). 

Le mode alpha normal se présente comme suit:

OUTᴀ = SRCᴀ + DSTᴀ (1 - SRCᴀ) OUTʀɢʙ = SRCʀɢʙ.SRCᴀ + DSTʀɢʙ.DSTᴀ (1 - SRCᴀ)

Dans le mode alpha prémultiplié, les valeurs RVB sont supposées être déjà multipliées par les valeurs alpha correspondantes (d'où le nom pré-multiplié). Dans ce cas, les équations sont réduites à:

OUTᴀ = SRCᴀ + DSTᴀ (1 - SRCᴀ) OUTʀɢʙ = SRCʀɢʙ + DSTʀɢʙ (1 - SRCᴀ)

Puisque nous n’utilisons pas d’alpha premultiplied-alpha, nous nous basons sur le premier ensemble d’équations. Ces équations supposent que les composantes de couleur sont des valeurs à virgule flottante allant de 0 à 1. Mais ce n'est pas ainsi qu'ils sont stockés en mémoire. Au lieu de cela, ils sont des valeurs entières allant de 0 à 255. Alors srcAlpha (0.5) devient 127 (ou 128, en fonction de la manière dont vous l’entourez), et 1 - srcAlpha (1 - 0,5) devient 128 (ou 127). C'est parce que la moitié 255 (lequel est 127,5) n'est pas un entier, on finit donc par perdre l'une des couches 0.5 et l'autre gagnant un 0.5 dans leurs valeurs alpha. Affaire classée!

Remarque: la composition alpha ne doit pas être confondue avec les modes de fusion CSS. La composition alpha est d'abord effectuée, puis la couleur calculée est mélangée au calque de destination à l'aide des modes de fusion..   

Retour à notre texte caché. Essayons de rendre l'arrière-plan en vert transparent:

arrière-plan: rgba (0, 255, 0, 0,5);

Finalement:

Vous devriez pouvoir voir le texte maintenant! C'est à cause de la façon dont ces couches sont peintes les unes sur les autres:

  1. Le texte est d'abord dessiné sur un fond blanc.
  2. La couleur d'arrière-plan (transparente) est dessinée par-dessus, ce qui donne un arrière-plan vert blanchâtre et un texte verdâtre.
  3. Le tampon de couleur est mélangé avec le résultat, ce qui donne ce qui précède…. 

Douloureux, non? Heureusement, nous n'avons pas à gérer tout cela si nous ne voulons pas que notre toile soit transparente.! 

Désactiver Alpha

var contextAttributes = profondeur: false, alpha: false; glContext = glCanvas.getContext ("webgl", contextAttributes) || glCanvas.getContext ("experimental-webgl", contextAttributes);

Maintenant, notre tampon de couleur n'aura pas de canal alpha pour commencer! Mais cela ne nous empêcherait-il pas de dessiner des trucs transparents? 

La réponse est non. J'ai mentionné précédemment que WebGL avait des fonctions de fusion flexibles indépendantes de la manière dont le navigateur mélange le fond avec d'autres éléments de page. Si nous utilisons une fonction de fusion qui aboutit à une fusion alpha prémultipliée, nous n'avons absolument pas besoin du canal alpha de la mémoire tampon de dessin:

OUTᴀ = SRCᴀ + DSTᴀ (1 - SRCᴀ) OUTʀɢʙ = SRCʀɢʙ + DSTʀɢʙ (1 - SRCᴀ) 

Si nous ne tenons pas compte outAlpha dans l'ensemble, nous ne perdons rien. C