Een beginnershandleiding voor codering Grafische Shaders deel 2

ShaderToy, dat we in de vorige tutorial in deze serie hebben gebruikt, is geweldig voor snelle tests en experimenten, maar het is eerder beperkt. U kunt bijvoorbeeld niet bepalen welke gegevens naar de arcering worden verzonden. Als je een eigen omgeving hebt waar je shaders kunt uitvoeren, kun je allerlei fancy-effecten doen en deze toepassen op je eigen projecten.!

We zullen Three.js gebruiken als ons framework om shaders in de browser uit te voeren. WebGL is de JavaScript-API waarmee we shaders kunnen weergeven; Three.js maakt deze taak gewoon eenvoudiger. 

Als u niet geïnteresseerd bent in JavaScript of het webplatform, hoeft u zich geen zorgen te maken: we richten ons niet op de details van webweergave (maar als u meer wilt weten over het framework, bekijk dan deze tutorial). Het instellen van shaders in de browser is de snelste manier om aan de slag te gaan, maar als u zich hiermee goed voelt, kunt u eenvoudig shaders instellen en gebruiken op elk platform dat u leuk vindt. 

De opzet

Dit gedeelte zal u helpen bij het lokaal instellen van shaders. Je kunt meevolgen zonder iets te hoeven downloaden met deze vooraf gebouwde CodePen:

U kunt dit vorkelen en bewerken op CodePen.

Hallo Three.js!

Three.js is een JavaScript-framework dat zorgt voor veel boilerplate-code voor WebGL die we nodig hebben om onze shaders weer te geven. De gemakkelijkste manier om aan de slag te gaan is om een ​​versie te gebruiken die wordt gehost op een CDN. 

Hier is een HTML-bestand dat u kunt downloaden en dat slechts een eenvoudige Threejs-scène heeft. 

Bewaar dat bestand op schijf en open het vervolgens in uw webbrowser. Je zou een zwart scherm moeten zien. Dat is niet erg spannend, dus laten we proberen een kubus toe te voegen om er zeker van te zijn dat alles werkt. 

Om een ​​kubus te maken, moeten we de geometrie en het materiaal ervan definiëren en deze dan aan de scène toevoegen. Voeg dit codefragment toe onder de plaats waar het staat Voeg hier uw code toe:

var geometry = new THREE.BoxGeometry (1, 1, 1); var material = new THREE.MeshBasicMaterial (color: 0x00ff00); // We maken het groen var cube = nieuw THREE.Mesh (geometrie, materiaal); // Voeg het toe aan het scherm scene.add (cube); cube.position.z = -3; // Verschuif de kubus terug zodat we hem kunnen zien

We zullen niet al te uitvoerig ingaan op al deze code, omdat we meer geïnteresseerd zijn in het shader-gedeelte. Maar als alles goed ging, zou je een groene kubus in het midden van het scherm moeten zien:

Terwijl we bezig zijn, laten we het draaien. De gevenfunctie voert elk frame uit. We kunnen de rotatie van de kubus openen cube.rotation.x (of .Y of .z). Probeer dat te verhogen, zodat je render-functie er zo uitziet:

function render () cube.rotation.y + = 0.02; requestAnimationFrame (render); renderer.render (scène, camera); 

Uitdaging: Kun je het langs een andere as laten draaien? Hoe zit het met langs twee assen tegelijkertijd??

Nu heb je alles ingesteld, laten we wat shaders toevoegen!

Shaders toevoegen

Op dit punt kunnen we gaan nadenken over het implementeren van shaders. Je bevindt je waarschijnlijk in een vergelijkbare situatie, ongeacht het platform dat je van plan bent om shaders te gebruiken: je hebt alles ingesteld en je hebt dingen getekend op het scherm, maar hoe krijg je toegang tot de GPU??

Stap 1: laden in GLSL-code

We gebruiken JavaScript om deze scène te bouwen. In andere situaties gebruikt u mogelijk C ++, of Lua of een andere taal. Shaders, ongeacht, zijn geschreven in een special Shading LanguageOpenGL's shading-taal is GLSL(OpenGL Shading Laal). Omdat we WebGL gebruiken, dat is gebaseerd op OpenGL, is GLSL wat we gebruiken.

