Aan de slag in WebGL, deel 3 WebGL-context en wissen

In de vorige artikelen leerden we eenvoudige vertex- en fragmentschilfers te schrijven, een eenvoudige webpagina te maken en een tekenpapier voor te bereiden. In dit artikel gaan we werken aan onze WebGL-boilerplate-code. 

We krijgen een WebGL-context en gebruiken deze om het canvas leeg te maken met de kleur van onze keuze. Woohoo! Dit kunnen zo weinig zijn als drie regels code, maar ik beloof je dat ik het niet zo makkelijk zal maken! Zoals gewoonlijk zal ik proberen de lastige JavaScript-concepten uit te leggen terwijl we ze tegenkomen, en zal je alle details krijgen die je nodig hebt om het bijbehorende WebGL-gedrag te begrijpen en te voorspellen.

Dit artikel maakt deel uit van de reeks "Aan de slag in WebGL". Als u de voorgaande delen nog niet hebt gelezen, raad ik u aan deze eerst te lezen:

  1. Introductie van Shaders
  2. Het canvaselement

Samenvatting

In het eerste artikel van deze serie schreven we een eenvoudige arcering die een kleurrijk verloop tekent en het lichtjes in en uit laat faden. Dit is de shader die we hebben geschreven:

In het tweede artikel van deze serie zijn we begonnen met het gebruik van deze arcering op een webpagina. Met kleine stapjes hebben we de noodzakelijke achtergrond van het canvaselement uitgelegd. Wij:

  • maakte een eenvoudige pagina
  • een canvas-element toegevoegd
  • verwierf een 2D-context om op het canvas te renderen
  • gebruikte de 2D-context om een ​​lijn te tekenen
  • afgehandelde problemen met het wijzigen van de pagina's
  • afgehandelde problemen met pixeldichtheid

Dit is wat we tot nu toe hebben gedaan:

In dit artikel lenen we enkele stukjes code uit het vorige artikel en stemmen onze ervaring af op WebGL in plaats van 2D-tekening. In het volgende artikel - als Allah het wil - behandel ik de verwerking van de viewport en het knippen van primitieven. Het duurt even, maar ik hoop dat je de hele serie erg nuttig zult vinden!

Initiële setup

Laten we onze door WebGL aangedreven pagina bouwen. We gebruiken dezelfde HTML die we hebben gebruikt voor het 2D-tekeningvoorbeeld:

        

... met een zeer kleine aanpassing. Hier noemen we het canvas glCanvas in plaats van gewoon canvas (Meh!).

We gebruiken ook dezelfde CSS:

html, body height: 100%;  body marge: 0;  canvas weergave: blok; breedte: 100%; hoogte: 100%; achtergrond: # 000; 

Behalve de achtergrondkleur, die nu zwart is.

We zullen niet dezelfde JavaScript-code gebruiken. We beginnen helemaal met geen JavaScript-code en voegen beetje bij beetje functionaliteit toe om verwarring te voorkomen. Dit is onze setup tot nu toe:

Laten we nu wat code schrijven!

WebGL-context

Het eerste wat we moeten doen is een WebGL-context voor het canvas verkrijgen. Net als bij het verkrijgen van een 2D-tekencontext, gebruiken we de ledfunctie getContext:

glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ( "experimentele WebGL");

Deze regel bevat twee getContext noemt. Normaal hebben we de tweede oproep niet nodig. Maar voor het geval de gebruiker een oude browser gebruikt waarin de WebGL-implementatie nog steeds experimenteel is (of Microsoft Edge), hebben we de tweede toegevoegd. 

Het leuke aan het || operator (of operator) Is dat het niet de gehele uitdrukking hoeft te evalueren als de eerste operand werd gevonden waar. Met andere woorden, in een uitdrukking a || b, als een evalueert naar waar, dan of b is waar of vals heeft helemaal geen invloed op de uitkomst. We hoeven dus niet te evalueren b en het wordt volledig overgeslagen. Dit heet Korte-kring evaluatie.

In ons geval, getContext ( "experimentele WebGL") wordt alleen uitgevoerd als getContext ( "WebGL") mislukt (retourneert nul, welke evalueert naar vals in een logische uitdrukking). 

