Komma igång i WebGL, Del 3 WebGL-kontext och Rensa

I de föregående artiklarna lärde vi oss att skriva enkla vertex- och fragmentskärmar, göra en enkel webbsida och förbereda en duk för ritning. I den här artikeln börjar vi arbeta med vår WebGL-kedjekodskod. 

Vi får ett WebGL-kontext och använder det för att rensa duken med den färg vi har valt. Woohoo! Det här kan vara så lite som tre rader kod, men jag lovar dig att jag inte kommer att göra det så enkelt! Som vanligt försöker jag förklara de knepiga JavaScript-koncepten när vi möter dem och ge dig alla detaljer du behöver för att förstå och förutsäga motsvarande WebGL-beteende.

Den här artikeln är en del av "Komma igång i WebGL" -serien. Om du inte har läst de föregående delarna rekommenderar jag att du läser dem först:

  1. Introduktion till Shaders
  2. The Canvas Element

Recap

I den första artikeln i denna serie skrev vi en enkel skuggning som drar en färgstark gradient och bleknar den in och ut lite. Här är den skuggare som vi skrev:

I den andra artikeln i den här serien började vi arbeta för att använda denna shader på en webbsida. Med små steg förklarade vi nödvändig bakgrund av dukelementet. Vi:

  • gjorde en enkel sida
  • lagt till ett dukelement
  • förvärvade ett 2D-kontext för att göra till duken
  • använde 2D-kontexten för att rita en rad
  • hanterade problem med omformning av sidor
  • hanterade pixeldensitetsproblem

Så här har vi gjort så här långt:

I den här artikeln lånar vi några kodstycken från föregående artikel och skräddarsyr vår erfarenhet till WebGL istället för 2D-ritning. I nästa artikel-Om Allah vill-Jag täcker viewporthantering och primitivklippning. Det tar ett tag, men jag hoppas att du hittar hela serien väldigt användbar!

Första installationen

Låt oss bygga vår WebGL-drivna sida. Vi använder samma HTML som vi använde för 2D-ritningsexemplet:

        

... med en mycket liten modifikation. Här ringer vi på duken glCanvas istället för bara duk (Meh!).

Vi använder också samma CSS:

html, kropp höjd: 100%;  kropp marginal: 0;  duk display: block; bredd: 100%; höjd: 100%; bakgrund: # 000; 

Förutom bakgrundsfärgen, som nu är svart.

Vi använder inte samma JavaScript-kod. Vi börjar med ingen JavaScript-kod alls och lägger till funktionalitet bit för bit för att undvika förvirring. Här är vår setup hittills:

Nu ska vi skriva lite kod!

WebGL-kontext

Det första vi bör göra är att få ett WebGL-kontext för duken. Precis som vi gjorde när vi fick ett 2D-teckningskontext använder vi medlemsfunktionen getContext:

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

Denna rad innehåller två getContext samtal. Normalt borde vi inte behöva det andra samtalet. Men bara om användaren använder en gammal webbläsare där WebGL-implementeringen fortfarande är experimentell (eller Microsoft Edge), lade vi till den andra. 

Den fina saken om || operatör (eller operatör) Är att det inte behöver utvärdera hela uttrycket om det första operandet befanns vara Sann. Med andra ord, i ett uttryck a || b, om en utvärderar till Sann, då huruvida b är Sann eller falsk påverkar inte resultatet alls. Således behöver vi inte utvärdera b och det hoppas helt och hållet. Det här kallas Kortslutningsutvärdering.

I vårat fall, getContext ( "experimentell-WebGL") kommer endast att utföras om getContext ( "WebGL") misslyckas (returnerar null, som utvärderas till falsk i ett logiskt uttryck). 

Vi har också använt en annan funktion av eller operatör. Resultatet av orning är varken Sann inte heller falsk. I stället är det Det första objektet som utvärderas till Sann. Om ingen av objekten utvärderas till Sann, eller-returnerar det högsta objektet i uttrycket. Detta innebär att du har kört ovanstående rad, glContext kommer antingen att innehålla ett kontextobjekt eller null, men inte Sann eller falsk.

