electricsquare / raymarching-atelier

adus la tine de Electric Square

creat și prezentat de AJ Weeks& Huw Bowles

Prezentare generală

redarea unei imagini implică determinarea culorii fiecărui pixel din imagine, ceea ce necesită să ne dăm seama ce suprafață se află în spatele pixelului din lume și apoi să-l ‘umbrim’ pentru a calcula o culoare finală.

GPU-urile de generație curentă iau ochiuri triunghiulare ca intrare, le rasterizează în pixeli (numite fragmente înainte de a fi trase la un afișaj) și apoi le umbresc pentru a calcula contribuția lor la imagine. Deși această conductă este în prezent omniprezentă, este, de asemenea, complicată și nu neapărat cea mai bună modalitate de a învăța Grafica.

o abordare alternativă este să arunci o rază prin fiecare pixel și să o intersectezi cu suprafețele din scenă, apoi să calculezi umbrirea.

acest curs introduce o tehnică pentru raycasting prin ‘câmpuri la distanță’. Un câmp de distanță este o funcție care returnează cât de aproape este un punct dat de cea mai apropiată suprafață din scenă. Această distanță definește raza unei sfere de spațiu gol în jurul fiecărui punct. Câmpurile de distanță semnate (SDFs) sunt câmpuri de distanță care sunt definite atât în interiorul, cât și în exteriorul obiectelor; dacă poziția interogată este ‘în interiorul’ unei suprafețe, distanța sa va fi raportată ca negativă, altfel va fi pozitivă.

ce este posibil cu Ray marching?

jocul ‘Claybook’ folosește exclusiv câmpurile de distanță pentru a reprezenta scena. Acest lucru îi oferă o mulțime de posibilități interesante, cum ar fi topologii de suprafață complet dinamice și transformarea formei. Aceste efecte ar fi foarte dificil de realizat cu ochiuri triunghiulare. Alte beneficii includ umbrele moi ușor de implementat și de înaltă calitate și ocluzia ambientală.

https://www.claybookgame.com/

următoarea imagine a fost, de asemenea, redată în timp real folosind tehnicile pe care le vom acoperi astăzi (plus multe tehnici fanteziste în care nu vom avea timp să ne scufundăm).

îl puteți rula live în browserul dvs. aici: https://www.shadertoy.com/view/ld3Gz2

folosind un SDF (signed distance field), geometria pentru această scenă nu a trebuit să fie creată într-un DCC ca Maya, ci este reprezentată în întregime parametric. Acest lucru face banală animarea formei prin simpla modificare a intrărilor la funcția de mapare a scenei.

alte efecte grafice sunt simplificate prin raymarching în comparație cu alternativele tradiționale de rasterizare. Împrăștierea subterană, de exemplu, necesită pur și simplu trimiterea câtorva raze suplimentare la suprafață pentru a vedea cât de groasă este. Ocluzia ambientală, anti-aliasing și adâncimea câmpului sunt alte trei tehnici care necesită doar câteva linii suplimentare și totuși îmbunătățesc foarte mult calitatea imaginii.

câmpuri de distanță Raymarching

vom mărșălui de-a lungul fiecărei raze și vom căuta o intersecție cu o suprafață în scenă. O modalitate de a face acest lucru ar fi să începeți de la originea razei (pe planul camerei) și să faceți pași uniformi de-a lungul razei, evaluând câmpul de distanță în fiecare punct. Când Distanța până la scenă este mai mică decât o valoare de prag, știm că am lovit o suprafață și, prin urmare, putem termina raza și umbra acelui pixel.

o abordare mai eficientă este utilizarea distanței returnate de SDF pentru a determina dimensiunea pasului următor. După cum sa menționat mai sus, distanța returnată de un SDF poate fi considerată ca raza unei sfere de spațiu gol în jurul punctului de intrare. Prin urmare, este sigur să pășim cu această cantitate de-a lungul razei, deoarece știm că nu vom trece prin nicio suprafață.

în următoarea reprezentare 2D a raymarching, centrul fiecărui cerc este locul de unde a fost eșantionată scena. Raza a fost apoi mărșăluită de-a lungul acelei distanțe (extinzându-se până la raza cercului), apoi reeșantionată.