We hebben ook een andere functie van de of operator. Het resultaat van oring is geen van beide waar noch vals. In plaats daarvan is het dat wel het eerste object dat evalueert waar. Als geen van de objecten evalueert waar, of retouren het meest rechtse object in de expressie. Dit betekent na het uitvoeren van de bovenstaande regel, glContext bevat een contextobject of nul, maar niet waar of vals.

Opmerking: als de browser beide modi ondersteunt (WebGL en experimenteel WebGL) dan worden ze behandeld als aliassen. Er zou absoluut geen verschil tussen hen zijn.

De bovenstaande regel plaatsen waar deze hoort:

var glContext; function initialize () // WebGL-context ophalen, var glCanvas = document.getElementById ("glCanvas"); glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ( "experimentele WebGL"); if (! glContext) alert ("Mislukt om een ​​WebGL-context te verkrijgen. Sorry!"); return false;  return true; 

Voila! We hebben onze initialiseren functie (ja, blijf dromen!).

GetContext-fouten afhandelen

Merk op dat we niet hebben gebruikt proberen en vangst detecteren getContext problemen zoals we in het vorige artikel hebben gedaan. Dat komt omdat WebGL zijn eigen mechanismen voor foutrapportage heeft. Het werpt geen uitzondering als contextcreatie mislukt. In plaats daarvan vuurt het een webglcontextcreationerror evenement. Als we geïnteresseerd zijn in de foutmelding, moeten we dit waarschijnlijk doen:

// Context creation error listener, var errorMessage = "Kan geen WebGL-context maken"; function onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage;  glCanvas.addEventListener ("webglcontextcreationerror", onContextCreationError, false);

Deze lijnen uit elkaar trekken:

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

Net als toen we een luisteraar toevoegden aan de vensterbelastingsgebeurtenis in het vorige artikel, voegden we een luisteraar toe aan het canvas webglcontextcreationerror evenement. De vals argument is optioneel; Ik neem het alleen op voor volledigheid (aangezien het het voorbeeld van de WebGL-specificatie heeft). Het wordt meestal meegeleverd voor compatibiliteit met eerdere versies. Het staat voor useCapture. Wanneer waar, het betekent dat de luisteraar wordt gebeld in de fase vastleggen van de gebeurtenisvoortplanting. Als vals, het zal worden genoemd in de bubbling fase in plaats daarvan. Raadpleeg dit artikel voor meer informatie over het verspreiden van evenementen.

Nu naar de luisteraar zelf:

var errorMessage = "Kan geen WebGL-context maken"; function onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage; 

In deze luisteraar bewaren we een kopie van het eventuele foutbericht. Ja, het hebben van een foutmelding is volledig optioneel:

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

Wat we hier hebben gedaan, is best interessant. errormessage werd verklaard buiten de functie, maar we gebruikten het binnen. Dit is mogelijk in JavaScript en wordt genoemd sluitingen. Wat interessant is aan sluitingen is hun levensduur. Terwijl errormessage is lokaal voor de initialiseren functie, omdat het binnen werd gebruikt onContextCreationError, het zal niet vernietigd worden, tenzij onContextCreationError naar zichzelf wordt niet meer verwezen.

Met andere woorden, zolang een identifier nog steeds toegankelijk is, kan er geen garbage verzameld worden. In onze situatie:

  • errormessage leeft omdat onContextCreationError verwijst ernaar.
  • onContextCreationError leeft omdat er ergens naar verwezen wordt tussen de luisteraars van canvas-evenementen. 

Dus, zelfs als initialiseren eindigt, onContextCreationError Er wordt nog steeds ergens in het canvasobject naar verwezen. Alleen wanneer het is vrijgegeven kan errormessage vuilnis worden verzameld. Bovendien, volgende oproepen van initialiseren heeft geen invloed op het vorige errormessage. elk initialiseren functieaanroep heeft zijn eigen functie errormessage en onContextCreationError.

Maar we willen niet echt onContextCreationError om verder te leven initialiseren beëindiging. We willen niet luisteren naar andere pogingen om WebGL-contexten ergens anders in de code te krijgen. Zo:

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

Alles bij elkaar:

Om te verifiëren dat we de context met succes hebben gemaakt, heb ik een eenvoudige toegevoegd alarm:

alert ("WebGL context succesvol aangemaakt!");