Obs! Om webbläsaren stöder båda lägena (WebGL och experimentell-WebGL) då behandlas de som aliaser. Det skulle inte finnas någon skillnad mellan dem.

Sätter ovanstående linje där den hör till:

var glContext; funktion initiera () // Hämta WebGL-kontext, var glCanvas = document.getElementById ("glCanvas"); glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ( "experimentell-WebGL"); om (! glContext) alert ("Misslyckades med att förvärva ett WebGL-sammanhang. Ledsen!"); returnera false;  returnera sant; 

Voila! Vi har vårt initialisera funktion (ja, fortsätt drömma!).

Hantera getContextfel

Observera att vi inte använde Prova och fånga att upptäcka getContext frågor som vi gjorde i föregående artikel. Det beror på att WebGL har egna felrapporteringsmekanismer. Det slänger inte ett undantag när kontextskapelsen misslyckas. Istället brinner den a webglcontextcreationerror händelse. Om vi ​​är intresserade av felmeddelandet så borde vi antagligen göra det här:

// Context creation error listener, var errorMessage = "Det gick inte att skapa ett WebGL-kontext"; funktion onContextCreationError (händelse) om (event.statusMessage) errorMessage = event.statusMessage;  glCanvas.addEventListener ("webglcontextcreationerror", onContextCreationError, false);

Med dessa linjer ifrån varandra:

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

Precis som när vi tillfogade en lyssnare till fönsterskiktet i föregående artikel, lade vi till en lyssnare på duken webglcontextcreationerror händelse. De falsk argumentet är valfritt; Jag inkluderar bara det för fullständighet (eftersom WebGL-specifikationsexemplet har det). Det ingår vanligtvis för bakåtkompatibilitet. Det står för useCapture. När Sann, det betyder att lyssnaren kommer att kallas i fångstfas av händelseförökningen. Om falsk, det kommer att kallas i bubblande fas istället. Kontrollera den här artikeln för mer information om händelseförökning.

Nu till lyssnaren själv:

var errorMessage = "Kunde inte skapa ett WebGL-kontext"; funktion onContextCreationError (händelse) om (event.statusMessage) errorMessage = event.statusMessage; 

I den här lyssnaren behåller vi en kopia av felmeddelandet, om det finns några. Ja, med ett felmeddelande är helt frivilligt:

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

Det vi har gjort här är ganska intressant. felmeddelande förklarades utanför funktionen, men vi använde det inomhus. Detta är möjligt i JavaScript och kallas förslutningar. Det som är intressant om stängningar är deras livstid. Medan felmeddelande är lokal till initialisera funktion, eftersom den användes inuti onContextCreationError, Det kommer inte att förstöras om inte onContextCreationError själv är inte längre refererad.

Med andra ord, så länge som en identifierare fortfarande är tillgänglig kan det inte hämtas skräp. I vår situation:

  • felmeddelande bor eftersom onContextCreationError hänvisar till det.
  • onContextCreationError bor eftersom det hänvisas någonstans bland kanfashändelselyttarna. 

Så, även om initialisera slutar, onContextCreationError är fortfarande refererad någonstans i kanvasobjektet. Bara när det släpps kan felmeddelande vara skräpuppsamlad. Vidare, efterföljande samtal av initialisera kommer inte att påverka det föregående felmeddelande. Varje initialisera Funktionssamtal kommer att ha sitt eget felmeddelande och onContextCreationError.

Men vi vill inte verkligen onContextCreationError att leva bortom initialisera uppsägning. Vi vill inte lyssna på andra försök att få WebGL-kontexten någon annanstans i koden. Så:

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

Få alltid att falla på plats:

För att verifiera att vi har lyckats skapa kontextet har jag lagt till en enkel varna:

varning ("WebGL-kontext skapades framgångsrikt!");

Växla nu till Resultat fliken för att köra koden.

Och det fungerar inte! Självklart, för initialisera kallades aldrig. Vi behöver ringa det direkt efter att sidan är laddad. För detta lägger vi till följande rader:

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

Låt oss försöka igen:

Det fungerar! Jag menar att det borde, om inte ett sammanhang kunde skapas! Om det inte gör det, se till att du tittar på den här artikeln från en WebGL-kompatibel enhet / webbläsare.