Dus hoe en waar schrijven we onze GLSL-code? De algemene regel is dat u uw GLSL-code wilt laden als a draad. U kunt het vervolgens verzenden om te worden geparseerd en uitgevoerd door de GPU. 

In JavaScript kunt u dit doen door simpelweg al uw code inline in een variabele te gooien, zoals zo:

var shaderCode = "Al uw shadercode hier;"

Dit werkt, maar omdat JavaScript geen manier heeft om multiline strings eenvoudig te maken, is dit niet erg handig voor ons. De meeste mensen schrijven de shadercode meestal in een tekstbestand en geven er een extensie van .GLSL of .frag (kort voor fragment shader), laad het bestand dan gewoon in.

Dit is geldig, maar we gaan onze shadercode in een nieuwe schrijven

We geven het de ID van fragShader is zodat we er later toegang toe hebben. Het type shader-code is eigenlijk een nep-scripttype dat niet bestaat. (Je zou daar een willekeurige naam kunnen geven en het zou werken). De reden dat we dit doen is zodat de code niet wordt uitgevoerd en niet wordt weergegeven in de HTML.

Laten we nu een heel eenvoudige arcering gebruiken die net wit retourneert.

(De componenten van vec4 komt in dit geval overeen met de rgba-waarde, zoals uitgelegd in de vorige tutorial.)

Ten slotte moeten we deze code laden. We kunnen dit doen met een eenvoudige JavaScript-regel die het HTML-element vindt en de innerlijke tekst trekt:

var shaderCode = document.getElementById ("fragShader"). innerHTML;

Dit zou onder je kubuscode moeten gaan.

Let op: alleen wat als een string is geladen, zal worden geparseerd als geldige GLSL-code (dat wil zeggen, void main () .... De rest is slechts HTML-boilerplate.) 

U kunt dit vorkelen en bewerken op CodePen.

Stap 2: De Shader toepassen

De methode voor het toepassen van de arcering kan verschillen, afhankelijk van welk platform u gebruikt en hoe het wordt gekoppeld aan de GPU. Het is echter nooit een ingewikkelde stap en een vluchtige Google-zoekopdracht toont ons hoe we een object kunnen maken en er shaders op kunnen toepassen met Three.js.

We moeten een speciaal materiaal maken en het onze shadercode geven. We zullen een vlak maken als ons shader-object (maar we kunnen net zo goed de kubus gebruiken). Dit is alles wat we moeten doen:

// Maak een object om de shaders toe te passen op var-materiaal = nieuw THREE.ShaderMaterial (fragmentShader: shaderCode) var geometry = new THREE.PlaneGeometry (10, 10); var sprite = nieuw THREE.Mesh (geometrie, materiaal); scene.add (sprite); sprite.position.z = -1; // Verplaats het terug zodat we het kunnen zien

Inmiddels zou je een wit scherm moeten zien:

U kunt dit vorkelen en bewerken op CodePen.


Als u de code in de arcering in een andere kleur wijzigt en vernieuwt, ziet u de nieuwe kleur!

Uitdaging: Kun je een deel van het scherm rood maken en een ander deel blauw? (Als je vastzit, zou de volgende stap je een hint moeten geven!)

Stap 3: Gegevens verzenden

Op dit punt kunnen we doen wat we willen met onze shader, maar er is niet veel wij kan do. We hebben alleen de ingebouwde pixelpositie gl_FragCoord om mee te werken, en als je je dat herinnert, dat is niet genormaliseerd. We moeten ten minste de schermafmetingen hebben. 

Als u gegevens naar onze arcering wilt verzenden, moeten we deze verzenden als een naam uniform variabel. Om dit te doen, creëren we een object genaamd uniformen en voeg onze variabelen eraan toe. Dit is de syntaxis voor het verzenden van de resolutie:

var uniforms = ; uniforms.resolution = type: 'v2', waarde: nieuw THREE.Vector2 (window.innerWidth, window.innerHeight);
Elke uniforme variabele moet een type en een waarde. In dit geval is het een tweedimensionale vector met de breedte en hoogte van het venster als coördinaten. In de onderstaande tabel (overgenomen uit de Doc.dr.js) ziet u alle gegevenstypes die u kunt verzenden en hun ID's:
Uniform type string GLSL-type JavaScript-type
'ik', '1i'
int
Aantal
'f', '1f' vlotter
Aantal
'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
Als u het daadwerkelijk naar de arcering wilt verzenden, wijzigt u de ShaderMaterial instantiator om het op te nemen, zoals dit:
var material = new THREE.ShaderMaterial (uniforms: uniforms, fragmentShader: shaderCode)

We zijn nog niet klaar! Nu ontvangt onze shader deze variabele, we moeten er iets mee doen. Laten we een verloop maken op dezelfde manier als in de vorige tutorial: door onze coördinaat te normaliseren en deze te gebruiken om onze kleurwaarde te creëren.

Pas je shader-code aan zodat het er zo uitziet:

uniforme vec2 resolutie; // Uniforme variabelen moeten hier eerst worden gedeclareerd void main () // Nu kunnen we onze coördinaat normaliseren vec2 pos = gl_FragCoord.xy / resolution.xy; // En maak een verloop! gl_FragColor = vec4 (1.0, pos.x, pos.y, 1.0); 

En je zou een mooi uitziend verloop moeten zien!

U kunt dit vorkelen en bewerken op CodePen.

Als je een beetje wazig bent over hoe we erin geslaagd zijn om zo'n mooi verloop te maken met slechts twee regels arceringscode, bekijk dan het eerste deel van deze tutorialserie voor een grondige analyse van de logica hierachter.

Uitdaging: Kun je het scherm opsplitsen in 4 gelijke secties met verschillende kleuren? Iets zoals dit:

Stap 4: Gegevens bijwerken

Het is goed om gegevens naar onze arcering te kunnen verzenden, maar wat als we deze moeten bijwerken? Als u bijvoorbeeld het vorige voorbeeld op een nieuw tabblad opent en vervolgens het formaat van het venster wijzigt, wordt het verloop niet bijgewerkt, omdat het nog steeds de beginschermafmetingen gebruikt..

Als u uw variabelen wilt bijwerken, moet u de uniforme variabele gewoon opnieuw verzenden en wordt deze bijgewerkt. Met Three.js hoeven we echter alleen de uniformen object in onze geven functie-niet nodig om het opnieuw te verzenden naar de arcering. 

Dus hier is hoe onze render-functie eruit ziet na het maken van die wijziging:

function render () cube.rotation.y + = 0.02; uniforms.resolution.value.x = window.innerWidth; uniforms.resolution.value.y = window.innerHeight; requestAnimationFrame (render); renderer.render (scène, camera); 

Als u de nieuwe CodePen opent en het formaat van het venster wijzigt, ziet u de kleuren veranderen (hoewel de oorspronkelijke viewportgrootte gelijk blijft). Het is het gemakkelijkst om dit te zien door te kijken naar de kleuren in elke hoek om te controleren of ze niet veranderen.

Notitie: Gegevens verzenden naar de GPU zoals deze is over het algemeen duur. Het verzenden van een handvol variabelen per frame is goed, maar je framerate kan echt langzamer werken als je honderden frames per frame verzendt. Het klinkt misschien niet als een realistisch scenario, maar als je een paar honderd objecten op het scherm hebt en allemaal verlichting op hen moet hebben, bijvoorbeeld allemaal met verschillende eigenschappen, dan kunnen dingen snel uit de hand lopen. We zullen meer leren over het optimaliseren van onze shaders in toekomstige artikelen!

Uitdaging: Kun je de kleuren in de loop van de tijd veranderen? (Als je vastzit, kijk dan hoe we het in het eerste deel van deze tutorialserie hebben gedaan.)

Stap 5: Omgaan met texturen

Ongeacht hoe u in uw structuren of in welke indeling laadt, u verzendt ze op dezelfde manier naar platforms op dezelfde manier als uniforme variabelen.