Schakel nu over naar Resultaat tab om de code uit te voeren.

En het werkt niet! Duidelijk, omdat initialiseren werd nooit gebeld. We moeten het meteen noemen nadat de pagina is geladen. Hiervoor voegen we deze regels erboven toe:

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

Laten we het opnieuw proberen:

Het werkt! Ik bedoel, het zou moeten tenzij een context niet gecreëerd kon worden! Als dit niet het geval is, zorg er dan voor dat u dit artikel vanuit een voor WebGL geschikt apparaat / browser bekijkt.

Merk op dat we hier nog iets interessants hebben gedaan. We gebruikten initialiseren in onze laden luisteraar voordat het zelfs werd verklaard. Dit is mogelijk in JavaScript vanwege hijsen. Hijsen betekent dat alle aangiften naar de top van hun scope worden verplaatst, terwijl hun initialisaties op hun plaats blijven.

Zou het niet leuk zijn om te testen of ons mechanisme voor foutrapportage echt werkt? Wij hebben nodig getContext falen. Een eenvoudige manier om dit te doen, is eerst een ander type context voor het canvas te verkrijgen voordat u probeert de WebGL-context te maken (herinner toen we zeiden dat de eerste succesvolle getContext verandert de canvasmodus permanent?). We voegen deze regel toe net voordat de WebGL-context wordt opgehaald:

glCanvas.getContext ( "2D");

En:

Super goed! Nu of het bericht dat u zag was "Kan geen WebGL-context maken"of zoiets"Canvas heeft een bestaande context van een ander type"hangt af van of uw browser dit ondersteunt webglcontextcreationerror of niet. Op het moment dat dit artikel wordt geschreven, ondersteunen Edge en Firefox dit niet (het was gepland voor Firefox 49, maar werkt nog steeds niet op Firefox 50.1). In dat geval wordt de gebeurtenislistener niet gebeld en errormessage blijft ingesteld op "Kan geen WebGL-context maken". Gelukkig, getContext keert nog steeds terug nul, dus we weten dat we de context niet konden creëren. We hebben gewoon niet de gedetailleerde foutmelding.

Het probleem met WebGL-foutmeldingen is dat ... er geen WebGL-foutmeldingen zijn! WebGL retourneert nummers die foutstatussen aangeven, niet foutberichten. En als het gebeurt dat foutmeldingen worden toegestaan, zijn ze afhankelijk van de bestuurder. De exacte bewoording van de foutmeldingen is niet opgenomen in de specificatie - het is aan de ontwikkelaars van de drivers om te beslissen hoe ze het moeten doen. Dus verwacht dezelfde fout anders te zien op verschillende apparaten.

Oke dan. Omdat we ervoor hebben gezorgd dat ons mechanisme voor foutrapportage werkt,succesvol gemaakt"alert en getContext ( "2D") zijn niet langer nodig. We zullen ze weglaten.

Context Attributen

Terug naar onze vereerde getContext functie:

glContext = glCanvas.getContext ("webgl");

Er is meer aan de hand dan op het eerste gezicht lijkt. getContext kan optioneel nog een argument aannemen: een woordenboek dat een set contextkenmerken en hun waarden bevat. Als er geen wordt opgegeven, worden de standaardwaarden gebruikt:

dictionary WebGLContextAttributes GLboolean alpha = true; GLboolean depth = true; GLboolean stencil = false; GLboolean antialias = true; GLboolean premultipliedAlpha = true; GLboolean preserveDrawingBuffer = false; GLboolean preferLowPowerToHighPerformance = false; GLboolean failedIfMajorPerformanceCaveat = false; ;

Ik zal enkele van deze kenmerken uitleggen terwijl we ze gebruiken. U kunt meer informatie over ze vinden in de WebGL-contextkenmerken van de WebGL-specificatie. Voor nu hebben we geen a nodig dieptebuffer voor onze eenvoudige shader (later meer). En om te voorkomen dat we het moeten uitleggen, zullen we ook uitschakelen vooraf vermenigvuldigde-alpha! Er is een eigen artikel voor nodig om de redenering erachter goed uit te leggen. Dus onze getContext regel wordt:

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