Observera att vi gjorde en annan intressant sak här. Vi använde initialisera i vår ladda lyssnare innan det ens tillkännagavs. Detta är möjligt i JavaScript på grund av godshantering. Hissning innebär att alla deklarationer flyttas överst i deras omfattning, medan deras initialiseringar förblir på sina platser.

Nu skulle det inte vara trevligt att testa om vår felrapporteringsmekanism verkligen fungerar? Vi behöver getContext att misslyckas. Ett enkelt sätt att göra det är att få en annan typ av kontext för duken först innan du försöker skapa WebGL-kontexten (kom ihåg när vi sa att den första framgångsrika getContext ändras kanvasläget permanent?). Vi lägger till den här raden strax innan vi får WebGL-kontext:

glCanvas.getContext ( "2d");

Och:

Bra! Nu om meddelandet du såg var "Det gick inte att skapa ett WebGL-kontext"eller något som"Canvas har ett befintligt sammanhang av en annan typ"beror på om din webbläsare stöder webglcontextcreationerror eller inte. Vid skrivning av denna artikel stöder inte Edge och Firefox det (det var planerat för Firefox 49, men fungerar fortfarande inte på Firefox 50.1). I så fall kommer händelselyttaren inte att ringas och felmeddelande kommer att vara inställd på "Det gick inte att skapa ett WebGL-kontext". Lyckligtvis, getContext återkommer fortfarande null, så vi vet att vi inte kunde skapa sammanhanget. Vi har bara inte det detaljerade felmeddelandet.

Saken med WebGL-felmeddelanden är att ... det finns inga WebGL-felmeddelanden! WebGL returnerar siffror som anger felstatus, inte felmeddelanden. Och när det händer att tillåta felmeddelanden är de förarens beroende. Den exakta ordalydelsen för felmeddelandena finns inte i specifikationen. Det är upp till drivrutinsutvecklare att bestämma hur de ska lägga den. Så förvänta dig att se samma fel som formuleras annorlunda på olika enheter.

Okej då. Eftersom vi såg till att vår felrapporteringsmekanism fungerar fungerar "framgångsrikt skapad"varning och getContext ( "2d") behövs inte längre. Vi kommer att utelämna dem.

Kontext attribut

Tillbaka till vår vördnad getContext fungera:

glContext = glCanvas.getContext ("webgl");

Det finns mer än det som möter ögat. getContext kan valfritt ta ett mer argument: en ordbok som innehåller en uppsättning sammanhangsattribut och deras värden. Om inget tillhandahålls används standardvärdena:

ordbok WebGLContextAttributes GLboolean alpha = true; GLboolean depth = true; GLboolean stencil = false; GLboolean antialias = true; GLboolean premultipliedAlpha = true; GLboolean preserveDrawingBuffer = false; GLboolean preferLowPowerToHighPerformance = false; GLboolean failIfMajorPerformanceCaveat = false; ;

Jag kommer att förklara några av dessa attribut som vi använder dem. Du hittar mer om dem i avsnittet WebGL Kontext Attribut i WebGL-specifikationen. För tillfället behöver vi inte en djupbuffert för vår enkla shader (mer om det senare). Och för att undvika att förklara det, kommer vi också att inaktivera förmultiplicerad-alfa! Det tar en egen artikel att korrekt förklara motiveringen bakom den. Således vår getContext linjen blir:

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

Notera: djup, stencil och antialias attribut, när den är inställd på Sann, är förfrågningar, inte krav. Webbläsaren ska försöka sitt bästa för att tillfredsställa dem, men det är inte garanterat. Men när de är inställda på falsk, webbläsaren måste följa.

Å andra sidan, alfa, premultipliedAlpha och preserveDrawingBuffer attribut är krav som måste vara uppfyllda av webbläsaren.

clearColor

Nu när vi har vårt WebGL-sammanhang är det dags att faktiskt använda det! En av de grundläggande funktionerna i WebGL-ritning rensar färgbufferten (eller helt enkelt duken i vår situation). Rensning av duken görs i två steg:

  1. Ställa in klarfärgen (kan endast göras en gång).
  2. Faktiskt rensar duken.

