elektrisk firkant / raymarching-værksted

bragt til dig af elektrisk firkant

oprettet og præsenteret af Aj uger & Huskåle

oversigt

gengivelse af et billede involverer bestemmelse af farven på hvert billedbillede i billedet, hvilket kræver at finde ud af, hvilken overflade der ligger bag billedpunktet i verden, og derefter ‘skygge’ det for at beregne en endelig farve.

nuværende generation af GPU ‘ er tager trekantmasker som input, rasteriserer dem i billedpunkter (kaldet Fragmenter, før de trækkes til et display), og skygger dem derefter for at beregne deres bidrag til billedet. Mens denne pipeline i øjeblikket er allestedsnærværende, er den også kompliceret og ikke nødvendigvis den bedste måde at lære grafik på.

en alternativ tilgang er at kaste en stråle gennem hvert punkt og krydse den med overfladerne i scenen og derefter beregne skyggen.

dette kursus introducerer en teknik til raycasting gennem ‘distance fields’. Et afstandsfelt er en funktion, der returnerer, hvor tæt et givet punkt er på den nærmeste overflade i scenen. Denne afstand definerer radius for en kugle med tomt rum omkring hvert punkt. Signerede afstandsfelter (SDF ‘er) er afstandsfelter, der er defineret både inden for og uden for objekter; hvis den forespurgte position er’ inde i ‘ en overflade, rapporteres dens afstand som negativ, ellers vil den være positiv.

hvad er muligt med ray marching?

spillet ‘Claybook’ bruger udelukkende afstandsfelter til at repræsentere scenen. Dette giver det en masse interessante muligheder, som helt dynamiske overfladetopologier og form morphing. Disse effekter ville være meget vanskelige at opnå med trekantmasker. Andre fordele omfatter nemme at implementere og bløde skygger af høj kvalitet og omgivende okklusion.

https://www.claybookgame.com/

følgende billede blev også gengivet i realtid ved hjælp af de teknikker, vi dækker i dag (plus mange fancy teknikker, som vi ikke har tid til at dykke ind i).

du kan køre det live i din bro. ser her: https://www.shadertoy.com/view/ld3Gz2

ved at bruge et SDF (signeret afstandsfelt) behøvede geometrien for denne scene ikke at blive oprettet i en DCC som Maya, men er i stedet repræsenteret helt parametrisk. Dette gør det trivielt at animere formen ved blot at variere input til scenekortfunktionen.

andre grafiske effekter gøres enklere ved raymarching sammenlignet med de traditionelle rasteriseringsalternativer. Undergrundsspredning kræver for eksempel blot at sende et par ekstra stråler ind i overfladen for at se, hvor tyk den er. Ambient okklusion, anti-aliasing, og dybdeskarphed er tre andre teknikker, som kræver blot et par ekstra linjer og alligevel i høj grad forbedre billedkvaliteten.

Raymarching afstandsfelter

vi marcherer langs hver stråle og ser efter et kryds med en overflade i scenen. En måde at gøre dette på ville være at starte ved stråleoprindelsen (på kameraplanet) og tage ensartede trin langs strålen og evaluere afstandsfeltet på hvert punkt. Når afstanden til scenen er mindre end en tærskelværdi, ved vi, at vi har ramt en overflade, og vi kan derfor afslutte raymarch og skygge det punkt.

en mere effektiv tilgang er at bruge den afstand, der returneres af SDF, til at bestemme den næste trinstørrelse. Som nævnt ovenfor kan afstanden, der returneres af en SDF, betragtes som radius for en kugle med tomt rum omkring inputpunktet. Det er derfor sikkert at træde med dette beløb langs strålen, fordi vi ved, at vi ikke vil passere gennem nogen overflader.

i den følgende 2D-repræsentation af raymarching er hver cirkels centrum, hvor scenen blev samplet fra. Strålen blev derefter marcheret langs denne afstand (strækker sig til cirkelens radius) og derefter resamplet.

