Õppige selgeks Frontend WebGL shader'ite optimeerimine selle süvaõpiku abil. Avastage GPU koodi jõudluse häälestamise tehnikaid GLSL-is, et saavutada kõrgeid kaadrisagedusi.
Frontend WebGL-i shader'ite optimeerimine: sügav sissevaade GPU koodi jõudluse häälestamisse
Veebibrauseris reaalajas 3D-graafika maagia, mida toetab WebGL, on avanud uue rinde interaktiivsetele kogemustele. Alates vapustavatest tootekonfiguraatoritest ja kaasahaaravatest andmete visualiseerimistest kuni köitvate mängudeni on võimalused tohutud. Selle võimsusega kaasneb aga kriitiline vastutus: jõudlus. Visuaalselt hingemattev stseen, mis jookseb kasutaja masinas 10 kaadrit sekundis (FPS), ei ole edu, vaid masendav kogemus. Saladus sujuvate ja suure jõudlusega WebGL-i rakenduste avamiseks peitub sügaval GPU-s, koodis, mis käivitatakse iga tipu ja iga piksli jaoks: shader'ites.
See põhjalik juhend on mõeldud frontend-arendajatele, loovtehnoloogidele ja graafikaprogrammeerijatele, kes soovivad liikuda WebGL-i põhitõdedest kaugemale ja õppida, kuidas oma GLSL-i (OpenGL Shading Language) koodi maksimaalse jõudluse saavutamiseks häälestada. Uurime GPU arhitektuuri põhiprintsiipe, tuvastame levinud kitsaskohad ja pakume tööriistakasti praktilisi tehnikaid, et muuta teie shader'id kiiremaks, tõhusamaks ja valmis igaks seadmeks.
GPU konveieri ja shader'ite kitsaskohtade mõistmine
Enne kui saame optimeerida, peame mõistma keskkonda. Erinevalt protsessorist (CPU), millel on mõned väga keerukad tuumad, mis on mõeldud järjestikuste ülesannete jaoks, on GPU massiivselt paralleelne protsessor sadade või tuhandete lihtsate ja kiirete tuumadega. See on loodud sama operatsiooni teostamiseks suurte andmehulkadega samaaegselt. See on SIMD (Single Instruction, Multiple Data) arhitektuuri süda.
Lihtsustatud graafika renderdustoru näeb välja selline:
- CPU: Valmistab ette andmed (tipu asukohad, värvid, maatriksid) ja väljastab joonistuskutsed.
- GPU - Tipuvarjendaja (Vertex Shader): Programm, mis käivitub üks kord iga teie geomeetria tipu kohta. Selle peamine ülesanne on arvutada tipu lõplik asukoht ekraanil.
- GPU - Rasterdamine: Riistvaraline etapp, mis võtab kolmnurga muundatud tipud ja selgitab välja, milliseid piksleid ekraanil see katab.
- GPU - Fragmendivarjendaja (Fragment Shader või Pixel Shader): Programm, mis käivitub üks kord iga geomeetria poolt kaetud piksli (või fragmendi) kohta. Selle ülesanne on arvutada selle piksli lõplik värv.
Kõige levinumad jõudluse kitsaskohad WebGL-i rakendustes leiduvad shader'ites, eriti fragmendivarjendajas. Miks? Sest kuigi mudelil võib olla tuhandeid tippe, võib see kõrge eraldusvõimega ekraanil hõlpsasti katta miljoneid piksleid. Väike ebaefektiivsus fragmendivarjendajas võimendub miljoneid kordi, iga kaadri jooksul.
Jõudluse põhiprintsiibid
- KISS (Keep It Simple, Shader): Kõige lihtsamad matemaatilised operatsioonid on kõige kiiremad. Keerukus on teie vaenlane.
- Madalaim sagedus esimesena: Tehke arvutused konveieris nii vara kui võimalik. Kui arvutus on objekti iga piksli jaoks sama, tehke see tipuvarjendajas. Kui see on kogu objekti jaoks sama, tehke see CPU-s ja edastage see uniform-muutujana.
- Profileeri, ära arva: Eeldused jõudluse kohta on sageli valed. Kasutage profileerimisvahendeid, et leida oma tegelikud kitsaskohad enne optimeerimise alustamist.
Tipuvarjendaja optimeerimise tehnikad
Tipuvarjendaja on teie esimene võimalus GPU-s optimeerimiseks. Kuigi see töötab harvemini kui fragmendivarjendaja, on tõhus tipuvarjendaja ülioluline stseenide jaoks, millel on kõrge polügoonide arvuga geomeetria.
1. Tehke matemaatika võimalusel CPU-s
Iga arvutus, mis on konstantne kõigi tippude jaoks ühes joonistuskutses, tuleks teha CPU-s ja edastada shader'ile uniform-muutujana. Klassikaline näide on mudeli-vaate-projektsiooni maatriks.
Selle asemel, et edastada kolm maatriksit (mudel, vaade, projektsioon) ja korrutada neid tipuvarjendajas...
// AEGLANE: Tipuvarjendajas
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
void main() {
mat4 modelViewProjectionMatrix = projectionMatrix * viewMatrix * modelMatrix;
gl_Position = modelViewProjectionMatrix * vec4(position, 1.0);
}
...arvutage kombineeritud maatriks eelnevalt CPU-s (nt oma JavaScripti koodis, kasutades teeki nagu gl-matrix või THREE.js-i sisseehitatud matemaatikat) ja edastage ainult üks.
// KIIRE: Tipuvarjendajas
uniform mat4 modelViewProjectionMatrix;
attribute vec3 position;
void main() {
gl_Position = modelViewProjectionMatrix * vec4(position, 1.0);
}
2. Minimeerige varying-andmeid
Andmetel, mis edastatakse tipuvarjendajast fragmendivarjendajale varying-muutujate kaudu (või `out`-muutujate kaudu GLSL 3.0+ versioonis), on oma hind. GPU peab neid väärtusi interpoleerima iga piksli jaoks. Saatke ainult seda, mis on absoluutselt vajalik.
- Pakkige andmed: Kahe `vec2` varying-muutuja asemel kasutage ĂĽhte `vec4` muutujat.
- Arvutage uuesti, kui see on odavam: Mõnikord võib olla odavam väärtus uuesti arvutada fragmendivarjendajas väiksemast varying-muutujate hulgast kui edastada suurt, interpoleeritud väärtust. Näiteks normaliseeritud vektori edastamise asemel edastage normaliseerimata vektor ja normaliseerige see fragmendivarjendajas. See on kompromiss, mida peate profileerima!
Fragmendivarjendaja optimeerimise tehnikad: raskekaallane
Siit leitakse tavaliselt suurimad jõudluse kasvud. Pidage meeles, et see kood võib käivituda miljoneid kordi kaadri kohta.
1. Hallake täpsuskvalifikaatoreid (`highp`, `mediump`, `lowp`)
GLSL võimaldab teil määrata ujukomaarvude täpsuse. See mõjutab otseselt jõudlust, eriti mobiilsete GPU-de puhul. Madalama täpsuse kasutamine tähendab, et arvutused on kiiremad ja kasutavad vähem energiat.
highp: 32-bitine ujukomaarv. Kõrgeim täpsus, kõige aeglasem. Oluline tipu asukohtade ja maatriksarvutuste jaoks.mediump: Sageli 16-bitine ujukomaarv. Fantastiline tasakaal vahemiku ja täpsuse vahel. Tavaliselt ideaalne tekstuurikoordinaatide, värvide, normaalide ja valgustusarvutuste jaoks.lowp: Sageli 8-bitine ujukomaarv. Madalaim täpsus, kiireim. Saab kasutada lihtsate värviefektide jaoks, kus täpsusartefaktid ei ole märgatavad.
Parim tava: Alustage `mediump`-ga kõige jaoks, välja arvatud tipu asukohad. Deklareerige oma fragmendivarjendaja ülaosas `precision mediump float;` ja tühistage ainult konkreetsete muutujate puhul `highp`-ga, kui märkate visuaalseid artefakte, nagu triibutamine või vale valgustus.
// Hea alguspunkt fragmendivarjendajale
precision mediump float;
uniform vec3 u_lightPosition;
varying vec3 v_normal;
void main() {
// Kõik siinsed arvutused kasutavad mediump-d
}
2. Vältige hargnemist ja tingimuslauseid (`if`, `switch`)
See on ehk kõige kriitilisem optimeerimine GPU-de jaoks. Kuna GPU-d täidavad lõimi gruppides (nimetatakse "warp'ideks" või "wave'ideks"), siis kui üks lõim grupis võtab `if`-tee, on kõik teised lõimed selles grupis sunnitud ootama, isegi kui nad võtavad `else`-tee. Seda nähtust nimetatakse lõimede lahknemiseks ja see tapab paralleelsuse.
`if`-lausete asemel kasutage GLSL-i sisseehitatud funktsioone, mis on implementeeritud ilma lahknemist põhjustamata.
Näide: määrake värv tingimuse alusel.
// HALB: Põhjustab lõimede lahknemist
float intensity = dot(normal, lightDir);
if (intensity > 0.5) {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // Punane
} else {
gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); // Sinine
}
GPU-sõbralik viis kasutab funktsioone `step()` ja `mix()`. `step(edge, x)` tagastab 0.0, kui x < edge, ja muidu 1.0. `mix(a, b, t)` interpoleerib lineaarselt `a` ja `b` vahel, kasutades `t`.
// HEA: Hargnemist ei toimu
float intensity = dot(normal, lightDir);
float t = step(0.5, intensity); // Tagastab 0.0 või 1.0
vec4 red = vec4(1.0, 0.0, 0.0, 1.0);
vec4 blue = vec4(0.0, 0.0, 1.0, 1.0);
gl_FragColor = mix(blue, red, t);
Teised olulised hargnemiseta funktsioonid on: clamp(), smoothstep(), min() ja max().
3. Algebraline lihtsustamine ja operatsioonide tugevuse vähendamine
Asendage kallid matemaatilised operatsioonid odavamatega. Kompilaatorid on head, kuid nad ei suuda kõike optimeerida. Aidake neile kaasa.
- Jagamine: Jagamine on väga aeglane. Asendage see võimaluse korral pöördarvuga korrutamisega. `x / 2.0` peaks olema `x * 0.5`.
- Astendamine: `pow(x, y)` on väga üldine ja aeglane funktsioon. Konstantsete täisarvuliste astmete puhul kasutage otsest korrutamist: `x * x` on palju kiirem kui `pow(x, 2.0)`.
- Trigonomeetria: Funktsioonid nagu `sin`, `cos`, `tan` on kallid. Kui te ei vaja täiuslikku täpsust, kaaluge matemaatilise lähenduse või tekstuurist lugemise kasutamist.
- Vektorite matemaatika: Kasutage sisseehitatud funktsioone. `dot(v, v)` on kiirem kui `length(v) * length(v)` ja palju kiirem kui `pow(length(v), 2.0)`. See arvutab ruudus pikkuse ilma kalli ruutjuureta. Võimalusel võrrelge ruudus pikkusi, et vältida `sqrt()` kasutamist.
4. Tekstuurilugemise optimeerimine
Tekstuuridest proovide võtmine (`texture2D()` või `texture()`) võib olla kitsaskoht, kuna see hõlmab juurdepääsu mälule.
- Minimeerige lugemisi: Kui vajate piksli jaoks mitut andmeosa, proovige need pakkida ĂĽhte tekstuuri (nt kasutades R, G, B ja A kanaleid erinevate halltoonide kaartide jaoks).
- Kasutage mipmap'e: Genereerige alati oma tekstuuridele mipmap'id. See mitte ainult ei hoia ära visuaalseid artefakte kaugetel pindadel, vaid parandab ka dramaatiliselt tekstuuri vahemälu jõudlust, kuna GPU saab andmeid hankida väiksemalt ja sobivamalt tekstuuri tasemelt.
- Sõltuvad tekstuurilugemised: Olge väga ettevaatlik tekstuurilugemistega, kus koordinaadid sõltuvad eelnevast tekstuurilugemisest. See võib rikkuda GPU võime tekstuurandmeid ette laadida, põhjustades seiskumisi.
Tööriistad: profileerimine ja silumine
Kul reegel on: Sa ei saa optimeerida seda, mida sa ei saa mõõta. Kitsaskohtade äraarvamine on raisatud aja retsept. Kasutage spetsiaalset tööriista, et analüüsida, mida teie GPU tegelikult teeb.
Spector.js
Uskumatu avatud lähtekoodiga tööriist Babylon.js-i meeskonnalt, Spector.js on hädavajalik. See on brauserilaiendus, mis võimaldab teil jäädvustada ühe kaadri oma WebGL-i rakendusest. Seejärel saate samm-sammult läbida iga joonistuskutse, kontrollida olekut, vaadata tekstuure ja näha täpselt kasutatavaid tipu- ja fragmendivarjendajaid. See on hindamatu silumiseks ja mõistmiseks, mis GPU-s tegelikult toimub.
Brauseri arendaja tööriistad
Kaasaegsetel brauseritel on üha võimsamad sisseehitatud GPU profileerimise tööriistad. Näiteks Chrome DevTools'is saab "Performance" paneel salvestada jälje ja näidata teile GPU tegevuse ajajoont. See aitab teil tuvastada kaadreid, mille renderdamine võtab liiga kaua aega, ja näha, kui palju aega kulub fragmendi versus tipu töötlemise etappidele.
Juhtumiuuring: lihtsa Blinn-Phongi valgustuse shader'i optimeerimine
Rakendame neid tehnikaid praktikas. Siin on levinud, optimeerimata fragmendivarjendaja Blinn-Phongi peegelvalgustuse jaoks.
Enne optimeerimist
// Optimeerimata fragmendivarjendaja
precision highp float; // Tarbetult kõrge täpsus
varying vec3 v_worldPosition;
varying vec3 v_normal;
uniform vec3 u_lightPosition;
uniform vec3 u_cameraPosition;
void main() {
vec3 normal = normalize(v_normal);
vec3 lightDir = normalize(u_lightPosition - v_worldPosition);
// Hajus
float diffuse = max(dot(normal, lightDir), 0.0);
// Peegeldus
vec3 viewDir = normalize(u_cameraPosition - v_worldPosition);
vec3 halfDir = normalize(lightDir + viewDir);
float shininess = 32.0;
float specular = 0.0;
if (diffuse > 0.0) { // Hargnemine!
specular = pow(max(dot(normal, halfDir), 0.0), shininess); // Kallis pow()
}
gl_FragColor = vec4(vec3(diffuse + specular), 1.0);
}
Pärast optimeerimist
Nüüd rakendame oma põhimõtteid selle koodi refaktoorimiseks.
// Optimeeritud fragmendivarjendaja
precision mediump float; // Kasuta sobivat täpsust
varying vec3 v_normal;
varying vec3 v_lightDir;
varying vec3 v_halfDir;
void main() {
// Kõik vektorid normaliseeritakse tipuvarjendajas ja edastatakse varying-muutujatena
// See liigutab töö piksli-põhiselt tipu-põhiseks
// Hajus
float diffuse = max(dot(v_normal, v_lightDir), 0.0);
// Peegeldus
float shininess = 32.0;
float specular = pow(max(dot(v_normal, v_halfDir), 0.0), shininess);
// Eemalda hargnemine lihtsa nipiga: kui hajus on 0, on valgus pinna taga,
// seega peaks ka peegeldus olema 0. Saame korrutada `step()`-ga.
specular *= step(0.001, diffuse);
// Märkus: Veelgi parema jõudluse saavutamiseks asenda pow() korduva korrutamisega,
// kui shininess on väike täisarv, või kasuta lähendust.
// float spec_dot = max(dot(v_normal, v_halfDir), 0.0);
// float spec_sq = spec_dot * spec_dot;
// float specular = spec_sq * spec_sq * spec_sq * spec_sq; // pow(x, 16)
gl_FragColor = vec4(vec3(diffuse + specular), 1.0);
}
Mida me muutsime?
- Täpsus: Lülitusime `highp`-lt `mediump`-le, mis on valgustuse jaoks piisav.
- Teisaldasime arvutused: `lightDir`, `viewDir` normaliseerimine ja `halfDir` arvutamine viidi tipuvarjendajasse. See on tohutu sääst, kuna see töötab nüüd tipu kohta piksli asemel.
- Eemaldasime hargnemise: `if (diffuse > 0.0)` kontroll asendati korrutisega `step(0.001, diffuse)`. See tagab, et peegelvalgustust arvutatakse ainult siis, kui on hajusvalgust, kuid ilma tingimusliku hargnemise jõudluskaristuseta.
- Tulevane samm: Märkisime, et kallist `pow()` funktsiooni saaks veelgi optimeerida, sõltuvalt `shininess` parameetri nõutavast käitumisest.
Kokkuvõte
Frontend WebGL-i shader'ite optimeerimine on sügav ja rahuldust pakkuv distsipliin. See muudab teid arendajast, kes lihtsalt kasutab shader'eid, arendajaks, kes juhib GPU-d tahtlikult ja tõhusalt. Mõistes aluseks olevat arhitektuuri ja rakendades süstemaatilist lähenemist, saate nihutada brauseris võimaliku piire.
Pidage meeles peamisi järeldusi:
- Profileeri esmalt: Ärge optimeerige pimesi. Kasutage tööriistu nagu Spector.js, et leida oma tegelikud jõudluse kitsaskohad.
- Tööta targalt, mitte raskelt: Teisaldage arvutused konveieris ülespoole, fragmendivarjendajast tipuvarjendajasse ja sealt CPU-sse.
- Võtke omaks GPU-põhine mõtlemine: Vältige hargnemist, kasutage madalamat täpsust ja võimendage sisseehitatud vektorifunktsioone.
Alustage oma shader'ite profileerimist juba täna. Uurige hoolikalt iga käsku. Iga optimeerimisega ei võida te mitte ainult kaadreid sekundis; te loote sujuvama, kättesaadavama ja muljetavaldavama kogemuse kasutajatele üle kogu maailma, mis tahes seadmes. Võim luua tõeliselt vapustavat reaalajas veebigraafikat on teie kätes – nüüd minge ja tehke see kiireks.