Notitie: diepte, stencil en antialias attributen, indien ingesteld op waar, zijn verzoeken, geen vereisten. De browser moet proberen het beste te doen om ze tevreden te stellen, maar het is niet gegarandeerd. Wanneer ze echter zijn ingesteld op vals, de browser moet blijven.

Aan de andere kant, de alpha, premultipliedAlpha en preserveDrawingBuffer kenmerken zijn vereisten waaraan de browser moet voldoen.

clearColor

Nu we onze WebGL-context hebben, is het tijd om deze daadwerkelijk te gebruiken! Een van de basisbewerkingen in WebGL-tekening is het opruimen van de kleurenbuffer (of gewoon het canvas in onze situatie). Het canvas opruimen gebeurt in twee stappen:

  1. De heldere kleur instellen (kan slechts één keer worden gedaan).
  2. Eigenlijk het canvas opruimen.

OpenGL / WebGL-aanroepen zijn duur en de apparaatstuurprogramma's zijn niet gegarandeerd zeer slim en vermijden onnodig werk. Daarom, als vuistregel, als we de API niet kunnen gebruiken, moeten we het gebruik ervan vermijden. 

Dus, tenzij we de clear-color in elk frame of in het midden van de tekening moeten wijzigen, moeten we de code schrijven in een initialisatiefunctie in plaats van een tekening. Op deze manier wordt het slechts één keer aan het begin genoemd en niet bij elk frame. Omdat de heldere kleur niet de enige is toestandsvariabele die we zullen initialiseren, we zullen een aparte functie maken voor statusinitialisatie:

function initializeState () ...

En we zullen deze functie vanuit het initialiseren functie:

function initialize () ... // Indien mislukt, indien (! glContext) alert (errorMessage); return false;  initializeState (); geef waar terug; 

Mooi! Modulariteit zal onze niet zo korte code schoner en leesbaarder houden. Nu om het te vullen initializeState functie:

function initializeState () // Stel clear-color in op rood, glContext.clearColor (1.0, 0.0, 0.0, 1.0); 

clearColor neemt vier parameters: rood, groen, blauw en alpha. Vier drijvers, waarvan de waarden zijn geklemd tot het bereik [0, 1]. Met andere woorden, elke waarde kleiner dan 0 wordt 0, elke waarde groter dan 1 wordt 1, en elke waarde daartussen blijft ongewijzigd. Aanvankelijk is de heldere kleur ingesteld op alle nullen. Dus als transparant zwart in orde was met ons, hadden we dit helemaal kunnen weglaten.

De tekenbuffer leegmaken

Na het instellen van de heldere kleur, blijft het canvas feitelijk vrij. Maar je kan het niet laten om een ​​vraag te stellen, moeten we het doek helemaal wissen?

Vroeger hoefden games die op het volledige scherm werden weergegeven niet elk scherm leeg te maken (probeer te typen idclip in DOOM 2 en ga ergens waar je niet hoort te zijn!). De nieuwe inhoud overschrijft gewoon de oude en we zouden de niet-triviale duidelijke bewerking opslaan. 

Op moderne hardware is het wissen van de buffers extreem snel. Bovendien kan het opschonen van de buffers de prestaties zelfs verbeteren! Simpel gezegd, als de inhoud van de buffer niet werd gewist, moet de GPU mogelijk de vorige inhoud ophalen voordat ze worden overschreven. Als ze zijn gewist, is het niet nodig om ze uit het relatief langzamere geheugen te halen.

Maar wat als u niet het hele scherm wilt overschrijven, maar er stap voor stap aan wilt toevoegen? Zoals bij het maken van een schilderprogramma. U wilt alleen de nieuwe streken tekenen, terwijl u de vorige behoudt. Is het niet zinvol om het doek te verlaten zonder te klaren??

Het antwoord is nog steeds nee. Op de meeste platforms die u zou gebruiken dubbele buffering. Dit betekent dat alle tekeningen die we uitvoeren op a staat achterbuffer terwijl de monitor de inhoud van a ophaalt front buffer. Tijdens de verticale terugval worden deze buffers uitgewisseld. De achterkant wordt de voorkant en de voorkant wordt de achterkant. Op deze manier vermijden we dat we naar hetzelfde geheugen schrijven dat momenteel door de monitor wordt gelezen en weergegeven, waardoor artefacten worden vermeden als gevolg van onvolledig tekenen of te snel tekenen (nadat meerdere frames zijn geschreven terwijl de monitor nog steeds één frame traceert).