som du kan se, giver prøveudtagning af SDF dig ikke det nøjagtige skæringspunkt for din stråle, men snarere en minimumsafstand, du kan rejse uden at passere gennem en overflade.

når denne afstand er under en bestemt tærskel, afslutter raymarch billedpunktet kan skygges baseret på egenskaberne af overfladen krydset med.

spil rundt med denne shader i din bro. ser her: (klik og træk i billedet for at indstille stråleretningen) https://www.shadertoy.com/view/lslXD8

sammenligning med strålesporing

på dette tidspunkt kan man spørge, hvorfor vi ikke bare beregner krydset med scenen direkte ved hjælp af analytisk matematik ved hjælp af en teknik kaldet strålesporing. Sådan fungerer offline gengivelser typisk-alle trekanter i scenen indekseres i en slags rumlig datastruktur som et afgrænsende Volumenhierarki (BVH) eller kD-træ, som muliggør effektiv krydsning af trekanter beliggende langs en stråle.

vi raymarch afstandsfelter i stedet fordi:

  • det er meget simpelt at implementere ray casting-rutinen
  • vi undgår al kompleksiteten ved implementering af ray – triangle-kryds og BVH-datastrukturer
  • vi behøver ikke at oprette den eksplicitte scenerepræsentation-trekantmasker, teks coords, farver osv
  • vi drager fordel af en række nyttige funktioner i afstandsfelter, hvoraf nogle er nævnt ovenfor

når det er sagt ovenfor, er der nogle elegante/enkle indgangspunkter i strålesporing. Strålesporingen i en uges gratis bog (og efterfølgende kapitler) anbefales meget stærkt og er vigtig læsning for alle, der er interesseret i grafik.

lad os begynde!

ShaderToy

ShaderToy er en shader skabelse hjemmeside og platform til at gennemse, dele og diskutere shaders.

mens du kan hoppe lige ind og begynde at skrive en ny shader uden at oprette en konto, er dette farligt, da du nemt kan miste arbejde, hvis der er forbindelsesproblemer, eller hvis du hænger GPU ‘ en (let udført ved f.eks.Derfor anbefaler vi stærkt at oprette en konto (det er hurtigt/nemt/gratis) ved at gå her: https://www.shadertoy.com/signin og gemme regelmæssigt.

For en ShaderToy oversigt og kom godt i gang guide, anbefaler vi at følge en tutorial som denne fra @The_ArtOfCode: https://www.youtube.com/watch?v=u5HAYVHsasc. Det grundlæggende her er nødvendigt for at følge resten af værkstedet.

2D SDF demo

vi leverer en enkel ramme til at definere og visualisere 2D signerede afstandsfelter.

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

før du definerer afstandsfeltet, vil resultatet være helt hvidt. Målet med dette afsnit er at designe en SDF, der giver den ønskede sceneform (hvid kontur). I kode beregnes denne afstand af funktionen sdf(), som får en 2D-position i rummet som input. De begreber, du lærer her, generaliserer direkte til 3D-plads og giver dig mulighed for at modellere en 3D-scene.

start simpelt-Prøv først at bare bruge K eller Y-komponenten i punktet p og observer resultatet:

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

resultatet skal se ud som følger:

grøn betegner ‘udvendige’ overflader, rød betegner ‘indvendige’ overflader, den hvide linje afgrænser selve overfladen, og skyggen i de indvendige/udvendige områder illustrerer afstand iso-linjer – linjer på faste afstande. I 2D modellerer denne SDF en vandret linje i 2D på y=0. Hvilken slags geometrisk primitiv ville dette repræsentere i 3D?

en anden god ting at prøve er at bruge afstande, for eksempel: return length(p);. Denne operatør returnerer størrelsen af vektoren, og i dette tilfælde giver den os det aktuelle punkts Afstand til oprindelsen.

et punkt er ikke en meget interessant ting at gengive, da et punkt er uendeligt, og vores stråler vil altid savne det!Vi kan give punktet noget område ved at trække den ønskede radius fra afstanden: return length(p) - 0.25;.Vi kan også ændre inputpunktet, inden vi tager dens størrelse: length(p - vec2(0.0, 0.2)) - 0.25;.Hvilken effekt har dette på formen?Hvilke værdier kan funktionen vende tilbage for punkter ‘inde’ i cirklen?

Tillykke – du har lige modelleret en cirkel ved hjælp af matematik :). Dette vil trivielt strække sig til 3D, i hvilket tilfælde Det modellerer en kugle. Kontrast denne scene repræsentation til andre ‘eksplicitte’ scener repræsentationer såsom trekant masker eller NURBS overflader. Vi skabte en kugle på få minutter med en enkelt kodelinje, og vores kode kortlægges direkte til en matematisk definition for en sfære – ‘sættet med alle punkter, der er lige langt fra et midtpunkt’.

