Ghidul începătorului pentru codarea graficelor Shaders Partea 2

ShaderToy, pe care am folosit-o în tutorialul precedent din această serie, este excelent pentru teste și experimente rapide, dar este destul de limitat. Nu puteți controla datele trimise la shader, de exemplu, printre altele. Având propriul dvs. mediu în care puteți executa shadere înseamnă că puteți face tot felul de efecte fantastice și le puteți aplica propriilor dvs. proiecte!

Vom folosi Trei.js ca cadru pentru a rula shadere în browser. WebGL este API-ul Javascript care ne va permite să facem shadere; Three.js face acest lucru mai ușor. 

Dacă nu sunteți interesat de JavaScript sau de platforma web, nu vă faceți griji: nu ne vom concentra pe specificul redării web (deși dacă doriți să aflați mai multe despre acest cadru, consultați acest tutorial). Instalarea shaderelor în browser este cea mai rapidă modalitate de a începe, dar a deveni confortabil cu acest proces vă va permite să configurați cu ușurință și să utilizați shadere pe orice platformă vă place. 

Pregatirea

Această secțiune vă va ghida prin configurarea shaderelor la nivel local. Puteți urmări împreună fără a fi nevoie să descărcați nimic cu acest CodPen pre-construit:

Puteți rula și edita acest lucru pe CodePen.

Bună ziua!

Three.js este un cadru JavaScript care are grijă de o mulțime de cod de boilerplate pentru WebGL pe care o vom avea nevoie pentru a ne face shaderele noastre. Cel mai simplu mod de a începe este să utilizați o versiune găzduită pe un CDN. 

Iată un fișier HTML pe care îl puteți descărca, care are doar o scenă de bază Threejs. 

Încercați să salvați fișierul pe disc, apoi să îl deschideți în browserul web. Ar trebui să vedeți un ecran negru. Nu este foarte interesant, așa că să încercăm să adăugăm un cub, doar pentru a ne asigura că totul funcționează. 

Pentru a crea un cub, trebuie să definim geometria și materialul său, apoi să îl adăugăm la scenă. Adăugați acest fragment de cod în locul în care se afișează Adăugați codul dvs. aici:

var geometrie = noua GHometrie triplă (1, 1, 1); var material = nou TARG.MeshBasicMaterial (culoare: 0x00ff00); // facem verde var cube = nou TREI.Mesh (geometrie, material); // Adăugați-o pe ecranul scene.add (cub); cube.position.z = -3; // Schimbați cubul înapoi pentru a îl vedea

Nu vom trece prea mult în detaliu în acest cod, deoarece suntem mai interesați de partea shader. Dar dacă totul merge bine, ar trebui să vedeți un cub verde în centrul ecranului:

În timp ce ne aflăm la asta, hai să facem rotirea. facefuncția rulează fiecare cadru. Putem accesa rotația cubului prin cube.rotation.x (sau .y sau .z). Încearcă să crești acest lucru, astfel încât funcția de redare să arate astfel:

funcția rendere () cube.rotation.y + = 0.02; requestAnimationFrame (render); renderer.render (scenă, cameră); 

Provocare: Puteți roti să se rotească de-a lungul unei axe diferite? Ce zici de-a lungul a două axe în același timp?

Acum aveți totul înființat, să adăugăm niște shadere!

Adăugarea de Shaders

În acest moment, putem începe să ne gândim la procesul de implementare a shaderelor. S-ar putea să vă aflați într-o situație similară, indiferent de platforma pe care intenționați să o utilizați: aveți totul configurat și aveți lucruri desenate pe ecran, acum cum accesați GPU-ul?

Pasul 1: Încărcare în codul GLSL

Utilizăm JavaScript pentru a construi această scenă. În alte situații este posibil să utilizați C ++, Lua sau orice altă limbă. Shaders, indiferent, sunt scrise într-un mod special Umbrelă LimbaOpenGL este limbajul de umbrire GLSL(DeschisGL Shading Language). Deoarece folosim WebGL, care se bazează pe OpenGL, atunci GLSL este ceea ce folosim.

