ShaderToy, som vi brukte i den forrige opplæringen i denne serien, er flott for raske tester og eksperimenter, men det er ganske begrenset. Du kan ikke kontrollere hvilke data som sendes til shaderen, for eksempel blant annet. Å ha ditt eget miljø der du kan kjøre shaders betyr at du kan gjøre alle slags fancy effekter, og du kan søke dem på dine egne prosjekter!
Vi skal bruke Three.js som rammeverk for å kjøre shaders i nettleseren. WebGL er Javascript API som tillater oss å gjengi shaders; Three.js gjør bare denne jobben enklere.
Hvis du ikke er interessert i JavaScript eller nettplattformen, ikke bekymre deg: Vi vil ikke fokusere på spesifikasjonene for webgjenoppretting (selv om du vil lære mer om rammen, sjekk denne opplæringen). Å sette opp shaders i nettleseren er den raskeste måten å komme i gang, men å bli komfortabel med denne prosessen lar deg enkelt sette opp og bruke shaders på hvilken plattform du liker.
Denne delen vil veilede deg gjennom å sette opp shaders lokalt. Du kan følge med uten å måtte laste ned noe med denne pre-built CodePen:
Du kan gaffel og redigere dette på CodePen.Three.js er et JavaScript-rammeverk som tar seg av mye kjedekode for WebGL som vi trenger for å gjøre våre shaders. Den enkleste måten å komme i gang er å bruke en versjon som er vert på en CDN.
Her er en HTML-fil du kan laste ned som bare har en grunnleggende Threejs-scene.
Prøv å lagre den filen på disk, og åpne den i nettleseren din. Du bør se en svart skjerm. Det er ikke veldig spennende, så la oss prøve å legge til en kube, bare for å sikre at alt fungerer.
For å lage en terning må vi definere geometri og materiale og legge den til scenen. Legg til dette kodestykket under der det står Legg til koden din her
:
var geometri = ny THREE.BoxGeometry (1, 1, 1); var material = nytt THREE.MeshBasicMaterial (color: 0x00ff00); // Vi gjør det grønt var kube = nytt THREE.Mesh (geometri, materiale); // Legg det til skjermscenen.add (kube); cube.position.z = -3; // Skift terningen tilbake slik at vi kan se den
Vi vil ikke gå for mye i denne koden, siden vi er mer interessert i shader-delen. Men hvis alt gikk rett, bør du se en grønn kube i midten av skjermen:
Mens vi er på det, la oss få det til å rotere. De gjengi
funksjonen kjører hver ramme. Vi kan få tilgang til kubens rotasjon gjennom cube.rotation.x
(eller .y
eller .z
). Prøv å øke det, slik at din gjengivelsesfunksjon ser slik ut:
funksjon gjengivelse () cube.rotation.y + = 0.02; requestAnimationFrame (render); renderer.render (scene, kamera);
Utfordring: Kan du få det til å rotere langs en annen akse? Hva med langs to akser på samme tid?
Nå har du alt satt opp, la oss legge til noen shaders!
På dette tidspunktet kan vi begynne å tenke på prosessen med å implementere shaders. Du er sannsynlig å finne deg selv i en lignende situasjon uansett hvilken plattform du planlegger å bruke shaders på: du har alt satt opp, og du har ting trukket på skjermen, nå hvordan får du tilgang til GPU?
Vi bruker JavaScript for å bygge denne scenen. I andre situasjoner kan du bruke C ++, eller Lua eller et annet språk. Shaders, uansett, er skrevet i en spesiell Shading Språk. OpenGLs skygge språk er GLSL(ÅpenGL Shading Language). Siden vi bruker WebGL, som er basert på OpenGL, er GLSL det vi bruker.
Så hvordan og hvor skriver vi GLSL-koden vår? Den generelle regelen er at du vil laste inn GLSL-koden din som en string
. Du kan deretter sende den av for å bli analysert og utført av GPU.
I JavaScript kan du gjøre dette ved å kaste all din kode inne i en variabel som så:
var shaderCode = "All din skyggekode her;"
Dette fungerer, men siden JavaScript ikke har en måte å enkelt lage multiline strenger, er dette ikke veldig praktisk for oss. De fleste har en tendens til å skrive skyggekoden i en tekstfil og gi den en utvidelse av .GLSL
eller .frag
(kort for fragment shader), så laster du bare inn den filen.
Dette er gyldig, men vi skal skrive vår skyggekode inni en ny tag and load it into the JavaScript from there, so that we can keep everything in one file for the purpose of this tutorial.
Create a new taginside the HTML that looks like this:
Vi gir den ID av fragShader
er slik at vi får tilgang til det senere. Typen shader-kode
er faktisk en falsk skripttype som ikke eksisterer. (Du kan sette inn noe navn der, og det ville fungere). Grunnen til at vi gjør dette er at koden ikke blir utført, og ikke vises i HTML-koden.
La oss nå kaste inn en veldig grunnleggende shader som bare returnerer hvit.
(Komponentene til vec4
i dette tilfellet tilsvarer rgba-verdien, som forklart i den forrige veiledningen.)
Til slutt må vi laste inn denne koden. Vi kan gjøre dette med en enkel JavaScript-linje som finner HTML-elementet og trekker den indre teksten:
var shaderCode = document.getElementById ("fragShader"). innerHTML;
Dette bør gå under kubekoden din.
Husk: bare hva som er lastet som en streng, blir analysert som gyldig GLSL-kode (det vil si, void main () ...
. Resten er bare HTML boilerplate.)
Metoden for å bruke shader kan være forskjellig avhengig av hvilken plattform du bruker og hvordan den grensesnittet med GPU. Det er aldri et komplisert trinn, og et oversiktlig Google-søk viser oss hvordan du lager et objekt og bruker shaders til det med Three.js.
Vi må lage et spesielt materiale, og gi det vår skyggekode. Vi lager et fly som vårt shaderobjekt (men vi kan like godt bruke terningen). Dette er alt vi trenger å gjøre:
// Lag et objekt for å bruke shaders til var material = nytt THREE.ShaderMaterial (fragmentShader: shaderCode) var geometri = nytt THREE.PlaneGeometry (10, 10); var sprite = nytt THREE.Mesh (geometri, materiale); scene.add (sprite); sprite.position.z = -1; // Flytt den tilbake slik at vi kan se den
Nå skal du se en hvit skjerm:
Du kan gaffel og redigere dette på CodePen.Hvis du endrer koden i skyggeren til annen farge og oppdatering, bør du se den nye fargen!
Utfordring: Kan du sette en del av skjermen til rød, og en annen del til blå? (Hvis du sitter fast, bør neste trinn gi deg et hint!)
På dette tidspunktet kan vi gjøre hva vi vil med vår skygge, men det er ikke mye vi kan gjøre. Vi har bare den innebygde pixelposisjonen gl_FragCoord
å jobbe med, og hvis du husker det, er det ikke normalisert. Vi må ha minst skjermdimensjonene.
For å sende data til vår skygge, må vi sende den som det som kalles a uniform variabel. For å gjøre dette, oppretter vi et objekt som heter uniformer
og legg til våre variabler til den. Her er syntaksen for å sende oppløsningen:
var uniformer = ; uniforms.resolution = type: 'v2', verdi: nytt THREE.Vector2 (window.innerWidth, window.innerHeight);Hver ensartet variabel må ha a
type
og a verdi
. I dette tilfellet er det en 2-dimensjonal vektor med vinduets bredde og høyde som dens koordinater. Tabellen nedenfor (hentet fra Three.js-dokumentene) viser deg alle datatyper du kan sende og deres identifikatorer:Ensartet type streng | GLSL type | JavaScript-type |
---|---|---|
'jeg', '1i' | int | Nummer |
'f', '1f' | flyte | Nummer |
'V2' | vec2 | THREE.Vector2 |
'V3' | vec3 | THREE.Vector3 |
'C' | vec3 | THREE.Color |
'V4' | vec4 | THREE.Vector4 |
'M3' | mat3 | THREE.Matrix3 |
'M4' | mat4 | THREE.Matrix4 |
'T' | sampler2D | THREE.Texture |
'T' | samplerCube | THREE.CubeTexture |
ShaderMaterial
instantiator for å inkludere det, slik:var material = nytt THREE.ShaderMaterial (uniformer: uniformer, fragmentShader: shaderCode)
Vi er ikke ferdige ennå! Nå er vår skygge mottar denne variabelen, vi må gjøre noe med det. La oss lage en gradient på samme måte som vi gjorde i forrige veiledning: ved å normalisere vår koordinat og bruke den til å skape vår fargevare.
Endre skyggekoden slik at den ser slik ut:
ensartet vec2 oppløsning; // Uniformvariabler må deklareres her første tomrom () // Nå kan vi normalisere vår koordinat vec2 pos = gl_FragCoord.xy / resolution.xy; // Og skape en gradient! gl_FragColor = vec4 (1,0, pos.x, pos.y, 1,0);
Og du bør se en fin seriens gradient!
Du kan gaffel og redigere dette på CodePen.Hvis du er litt uklar på hvordan vi klarte å lage en så fin gradient med bare to linjer med skyggekode, sjekk ut den første delen av denne opplæringsserien for en grundig nedlasting av logikken bak dette.
Utfordring: Kan du dele skjermen i 4 like seksjoner med forskjellige farger? Noe sånt som dette:
Det er fint å kunne sende data til vår shader, men hva om vi trenger å oppdatere den? Hvis du for eksempel åpner det forrige eksempelet i en ny kategori, og deretter endrer størrelsen på vinduet, oppdateres ikke gradienten fordi den fortsatt bruker de første skjermdimensjonene.
For å oppdatere variablene dine, vil du vanligvis bare sende den ensartede variabelen tilbake, og den vil oppdatere. Med Three.js må vi imidlertid bare oppdatere uniformer
objekt i vår gjengi
funksjon-ingen behov for å sende den til shader igjen.
Så her er hva vår gjengivelsesfunksjon ser ut etter at du har gjort endringen:
funksjon gjengivelse () cube.rotation.y + = 0.02; uniforms.resolution.value.x = window.innerWidth; uniforms.resolution.value.y = window.innerHeight; requestAnimationFrame (render); renderer.render (scene, kamera);
Hvis du åpner den nye CodePen og endrer størrelsen på vinduet, vil du se at fargene endres (selv om den opprinnelige visningsportstørrelsen forblir den samme). Det er enklest å se dette ved å se på fargene i hvert hjørne for å bekrefte at de ikke endres.
Merk: Det er generelt kostbart å sende data til GPU-en slik. Å sende en håndfull variabler per ramme er greit, men din framerate kan virkelig sakte ned hvis du sender hundrevis per ramme. Det kan ikke høres ut som et realistisk scenario, men hvis du har noen få hundre objekter på skjermen, og alle trenger å ha belysning påført dem, for eksempel alle med forskjellige egenskaper, så kan det raskt komme ut av kontroll. Vi lærer mer om å optimalisere våre shaders i fremtidige artikler!
Utfordring: Kan du få fargene til å endres over tid? (Hvis du sitter fast, se på hvordan vi gjorde det i første del av denne opplæringsserien.)
Uansett hvordan du laster inn teksturer eller i hvilket format, sender du dem til din shader på samme måte på tvers av plattformer, som ensartede variabler.
Et raskt notat om lasting av filer i JavaScript: Du kan laste bilder fra en ekstern nettadresse uten mye problemer (det er det vi skal gjøre her), men hvis du vil laste et bilde lokalt, kommer du til å få lov til å få lov, fordi JavaScript kan ikke, og bør ikke, normalt få tilgang til filer på systemet ditt. Den enkleste måten å komme seg rundt på er å starte en lokal Python-server, noe som er enklere enn det kanskje høres ut.
Three.js gir oss en praktisk liten funksjon for å laste et bilde som en tekstur:
THREE.ImageUtils.crossOrigin = "; // Lar oss laste et eksternt bilde var tex = THREE.ImageUtils.loadTexture (" https://tutsplus.github.io/Beginners-Guide-to-Shaders/Part2/SIPI_Jelly_Beans.jpg ");
Den første linjen må bare settes en gang. Du kan legge inn en hvilken som helst URL til et bilde der.
Deretter vil vi legge til vår tekstur til uniformer
gjenstand.
uniforms.texture = type: 't', verdi: tex;
Til slutt ønsker vi å erklære vår ensartede variabel i vår skyggekode, og tegne den på samme måte som vi gjorde i den forrige opplæringen, med texture2D
funksjon:
enhetlig vec2 oppløsning; uniform sampler2D tekstur; void main () vec2 pos = gl_FragCoord.xy / resolution.xy; gl_FragColor = texture2D (tekstur, pos);
Og du bør se noen smakfulle gelébønner, strukket over vår skjerm:
Du kan gaffel og redigere dette på CodePen.(Dette bildet er et standardtestbilde innen datagrafikk, hentet fra Universitetet i Sør-Kaliforniens Signal- og bildebehandlingsinstitutt (dermed IPI-initialene). Det virker passende å bruke det som vårt testbilde mens du lærer om grafikkskyggere! )
Utfordring: Kan du gjøre tekstur gå fra full farge til gråtoner over tid? (Igjen, hvis du sitter fast, gjorde vi dette i første del av denne serien.)
Det er ikke noe spesielt med flyet vi har opprettet. Vi kunne ha brukt alt dette på kuben vår. Faktisk kan vi bare endre planet geometri linjen:
var geometri = ny THREE.PlaneGeometry (10, 10);
til:
var geometri = ny THREE.BoxGeometry (1, 1, 1);
Voila, gelébønner på en terning:
Du kan gaffel og redigere dette på CodePen.Nå kan du tenke, "Fortsett, det ser ikke ut som en riktig projeksjon av en tekstur på en terning!". Og du ville ha rett; Hvis vi ser tilbake til vår skygge, ser vi at alt vi egentlig gjorde var, var å si "kart alle piksler av dette bildet på skjermen". Det faktum at det er på en terning betyr bare at piksler utenfor blir kastet.
Hvis du vil bruke den slik at den ser ut som den er trukket fysisk på terningen, ville det innebære mye å gjenoppfinne en 3D-motor (noe som høres litt dumt ut i motsetning til at vi allerede bruker en 3D-motor, og vi kan bare be om å tegne tekstur på hver side hver for seg). Denne opplæringsserien handler mer om å bruke shaders til å gjøre ting vi ikke kunne oppnå ellers, så vi vil ikke være å dykke inn i detaljer som det. (Udacity har et bra kurs på grunnleggende 3D grafikk, hvis du er ivrig etter å lære mer!)
På dette tidspunktet bør du kunne gjøre alt vi har gjort i ShaderToy, bortsett fra nå har du friheten til å bruke hvilke teksturer du vil ha, uansett hva du liker, og forhåpentligvis på hvilken plattform du velger.
Med denne friheten kan vi nå gjøre noe som å sette opp et belysningssystem, med realistiske skygger og høydepunkter. Dette er hva neste del vil fokusere på, samt tips og teknikker for optimalisering av shaders!