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:
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:
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!
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!
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!).
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.
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.
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:
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.
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).
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:
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!
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.
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:
Douloureux, non? Heureusement, nous n'avons pas à gérer tout cela si nous ne voulons pas que notre toile soit transparente.!
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