Deci, cum și unde scriem codul nostru GLSL? Regula generală este că doriți să încărcați codul GLSL în ca a şir. Apoi puteți să o trimiteți pentru a fi analizată și executată de GPU. 

În JavaScript, puteți face acest lucru prin simpla aruncare a întregului cod în interiorul unei variabile, cum ar fi:

var shaderCode = "Toate codurile shader aici;"

Acest lucru funcționează, dar din moment ce JavaScript nu are o modalitate de a face cu ușurință șiruri multiline, acest lucru nu este foarte convenabil pentru noi. Majoritatea oamenilor tind să scrie codul shader într-un fișier text și să îi dea o extensie .GLSL sau .frag (scurt pentru fragment shader), apoi încărcați acel fișier în.

Acest lucru este valabil, dar vom scrie codul shader într-un nou

Îi dăm ID-ul fragShader este astfel încât să putem accesa mai târziu. Tipul shader-cod este de fapt un tip de script fals care nu există. (Ai putea pune orice nume acolo și ar funcționa). Motivul pentru care facem acest lucru este ca codul să nu fie executat și nu este afișat în HTML.

Acum hai să aruncăm într-un shader foarte de bază care doar revine alb.

(Componentele vec4 în acest caz corespund valorii rgba, așa cum sa explicat în tutorialul anterior.)

În cele din urmă, trebuie să încărcați acest cod. Putem face acest lucru cu o linie simplă JavaScript care găsește elementul HTML și trage textul interior:

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

Acest lucru ar trebui să meargă sub codul dvs. de cub.

Amintiți-vă că numai ceea ce este încărcat ca un șir va fi analizat ca un cod GLSL valid (adică, void main () .... Restul este doar boilerplate HTML.) 

Puteți rula și edita acest lucru pe CodePen.

Pasul 2: Aplicarea Shader-ului

Metoda de aplicare a shader-ului poate fi diferită în funcție de platforma pe care o utilizați și de modul în care interfața cu GPU-ul. Totuși, niciodată nu este un pas complicat și o căutare rapidă Google ne arată cum să creăm un obiect și să aplicăm shadere la acesta cu Three.js.

Trebuie să creăm un material special și să îi oferim codul shader. Vom crea un avion ca obiect shader (dar am putea folosi la fel de bine cubul). Acesta este tot ce trebuie să facem:

// Creați un obiect pentru a aplica shaderele la materialul var = nou THREE.ShaderMaterial (fragmentShader: shaderCode) var geometry = noua geometrie triplă (10, 10); var sprite = nou TREI.Mesh (geometrie, material); scene.add (sprite); sprite.position.z = -1; // mutați-l înapoi pentru a putea vedea

Până acum, ar trebui să vedeți un ecran alb:

Puteți rula și edita acest lucru pe CodePen.


Dacă schimbați codul în shader la orice altă culoare și reîmprospătați, ar trebui să vedeți noua culoare!

Provocare: Puteți seta o porțiune a ecranului pe roșu, iar o altă porțiune albastră? (Dacă sunteți blocat, pasul următor ar trebui să vă dea un indiciu!)

Pasul 3: Trimiterea datelor

În acest moment, putem face tot ce ne dorim cu shaderul nostru, dar nu prea avem poate sa do. Avem doar poziția pixelilor încorporați gl_FragCoord pentru a lucra cu, și dacă vă amintiți, acest lucru nu este normalizat. Trebuie să avem cel puțin dimensiunile ecranului. 

Pentru a trimite date shader-ului nostru, trebuie să-l trimitem așa cum se numește a uniformă variabil. Pentru a face acest lucru, vom crea un obiect numit uniforme și adăugați variabilele noastre la acesta. Iată sintaxa pentru trimiterea rezoluției:

var uniforme = ; uniforms.resolution = tip: 'v2', valoare: nou THREE.Vector2 (window.innerWidth, window.innerHeight);
Fiecare variabilă uniformă trebuie să aibă a tip și a valoare. În acest caz, este un vector 2 dimensional cu lățimea și înălțimea ferestrei ca coordonate. Tabelul de mai jos (preluat din documentele Three.js) vă arată toate tipurile de date pe care le puteți trimite și identificatorii acestora:
Șir de tip uniform Tipul GLSL Tipul JavaScript
'i', '1i'
int
Număr
'f', '1f' pluti
Număr
'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
Pentru al trimite efectiv la shader, modificați-l ShaderMaterial instantiator pentru ao include, astfel:
var material = nou material THREE.ShaderMaterial (uniforme: uniforme, fragmentShader: shaderCode)

Nu am terminat încă! Acum, shaderul nostru primește această variabilă, trebuie să facem ceva cu ea. Să creăm un gradient în același mod pe care l-am făcut și în tutorialul precedent: prin normalizarea coordonatelor și folosirea lor pentru a crea valoarea culorii.

Modificați codul de shader astfel încât acesta să arate astfel:

rezoluție uniformă vec2; // variabile uniforme trebuie să fie declarate aici void principal () // Acum putem normaliza coordonatele noastre vec2 pos = gl_FragCoord.xy / resolution.xy; // Și a crea un gradient! gl_FragColor = vec4 (1.0, pos.x, poz.y, 1.0); 

Și ar trebui să vezi un gradient frumos!

Puteți rula și edita acest lucru pe CodePen.

Dacă sunteți puțin dezorientat în legătură cu modul în care am reușit să creăm un astfel de gradient frumos, cu doar două linii de cod shader, verificați prima parte a acestei serii tutorial pentru o scurgere în profunzime a logicii din spatele acestei.

Provocare: Puteți împărți ecranul în 4 secțiuni egale cu culori diferite? Ceva de genul:

Pasul 4: Actualizarea datelor

Este frumos să putem trimite date la shader-ul nostru, dar dacă avem nevoie să îl actualizăm? De exemplu, dacă deschideți exemplul anterior într-o filă nouă, apoi redimensionați fereastra, gradientul nu se actualizează, deoarece încă utilizează dimensiunile inițiale ale ecranului.

Pentru a vă actualiza variabilele, de obicei, ar trebui să retrimiteți variabila uniformă și aceasta va fi actualizată. Cu Trei.js, totuși, trebuie doar să actualizăm uniforme obiect în nostru face funcția - nu este nevoie să o retrimiteți la shader. 

Deci, iată ce arată funcția noastră de redare după ce ați făcut această schimbare:

funcția rendere () cube.rotation.y + = 0.02; uniforms.resolution.value.x = window.innerWidth; uniforms.resolution.value.y = window.innerHeight; requestAnimationFrame (render); renderer.render (scenă, cameră); 

Dacă deschideți noul CodePen și redimensionați fereastra, veți vedea schimbarea culorilor (deși dimensiunea inițială a ferestrei de vizualizare rămâne aceeași). Este mai ușor să vedeți acest lucru uitându-vă la culorile din fiecare colț pentru a verifica dacă acestea nu se schimbă.

Notă: Transmiterea de date către GPU ca aceasta este, în general, costisitoare. Trimiterea unei mii de variabile pe cadru este în regulă, dar frameratul dvs. poate încetini într-adevăr dacă trimiteți sute pe cadru. S-ar putea să nu părea un scenariu realist, dar dacă aveți câteva sute de obiecte pe ecran și toți trebuie să li se aplice iluminare, de exemplu, toate cu proprietăți diferite, atunci lucrurile pot scăpa rapid de sub control. Vom afla mai multe despre optimizarea shaderelor noastre în articolele viitoare!

Provocare: Puteți schimba culorile în timp? (Dacă sunteți blocat, uitați-vă la modul în care am făcut-o în prima parte a acestei serii tutorial.)

Pasul 5: Confruntarea cu texturile

Indiferent de modul în care încărcați în texturi sau în ce format, le veți trimite la shader în același mod pe platforme, ca variabile uniforme.