for andre typer primitiver er afstandsfunktionerne ligeledes elegante. Ik lavet en stor reference side med billeder: http://iquilezles.org/www/articles/distfunctions/distfunctions.htm

når du først har forstået, hvordan en afstand til en primitiv fungerer – læg den i en boks – Definer en funktion til den, så du ikke behøver at huske og skrive koden hver gang. Der er en funktion, der allerede er defineret for cirklen sdCircle(), som du kan finde i skyggen. Tilføj eventuelle primitiver, du ønsker.

kombination af figurer

nu ved vi, hvordan man opretter individuelle primitiver, hvordan kan vi kombinere dem for at definere en scene med flere former?

en måde at gøre dette på er ‘union’ – operatøren-som er defineret som minimum af to afstande. Det er bedst at eksperimentere med koden for at få en stærk forståelse af dette, men intuitionen er, at SDF Giver afstanden til nærmeste overflade, og hvis scenen har flere objekter, vil du have afstanden til det nærmeste objekt, som vil være minimum af afstandene til hvert objekt.

i kode kan dette se ud som følger:

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;}

på denne måde kan vi kompakt kombinere mange former. Når dette er forstået, skal funktionen opU() bruges, som står for ‘operation union’.

dette skraber kun overfladen af, hvad der er muligt. Vi kan få glatte blandinger ved hjælp af en fancy blød min – funktion-prøv at bruge den medfølgende opBlend(). Der er mange andre interessante teknikker, der kan anvendes, den interesserede læser henvises til denne udvidede introduktion til byggescener med SDFs: https://www.youtube.com/watch?v=s8nFqwOho-s

eksempel:

overgang til 3D

forhåbentlig har du fået en grundlæggende forståelse af, hvordan afstandsfelter kan bruges til at repræsentere scenedata, og hvordan vi bruger raymarching til at finde skæringspunkter med scenen. Vi skal nu begynde at arbejde i tre dimensioner, hvor den virkelige magi sker.

vi anbefaler at gemme din nuværende shader og starte en ny, så du kan henvise Tilbage til din 2D-visualisering senere.De fleste af hjælperne kan kopieres til din nye shader og gøres til at arbejde i 3D ved at bytte vec2s med vec3s.

Ray marching loop

i stedet for at visualisere SDF som vi gjorde i 2d, vil vi hoppe lige ind for at gøre scenen. Her er den grundlæggende ide om, hvordan vi implementerer ray marching (i pseudokode):

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

disse trin vil nu hver blive beskrevet mere detaljeret.

kamera

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;}

denne funktion beregner først de tre akser i kameraets ‘Vis’ – matrice; vektorerne fremad, højre og op.Den forreste vektor er den normaliserede vektor fra kamerapositionen til lookmålpositionen.Den rigtige vektor findes ved at krydse den forreste vektor med verden op akse.De forreste og højre vektorer krydses derefter for at få kameraet op vektor.