OpenGL / WebGL-samtal är dyra, och drivrutinerna är inte garanterade att de är väldigt smarta och undviker onödigt arbete. Därför, om vi kan undvika att använda API-en, så bör vi undvika att använda det. 

Så, om vi inte behöver ändra klarfärg varje ram eller mittritning, borde vi skriva koden som ställer in den i en initialiseringsfunktion istället för en ritning. På så sätt kallas det bara en gång i början och inte med varje ram. Eftersom klarfärgen inte är den enda tillståndsvariabel att vi kommer att initialisera, skapar vi en separat funktion för tillståndsinitiering:

funktion initieraState () ...

Och vi ringer denna funktion inifrån initialisera fungera:

funktion initiera () ... // Om det misslyckades, om (! glContext) alert (errorMessage); returnera false;  initieraState (); återvänd sant; 

Skön! Modularity kommer att hålla vår inte så kort kod renare och mer läsbar. Nu för att fylla i initializeState fungera:

funktion initializeState () // Ange klarfärg till röd, glContext.clearColor (1.0, 0.0, 0.0, 1.0); 

clearColor tar fyra parametrar: röd, grön, blå och alfa. Fyra flottor, vars värden är fastklämd till intervallet [0, 1]. Med andra ord blir något värde mindre än 0 0, vilket värde som helst större än 1 blir 1, och något värde däremellan förblir oförändrat. Ursprungligen är klarfärgen inställd på alla nollor. Så, om transparent svart var ok med oss, kunde vi ha utelämnat detta helt och hållet.

Rensa ritningsbufferten

Efter att ha satt klarfärgen är det kvar att faktiskt rensa duken. Men man kan inte låta bli att ställa en fråga, vi måste rensa lerret alls?

Tillbaka i gamla dagar behövde spel som utför fullskärmsvisning inte rensa skärmen varje ram (försök att skriva idclip i DOOM 2 och gå någonstans är du inte tänkt att vara!). Det nya innehållet skulle bara skriva över de gamla, och vi skulle rädda den icke-triviala clear operationen. 

På modern maskinvara är rensning av buffertarna extremt snabb. Dessutom kan rensning av buffertarna faktiskt förbättra prestanda! För att uttrycka det enkelt, om buffertinnehållet inte rensades, kan GPUet behöva hämta det föregående innehållet innan de skrivs över. Om de rensades, behöver man inte hämta dem från det relativt långsamma minnet.

Men vad händer om du inte vill skriva över hela skärmen, men stegvis lägga till den? Gilla när du gör ett målningsprogram. Du vill bara rita de nya strokerna medan du behåller de tidigare. Är inte lagen att lämna tecknet utan att rensa förnuft nu?

Svaret är fortfarande nej. På de flesta plattformar skulle du använda dubbel buffring. Det betyder att alla ritningar vi utför är gjort på en backbuffert medan bildskärmen hämtar innehållet från a främre bufferten. Under den vertikala retraceen byts dessa buffertar. Baksidan blir framsidan och framsidan blir baksidan. På det här sättet undviker vi att skriva till samma minne som för närvarande läses av bildskärmen och visas. Därför undviks artefakter på grund av ofullständig ritning eller ritning för snabbt (har ritat flera ramar skrivna medan monitorn fortfarande spårar en enda).

Således skriver inte nästa ram den aktuella ramen, eftersom den inte skrivs till samma buffert. I stället skriver den över den som var i främre bufferten innan byte. Det är den sista ramen. Och vad vi har ritat i denna ram kommer inte att visas i nästa. Det kommer att visas i nästa nästa. Denna inkonsekvens mellan buffertar orsakar flimmer vilket normalt är oönskat.

Men det skulle ha fungerat om vi använde en enda buffrad inställning. I OpenGL på de flesta plattformar har vi explicit kontroll över buffring och byte av buffertarna, men inte i WebGL. Det är upp till webbläsaren att hantera det själv. 

Umm ... Kanske är det inte den bästa tiden, men det finns en sak om att rensa ritningsbufferten som jag inte nämnde tidigare. Om vi ​​inte tydligt klargör det skulle det implicit röjas för oss! 