după cum puteți vedea, eșantionarea SDF nu vă oferă punctul exact de intersecție al razei dvs., ci mai degrabă o distanță minimă pe care o puteți parcurge fără a trece printr-o suprafață.

odată ce această distanță este sub un anumit prag, raymarch termină pixelul poate fi umbrit pe baza proprietăților suprafeței intersectate cu.

Joaca-te cu acest shader în browser-ul dvs. aici: (faceți clic și trageți în imagine pentru a seta direcția ray) https://www.shadertoy.com/view/lslXD8

comparație cu ray tracing

în acest moment s-ar putea întreba de ce nu calculăm intersecția cu scena direct folosind matematica analitică, folosind o tehnică denumită Ray Tracing. Acesta este modul în care randările offline ar funcționa de obicei-toate triunghiurile din scenă sunt indexate într-un fel de structură de date spațiale, cum ar fi o ierarhie a volumului de delimitare (BVH) sau KD-tree, care permit intersecția eficientă a triunghiurilor situate de-a lungul unei raze.

noi raymarch câmpuri distanță în schimb, deoarece:

  • este foarte simplu să implementăm rutina de turnare a razelor
  • evităm toată complexitatea implementării intersecțiilor ray-triangle și a structurilor de date BVH
  • nu este nevoie să creăm reprezentarea explicită a scenei – ochiuri triunghiulare, coordine tex, culori etc
  • beneficiem de o serie de caracteristici utile ale câmpurilor de distanță, dintre care unele sunt menționate mai sus

acestea fiind spuse mai sus, există câteva puncte de intrare elegante/simple în urmărirea razelor. Ray Tracing-ul într-o carte gratuită de Weekend (și capitolele ulterioare) este foarte recomandat și este o lectură esențială pentru oricine este interesat de grafică.

să începem!

ShaderToy

ShaderToy este un site web și o platformă de creare a shader-urilor pentru navigarea, partajarea și discutarea shaderelor.

în timp ce puteți sări direct și puteți începe să scrieți un nou shader fără a crea un cont, acest lucru este periculos, deoarece puteți pierde cu ușurință munca dacă există probleme de conexiune sau dacă închideți GPU-ul (ușor de făcut, de exemplu, creând o buclă infinită).Prin urmare, vă recomandăm cu tărie să creați un cont (este rapid/ușor/gratuit), îndreptându-vă aici: https://www.shadertoy.com/signin și salvând în mod regulat.

pentru o prezentare generală ShaderToy și ghid de inițiere, vă recomandăm să urmați un tutorial precum acesta de la @The_ArtOfCode: https://www.youtube.com/watch?v=u5HAYVHsasc. Elementele de bază aici sunt necesare pentru a urmări restul atelierului.

2D SDF demo

oferim un cadru simplu pentru definirea și vizualizarea câmpurilor de distanță semnate 2D.

https://www.shadertoy.com/view/Wsf3Rj

înainte de definirea câmpului distanță rezultatul va fi în întregime alb. Scopul acestei secțiuni este de a proiecta un SDF care să ofere forma dorită a scenei (contur alb). În cod, această distanță este calculată de funcția sdf(), căreia i se dă o poziție 2D în spațiu ca intrare. Conceptele pe care le învățați aici se vor generaliza direct în spațiul 3D și vă vor permite să modelați o scenă 3D.

Start simplu-încercați mai întâi să utilizați doar componenta X sau y a punctului p și observați rezultatul:

float sdf(vec2 p){ return p.y;}

rezultatul ar trebui să arate după cum urmează:

Verde denotă suprafețele ‘exterioare’, roșu denotă suprafețele’ interioare’, linia albă delimitează suprafața în sine, iar umbrirea în regiunile interioare/exterioare ilustrează distanța iso-linii – linii la distanțe fixe. În 2D, acest SDF modelează o linie orizontală în 2D la y=0. Ce fel de primitiv geometric ar reprezenta acest lucru în 3D?

un alt lucru bun de încercat este utilizarea distanțelor, de exemplu: return length(p);. Acest operator returnează magnitudinea vectorului, și în acest caz ne dă distanța punctului curent față de origine.

