I de forrige artiklene lærte vi å skrive enkle toppunkts- og fragmentskaders, lage en enkel nettside og lage et lerret for tegning. I denne artikkelen vil vi begynne å jobbe med vår WebGL boilerplate kode.
Vi får en WebGL-kontekst og bruker den til å fjerne lerretet med fargen etter eget valg. Woohoo! Dette kan være så lite som tre linjer med kode, men jeg lover at jeg ikke vil gjøre det så enkelt! Som vanlig vil jeg prøve å forklare de vanskelige JavaScript-konseptene når vi møter dem, og gi deg alle detaljene du trenger for å forstå og forutsi den tilsvarende WebGL-oppførselen.
Denne artikkelen er en del av "Komme i gang i WebGL" -serien. Hvis du ikke har lest de forrige delene, anbefaler jeg at du leser dem først:
I den første artikkelen i denne serien skrev vi en enkel skygge som trekker en fargerik gradient og fader den inn og ut litt. Her er skyggen som vi skrev:
I den andre artikkelen i denne serien begynte vi å jobbe for å bruke denne shaderen på en nettside. Med små skritt, forklarte vi den nødvendige bakgrunnen for lerretelementet. Vi:
Her er det vi laget så langt:
I denne artikkelen låner vi noen stykker kode fra forrige artikkel og skredder vår erfaring til WebGL i stedet for 2D tegning. I den neste artikkelen - hvis Allah vil - jeg skal dekke viewporthåndtering og primitivklipping. Det tar litt tid, men jeg håper du finner hele serien veldig nyttig!
La oss bygge vår WebGL-drevne side. Vi bruker samme HTML som vi brukte for 2D tegning eksempel:
... med en veldig liten modifikasjon. Her kaller vi lerretet glCanvas
i stedet for bare lerret
(Meh!).
Vi bruker også samme CSS:
html, kropp høyde: 100%; kropp margin: 0; lerret display: block; bredde: 100%; høyde: 100%; bakgrunn: # 000;
Bortsett fra bakgrunnsfargen, som nå er svart.
Vi bruker ikke samme JavaScript-kode. Vi starter med ingen JavaScript-kode i det hele tatt, og legger til funksjonalitet bit for bit for å unngå forvirring. Her er vår oppsett så langt:
La oss nå skrive noen kode!
Det første vi bør gjøre er å få en WebGL-kontekst for lerretet. Akkurat som vi gjorde da vi fikk en 2D tegningskontekst, bruker vi medlemsfunksjonen getContext
:
glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ( "eksperimentelle-webgl");
Denne linjen inneholder to getContext
samtaler. Normalt bør vi ikke trenge det andre anropet. Men bare hvis brukeren bruker en gammel nettleser der WebGL-implementeringen fortsatt er eksperimentell (eller Microsoft Edge), la vi til den andre.
Den kule tingen om ||
operatør (eller operatør) Er at den ikke trenger å evaluere hele uttrykket hvis den første operand ble funnet å være ekte
. Med andre ord, i et uttrykk a || b
, hvis en
evaluerer til ekte
, så om b
er ekte
eller falsk
har ingen innvirkning på resultatet. Dermed trenger vi ikke å evaluere b
og det hoppes over helt. Dette kalles Kortslutningsvurdering.
I vårt tilfelle, getContext ( "eksperimentelle-webgl")
vil bli utført bare hvis getContext ( "webgl")
mislykkes (returnerer null
, som vurderer til falsk
i et logisk uttrykk).
Vi har også brukt en annen funksjon av eller
operatør. Resultatet av orngingen er heller ikke ekte
eller falsk
. I stedet er det det første objektet som evaluerer til ekte
. Hvis ingen av objektene vurderes til ekte
, eller returnering det høyeste objektet i uttrykket. Dette betyr at du har kjørt linjen ovenfor, glContext
vil enten inneholde en kontekstobjekt eller null
, men ikke ekte
eller falsk
.
Merk: Hvis nettleseren støtter begge modusene (WebGL
og eksperimentell-webgl
) så blir de behandlet som aliaser. Det ville være absolutt ingen forskjell mellom dem.
Setter linjen over hvor den tilhører:
var glContext; funksjon initialisere () // Få WebGL kontekst, var glCanvas = document.getElementById ("glCanvas"); glContext = glCanvas.getContext ("webgl") || glCanvas.getContext ( "eksperimentelle-webgl"); hvis (! glContext) alert ("Kunne ikke skaffe en WebGL-kontekst. Beklager!"); returner falsk; returnere sann;
Voila! Vi har vår initial
funksjon (ja, fortsett å drømme!).
Legg merke til at vi ikke brukte prøve
og å fange
å oppdage getContext
problemer som vi gjorde i forrige artikkel. Det er fordi WebGL har sine egne feilrapporteringsmekanismer. Det kaster ikke unntak når kontekstopprettelsen mislykkes. I stedet brenner den a webglcontextcreationerror
begivenhet. Hvis vi er interessert i feilmeldingen, bør vi sannsynligvis gjøre dette:
// Kontekstopprettingsfeillytter, var errorMessage = "Kunne ikke opprette en WebGL-kontekst"; funksjon onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage; glCanvas.addEventListener ("webglcontextcreationerror", onContextCreationError, false);
Ta disse linjene fra hverandre:
glCanvas.addEventListener ("webglcontextcreationerror", onContextCreationError, false);
Akkurat som når vi la til en lytter til vindusbegivenheten i forrige artikkel, la vi til lytteren til lerretet webglcontextcreationerror
begivenhet. De falsk
argument er valgfritt; Jeg inkluderer bare det for fullstendighet (siden WebGL-spesifikasjonseksemplet har det). Det er vanligvis inkludert for bakoverkompatibilitet. Det står for useCapture
. Når ekte
, det betyr at lytteren skal bli kalt i fangstfase av arrangementets forplantning. Hvis falsk
, det kommer til å bli kalt i boblende fase i stedet. Sjekk denne artikkelen for mer informasjon om forplantning av hendelser.
Nå til lytteren selv:
var errorMessage = "Kunne ikke opprette en WebGL-kontekst"; funksjon onContextCreationError (event) if (event.statusMessage) errorMessage = event.statusMessage;
I denne lytteren beholder vi en kopi av feilmeldingen, hvis noen. Ja, det er helt valgfritt å ha en feilmelding:
hvis (event.statusMessage) errorMessage = event.statusMessage;
Det vi har gjort her er ganske interessant. feilmelding
ble erklært utenfor funksjonen, men vi brukte det inni. Dette er mulig i JavaScript og kalles nedleggelser. Det som er interessant om nedleggelser er deres levetid. Samtidig som feilmelding
er lokal til initial
funksjon, siden den ble brukt inni onContextCreationError
, det vil ikke bli ødelagt med mindre onContextCreationError
i seg selv er ikke lenger referert.
Med andre ord, så lenge en identifikator fortsatt er tilgjengelig, kan det ikke bli søppel samlet. I vår situasjon:
feilmelding
lever fordi onContextCreationError
refererer til det.onContextCreationError
lever fordi det er referert et sted blant lærredene på lerretet. Så, selv om initial
opphører, onContextCreationError
er fortsatt referert et sted i lerretobjektet. Bare når den er utgitt kan feilmelding
bli søppel innsamlet. Videre, etterfølgende samtaler av initial
vil ikke påvirke det forrige feilmelding
. Hver initial
funksjonsanrop vil ha sin egen feilmelding
og onContextCreationError
.
Men vi vil egentlig ikke onContextCreationError
å leve utover initial
avslutning. Vi ønsker ikke å lytte til andre forsøk på å få WebGL-sammenhenger andre steder i koden. Så:
glCanvas.removeEventListener ("webglcontextcreationerror", onContextCreationError, false);
Sette alt sammen:
For å bekrefte at vi har opprettet konteksten, har jeg lagt til en enkel varsling
:
varsling ("WebGL-kontekst vellykket opprettet!");
Bytt nå til Resultat
fanen for å kjøre koden.
Og det virker ikke! Tydeligvis fordi initial
ble aldri kalt. Vi må ringe det rett etter at siden er lastet inn. For dette vil vi legge til disse linjene over det:
window.addEventListener ('load', funksjon () initialiser ();, false);
La oss prøve igjen:
Det fungerer! Jeg mener, det burde ikke sammen med en kontekst opprettes! Hvis det ikke gjør det, må du sørge for at du ser denne artikkelen fra en WebGL-kompatibel enhet / nettleser.
Legg merke til at vi gjorde en annen interessant ting her. Vi brukte initial
i vår laste
lytter før det ble til og med erklært. Dette er mulig i JavaScript på grunn av heising. Heising betyr at alle deklarasjoner flyttes til toppen av deres omfang, mens deres initialiseringer forblir på deres plasser.
Nå ville det ikke vært fint å teste om feilmeldingsmekanismen faktisk fungerer? Vi trenger getContext
å mislykkes. En enkel måte å gjøre det på er å skaffe en annen type kontekst for lerretet før du forsøker å lage WebGL-konteksten (husk da vi sa at den første vellykkede getContext
endrer lerretmodus permanent?). Vi legger til denne linjen like før du får WebGL-konteksten:
glCanvas.getContext ( "2D");
Og:
Flott! Nå om meldingen du så, var "Kunne ikke opprette en WebGL-kontekst
"eller noe som"Lerret har en eksisterende kontekst av en annen type
"avhenger av om nettleseren din støtter webglcontextcreationerror
eller ikke. Når du skriver denne artikkelen, støtter ikke Edge og Firefox det (det var planlagt for Firefox 49, men fungerer fortsatt ikke på Firefox 50.1). I så fall vil hendelseslytteren ikke bli kalt og feilmelding
vil forbli satt til "Kunne ikke opprette en WebGL-kontekst
". Heldigvis, getContext
returnerer fortsatt null
, så vi vet at vi ikke kunne lage konteksten. Vi har bare ikke den detaljerte feilmeldingen.
Tingen om WebGL feilmeldinger er at ... det er ingen WebGL feilmeldinger! WebGL returnerer tall som indikerer feilstatus, ikke feilmeldinger. Og når det skjer å tillate feilmeldinger, er de avhengig av sjåføren. Den eksakte ordlyden av feilmeldingene er ikke oppgitt i spesifikasjonen. Det er opp til driverutviklerne å avgjøre hvordan de skal sette den. Så forvent å se samme feil formulert annerledes på forskjellige enheter.
Ok da. Siden vi sørget for at feilmeldingsmekanismen fungerer, er "vellykket opprettet
"varsel og getContext ( "2D")
er ikke lenger nødvendig. Vi utelater dem.
Tilbake til vår ærverdige getContext
funksjon:
glContext = glCanvas.getContext ("webgl");
Det er mer enn det som møter øyet. getContext
kan eventuelt ta enda et argument: en ordbok som inneholder et sett med kontekstattributter og deres verdier. Hvis ingen er oppgitt, brukes standardene:
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; ;
Jeg vil forklare noen av disse egenskapene når vi bruker dem. Du finner mer om dem i WebGL-kontekstattributter-delen av WebGL-spesifikasjonen. For nå trenger vi ikke en dybdebuffer for vår enkle shader (mer om det senere). Og for å unngå å forklare det, deaktiverer vi også på forhånd multiplisert-alfa! Det tar en artikkel av seg selv å skikkelig forklare begrunnelsen bak den. Dermed vår getContext
linjen blir:
var contextAttributes = deep: false, premultipliedAlpha: false; glContext = glCanvas.getContext ("webgl", contextAttributes) || glCanvas.getContext ("eksperimentell-webgl", contextAttributes);
Merk: dybde
, sjablong
og antialias
attributter, når de er satt til ekte
, er forespørsler, ikke krav. Nettleseren bør prøve sitt beste for å tilfredsstille dem, men det er ikke garantert. Men når de er satt til falsk
, nettleseren må overholde.
På den annen side, alfa
, premultipliedAlpha
og preserveDrawingBuffer
Attributter er krav som må oppfylles av nettleseren.
Nå som vi har vår WebGL-kontekst, er det på tide å faktisk bruke det! En av de grunnleggende operasjonene i WebGL-tegning er å slette fargebufferen (eller bare lerretet i vår situasjon). Rydde lerretet utføres i to trinn:
OpenGL / WebGL-samtaler er dyre, og enhetsdriverne er ikke garantert å være veldig smart og unngår unødvendig arbeid. Derfor, hvis vi kan unngå å bruke API-en, bør vi derfor unngå å bruke den.
Så, med mindre vi må endre klarfarge hver ramme eller midt tegning, bør vi skrive koden som angir den i en initialiseringsfunksjon i stedet for en tegning en. På denne måten kalles det bare en gang i begynnelsen og ikke med hver ramme. Siden klarfargen ikke er den eneste tilstandsvariabel at vi vil initialisere, vil vi lage en egen funksjon for statlig initialisering:
funksjon initialisererState () ...
Og vi ringer denne funksjonen fra initial
funksjon:
funksjon initialisere () ... // Hvis feil, hvis (! glContext) alert (errorMessage); returner falsk; initialiserStat (); returnere sant;
Vakker! Modularity vil holde vår ikke så kort kode renere og mer lesbar. Nå for å fylle initializeState
funksjon:
funksjon initializeState () // Angi farger til rødt, glContext.clearColor (1.0, 0.0, 0.0, 1.0);
clearColor
tar fire parametere: rød, grønn, blå og alfa. Fire flyter, hvis verdier er klemt til området [0, 1]. Med andre ord blir enhver verdi mindre enn 0 0, en verdi større enn 1 blir 1, og en hvilken som helst verdi i mellom forblir uendret. I utgangspunktet er klarfargen satt til alle nuller. Så, hvis gjennomsiktig svart var ok med oss, kunne vi ha utelatt dette helt.
Etter å ha satt klar fargen, er det som er igjen å faktisk fjerne lerretet. Men man kan ikke annet enn å stille et spørsmål, må vi slette lerretet i det hele tatt?
Tilbake i gamle dager, spill som gjorde fullskjerm gjengivelse, behøvde ikke å rydde skjermen hver ramme (prøv å skrive idclip
i DOOM 2 og gå et sted du ikke skal være!). Det nye innholdet ville bare overskrive de gamle, og vi ville redde den ikke-trivielle klare operasjonen.
På moderne maskinvare er rydding av bufferne ekstremt rask. Videre kan rydding av buffere faktisk forbedre ytelsen! For å si det enkelt, hvis bufferinnholdet ikke ble ryddet, må GPUen måtte hente det forrige innholdet før det skrives om. Hvis de ble ryddet, er det ikke nødvendig å hente dem fra det relativt langsommere minnet.
Men hva om du ikke vil overskrive hele skjermen, men øker gradvis til det? Liker når du lager et maleri program. Du vil bare tegne de nye slagene mens du holder de forrige. Gjør ikke loven om å forlate lerretet uten å rydde fornuftig nå?
Svaret er fortsatt nei. På de fleste plattformer vil du bruke dobbel-buffering. Dette betyr at alle tegningene vi utfører er gjort på en tilbakebuffer mens skjermen henter innholdet fra a frontbuffer. Under den vertikale retrace blir disse buffere byttet. Baksiden blir foran og fronten blir baksiden. På denne måten unngår vi å skrive til det samme minnet som for øyeblikket leses av skjermen og vises, og dermed unngår gjenstander på grunn av ufullstendig tegning eller tegning for fort (har trukket flere rammer skrevet mens skjermen fortsatt sporer en enkelt).
Således overskriver neste ramme ikke gjeldende ramme, fordi den ikke er skrevet til samme buffer. I stedet overskriver den den som var i frontbufferen før bytte. Det er den siste rammen. Og hva vi har trukket i denne rammen, vil ikke vises i den neste. Det vises i neste neste. Denne uoverensstemmelsen mellom buffere forårsaker flimring som normalt er uønsket.
Men dette ville ha fungert hvis vi brukte et enkelt buffret oppsett. I OpenGL på de fleste plattformer har vi eksplisitt kontroll over buffering og bytte buffere, men ikke i WebGL. Det er opp til nettleseren å håndtere det selv.
Umm ... Kanskje det ikke er den beste tiden, men det er en ting om å rydde tegningsbufferen som jeg ikke nevnte før. Hvis vi ikke eksplisitt fjerner det, vil det bli implisitt ryddet for oss!
Det er bare tre tegnefunksjoner i WebGL 1.0: klar
, drawArrays
, og drawElements
. Bare hvis vi kaller en av disse på den aktive tegningsbufferen (eller hvis vi nettopp har opprettet konteksten eller endret størrelsen på lerretet), skal den presenteres til HTML-siden-komposanten ved begynnelsen av neste sammensatte operasjon.
Etter sammensetning er tegningsbufferne automatisk ryddet. Nettleseren har lov til å være smart og unngå å rydde bufferne automatisk hvis vi fjernet dem selv. Men sluttresultatet er det samme; bufferne kommer til å bli ryddet uansett.
Den gode nyheten er at det fortsatt er en måte å gjøre malingsprogrammet ditt. Hvis du insisterer på å gjøre trinnvis tegning, kan vi sette preserveDrawingBuffer
kontekstattributt når man skaffer sammenhengen:
glContext = glCanvas.getContext ("webgl", preserveDrawingBuffer: true);
Dette forhindrer at lerretet automatisk blir ryddet etter komposisjon, og simulerer et enkelt buffertoppsett. En måte er det gjort ved å kopiere innholdet til frontbufferen til bakbufferen etter bytte. Tegning til en bakbuffer er fortsatt nødvendig for å unngå å tegne artefakter, så det kan ikke bli hjulpet. Dette kommer selvfølgelig med en pris. Det kan påvirke ytelsen. Så, hvis det er mulig, bruk andre tilnærminger for å bevare innholdet i tegningsbufferen, som å tegne til a ramme bufferobjekt (som ligger utenfor omfanget av denne opplæringen).
Brace deg selv, vi vil rydde lerretet et øyeblikk nå! Igjen, for modularitet, la oss skrive koden som trekker scenen hver ramme i en egen funksjon:
funksjon drawScene () // Fjern fargebufferen, glContext.clear (glContext.COLOR_BUFFER_BIT);
Nå har vi gjort det! klar
tar en parameter, et bitfelt som angir hvilke buffere som skal slettes. Det viser seg at vi vanligvis trenger mer enn bare en fargebuffer for å tegne 3D-ting. For eksempel brukes en dybdebuffer for å holde oversikt over dybden av hver tegnet piksel. Ved å bruke denne bufferen, når GPUen er i ferd med å tegne en ny piksel, kan det enkelt avgjøre om denne piksletten lukkes eller er okkludert av den forrige piksel som ligger i sin plass.
Det går slik:
Jeg brukte "nærmere" i stedet for "mindre" fordi vi har eksplisitt kontroll over dybdefunksjon (hvilken operatør som skal brukes i sammenligning). Vi kommer til å avgjøre om en større verdi betyr en nærmere piksel (høyrehånds koordinatsystem) eller omvendt (venstrehånds).
Begrepet høyre- eller venstrehendighet refererer til retningen til tommelen (z-aksen) mens du krurer fingrene fra x-aksen til y-aksen. Jeg er dårlig på tegning, så se på denne artikkelen i Windows Dev Center. WebGL er som standard venstrehåndet, men du kan gjøre det til høyre ved å endre dybdefunksjonen, så lenge du tar dybdeområdet og de nødvendige transformasjonene i betraktning.
Siden vi valgte å ikke ha en dybdebuffer når vi opprettet konteksten, er den eneste bufferen som må slettes, fargerabufferen. Således satte vi COLOR_BUFFER_BIT
. Hvis vi hadde en dybdebuffer, ville vi ha gjort dette i stedet:
glContext.clear (glContext.COLOR_BUFFER_BIT | glContext.GL_DEPTH_BUFFER_BIT);
Det eneste som er igjen er å ringe Metoden drawscene
. La oss gjøre det like etter initialisering:
window.addEventListener ('load', funksjon () // Initialiser alt, initialiser (); // Start tegning, drawScene ();, false);
Bytt til Resultat
fan for å se vår vakre, røde farger!
En av de viktige fakta om klar
er at den ikke bruker noen alfa-compositing. Selv om vi eksplisitt bruker en verdi for alfa som gjør den gjennomsiktig, vil den klare fargen bare skrives til bufferen uten komposisjon, og erstatte alt som ble trukket før. Dermed, hvis du har en scene tegnet på lerretet og så klarer du med en gjennomsiktig farge, blir scenen helt slettet.
Men nettleseren gjør fortsatt alfa-compositing for hele lerretet, og den bruker alfaverdien til stede i fargebufferen, som kunne ha blitt satt mens du slette. La oss legge til litt tekst under lerretet og deretter fjerne med en halv gjennomsiktig rød farge for å se den i aksjon. Vår HTML ville være:
Shhh, jeg gjemmer meg bak lerretet slik at du ikke kan se meg.
og klar
linjen blir:
// Sett klar fargen til gjennomsiktig rød, glContext.clearColor (1.0, 0.0, 0.0, 0.5);
Og nå avslører:
Se nærmere. Der oppe i øverste venstre hjørne ... er det absolutt ingenting! Selvfølgelig kan du ikke se teksten! Det er fordi i vårt CSS, har vi spesifisert # 000
som lerretbakgrunnen. Bakgrunnen fungerer som et ekstra lag under lerretet, slik at nettleseren alfa-kompositterer fargebufferen mot den mens den helt gjemmer teksten. For å gjøre dette klarere, endrer vi bakgrunnen til grønt og ser hva som skjer:
bakgrunn: # 0f0;
Og resultatet:
Ser rimelig ut. Den fargen ser ut til å være rgb (128, 127, 0)
, som kan betraktes som et resultat av å blande rødt og grønt med alfa, er lik 0,5 (unntatt hvis du bruker Microsoft Edge, der fargen skal være rgb (255, 127, 0)
fordi det ikke støtter premultiplied-alfa for tiden). Vi kan fortsatt ikke se teksten, men i det minste vet vi hvordan bakgrunnsfargen påvirker tegningen vår.
Resultatet inviterer imidlertid nysgjerrighet. Hvorfor ble rødt halvert til 128
, mens grønn ble halvert til 127
? Bør ikke de begge være enten 128
eller 127
, avhengig av flytpunktet avrundet? Den eneste forskjellen mellom dem er at den røde fargen ble satt som klarfargen i WebGL-koden, mens den grønne fargen ble satt i CSS. Jeg vet ærlig ikke hvorfor dette skjer, men jeg har en teori. Det er sannsynligvis på grunn av blandingsfunksjon pleide å fusjonere de to farger.
Når du tegner noe gjennomsiktig på toppen av noe annet, sparker blendingfunksjonen inn. Den definerer hvordan pikselens endelige farge (UTE
) skal beregnes fra laget øverst (kilde lag, SRC
) og laget nedenfor (destinasjonslag, DST
). Når du tegner med WebGL, har vi mange blandingsfunksjoner å velge mellom. Men når nettleseren alfa-kompositterer lerretet med de andre lagene, har vi bare to moduser (for nå): på forhånd multiplisert-alfa og ikke premultiplied-alfa (la oss kalle det normal modus).
Den normale alfa-modusen går som:
OUTᴀ = SRCᴀ + DSTᴀ (1 - SRCᴀ) OUTʀɢʙ = SRCʀɢʙ.SRCᴀ + DSTʀɢʙ.DSTᴀ (1 - SRCᴀ)
I premultiplied-alpha-modus antas RGB-verdiene allerede multiplisert med deres tilsvarende alfaverdier (dermed navnet pre-multiplisert). I slike tilfeller reduseres ligningene til:
OUTᴀ = SRCᴀ + DSTᴀ (1 - SRCᴀ) OUTʀɢʙ = SRCʀɢʙ + DSTʀɢʙ (1 - SRCᴀ)
Siden vi ikke bruker premultiplied-alpha, stole vi på det første settet av ligninger. Disse ligningene antar at fargekomponentene er flytende punktverdier som spenner fra 0
til 1
. Men dette er ikke hvordan de faktisk lagres i minnet. I stedet er de heltallverdier som spenner fra 0
til 255
. Så srcAlpha
(0.5
) blir 127
(eller 128
, basert på hvordan du runder det), og 1 - srcAlpha
(1 - 0.5
) blir 128
(eller 127
). Det er fordi halvparten 255
(som er 127,5
) er ikke et heltall, så vi ender med et av lagene som mister en 0.5
og den andre får a 0.5
i deres alfa verdier. Saken avsluttet!
Merk: alfa-compositing skal ikke forveksles med CSS-blandemodusene. Alpha-compositing utføres først, og deretter blandes den beregnede farge med mållaget ved å bruke blandemodusene.
Tilbake til skjult tekst. La oss prøve å gjøre bakgrunnen gjennomsiktig-grønn:
bakgrunn: rgba (0, 255, 0, 0,5);
Endelig:
Du bør kunne se teksten nå! Det er på grunn av hvordan disse lagene er malt ovenpå hverandre:
Smertefullt, ikke sant? Heldigvis trenger vi ikke å takle alt dette hvis vi ikke vil at lerretet skal være gjennomsiktig!
var contextAttributes = dybde: false, alpha: false; glContext = glCanvas.getContext ("webgl", contextAttributes) || glCanvas.getContext ("eksperimentell-webgl", contextAttributes);
Nå har fargebufferen ikke en alfakanal til å begynne med! Men ville det ikke hindre oss i å tegne gjennomsiktige ting?
Svaret er nei. Tidligere nevnte jeg noe om WebGL som har fleksible blandingsfunksjoner som er uavhengige av hvordan nettleseren blander lerretet med andre sideelementer. Hvis vi bruker en blandingsfunksjon som resulterer i en premultiplied-alfa-blanding, har vi absolutt ikke behov for tegningsbuffer-alfakanalen:
OUTᴀ = SRCᴀ + DSTᴀ (1 - SRCᴀ) OUTʀɢʙ = SRCʀɢʙ + DSTʀɢʙ (1 - SRCᴀ)
Hvis vi bare ignorerer outAlpha
Alt i alt mister vi ikke noe. Men uansett hva vi tegner, trenger vi en alfakanal for å være gjennomsiktig. Det er bare tegningsbufferen som mangler en.
Premultiplied-alpha spiller godt med teksturfiltrering og andre ting, men ikke med de fleste bildehåndteringsverktøy (vi har ikke diskutert teksturer ennå - antar at de er bilder vi må tegne). Redigering av et bilde som er lagret i premultiplied-alpha-modus, er ikke praktisk fordi det akkumulerer avrundingsfeil. Dette betyr at vi vil holde våre teksturer ikke forutbestemt så lenge vi fremdeles jobber med dem. Når det er på tide å teste eller slippe, må vi enten:
glContext.pixelStorei (glContext.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
Merk: pixelStorei
har ingen effekt på komprimerte teksturer (Umm ... senere!).
Alle disse alternativene kan være litt ubeleilig. Heldigvis kan vi likevel oppnå gjennomsiktighet uten å ha en alfakanal og uten å bruke premultiplied-alpha:
OUTʀɢʙ = SRCʀɢʙ.SRCᴀ + DSTʀɢʙ (1 - SRCᴀ)
Bare ignorer outAlpha
helt og fjern dstAlpha
fra outRGB
ligningen. Det fungerer! Hvis du blir vant til å bruke den, kan du begynne å stille spørsmål til grunnen dstAlpha
var noen gang inkludert i den opprinnelige ligningen til å begynne med!
Siden vi ikke tegnet primitiver i denne opplæringen (vi brukte bare klar
, som ikke bruker alfa-blanding), trenger vi ikke å skrive noen alfanumerisk berørt WebGL-kode. Men bare for referanse, her er trinnene som trengs for å aktivere ovennevnte alfa-blanding i WebGL:
funksjon initializeState () ... glContext.enable (glContext.BLEND); glContext.blendFunc (glContext.SRC_ALPHA, glContext.ONE_MINUS_SRC_ALPHA);
Hvis du fortsatt insisterer på å ha en alfakanal i fargebufferen, kan du bruke blendFuncSeparate til å spesifisere separate blandingsfunksjoner for RGB og alpha.
Hvis du trenger en alfakanal i fargebufferen for en viss blanding, men du ikke vil at den skal blandes med bakgrunnen, kan du slette alfakanalen når du er ferdig med gjengivelsen:
glContext.colorMask (sant, sant, sant, sant); / * ... tegne ting ... * / glContext.colorMask (false, false, false, true); glContext.clear (glContext.COLOR_BUFFER_BIT);
Dette avslutter vår veiledning. Puh! Så mye for den enkle handlingen å rydde skjermen! Jeg håper du har funnet denne artikkelen nyttig. Neste gang skal jeg forklare WebGL-visningsportaler og primitives-klipping. Takk mye for lesing!