O notă rapidă despre încărcarea fișierelor în JavaScript: puteți încărca imagini dintr-o adresă URL externă fără prea multe probleme (ceea ce vom face aici), dar dacă doriți să încărcați o imagine la nivel local, veți întâlni probleme de permisiune, deoarece JavaScript nu poate, și nu ar trebui, să acceseze în mod normal fișiere în sistemul dvs. Cel mai simplu mod de a obține acest lucru este să porniți un server local Python, ceea ce este mai simplu decât pare.

Three.js ne oferă o funcție mică pentru încărcarea unei imagini ca textura:

THREE.ImageUtils.crossOrigin = "; // Permite încărcarea unei imagini externe var tex = THREE.ImageUtils.loadTexture (" https://tutsplus.github.io/Beginners-Guide-to-Shaders/Part2/SIPI_Jelly_Beans.jpg ");

Prima linie trebuie doar să fie setată o singură dată. Puteți pune orice URL la o imagine acolo. 

Apoi, dorim să adăugăm textura noastră la uniforme obiect.

uniforms.texture = tip: 't', valoare: tex;

În cele din urmă, vrem să ne declarăm variabila uniformă în codul shader și să o remizăm în același mod pe care am făcut-o în tutorialul anterior, texture2D funcţie:

rezoluție uniformă vec2; textura uniformă a eșantionului2D; void principală () vec2 pos = gl_FragCoord.xy / resolution.xy; gl_FragColor = textură2D (textură, pos); 

Și ar trebui să vedeți niște boabe de jeleuri gustoase, întinse pe ecranul nostru:

Puteți rula și edita acest lucru pe CodePen.

(Această imagine este o imagine standard de testare în domeniul graficii computerizate, preluată de la Institutul de semnal și de prelucrare a imaginilor din cadrul Universității din California de Sud (de aici inițialele IPI). Se pare că este potrivit să o folosim ca imagine de test în timp ce învățăm despre shaderele grafice! )

Provocare: Puteți face ca textura să treacă de la culoarea plină la gri în timp? (Din nou, dacă sunteți blocați, am făcut acest lucru în prima parte a seriei.)

Pasul bonus: Aplicarea shaderelor la alte obiecte

Nu e nimic deosebit în planul pe care l-am creat. Am fi putut aplica toate astea pe cubul nostru. De fapt, putem schimba linia de geometrie a planului:

var geometria = noua geometrie 3D (10, 10);

la:

var geometrie = noua GHometrie triplă (1, 1, 1);

Voila, fasole de jeleu pe un cub:

Puteți rula și edita acest lucru pe CodePen.

Acum te-ai gândi, "Stai așa, nu arată ca o proiecție corectă a unei texturi pe un cub!". Și ai avea dreptate; dacă ne uităm la shaderul nostru, vom vedea că tot ce am făcut a fost să spunem "hartă toți pixelii acestei imagini pe ecran". Faptul că se află pe un cub doar înseamnă că pixelii din exterior sunt eliminați. 

Dacă doriți să o aplicați astfel încât să pară că este atras fizic pe cub, asta ar implica o mulțime de reinventare a unui motor 3D (ceea ce sună un pic prost, având în vedere că deja folosim un motor 3D și îl putem cere să trageți textura pe fiecare parte în parte). Această serie de tutorial este mai mult despre utilizarea shaderelor pentru a face lucruri pe care nu le-am putea realiza altfel, așa că nu vom fi delfinat în detalii de genul asta. (Udacity are un curs foarte bun pe fundalul graficii 3D, dacă sunteți dornici să aflați mai multe!)

Pasii urmatori

În acest moment, ar trebui să fii capabil să faci tot ce am făcut în ShaderToy, cu excepția că acum ai libertatea de a folosi texturile pe care le vrei pe orice formă doriți și, sperăm, pe orice platformă alegi. 

Cu această libertate, acum putem face ceva asemănător cu instalarea unui sistem de iluminare, cu umbre și subliniază realist. Așa se va concentra următoarea parte, precum și sfaturi și tehnici pentru optimizarea shaderelor!