un punct nu este un lucru foarte interesant de redat, deoarece un punct este infinitezimal, iar razele noastre ar lipsi întotdeauna!Putem da punctului o anumită zonă scăzând raza dorită de la distanță: return length(p) - 0.25;.De asemenea, putem modifica punctul de intrare înainte de a-i lua magnitudinea: length(p - vec2(0.0, 0.2)) - 0.25;.Ce efect are acest lucru asupra formei?Ce valori ar putea reveni funcția pentru punctele din interiorul cercului?

felicitări – tocmai ați modelat un cerc folosind matematica:). Acest lucru se va extinde trivial la 3D, caz în care modelează o sferă. Contrastează această reprezentare a scenei cu alte reprezentări ale scenelor’ explicite’, cum ar fi ochiuri triunghiulare sau suprafețe NURBS. Am creat o sferă în câteva minute cu o singură linie de cod, iar codul nostru se mapează direct la o definiție matematică pentru o sferă – ‘mulțimea tuturor punctelor care sunt echidistante de un punct central’.

pentru alte tipuri de primitive, funcțiile de distanță sunt la fel de elegante. iq a făcut o pagină de referință excelentă cu imagini: http://iquilezles.org/www/articles/distfunctions/distfunctions.htm

odată ce ați înțeles cum o distanță la un primitiv lucrări – pune – l într-o cutie-defini o funcție pentru ea, astfel încât să nu aveți nevoie să vă amintiți și scrie codul de fiecare dată. Există o funcție deja definită pentru cercul sdCircle() pe care o puteți găsi în shader. Adăugați orice primitive doriți.

combinarea formelor

acum știm cum să creăm primitive individuale, cum le putem combina pentru a defini o scenă cu mai multe forme?

o modalitate de a face acest lucru este operatorul ‘Uniunii’ – care este definit ca fiind minimul a două distanțe. Cel mai bine este să experimentați codul pentru a obține o înțelegere puternică a acestui lucru, dar intuiția este că SDF dă distanța până la cea mai apropiată suprafață, iar dacă scena are mai multe obiecte doriți Distanța până la cel mai apropiat obiect, care va fi minimul distanțelor față de fiecare obiect.

în cod acest lucru poate arăta după cum urmează:

float sdf(vec2 p){ float d = 1000.0; d = min(d, sdCircle(p, vec2(-0.1, 0.4), 0.15)); d = min(d, sdCircle(p, vec2( 0.5, 0.1), 0.35)); return d;}

în acest fel putem combina compact multe forme. Odată ce acest lucru este înțeles, ar trebui utilizată funcția opU(), care înseamnă ‘operation union’.

aceasta este doar zgârierea suprafeței a ceea ce este posibil. Putem obține amestecuri netede folosind o funcție fantezie soft min-încercați să utilizați opBlend() furnizate. Există multe alte tehnici interesante care pot fi aplicate, cititorul interesat este referit la această introducere extinsă la construirea scenelor cu SDFs: https://www.youtube.com/watch?v=s8nFqwOho-s

exemplu:

trecerea la 3D

sperăm că ați dobândit o înțelegere de bază a modului în care câmpurile de distanță pot fi utilizate pentru a reprezenta datele scenei și cum vom folosi raymarching pentru a găsi puncte de intersecție cu scena. Acum vom începe să lucrăm în trei dimensiuni, unde se întâmplă adevărata magie.

vă recomandăm să salvați shader-ul curent și să începeți unul nou, astfel încât să vă puteți referi mai târziu la vizualizarea 2D.Majoritatea ajutoarelor pot fi copiate în noul shader și făcute să funcționeze în 3D schimbând vec2s cu vec3s.

Ray marching loop

mai degrabă decât să vizualizăm SDF așa cum am făcut în 2D, vom sări direct la redarea scenei. Iată ideea de bază a modului în care vom implementa Ray marching (în pseudo cod):

Main function Evaluate camera Call RenderRayRenderRay function Raymarch to find intersection of ray with scene Shade

acești pași vor fi acum descriși mai detaliat.

aparat foto