Det finns bara tre ritningsfunktioner i WebGL 1.0: klar, drawArrays, och drawElements. Endast om vi kallar en av dessa på den aktiva ritningsbufferten (eller om vi just har skapat sammanhanget eller ändrat mållinjen) ska den presenteras för HTML-sidans kompositör i början av nästa komposition. 

Efter sammansättning är ritningsbuffertarna rensas automatiskt. Webbläsaren får lov att vara smart och undvika att rensa buffertarna automatiskt om vi rensade dem själva. Men slutresultatet är detsamma; buffertarna kommer ändå att rensas.

Den goda nyheten är att det fortfarande finns ett sätt att få ditt färgprogram att fungera. Om du insisterar på inkrementell ritning kan vi ställa in preserveDrawingBuffer kontext attribut när man förvärvar sammanhanget:

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

Detta förhindrar att duken automatiskt raderas efter sammansättning och simulerar en enda buffrad inställning. Ett sätt det är gjort är att kopiera innehållet på den främre bufferten till bakbufferten efter byte. Ritning till en bakbuffert är fortfarande nödvändig för att undvika att dra artefakter, så det kan inte hjälpas. Detta kommer givetvis med ett pris. Det kan påverka prestanda. Så, om möjligt, använd andra metoder för att bevara innehållet i ritningsbufferten, som att dra till en rambuffertobjekt (som ligger utanför omfattningen av denna handledning).

klar

Brace dig, vi kommer att rensa duken någonstans nu! Återigen, för modularitet, låt oss skriva koden som drar scenen varje ram i en separat funktion:

funktion drawScene () // Rensa färgbufferten, glContext.clear (glContext.COLOR_BUFFER_BIT); 

Nu har vi gjort det! klar tar en parameter, ett bitfält som anger vilka buffertar som ska rensas. Det visar sig att vi vanligtvis behöver mer än bara en färgbuffert för att rita 3D-saker. Till exempel används ett djupbuffert för att hålla koll på djupet för varje draspixel. Med den här bufferten kan GPU enkelt bestämma om den här pixeln stannar eller är tillsluten av den tidigare pixeln som ligger på plats. 

Den går såhär:

  1. Beräkna djupet på den nya pixeln.
  2. Läs djupet av den gamla pixeln från djupbufferten.
  3. Om den nya pixelns djup är närmare än den gamla pixelns djup, skriv över pixelens färg (eller blanda med den) och ställ in djupet till det nya djupet. Annars, kassera den nya pixeln.

Jag använde "närmare" istället för "mindre" eftersom vi har explicit kontroll över djupfunktion (vilken operatör som ska användas i jämförelse). Vi får bestämma om ett större värde betyder en närmare pixel (högerhänt koordinatsystem) eller omvänd (vänsterhänt). 

Begreppet höger- eller vänsterhänthet hänvisar till tumriktriktningen (z-axeln) när du krular fingrarna från x-axeln till y-axeln. Jag är dålig på att dra, så titta på den här artikeln i Windows Dev Center. WebGL är som vänsterhänt som standard, men du kan göra det högerhänt genom att ändra djupfunktionen, så länge du tar djupintervallet och nödvändiga omvandlingar med i beräkningen.

Eftersom vi valde att inte ha ett djupbuffert när vi skapade vårt sammanhang är den enda bufferten som behöver rensas färgfärgen. Således satte vi COLOR_BUFFER_BIT. Om vi ​​hade djupbuffert skulle vi ha gjort det här istället:

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

Det enda som kvar är att ringa drawScene. Låt oss göra det direkt efter initialiseringen:

window.addEventListener ('load', function () // Initialisera allt, initiera (); // Börja ritning, drawScene ();, false);

Byt till Resultat fliken för att se vår vackra röda klarfärg!

Canvas Alpha-Compositing

En av de viktiga fakta om klar är att det inte gäller någon alfabetisk sammansättning. Även om vi uttryckligen använder ett värde för alfabetet som gör det öppet, kommer den klara färgen bara att skrivas till bufferten utan kompositionering och ersätta allt som ritades tidigare. Således, om du har en scen ritad på duken och då klarer du med en genomskinlig färg, blir scenen helt raderad. 

Men webbläsaren gör fortfarande alfabetisk för hela duken, och den använder det alfabetiska värdet som finns i färgbufferten, som kunde ha ställts in under rensningen. Låt oss lägga till lite text under duken och sedan rensa med en halv transparent röd färg för att se den i åtgärd. Vår HTML skulle vara:

 

Shhh, jag gömmer mig bakom duken så att du inte kan se mig.

och den klar linjen blir:

// Ange klarfärg till transparent rött, glContext.clearColor (1.0, 0.0, 0.0, 0.5);

Och nu avslöjar:

Titta närmare. Uppe i det övre vänstra hörnet ... det är absolut ingenting! Självklart kan du inte se texten! Det beror på att vi har angett i vår CSS # 000 som kanvasbakgrunden. Bakgrunden fungerar som ett extra lager under duken, så webbläsaren alfa-kompositerar färgbufferten mot den medan den fullständigt gömmer texten. För att göra det tydligare ändrar vi bakgrunden till grön och ser vad som händer:

bakgrund: # 0f0;

Och resultatet:

Ser rimligt ut. Den färgen verkar vara rgb (128, 127, 0), vilket kan betraktas som ett resultat av att blanda rött och grönt med alfas lika med 0,5 (förutom om du använder Microsoft Edge, där färgen ska vara rgb (255, 127, 0) för att den inte stöder förhandsuppföljning-alfa för närvarande). Vi kan fortfarande inte se texten, men vi vet åtminstone hur bakgrundsfärgen påverkar vår ritning.

Alfa Blandning

Resultatet inbjuder dock nyfikenhet. Varför var halverad till 128, medan grönt halverades till 127? Borde inte de båda vara heller 128 eller 127, beroende på flytpunkten avrundning? Den enda skillnaden mellan dem är att den röda färgen sattes som klarfärg i WebGL-koden medan den gröna färgen sattes i CSS. Jag vet inte riktigt varför detta händer, men jag har en teori. Det är förmodligen på grund av blandningsfunktion brukade slå samman de två färgerna.

När du ritar något transparent på toppen av något annat, slår blandningsfunktionen in. Det definierar hur pixelens slutliga färg (UT) ska beräknas från skiktet på toppen (källlagret, SRC) och skiktet nedan (destinationslagret, DST). Vid ritning med WebGL har vi många blandningsfunktioner att välja mellan. Men när webbläsaren alfa-kompositerar duken med de andra lagren, har vi bara två lägen (för nu): förmultiplicerad-alfa och inte premultiplied-alfa (låt oss kalla det vanligt läge). 

Det normala alfa läget går som:

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

I premultiplied-alfa-läget antas RGB-värdena redan multipliceras med motsvarande alfavärden (därav namnet pre-multiplicerad). I sådant fall reduceras ekvationerna till:

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

Eftersom vi inte använder premultiplied-alfa, lita vi på den första uppsättningen ekvationer. Dessa ekvationer förutsätter att färgkomponenterna är flytande punktvärden som sträcker sig från 0 till 1. Men det här är inte hur de lagras i minnet. I stället är de heltalvärden som sträcker sig från 0 till 255. Så srcAlpha (0,5) blir 127 (eller 128, baserat på hur du runda det), och 1 - srcAlpha (1 - 0,5) blir 128 (eller 127). Det beror på hälften 255 (vilket är 127,5) är inte ett heltal, så vi slutar med ett av lagren att förlora en 0,5 och den andra får en 0,5 i deras alfavärden. Avslutat fall!

Obs! Alfa-compositing ska inte förväxlas med CSS-blandningslägena. Alpha-compositing utförs först och sedan blandas den beräknade färgen med destinationsskiktet med blandningslägena.   

Tillbaka till vår dolda text. Låt oss försöka göra bakgrunden till genomskinlig grön:

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

Till sist:

Du borde kunna se texten nu! Det är på grund av hur dessa lager är målade ovanpå varandra:

  1. Texten ritas först på en vit bakgrund.
  2. Bakgrundsfärgen (som är transparent) är ritad ovanpå den, vilket resulterar i en vitgrön bakgrund och en grön text.
  3. Färgbufferten blandas med resultatet, vilket resulterar i ovanstående ... sak. 

Smärtsamt, eller hur? Lyckligtvis behöver vi inte ta itu med allt detta om vi inte vill ha vår duk att vara transparent! 

Inaktivering av Alpha

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

Nu har vår färgbuffert inte en alfakanal att börja med! Men skulle det inte hindra oss från att skriva genomskinliga saker? 

Svaret är nej. Tidigare nämnde jag någonting om WebGL med flexibla blandningsfunktioner som är oberoende av hur webbläsaren blandar duken med andra sidelement. Om vi ​​använder en blandningsfunktion som resulterar i en premultiplied-alfa-blandning, har vi absolut inget behov av teckning-buffert-alfakanalen:

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

Om vi ​​bara ignorerar outAlpha Sammantaget förlorar vi inte riktigt någonting. Men vad vi än behöver rita behöver en alfakanal vara transparent. Det är bara ritningsbufferten som saknar en.

Premultiplied-alpha spelar bra med texturfiltrering och andra saker, men inte med de flesta bildhanteringsverktyg (vi har inte diskuterat texturer än-förutsatt att de är bilder vi behöver rita). Att redigera en bild som lagras i pre-multiplicerad-alfa-läget är inte bekväm eftersom det ackumulerar avrundningsfel. Det innebär att vi vill hålla våra texturer inte förutbestämda så länge vi fortfarande arbetar på dem. När det är dags att testa eller släppa, måste vi antingen:

  • Konvertera alla texturer till premultiplied-alfa innan de sammanfogas med applikationen.
  • Lämna texturerna och konvertera dem på flyga när de laddas.
  • Lämna texturerna och få WebGL att förutbestämma dem för oss med hjälp av: 
glContext.pixelStorei (glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);

Notera: pixelStorei har ingen effekt på komprimerade texturer (Umm ... senare!).

Alla dessa alternativ kan vara lite obekväma. Lyckligtvis kan vi fortfarande uppnå genomskinlighet utan att ha en alfakanal och utan att använda premultiplied-alfa:

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

Bara ignorera outAlpha helt och ta bort dstAlpha från outRGB ekvation. Det fungerar! Om du blir van att använda den kan du börja ifrågasätta orsaken till varför dstAlpha var någonsin inkluderad i den ursprungliga ekvationen till att börja med! 

Eftersom vi inte ritar primitiva i denna handledning (vi använde bara klar, som inte använder alfa-blandning) behöver vi inte skriva någon alfanumerisk berörd WebGL-kod. Men bara för referens, här är de steg som behövs för att aktivera ovanstående alfa-blandning i WebGL:

funktion initializeState () ... glContext.enable (glContext.BLEND); glContext.blendFunc (glContext.SRC_ALPHA, glContext.ONE_MINUS_SRC_ALPHA); 

Om du fortfarande insisterar på att ha en alfakanal i färgbufferten kan du använda blendFuncSeparate för att ange separata blandningsfunktioner för RGB och alfa.

Clearing Alpha

Om du behöver en alfakanal i din färgbuffert för en viss blandningseffekt, men du bara inte vill att den ska blandas med bakgrunden, kan du rensa alfakanalen när du har gjort det:

glContext.colorMask (sant, sant, sant, sant); / * ... dra saker ... * / glContext.colorMask (falskt, falskt, falskt, sant); glContext.clear (glContext.COLOR_BUFFER_BIT);

Detta avslutar vår handledning. Phew! Så mycket för den enkla handlingen att rensa skärmen! Jag hoppas att du har hittat den här artikeln användbar. Nästa gång ska jag förklara WebGL-visningsportar och primitivsklippning. Tack så mycket för att läsa!

referenser

  • WebGL-specifikation på ritningsbufferten och kontextskapande
  • W3C teknisk rapport om sammansättning och blandning
  • W3C teknisk rapport om stabling av sammanhang och målningsordning
  • WebGL Grundläggande om alfa-blandning