endelig beregnes kamerastrålen ved hjælp af denne ramme ved at tage et punkt foran kameraet og modregne det i kameraets højre og opadgående retning ved hjælp af billedkoordinaterne uv.fPersp giver os mulighed for indirekte at kontrollere vores kameras synsfelt. Du kan tænke på denne multiplikation som at flytte det nærmeste plan tættere og længere væk fra kameraet. Eksperimenter med forskellige værdier for at se resultatet.

Scenedefinition

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;}

som du kan se, har vi tilføjet en sdSphere(), som er identisk med sdCircle Gem for antallet af komponenter i vores inputpunkt.

Raymarching

pseudokode:

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

prøv at skrive dette selv – hvis du kun sidder fast, så kig på løsningen nedenfor.

rigtig kode:

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;}

vi tilføjer nu en render funktion, som i sidste ende vil være ansvarlig for at skygge det fundne skæringspunkt. For nu kan vi dog vise afstanden til scenen for at kontrollere, at vi er på sporet. Vi skalerer og inverterer det for bedre at se forskellene.

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

for at beregne hver stråles retning, vil vi gerne omdanne punktkoordinatindgangen fragCoord fra området , , hvor w og h er bredden og højden på skærmen i billedpunkter, og a er skærmformatet. Vi kan derefter overføre den værdi, der returneres fra denne hjælper, til getCameraRayDir – funktionen, vi definerede ovenfor for at få stråleretningen.

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;}

vores vigtigste Billedfunktion ser derefter ud som følger:

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}

øvelser:

  • eksperimenter med trinoptællingen, og observer, hvordan resultatet ændres.
  • eksperimenter med termineringstærsklen og observer, hvordan resultatet ændres.

for fuldt arbejdsprogram, se Shadertoy: del 1a

Ambient term

for at få lidt farve ind i scenen skal vi først skelne mellem objekter og baggrunden.

for at gøre dette kan vi vende tilbage -1 i castRay for at signalere, at intet blev ramt. Vi kan derefter håndtere denne sag i 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

diffus term

for at få mere realistisk belysning lad os beregne overfladen normal, så vi kan beregne grundlæggende Lambertian belysning.

for at beregne det normale skal vi beregne overfladens gradient i alle tre akser.

hvad dette betyder i praksis er prøveudtagning af SDF fire ekstra gange, hver lidt forskudt fra vores primære stråle.

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);}

en god måde at inspicere normaler på er ved at vise dem som om de repræsenterede farve. Sådan skal en kugle se ud, når den viser sin skalerede og partiske normale (bragt fra til , da din skærm ikke kan vise negative farveværdier)

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

nu hvor vi har en normal, kan vi tage prikproduktet mellem det og lysretningen.

dette fortæller os, hvor direkte overfladen vender mod lyset, og derfor hvor lys den skal være.

vi tager maks.af denne værdi med 0 for at forhindre negative værdier i at give uønskede effekter på den mørke side af objekter.

// 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);

en meget vigtig del af gengivelsen, som let kan overses, er gammakorrektion. Billedværdier, der sendes til skærmen, er i gamma-rum, som er et ikke-lineært rum, der bruges til at maksimere præcisionen ved at bruge mindre bits i intensitetsområder, som mennesker er mindre følsomme over for.

da skærme ikke fungerer i “lineært” rum, er vi nødt til at kompensere for deres gammakurve, inden vi udsender en farve. Forskellen er meget mærkbar og bør altid korrigeres for. I virkeligheden ved vi ikke, at gamma kurven for en bestemt displayenhed er, så hele situationen med displayteknologi er et forfærdeligt rod (dermed gamma tuning trin i mange spil), men en fælles antagelse er følgende gamma kurve:

den konstante 0.4545 er simpelthen 1.0 / 2.2

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

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

skygger

for at beregne skygger kan vi skyde en stråle, der starter ved det punkt, vi krydsede scenen og går i retning af lyskilden.

hvis denne strålemarsch resulterer i, at vi rammer noget, så ved vi, at lyset også vil blive blokeret, og så er dette punkt i skygge.

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);