Een korte opmerking over het laden van bestanden in JavaScript: u kunt zonder al te veel problemen afbeeldingen van een externe URL laden (wat we hier zullen doen) maar als u een afbeelding lokaal wilt laden, krijgt u problemen met toestemmingen, omdat JavaScript heeft normaal gesproken geen toegang tot bestanden op uw systeem en zou dit ook niet moeten doen. De gemakkelijkste manier om dit te omzeilen is om een ​​lokale Python-server te starten, die eenvoudiger is dan hij misschien klinkt.

Three.js biedt ons een handige kleine functie voor het laden van een afbeelding als een structuur:

THREE.ImageUtils.crossOrigin = "; // Stelt ons in staat om een ​​externe afbeelding te laden var tex = THREE.ImageUtils.loadTexture (" https://tutsplus.github.io/Beginners-Guide-toShaders/Part2/SIPI_Jelly_Beans.jpg ");

De eerste regel hoeft maar één keer te worden ingesteld. Je kunt daar elke URL naar een afbeelding plaatsen. 

Vervolgens willen we onze textuur toevoegen aan de uniformen voorwerp.

uniforms.texture = type: 't', value: tex;

Ten slotte willen we onze uniforme variabele in onze arceringscode declareren en deze op dezelfde manier tekenen als in de vorige zelfstudie, met de texture2D functie:

uniforme vec2-resolutie; uniforme sampler2D textuur; void main () vec2 pos = gl_FragCoord.xy / resolution.xy; gl_FragColor = texture2D (texture, pos); 

En je zou een paar lekkere jelly beans moeten zien, uitgerekt over ons scherm:

U kunt dit vorkelen en bewerken op CodePen.

(Deze foto is een standaard testbeeld op het gebied van computergraphics, genomen van het Signal and Image Processing Institute van de University of Southern California (vandaar de IPI-initialen) .Het lijkt ons gepast om het als ons testbeeld te gebruiken terwijl we leren over grafische shaders! )

Uitdaging: Kun je ervoor zorgen dat de textuur in de loop van de tijd van volledige kleur naar grijstinten gaat? (Nogmaals, als je vastzit, hebben we dit in het eerste deel van deze serie gedaan.)

Bonusstap: Shaders toepassen op andere objecten

Er is niets speciaals aan het vliegtuig dat we hebben gemaakt. We hadden dit alles op onze kubus kunnen toepassen. In feite kunnen we alleen de geometrie van het vlak wijzigen:

var geometry = new THREE.PlaneGeometry (10, 10);

naar:

var geometry = new THREE.BoxGeometry (1, 1, 1);

Voila, jelly beans op een kubus:

U kunt dit vorkelen en bewerken op CodePen.

Nu denk je misschien, "Wacht even, dat ziet er niet uit als een goede projectie van een textuur op een kubus!". En je zou gelijk hebben; als we terugkijken naar onze shader, zullen we zien dat het enige wat we echt deden was zeggen "kaart alle pixels van deze afbeelding op het scherm". Het feit dat het op een kubus staat, betekent alleen dat de buitenste pixels worden weggegooid. 

Als je het zou willen toepassen zodat het lijkt alsof het fysiek op de kubus is getekend, zou dat betekenen dat er veel opnieuw een 3D-engine moet worden uitgevonden (wat een beetje stom klinkt omdat we al een 3D-engine gebruiken en we kunnen het gewoon vragen om teken de textuur op elke kant afzonderlijk). Deze tutorialserie gaat meer over het gebruik van shaders om dingen te doen die we anders niet zouden kunnen bereiken, dus we zullen ons niet verdiepen in details zoals dat. (Udacity heeft een geweldige cursus over de basisprincipes van 3D-graphics, als je meer wilt weten!)

Volgende stappen

Op dit punt zou je alles moeten kunnen doen wat we in ShaderToy gedaan hebben, behalve nu, heb je de vrijheid om welke texturen je maar wilt, op welke vorm dan ook, en hopelijk op welk platform je ook kiest. 

Met deze vrijheid kunnen we nu iets doen zoals het opzetten van een verlichtingssysteem met realistisch uitziende schaduwen en hooglichten. Dit is waar het volgende deel op zal focussen, evenals tips en technieken voor het optimaliseren van shaders!