Het volgende frame overschrijft dus niet het huidige frame, omdat het niet naar dezelfde buffer is geschreven. In plaats daarvan wordt het exemplaar overschreven dat zich in de frontbuffer bevond voordat het werd verwisseld. Dat is het laatste frame. En alles wat we in dit kader hebben getekend, zal niet in de volgende verschijnen. Het verschijnt in de volgende. Deze inconsistentie tussen buffers veroorzaakt flikkeren dat normaal ongewenst is.

Maar dit zou gewerkt hebben als we een enkele gebufferde opstelling gebruikten. In OpenGL op de meeste platforms hebben we expliciete controle over het bufferen en omwisselen van de buffers, maar niet in WebGL. Het is aan de browser om het zelf te regelen. 

Umm ... Misschien is het niet de beste tijd, maar er is één ding over het opruimen van de tekenbuffer die ik niet eerder heb genoemd. Als we het niet expliciet opruimen, wordt dit impliciet voor ons gewist! 

Er zijn slechts drie tekenfuncties in WebGL 1.0: duidelijk, drawArrays, en drawElements. Alleen als we een van deze oproepen in de actieve tekenbuffer (of als we zojuist de context hebben gemaakt of het formaat van het canvas hebben gewijzigd), moet deze aan het begin van de volgende compositiefunctie aan de HTML-pagina-compositor worden gepresenteerd. 

Na het compositeren zijn de tekenbuffers automatisch gewist. De browser mag slim zijn en voorkomen dat de buffers automatisch worden gewist als we ze zelf opschonen. Maar het eindresultaat is hetzelfde; de buffers zullen hoe dan ook worden gewist.

Het goede nieuws is dat er nog steeds een manier is om je verfprogramma te laten werken. Als u erop staat om incrementeel tekenen te doen, kunnen we de preserveDrawingBuffer contextattribuut bij het verkrijgen van de context:

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

Dit voorkomt dat het canvas automatisch wordt gewist na het compositeren en simuleert een enkele gebufferde instelling. Een manier om dat te doen is door de inhoud van de frontbuffer na het swappen naar de backbuffer te kopiëren. Het tekenen naar een achtergrondbuffer is nog steeds nodig om tekenobjecten te voorkomen, dus het kan niet worden geholpen. Dit komt natuurlijk met een prijs. Dit kan de prestaties beïnvloeden. Gebruik dus, indien mogelijk, andere benaderingen om de inhoud van de tekenbuffer te behouden, zoals tekenen naar a framebufferobject (wat buiten het bestek van deze tutorial valt).

duidelijk

Zet je schrap, we zullen het canvas nu elk moment opruimen! Nogmaals, voor modulariteit, laten we de code schrijven die de scène elk frame in een aparte functie tekent:

functie drawScene () // Kleurbuffer wissen, glContext.clear (glContext.COLOR_BUFFER_BIT); 

Nu hebben we het gedaan! duidelijk neemt één parameter, een bitveld dat aangeeft welke buffers moeten worden gewist. Het blijkt dat we meestal meer nodig hebben dan alleen een kleurenbuffer om 3D-dingen te tekenen. Er wordt bijvoorbeeld een dieptebuffer gebruikt om de diepten van elke getrokken pixel bij te houden. Met deze buffer kan de GPU, wanneer hij op het punt staat een nieuwe pixel te tekenen, gemakkelijk beslissen of deze pixel de vorige pixel afsluit of wordt afgesloten door de vorige pixel die op zijn plaats zit. 

Het gaat als volgt:

  1. Bereken de diepte van de nieuwe pixel.
  2. Lees de diepte van de oude pixel uit de dieptebuffer.
  3. Als de diepte van de nieuwe pixel is dichterbij dan de diepte van de oude pixel, overschrijf de pixelkleur (of meng ermee) en stel de diepte in op de nieuwe diepte. Anders gooit u de nieuwe pixel weg.

Ik heb "closer" gebruikt in plaats van "smaller" omdat we expliciete controle hebben over de dieptefunctie (welke operator te gebruiken in vergelijking). We kunnen beslissen of een grotere waarde een dichterbij gelegen pixel betekent (rechtshandig coördinatenstelsel) of andersom (linksdraaiend). 