jordplan

lad os tilføje et jordplan, så vi bedre kan se skygger kastet af vores kugler.

h-komponenten i n repræsenterer den afstand, flyet er fra oprindelsen.

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

bløde skygger

skygger i det virkelige liv stopper ikke straks, de har noget fald, kaldet en penumbra.

vi kan modellere dette ved at tage marchere flere stråler fra vores overfladepunkt, hver med lidt forskellige retninger.

vi kan derefter opsummere resultatet og gennemsnittet over antallet af iterationer, vi gjorde. Dette vil medføre, at skyggens kanter har

nogle stråler rammer, og andre savner, hvilket giver et 50% mørke.

at finde noget pseudo tilfældigt tal kan gøres på en række måder, vi bruger dog følgende:

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

denne funktion returnerer et tal i området [0, 1). Vi ved, at output er bundet til dette interval, fordi den yderste operation er fract, som returnerer fraktionskomponenten i et flydende punktnummer.

vi kan derefter bruge dette til at beregne vores skyggestråle som følger:

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);

Teksturkortlægning

i stedet for at definere en enkelt overfladefarve (eller anden egenskab) ensartet over hele overfladen, kan man definere mønstre, der skal påføres overfladen ved hjælp af teksturer.Vi dækker tre måder at opnå dette på.

3D tekstur kortlægning

der er volumen teksturer let tilgængelige i shadertoy, der kan tildeles til en kanal. Prøv at prøve en af disse teksturer ved hjælp af overfladepunktets 3D-position:

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

en måde at prøve støj på er at tilføje flere skalaer ved hjælp af noget som følgende:

// 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 ;

konstanterne / vægtene ovenfor bruges typisk til en fraktal støj, men de kan tage de ønskede værdier. Prøv at eksperimentere med vægte/skalaer/farver og se, hvilke interessante effekter du kan opnå.

prøv at animere dit objekt ved hjælp af iTime og observere, hvordan lydstyrketeksturen opfører sig. Kan denne adfærd ændres?

2D Teksturkortlægning

anvendelse af en 2D – struktur er et interessant problem-hvordan projiceres tekstur på overfladen? I normal 3D-grafik har hver trekant i et objekt en eller flere UV ‘ er tildelt, som giver koordinaterne for teksturområdet, som skal mig kortlægges til trekanten (teksturkortlægning). I vores tilfælde har vi ikke UV ‘ er leveret, så vi er nødt til at finde ud af, hvordan vi prøver strukturen.

en tilgang er at prøve tekstur ved hjælp af en top ned verdensprojektion ved at prøve tekstur baseret på & å koordinater:

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

hvilke begrænsninger ser du med denne tilgang?

Triplanar mapping

en mere avanceret måde at kortlægge teksturer på er at lave 3 fremskrivninger fra de primære akser og derefter blande resultatet ved hjælp af triplanar mapping. Målet med blandingen er at vælge den bedste tekstur for hvert punkt på overfladen. En mulighed er at definere blandingsvægtene baseret på justeringen af overfladen normal med hver verdensakse. En overflade, der vender foran med en af akserne, får en stor blandingsvægt:

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);}

hvilke begrænsninger ser du med denne tilgang?

materialer

sammen med den afstand, vi vender tilbage fra castRay-funktionen, kan vi også returnere et indeks, der repræsenterer materialet i objektet hit. Vi kan bruge dette indeks til at farve objekter i overensstemmelse hermed.

vores operatører bliver nødt til at tage vec2s snarere end flyder, og sammenligne den første komponent af hver.

når vi definerer vores scene, specificerer vi også et materiale til hver primitiv som y-komponenten i en 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;

vi kan derefter multiplicere dette materialeindeks med nogle værdier i gengivelsesfunktionen for at få forskellige farver for hvert objekt. Prøv forskellige værdier ud.

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