vec3 getCameraRayDir(vec2 uv, vec3 camPos, vec3 camTarget){ // Calculate camera's "orthonormal basis", i.e. its transform matrix components vec3 camForward = normalize(camTarget - camPos); vec3 camRight = normalize(cross(vec3(0.0, 1.0, 0.0), camForward)); vec3 camUp = normalize(cross(camForward, camRight)); float fPersp = 2.0; vec3 vDir = normalize(uv.x * camRight + uv.y * camUp + camForward * fPersp); return vDir;}

această funcție calculează mai întâi cele trei axe ale matricei de vizualizare a camerei; vectorii înainte, dreapta și sus.Vectorul înainte este Vectorul normalizat de la poziția camerei până la poziția țintă a aspectului.Vectorul drept se găsește prin traversarea vectorului înainte cu axa mondială în sus.Vectorii înainte și dreapta sunt apoi traversați pentru a obține vectorul camerei în sus.

în cele din urmă, raza camerei este calculată folosind acest cadru, luând un punct în fața camerei și compensând-o în direcțiile din dreapta și în sus ale camerei, folosind coordonatele pixelilor uv.fPersp ne permite să controlăm indirect câmpul vizual al camerei noastre. Vă puteți gândi la această multiplicare ca la mutarea planului apropiat mai aproape și mai departe de cameră. Experimentați cu valori diferite pentru a vedea rezultatul.

definiția scenei

float sdSphere(vec3 p, float r){ return length(p) - r;} float sdf(vec3 pos){ float t = sdSphere(pos-vec3(0.0, 0.0, 10.0), 3.0); return t;}

după cum puteți vedea, am adăugat un sdSphere() care este identic cu sdCircle salvați pentru numărul de componente din punctul nostru de intrare.

Raymarching

Pseudo cod:

castRay for i in step count: sample scene if within threshold return dist return -1

încercați să scrieți acest lucru singur – dacă vă blocați numai atunci aruncați o privire la soluția de mai jos.

Cod Real:

float castRay(vec3 rayOrigin, vec3 rayDir){ float t = 0.0; // Stores current distance along ray for (int i = 0; i < 64; i++) { float res = SDF(rayOrigin + rayDir * t); if (res < (0.0001*t)) { return t; } t += res; } return -1.0;}

vom adăuga acum o funcție render, care va fi în cele din urmă responsabilă pentru umbrirea punctului de intersecție găsit. Pentru moment, cu toate acestea, vă permite să afișați Distanța până la scena pentru a verifica suntem pe drumul cel bun. O vom scala și o vom inversa pentru a vedea mai bine diferențele.

vec3 render(vec3 rayOrigin, vec3 rayDir){ float t = castRay(rayOrigin, rayDir); // Visualize depth vec3 col = vec3(1.0-t*0.075); return col;}

pentru a calcula direcția fiecărei raze, vom dori să transformăm intrarea coordonatelor pixelilor fragCoord din intervalul , , unde w și h sunt lățimea și înălțimea ecranului în pixeli, iar a este raportul de aspect al ecranului. Putem trece apoi valoarea returnată de la acest ajutor în funcția getCameraRayDir pe care am definit-o mai sus pentru a obține direcția razei.

vec2 normalizeScreenCoords(vec2 screenCoord){ vec2 result = 2.0 * (screenCoord/iResolution.xy - 0.5); result.x *= iResolution.x/iResolution.y; // Correct for aspect ratio return result;}

funcția noastră principală de imagine arată după cum urmează:

void mainImage(out vec4 fragColor, vec2 fragCoord){ vec3 camPos = vec3(0, 0, -1); vec3 camTarget = vec3(0, 0, 0); vec2 uv = normalizeScreenCoords(fragCoord); vec3 rayDir = getCameraRayDir(uv, camPos, camTarget); vec3 col = render(camPos, rayDir); fragColor = vec4(col, 1); // Output to screen}

exerciții:

  • experimentați cu numărul de pași și observați cum se schimbă rezultatul.
  • experimentați cu pragul de terminare și observați cum se schimbă rezultatul.

pentru programul complet de lucru, consultați Shadertoy: partea 1a

termen ambiental

pentru a obține o culoare în scenă, vom face mai întâi diferența între obiecte și fundal.

pentru a face acest lucru, putem reveni -1 în castRay pentru a semnala nimic nu a fost lovit. Ne putem ocupa apoi de acest caz în render.

vec3 render(vec3 rayOrigin, vec3 rayDir){ vec3 col; float t = castRay(rayOrigin, rayDir); if (t == -1.0) { // Skybox colour col = vec3(0.30, 0.36, 0.60) - (rayDir.y * 0.7); } else { vec3 objectSurfaceColour = vec3(0.4, 0.8, 0.1); vec3 ambient = vec3(0.02, 0.021, 0.02); col = ambient * objectSurfaceColour; } return col;}

https://www.shadertoy.com/view/4tdBzj

termen difuz

pentru a obține o iluminare mai realistă, să calculăm suprafața normală, astfel încât să putem calcula iluminatul Lambertian de bază.

pentru a calcula normalul, vom calcula gradientul suprafeței în toate cele trei axe.

ceea ce înseamnă acest lucru în practică este eșantionarea SDF de patru ori în plus, fiecare ușor compensată de raza noastră primară.

vec3 calcNormal(vec3 pos){ // Center sample float c = sdf(pos); // Use offset samples to compute gradient / normal vec2 eps_zero = vec2(0.001, 0.0); return normalize(vec3( sdf(pos + eps_zero.xyy), sdf(pos + eps_zero.yxy), sdf(pos + eps_zero.yyx) ) - c);}

o modalitate excelentă de a inspecta normalele este prin afișarea lor ca și cum ar reprezenta culoarea. Așa ar trebui să arate o sferă atunci când afișează normalul scalat și părtinitor (adus de la la , deoarece monitorul dvs. nu poate afișa valori negative ale culorilor)

col = N * vec3(0.5) + vec3(0.5);

acum, că avem un normal, putem lua produsul punct între ea și direcția luminii.

acest lucru ne va spune cât de direct suprafața este orientată spre lumină și, prin urmare, cât de strălucitoare ar trebui să fie.

luăm valoarea maximă a acestei valori cu 0 pentru a împiedica valorile negative să producă efecte nedorite asupra părții întunecate a obiectelor.

// L is vector from surface point to light, N is surface normal. N and L must be normalized!float NoL = max(dot(N, L), 0.0);vec3 LDirectional = vec3(0.9, 0.9, 0.8) * NoL;vec3 LAmbient = vec3(0.03, 0.04, 0.1);vec3 diffuse = col * (LDirectional + LAmbient);

o parte foarte importantă a randării care poate fi ușor trecută cu vederea este corecția gamma. Valorile pixelilor trimise monitorului sunt în spațiul gamma, care este un spațiu neliniar utilizat pentru a maximiza precizia, folosind mai puțini biți în intervale de intensitate la care oamenii sunt mai puțin sensibili.

deoarece monitoarele nu funcționează în spațiu „liniar”, trebuie să compensăm curba lor gamma înainte de a scoate o culoare. Diferența este foarte vizibilă și trebuie întotdeauna corectată. În realitate, nu știm curba gamma pentru un anumit dispozitiv de afișare este, deci întreaga situație cu tehnologia de afișare este o mizerie îngrozitoare (de aici pasul de reglare gamma în multe jocuri), dar o presupunere comună este următoarea curbă gamma:

Constanta 0.4545 este pur și simplu 1.0 / 2.2

col = pow(col, vec3(0.4545)); // Gamma correction

https://www.shadertoy.com/view/4t3fzn

umbre

pentru a calcula umbrele, putem trage o rază începând din punctul în care am intersectat scena și mergând în direcția sursei de lumină.

dacă acest marș de raze ne face să lovim ceva, atunci știm că lumina va fi obstrucționată și astfel acest pixel este în umbră.

float shadow = 0.0;vec3 shadowRayOrigin = pos + N * 0.01;vec3 shadowRayDir = L;IntersectionResult shadowRayIntersection = castRay(shadowRayOrigin, shadowRayDir);if (shadowRayIntersection.mat != -1.0){ shadow = 1.0;}col = mix(col, col*0.2, shadow);

planul de la sol

să adăugăm un plan de la sol, astfel încât să putem vedea umbrele aruncate de sferele noastre mai bine.

componenta w a lui n reprezintă distanța pe care planul o are față de origine.

float sdPlane(vec3 p, vec4 n){ return dot(p, n.xyz) + n.w;}

umbrele moi

umbrele din viața reală nu se opresc imediat, au o cădere, denumită penumbra.

putem modela acest lucru luând mai multe raze din punctul nostru de suprafață, fiecare cu direcții ușor diferite.

putem apoi rezuma rezultatul și media pe numărul de iterații pe care le-am făcut. Acest lucru va face ca marginile umbrei să aibă

unele raze lovite, iar altele ratează, dând un întuneric de 50%.

găsirea oarecum pseudo număr aleatoriu se poate face un număr de moduri, vom folosi următoarele, deși:

float rand(vec2 co){ return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);}

această funcție va returna un număr în intervalul [0, 1). Știm că ieșirea este legată de acest interval, deoarece operația cea mai exterioară este fract, care returnează componenta fracționată a unui număr în virgulă mobilă.

putem folosi apoi acest lucru pentru a calcula raza noastră de umbră după cum urmează:

float shadow = 0.0;float shadowRayCount = 1.0;for (float s = 0.0; s < shadowRayCount; s++){ vec3 shadowRayOrigin = pos + N * 0.01; float r = rand(vec2(rayDir.xy)) * 2.0 - 1.0; vec3 shadowRayDir = L + vec3(1.0 * SHADOW_FALLOFF) * r; IntersectionResult shadowRayIntersection = castRay(shadowRayOrigin, shadowRayDir); if (shadowRayIntersection.mat != -1.0) { shadow += 1.0; }}col = mix(col, col*0.2, shadow/shadowRayCount);

maparea texturilor

în loc să definească o singură culoare de suprafață (sau altă caracteristică) uniform pe întreaga suprafață, se pot defini modele care să se aplice suprafeței folosind texturi.Vom acoperi trei moduri de a realiza acest lucru.

maparea texturii 3D

există texturi de volum ușor accesibile în shadertoy care pot fi atribuite unui canal. Încercați să eșantionați una dintre aceste texturi folosind poziția 3D a punctului de suprafață:

// assign a 3D noise texture to iChannel0 and then sample based on world positionfloat textureFreq = 0.5;vec3 surfaceCol = texture(iChannel0, textureFreq * surfacePos).xyz;

o modalitate de a proba zgomotul este de a adăuga împreună mai multe scale, folosind ceva de genul următor:

// assign a 3D noise texture to iChannel0 and then sample based on world positionfloat textureFreq = 0.5;vec3 surfaceCol = 0.5 * texture(iChannel0, 1.0 * textureFreq * surfacePos).xyz + 0.25 * texture(iChannel0, 2.0 * textureFreq * surfacePos).xyz + 0.125 * texture(iChannel0, 4.0 * textureFreq * surfacePos).xyz + 0.0625 * texture(iChannel0, 8.0 * textureFreq * surfacePos).xyz ;

constantele / greutățile de mai sus sunt de obicei utilizate pentru un zgomot fractal, dar pot lua orice valori dorite. Încercați să experimentați cu greutăți/Cântare / culori și să vedeți ce efecte interesante puteți obține.

încercați să animați obiectul folosind iTime și să observați cum se comportă textura volumului. Se poate schimba acest comportament?

maparea texturii 2D

aplicarea unei texturi 2D este o problemă interesantă-cum să proiectați textura pe suprafață? În grafica 3D normală, fiecare triunghi dintr-un obiect are unul sau mai multe UV-uri atribuite care furnizează coordonatele regiunii texturii care ar trebui să fie mapate la triunghi (maparea texturii). În cazul nostru, nu avem UV-uri furnizate, așa că trebuie să ne dăm seama cum să eșantionăm textura.

o abordare este de a proba textura folosind o proiecție mondială de sus în jos, prin eșantionarea texturii pe baza coordonatelor X & Z:

// top down projectionfloat textureFreq = 0.5;vec2 uv = textureFreq * surfacePos.xz; // sample texturevec3 surfaceCol = texture2D(iChannel0, uv).xyz;

ce limitări vedeți cu această abordare?

mapare Triplanară

o modalitate mai avansată de a mapa texturile este de a face 3 proiecții din axele primare și apoi de a amesteca rezultatul folosind maparea triplanară. Scopul amestecului este de a alege cea mai bună textură pentru fiecare punct de pe suprafață. O posibilitate este de a defini greutățile amestecului pe baza alinierii suprafeței normale cu fiecare axă mondială. O suprafață care se confruntă față cu una dintre axe va primi o greutate mare de amestec:

vec3 triplanarMap(vec3 surfacePos, vec3 normal){ // Take projections along 3 axes, sample texture values from each projection, and stack into a matrix mat3 triMapSamples = mat3( texture(iChannel0, surfacePos.yz).rgb, texture(iChannel0, surfacePos.xz).rgb, texture(iChannel0, surfacePos.xy).rgb ); // Weight three samples by absolute value of normal components return triMapSamples * abs(normal);}

ce limitări vedeți cu această abordare?

materiale

împreună cu distanța ne întoarcem de la funcția castRay, putem returna, de asemenea, un indice care reprezintă materialul obiectului lovit. Putem folosi acest index pentru a colora obiectele în consecință.

operatorii noștri vor trebui să ia vec2s, mai degrabă decât flotoare, și compara prima componentă a fiecăruia.

acum, la definirea scenei noastre vom specifica, de asemenea, un material pentru fiecare primitiv ca componenta y a unui vec2:

vec2 res = vec2(sdSphere(pos-vec3(3,-2.5,10), 2.5), 0.1);res = opU(res, vec2(sdSphere(pos-vec3(-3, -2.5, 10), 2.5), 2.0));res = opU(res, vec2(sdSphere(pos-vec3(0, 2.5, 10), 2.5), 5.0));return res;

apoi putem multiplica acest indice de material cu unele valori din funcția de randare pentru a obține culori diferite pentru fiecare obiect. Încercați valori diferite.

col = vec3(0.18*m, 0.6-0.05*m, 0.2)if (m == 2.0){ col *= triplanarMap(pos, N, 0.6);}

să colorăm planul de la sol folosind un model de șah. Am luat această fantezie analitic anti-aliased funcție caseta de selectare de pe site-ul Inigo Quilez.

float checkers(vec2 p){ vec2 w = fwidth(p) + 0.001; vec2 i = 2.0*(abs(fract((p-0.5*w)*0.5)-0.5)-abs(fract((p+0.5*w)*0.5)-0.5))/w; return 0.5 - 0.5*i.x*i.y;}

vom trece în componentele xz ale poziției noastre avion pentru a obține modelul să se repete în aceste dimensiuni.

https://www.shadertoy.com/view/Xl3fzn

ceață

putem adăuga acum ceață la scenă în funcție de cât de departe a avut loc fiecare intersecție de la cameră.

vedeți dacă puteți obține ceva similar cu următoarele:

https://www.shadertoy.com/view/Xtcfzn

Shape & material blendingpentru a evita cutele dure date de operatorul min, putem folosi un operator mai sofisticat care îmbină formele fără probleme.

// polynomial smooth min (k = 0.1);float sminCubic(float a, float b, float k){ float h = max(k-abs(a-b), 0.0); return min(a, b) - h*h*h/(6.0*k*k);} vec2 opBlend(vec2 d1, vec2 d2){ float k = 2.0; float d = sminCubic(d1.x, d2.x, k); float m = mix(d1.y, d2.y, clamp(d1.x-d,0.0,1.0)); return vec2(d, m);}

Anti-aliasing

prin eșantionarea scenei de mai multe ori cu vectori de direcție ușor compensați, putem obține o valoare netezită care evită aliasing.

am scos calculul culorii scenei la propria funcție pentru a face apelarea în buclă mai clară.

float AA_size = 2.0;float count = 0.0;for (float aaY = 0.0; aaY < AA_size; aaY++){ for (float aaX = 0.0; aaX < AA_size; aaX++) { fragColor += getSceneColor(fragCoord + vec2(aaX, aaY) / AA_size); count += 1.0; }}fragColor /= count;

optimizarea numărului de pași

dacă vizualizăm câți pași facem pentru fiecare pixel în roșu, putem vedea clar că razele care nu lovesc nimic sunt responsabile pentru majoritatea iterațiilor noastre.

acest lucru poate oferi un impuls semnificativ de performanță pentru anumite scene.

if (t > drawDist) return backgroundColor;

forma & interpolarea materialului

putem interpola între două forme folosind funcția mix și folosind iTime pentru a modula în timp.

vec2 shapeA = vec2(sdBox(pos-vec3(6.5, -3.0, 8), vec3(1.5)), 1.5);vec2 shapeB = vec2(sdSphere(pos-vec3(6.5, -3.0, 8), 1.5), 3.0);res = opU(res, mix(shapeA, shapeB, sin(iTime)*0.5+0.5));

repetarea domeniului

este destul de ușor să repetați o formă folosind un câmp de distanță semnat, în esență trebuie doar să modulo poziția de intrare într-una sau mai multe dimensiuni.

această tehnică poate fi utilizată, de exemplu, pentru a repeta o coloană de mai multe ori fără a crește dimensiunea reprezentării scenei.

aici am repetat toate cele trei componente ale poziției de intrare, apoi am folosit operatorul de scădere ( max() ) pentru a limita repetarea la o cutie de încadrare.

un gotcha este că aveți nevoie pentru a scădea jumătate din valoarea pe care sunt modularea de pentru a centra repetarea pe forma ta ca să nu-l taie în jumătate.

float repeat(float d, float domain){ return mod(d, domain)-domain/2.0;}

efecte post-procesare

vinietă

prin întunecarea pixelilor care sunt mai departe de centrul ecranului putem obține un efect de vinietă simplu.

Contrast

valorile mai întunecate și mai deschise pot fi accentuate, determinând creșterea intervalului dinamic perceput împreună cu intensitatea imaginii.

col = smoothstep(0.0,1.0,col);

„ocluzie ambientală”

dacă luăm inversul imaginii prezentate mai sus (în optimizări), putem obține un efect ciudat asemănător AO.

col *= (1.0-vec3(steps/maxSteps));

ad infinitum

după cum puteți vedea, multe efecte post-procesare pot fi implementate trivial; jucați-vă cu diferite funcții și vedeți ce alte efecte puteți crea.

www.shadertoy.com/view/MtdBzs

ce urmează?

tocmai am acoperit elementele de bază aici; există mult mai multe de explorat în acest domeniu, cum ar fi:

  • împrăștierea subterană
  • ocluzie ambientală
  • primitive animate
  • funcții primitive de deformare (răsucire ,îndoire, …)
  • transparență(refracție, caustică,…)
  • optimizări (ierarhii de volum de delimitare)

răsfoiți ShaderToy pentru a obține unele inspirație despre ceea ce se poate face și poke prin diferite shadere pentru a vedea modul în care sunt puse în aplicare diferite efecte. Multe shadere au variabile pe care le puteți modifica și vedea instantaneu efectele (alt-enter este comanda rapidă pentru a compila!).

de asemenea, dați referințelor o citire dacă sunteți interesat să aflați mai multe!

Vă mulțumim pentru lectură! Asigurați-vă că ne trimiteți shaderele dvs. cool! Dacă aveți orice feedback cu privire la cursul ne-ar plăcea, de asemenea, să-l aud!

contactați-ne pe twitter @ liqwidice & @ hdb1

electric Square angajează!

lectură recomandată:

funcții SDF: http://jamie-wong.com/2016/07/15/ray-marching-signed-distance-functions/

Claybook demo: https://www.youtube.com/watch?v=Xpf7Ua3UqOA

Ray Tracing într-un Weekend: http://in1weekend.blogspot.com/2016/01/ray-tracing-in-one-weekend.html

Biblia De redare bazată fizic, PBRT: https://www.pbrt.org/

Lasă un răspuns

Adresa ta de email nu va fi publicată.