Het begrip rechts- of linkshandigheid verwijst naar de richting van uw duim (z-as) terwijl u uw vingers van de x-as naar de y-as krult. Ik ben slecht in tekenen, dus bekijk dit artikel in het Windows Dev Center. WebGL is standaard linkshandig, maar u kunt het rechtshandig maken door de dieptefunctie te wijzigen, zolang u rekening houdt met het dieptebereik en de noodzakelijke transformaties.

Aangezien we ervoor kozen om geen dieptebuffer te hebben toen we onze context creëerden, is de enige buffer die moet worden gewist de kleurbuffer. Dus hebben we de COLOR_BUFFER_BIT. Als we een dieptebuffer hadden, hadden we dit in plaats daarvan gedaan:

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

Het enige dat overblijft is bellen drawScene. Laten we het doen vlak na de initialisatie:

window.addEventListener ('load', function () // Alles initialiseren, initialiseren (); // Start drawing, drawScene ();, false);

Schakel over naar de Resultaat tab om onze mooie rode clear-kleur te zien!

Canvas Alpha-compositie

Een van de belangrijke feiten over duidelijk is dat het geen alfa-compositing toepast. Zelfs als we expliciet een waarde gebruiken voor alpha die deze transparant maakt, wordt de clear-kleur alleen naar de buffer weggeschreven zonder compositing en vervangt alles wat eerder is getekend. Als u dus een scène hebt getekend op het canvas en vervolgens wist met een transparante kleur, wordt de scène volledig gewist. 

De browser voert echter nog steeds alpha-compositing uit voor het hele canvas en gebruikt de alpha-waarde die aanwezig is in de kleurbuffer, die tijdens het wissen had kunnen worden ingesteld. Laten we wat tekst toevoegen onder het canvas en vervolgens wissen met een halftransparante rode kleur om het in actie te zien. Onze HTML zou zijn:

 

Shhh, ik verstop me achter het canvas zodat je me niet kunt zien.

en de duidelijk regel wordt:

// Stel clear-color in op transparant rood, glContext.clearColor (1.0, 0.0, 0.0, 0.5);

En nu, de onthulling:

Kijk goed. Daarboven in de linkerbovenhoek ... er is absoluut niets! Natuurlijk kun je de tekst niet zien! Dat komt omdat we in onze CSS hebben gespecificeerd # 000 als de canvasachtergrond. De achtergrond fungeert als een extra laag onder het canvas, dus de browser alpha-composities de kleur buffer tegen, terwijl het volledig de tekst verbergt. Om dit duidelijker te maken, zullen we de achtergrond in groen veranderen en kijken wat er gebeurt:

achtergrond: # 0f0;

En het resultaat:

Ziet er redelijk uit. Die kleur lijkt te zijn rgb (128, 127, 0), wat kan worden beschouwd als het resultaat van het mengen van rood en groen met alpha is gelijk aan 0,5 (behalve als u Microsoft Edge gebruikt, waarbij de kleur moet zijn rgb (255, 127, 0) omdat het voorlopig geen premultiplied-alfa ondersteunt). We kunnen de tekst nog steeds niet zien, maar we weten tenminste hoe de achtergrondkleur van invloed is op onze tekening.

Alpha Blending

Het resultaat nodigt echter nieuwsgierigheid uit. Waarom werd rood gehalveerd 128, terwijl groen werd gehalveerd 127? Moeten ze dat ook niet zijn 128 of 127, afhankelijk van de afronding van het zwevende punt? Het enige verschil tussen beide is dat de rode kleur is ingesteld als de heldere kleur in de WebGL-code, terwijl de groene kleur is ingesteld in de CSS. Ik weet eerlijk gezegd niet waarom dit gebeurt, maar ik heb een theorie. Het is waarschijnlijk vanwege de mengfunctie gebruikt om de twee kleuren samen te voegen.

Wanneer je iets transparants tekent op iets anders, begint de mengfunctie. Het definieert hoe de uiteindelijke kleur van de pixel (UIT) moet worden berekend vanaf de laag erboven (bronlaag, SRC) en de onderliggende laag (bestemmingslaag, DST). Bij het tekenen met WebGL hebben we veel overvloeifuncties om uit te kiezen. Maar wanneer de browser het canvas samen met de andere lagen in elkaar zet, hebben we vooralsnog slechts twee modi: vooraf vermenigvuldigde-alpha en niet premultiplied-alfa (laten we het noemen normaal mode). 

De normale alpha-modus gaat als volgt:

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

In de premultiplied-alpha-modus wordt aangenomen dat de RGB-waarden al zijn vermenigvuldigd met de bijbehorende alfawaarden (vandaar de naam pre-vermenigvuldigd). In een dergelijk geval worden de vergelijkingen gereduceerd tot:

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

Omdat we geen premultiplied-alfa gebruiken, vertrouwen we op de eerste reeks vergelijkingen. Deze vergelijkingen gaan ervan uit dat de kleurcomponenten drijvende-kommawaarden zijn die variëren van 0 naar 1. Maar dit is niet hoe ze daadwerkelijk in het geheugen worden opgeslagen. In plaats daarvan zijn het gehele getallen variërend van 0 naar 255. Zo srcAlpha (0.5) wordt 127 (of 128, op basis van hoe je het afrondt), en 1 - srcAlpha (1 - 0,5) wordt 128 (of 127). Het is omdat de helft 255 (dat is 127,5) is geen geheel getal, dus eindigen we met het verlies van een van de lagen 0.5 en de andere wint een 0.5 in hun alpha-waarden. Zaak gesloten!

Opmerking: alfa-compositie moet niet worden verward met de CSS-overvloeimodi. Alfa-compositing wordt eerst uitgevoerd en vervolgens wordt de berekende kleur gemengd met de bestemmingslaag met behulp van de overvloeimodi.   

Terug naar onze verborgen tekst. Laten we de achtergrond in transparant-groen maken:

achtergrond: rgba (0, 255, 0, 0,5);

Tenslotte:

Je zou nu de tekst moeten kunnen zien! Het is vanwege de manier waarop deze lagen op elkaar zijn geschilderd:

  1. De tekst wordt eerst op een witte achtergrond getekend.
  2. De achtergrondkleur (die transparant is) wordt erop getekend, wat resulteert in een witachtig groene achtergrond en een groenige tekst.
  3. De kleurenbuffer wordt gemengd met het resultaat, wat resulteert in het bovenstaande ... ding. 

Pijnlijk, toch? Gelukkig hoeven we hier niet alles mee te doen als we niet willen dat ons canvas transparant is! 

Alpha uitschakelen

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

Nu heeft onze kleurenbuffer om te beginnen geen alfakanaal! Maar zou dat ons niet beletten transparante dingen te tekenen?? 

Het antwoord is nee. Eerder noemde ik iets over WebGL met flexibele overvloeifuncties die onafhankelijk zijn van hoe de browser het canvas combineert met andere pagina-elementen. Als we een overvloeifunctie gebruiken die resulteert in een vermenging vooraf vermenigvuldigd met alfa, hebben we absoluut geen behoefte aan het tekenbuffer-alfakanaal:

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

Als we gewoon negeren outAlpha samen verliezen we niets. Wat we ook tekenen, heeft echter nog steeds een alfakanaal nodig om transparant te zijn. Alleen de tekenbuffer ontbreekt.

Premultiplied-alpha speelt goed met textuurfiltering en andere dingen, maar niet met de meeste hulpmiddelen voor beeldmanipulatie (we hebben nog niet over texturen gesproken - neem aan dat het afbeeldingen zijn die we moeten tekenen). Het bewerken van een afbeelding die is opgeslagen in de premultiplied-alpha-modus is niet handig omdat er afrondingsfouten zijn. Dit betekent dat we onze texturen niet voorverouderd willen houden zolang we er nog aan werken. Wanneer het tijd is om te testen of vrij te geven, moeten we:

  • Converteer alle texturen naar premultiplied-alpha voordat u ze bundelt met de applicatie.
  • Laat de texturen achter en converteer ze on-the-fly tijdens het laden.
  • Laat de texturen achter en laat WebGL ze voor ons premultiply gebruiken: 
glContext.pixelStorei (glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

Notitie: pixelStorei heeft geen effect op gecomprimeerde texturen (Umm ... later!).

Al deze opties kunnen een be