lad os farve jordplanet ved hjælp af et skakternet mønster. Jeg har taget denne fancy analytisk-anti-aliased checkboksfunktion fra Inigo’ s hjemmeside.

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;}

vi passerer komponenterne i vores planposition for at få mønsteret til at gentage i disse dimensioner.

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

tåge

vi kan nu tilføje tåge til scenen baseret på hvor langt hvert kryds opstod fra kameraet.

se om du kan få noget, der ligner følgende:

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

form & materialeblandingfor at undgå den hårde fold, der er givet af min-operatøren, kan vi bruge en mere sofistikeret operatør, der blander formerne glat.

// 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

ved at prøve scenen mange gange med lidt forskudte kameraretningsvektorer, kan vi få en udjævnet værdi, der undgår aliasing.

jeg har bragt scenefarveberegningen ud til sin egen funktion for at gøre det lettere at kalde det i løkken.

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;

optimering af trinoptælling

hvis vi visualiserer, hvor mange trin vi tager for hvert punkt i rødt, kan vi tydeligt se, at de stråler, der ikke rammer noget, er ansvarlige for de fleste af vores iterationer.

dette kan give en betydelig ydeevne boost for visse scener.

if (t > drawDist) return backgroundColor;

form & materialeinterpolering

vi kan interpolere mellem to former ved hjælp af blandingsfunktionen og bruge iTime til at modulere over tid.

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));

domæne gentagelse

det er ret nemt at gentage en figur ved hjælp af et signeret afstandsfelt, i det væsentlige skal du bare modulo inputpositionen i en eller flere dimensioner.

denne teknik kan f.eks. bruges til at gentage en kolonne flere gange uden at øge scenens repræsentationsstørrelse.

her har jeg gentaget alle tre komponenter i inputpositionen og derefter brugt subtraktionsoperatoren ( maks.

en gotcha er, at du skal trække halvdelen af den værdi, du modulerer med, for at centrere gentagelsen på din form for ikke at skære den i halvdelen.

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

Efterbehandlingseffekter

vignet

ved mørkere billedpunkter, der er længere væk fra midten af skærmen, kan vi få en simpel vigneteffekt.

kontrast

mørkere og lysere værdier kan fremhæves, hvilket får det opfattede dynamiske område til at stige sammen med billedets intensitet.

col = smoothstep(0.0,1.0,col);

“Ambient occlusion”

hvis vi tager det inverse af billedet vist ovenfor (i optimeringer), kan vi få en underlig AO-lignende effekt.

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

ad infinitum

som du kan se, kan mange efterbehandlingseffekter implementeres trivielt; leg rundt med forskellige funktioner og se, hvilke andre effekter du kan oprette.

www.shadertoy.com/view/MtdBzs

Hvad er det næste?

vi har lige dækket det grundlæggende her; der er meget mere at udforske på dette område, såsom:

  • undergrundsspredning
  • omgivende okklusion
  • animerede primitiver
  • Primitive vridningsfunktioner (vridning, bøjning, …)
  • gennemsigtighed (brydning, kaustik, …)
  • optimeringer (afgrænsende volumenhierarkier)

Gennemse ShaderToy for at få inspiration til, hvad der kan gøres, og stikke gennem forskellige shaders for at se, hvordan forskellige effekter implementeres. Mange shaders har variabler, som du kan justere og straks se virkningerne af (alt-enter er genvejen til at kompilere!).

giv også referencerne en gennemlæsning, hvis du er interesseret i at lære mere!

tak for læsning! Sørg for at sende os dine seje shaders! Hvis du har feedback på kurset, vil vi også meget gerne høre det!

Kontakt os på Facebook & @hdb1

elektrisk firkant ansætter!

Anbefalet læsning:

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

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

Ray Tracing på en uge: http://in1weekend.blogspot.com/2016/01/ray-tracing-in-one-weekend.html

fysisk-baserede rendering Bibelen, PBRT: https://www.pbrt.org/

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.