electricsquare / raymarching-workshop

Gebracht aan u door Elektrische Plein

Gemaakt en gepresenteerd door AJ Weken & Huw Bowles

Overzicht

het Renderen van een afbeelding gaat om het bepalen van de kleur van elke pixel in het beeld, dat vereist uitzoeken hoe het oppervlak ligt achter de pixel in de wereld, en dan ‘shading’ het berekenen van een definitieve kleur.

GPU ‘ s van de huidige generatie nemen driehoekige mazen als invoer, raster ze in pixels (fragmenten genoemd voordat ze naar een display worden getrokken), en schaduw ze vervolgens om hun bijdrage aan de afbeelding te berekenen. Hoewel deze pijplijn momenteel alomtegenwoordig is, is het ook ingewikkeld en niet noodzakelijk de beste manier om graphics te leren.

een alternatieve benadering is om een straal door elke pixel te werpen en deze te snijden met de oppervlakken in de scène, en vervolgens de arcering te berekenen.

deze cursus introduceert een techniek voor raycasting door “afstandsvelden”. Een afstandsveld is een functie die aangeeft hoe dicht een bepaald punt is bij het dichtstbijzijnde oppervlak in de scène. Deze afstand definieert de straal van een bol met lege ruimte rond elk punt. Signed distance fields (SDF ‘s) zijn afstandsvelden die zowel binnen als buiten objecten worden gedefinieerd; als de opgevraagde positie’ binnen ‘ een oppervlak is, wordt de afstand als negatief gerapporteerd, anders is deze positief.

Wat is mogelijk met ray marching?

het spel ‘Claybook’ gebruikt alleen afstandsvelden om de scène weer te geven. Dit biedt het een heleboel interessante mogelijkheden, zoals volledig dynamische oppervlaktetopologieën en vorm morphing. Deze effecten zouden zeer moeilijk te bereiken zijn met driehoeken mazen. Andere voordelen zijn eenvoudig te implementeren en hoogwaardige zachte schaduwen en omringende occlusie.

https://www.claybookgame.com/

het volgende beeld werd ook weergegeven in real-time met behulp van de technieken die we vandaag behandelen (plus vele mooie technieken die we geen tijd hebben om in te duiken).

u kunt het live uitvoeren in uw browser hier: https://www.shadertoy.com/view/ld3Gz2

door gebruik te maken van een SDF (signed distance field) hoefde de geometrie voor deze scène niet in een DCC zoals Maya te worden gemaakt, maar is in plaats daarvan volledig parametrisch weergegeven. Dit maakt het triviaal om de vorm te animeren door simpelweg de ingangen van de scene mapping-functie te variëren.

andere grafische effecten worden eenvoudiger gemaakt door raymarching in vergelijking met de traditionele rasterisatie alternatieven. Ondergrondverstrooiing, bijvoorbeeld, vereist gewoon het sturen van een paar extra stralen in het oppervlak om te zien hoe dik het is. Ambient occlusion, anti-aliasing en scherptediepte zijn drie andere technieken die slechts een paar extra regels nodig hebben en toch de beeldkwaliteit sterk verbeteren.

Raymarching distance fields

we marcheren langs elke straal en zoeken naar een snijpunt met een oppervlak in de scène. Een manier om dit te doen zou zijn om te beginnen bij de straal oorsprong (op de camera vliegtuig), en neem uniforme stappen langs de straal, het evalueren van de afstand veld op elk punt. Als de afstand tot de scène kleiner is dan een drempelwaarde, weten we dat we een oppervlak hebben geraakt en kunnen we daarom de raymarch uitschakelen en die pixel afschermen.

een efficiëntere aanpak is het gebruik van de afstand die door de SDF wordt geretourneerd om de grootte van de volgende stap te bepalen. Zoals hierboven vermeld, kan de afstand die door een SDF wordt geretourneerd worden beschouwd als de straal van een bol met lege ruimte rond het invoerpunt. Het is daarom veilig om te stappen door deze hoeveelheid langs de straal omdat we weten dat we niet zullen passeren door alle oppervlakken.

in de volgende 2D-weergave van raymarching is het centrum van elke cirkel waar de scène werd bemonsterd. De straal werd vervolgens langs die afstand (die zich uitstrekt tot de straal van de cirkel), en vervolgens opnieuw bemonsterd.

zoals u kunt zien, geeft het bemonsteren van de SDF u niet het exacte snijpunt van uw straal, maar eerder een minimale afstand die u kunt afleggen zonder door een oppervlak te gaan.

zodra deze afstand onder een bepaalde drempel ligt, kan het raymarch-einde van de pixel worden gearceerd op basis van de eigenschappen van het snijvlak.

speel hier met deze shader in je browser: (klik en sleep in de afbeelding om de straalrichting in te stellen) https://www.shadertoy.com/view/lslXD8

vergelijking met ray tracing

op dit punt kan men zich afvragen waarom we niet gewoon berekenen de kruising met de scène direct met behulp van analytische wiskunde, met behulp van een techniek aangeduid als Ray Tracing. Dit is hoe offline renders normaal gesproken zouden werken-alle driehoeken in de scène worden geïndexeerd in een soort ruimtelijke gegevensstructuur, zoals een Bounding Volume hiërarchie (BVH) of kD-tree, die een efficiënte kruising van driehoeken langs een straal mogelijk maken.

we raymarch afstand velden in plaats daarvan omdat:

  • het is heel eenvoudig om de straalgietroutine te implementeren
  • we vermijden alle complexiteit van het implementeren van straaldriehoek-kruispunten en BVH – gegevensstructuren
  • we hoeven geen expliciete scèneweergave te schrijven-driehoeken mazen, Tex coordens, kleuren, enz
  • we profiteren van een reeks nuttige functies van afstandsvelden, waarvan sommige hierboven zijn vermeld

dit gezegd zijnde, er zijn enkele elegante/eenvoudige toegangspunten in ray tracing. De ray Tracing in een Weekend gratis boek (en volgende hoofdstukken) zijn zeer aan te bevelen en zijn essentieel lezen voor iedereen die geïnteresseerd is in graphics.

laten we beginnen!

ShaderToy

ShaderToy is een shadercreatiewebsite en platform voor het browsen, delen en bespreken van shaders.

terwijl u direct kunt beginnen met het schrijven van een nieuwe shader zonder een account aan te maken, is dit gevaarlijk omdat u gemakkelijk werk kunt verliezen als er verbindingsproblemen zijn of als u de GPU ophangt (gemakkelijk gedaan door bijvoorbeeld een oneindige lus aan te maken).Daarom raden we ten zeerste aan om een account aan te maken (het is snel/gemakkelijk/gratis) door hier te klikken: https://www.shadertoy.com/signin, en regelmatig op te slaan.

voor een ShaderToy overzicht en handleiding aan de slag, bevelen we aan om een tutorial te volgen zoals deze van @The_ArtOfCode: https://www.youtube.com/watch?v=u5HAYVHsasc. De basis is hier nodig om de rest van de workshop te volgen.

2D SDF demo

we bieden een eenvoudig framework voor het definiëren en visualiseren van 2D ondertekende afstandsvelden.

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

voorafgaand aan het definiëren van het afstandsveld zal het resultaat volledig wit zijn. Het doel van deze sectie is om een SDF te ontwerpen die de gewenste scènevorm geeft (witte omtrek). In code wordt deze afstand berekend door de functie sdf(), die als invoer een 2D-positie in de ruimte krijgt. De concepten die je hier leert zullen direct naar 3D-ruimte veralgemenen en je in staat stellen om een 3D-scène te modelleren.

begin eenvoudig-probeer eerst de x-of y-component van het punt p te gebruiken en observeer het resultaat:

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

het resultaat moet er als volgt uitzien:

groen betekent ‘buiten’ oppervlakken, rood betekent ‘binnen’ oppervlakken, de witte lijn omlijnt het oppervlak zelf, en de schaduw in de binnen/buiten gebieden illustreert afstand iso-lijnen – lijnen op vaste afstanden. In 2D modelleert deze SDF een horizontale lijn in 2D op y=0. Wat voor geometrische primitieve zou dit voorstellen in 3D?

een ander goed ding om te proberen is om afstanden te gebruiken, bijvoorbeeld: return length(p);. Deze operator geeft de grootte van de vector terug, en in dit geval geeft het ons de afstand van het huidige punt tot de oorsprong.

een punt is niet erg interessant om weer te geven omdat een punt oneindig klein is, en onze stralen zouden het altijd missen!We kunnen het punt een gebied geven door de gewenste straal van de afstand af te trekken: return length(p) - 0.25;.We kunnen ook het invoerpunt wijzigen voordat we de grootte ervan nemen: length(p - vec2(0.0, 0.2)) - 0.25;.Welk effect heeft dit op de vorm?Welke waarden zou de functie kunnen terugkeren voor punten ‘binnen’ de cirkel?Gefeliciteerd – je hebt net een cirkel gemodelleerd met behulp van wiskunde:). Dit zal triviaal uitbreiden naar 3D in welk geval het modelleert een bol. Vergelijk deze scènevoorstelling met andere ‘expliciete’ scènevoorstellingen zoals driehoeksmaas of NURBS-oppervlakken. We creëerden een sfeer in minuten met een enkele regel code, en onze code geeft direct een wiskundige definitie voor een sfeer – ‘de verzameling van alle punten die op gelijke afstand van een middelpunt staan’.

voor andere typen primitieven zijn de afstandsfuncties even elegant. iq maakte een geweldige referentiepagina met afbeeldingen: http://iquilezles.org/www/articles/distfunctions/distfunctions.htm

zodra u begrijpt hoe een afstand tot een primitieve werkt – zet het in een kader – definieer een functie voor het, zodat u niet hoeft te onthouden en schrijven van de code elke keer. Er is al een functie gedefinieerd voor de cirkel sdCircle() die u kunt vinden in de shader. Voeg alle primitieven toe die je wilt.

vormen combineren

nu we weten hoe we individuele primitieven moeten maken, hoe kunnen we ze combineren om een scène met meerdere vormen te definiëren?

een manier om dit te doen is de “union” operator – die wordt gedefinieerd als het minimum van twee afstanden. Het is het beste om te experimenteren met de code om een sterke greep van dit te krijgen, maar de intuïtie is dat de SDF geeft de afstand tot het dichtstbijzijnde oppervlak, en als de scène heeft meerdere objecten wilt u de afstand tot het dichtstbijzijnde object, dat zal het minimum van de afstanden tot elk object.

in code ziet dit er als volgt uit:

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

op deze manier kunnen we veel vormen compact combineren. Zodra dit is begrepen, moet de functie opU() worden gebruikt, die staat voor “operation union”.

dit is slechts het oppervlak van wat mogelijk is. We kunnen vloeiende mengsels krijgen met behulp van een mooie zachte min – functie-probeer de meegeleverde opBlend()te gebruiken. Er zijn vele andere interessante technieken die kunnen worden toegepast, de geïnteresseerde lezer wordt verwezen naar deze uitgebreide inleiding tot het gebouw scènes met SDFs: https://www.youtube.com/watch?v=s8nFqwOho-s

Voorbeeld:

de Overgang naar 3D

Hopelijk heb je opgedaan in een fundamenteel begrip van hoe afstand velden kunnen worden gebruikt om aan te geven scène gegevens, en hoe gebruiken we raymarching te vinden snijpunten met de scène. We gaan nu werken in drie dimensies, waar de echte magie gebeurt.

we raden u aan uw huidige shader op te slaan en een nieuwe te starten, zodat u later naar uw 2D-weergave kunt verwijzen.De meeste helpers kunnen in uw nieuwe shader gekopieerd worden en in 3D gemaakt worden door de vec2s te verwisselen met vec3s.

Ray marching loop

in plaats van de SDF te visualiseren zoals we in 2D deden, springen we meteen in om de scène te renderen. Hier is het basisidee van hoe we zullen implementeren ray marching (in pseudo-code):

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

deze stappen zullen nu elk in meer detail worden beschreven.

Camera

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

deze functie berekent eerst de drie assen van de ‘view’ matrix van de camera; de vectoren vooruit, rechts en omhoog.De voorwaartse vector is de genormaliseerde vector van de camerapositie naar de kijkdoelpositie.De juiste vector wordt gevonden door de voorwaartse vector met de wereld omhoog as te kruisen.De voorwaartse en rechtse vectoren worden dan gekruist om de camera omhoog vector te verkrijgen.

ten slotte wordt de camerastraal berekend met behulp van dit frame door een punt voor de camera te nemen en het in de camera rechts en omhoog te verleggen met behulp van de pixelcoördinaten uv.fPersp stelt ons in staat om indirect het gezichtsveld van onze camera te controleren. Je kunt deze vermenigvuldiging zien als het vlak dichterbij en verder van de camera bewegen. Experimenteer met verschillende waarden om het resultaat te zien.

Scene-definitie

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

zoals u kunt zien, hebben we een sdSphere() toegevoegd die identiek is aan sdCircle behalve voor het aantal componenten in ons invoerpunt.

Raymarching

Pseudo-code:

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

probeer dit zelf te schrijven – als je vast komt te zitten, kijk dan naar de oplossing hieronder.

reële code:

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

we zullen nu een render functie toevoegen, die uiteindelijk verantwoordelijk zal zijn voor het arceren van het gevonden snijpunt. Voor nu echter, laten we de afstand tot de scène weer te geven om te controleren dat we op schema. We schalen en keren het om om de verschillen beter te zien.

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

om de richting van elke straal te berekenen, willen we de invoer van pixelcoördinaten fragCoord transformeren uit het bereik , , waarbij w en h de breedte en hoogte van het scherm in pixels zijn, en a de beeldverhouding van het scherm. We kunnen dan de waarde die van deze helper wordt geretourneerd doorgeven aan de getCameraRayDir functie die we hierboven hebben gedefinieerd om de straalrichting te krijgen.

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

onze hoofdbeeldfunctie ziet er dan als volgt uit:

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}

oefeningen:

  • experimenteer met het aantal stappen en observeer hoe het resultaat verandert.
  • experimenteer met de beëindigingsdrempel en observeer hoe het resultaat verandert.

voor het volledige werkprogramma, zie Shadertoy: Part 1a

Ambient term

om wat kleur in de scène te krijgen gaan we eerst een onderscheid maken tussen objecten en de achtergrond.

om dit te doen, kunnen we -1 retourneren in castRay om aan te geven dat er niets is geraakt. We kunnen die zaak dan in render afhandelen.

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

Diffuse term

om meer realistische verlichting te krijgen laten we het oppervlak normaal berekenen zodat we de basis Lambertiaanse verlichting kunnen berekenen.

om de normale te berekenen, gaan we de gradiënt van het oppervlak in alle drie de assen berekenen.

in de praktijk betekent dit dat de SDF vier keer extra wordt bemonsterd, elk iets ten opzichte van onze primaire straal.

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

een geweldige manier om normalen te inspecteren is door ze weer te geven alsof ze kleur vertegenwoordigen. Dit is hoe een bol eruit zou moeten zien als het zijn geschaalde en bevooroordeelde normaal weergeeft (gebracht van naar omdat uw monitor geen negatieve kleurwaarden kan weergeven)

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

nu we een normale hebben, kunnen we het dot product tussen het en de lichtrichting nemen.

dit zal ons vertellen hoe direct het oppervlak naar het licht is gericht en dus hoe helder het zou moeten zijn.

we nemen de max van deze waarde met 0 om te voorkomen dat negatieve waarden ongewenste effecten geven aan de donkere kant van objecten.

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

een zeer belangrijk onderdeel van rendering dat gemakkelijk over het hoofd kan worden gezien is gammacorrectie. Pixelwaarden die naar de monitor worden verzonden, bevinden zich in gammaruimte, een niet-lineaire ruimte die wordt gebruikt om de precisie te maximaliseren, door minder bits in intensiteitsbereiken te gebruiken waar mensen minder gevoelig voor zijn.

omdat monitors niet in “lineaire” ruimte werken, moeten we hun gammacurve compenseren voordat een kleur wordt uitgevoerd. Het verschil is zeer merkbaar en moet altijd worden gecorrigeerd voor. In werkelijkheid weten we niet van de gamma-curve voor een bepaalde weergave-apparaat is, zodat de hele situatie met display-technologie is een enorme puinhoop (vandaar de gamma-afsteminterval is in veel games), maar een algemene aanname, is de volgende gamma-curve:

De constante 0.4545 is gewoon 1.0 / 2.2

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

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

Schaduwen

Voor het berekenen van schaduwen, kunnen we het vuur van een ray begint op het moment dat we elkaar ontmoetten op de scène en in de richting van de lichtbron.

als deze straalmars ertoe leidt dat we iets raken, dan weten we dat het licht ook Geblokkeerd zal zijn en dus staat deze pixel in de schaduw.

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

grondvlak

laten we een grondvlak toevoegen zodat we de schaduwen van onze bollen beter kunnen zien.

de W-component van n staat voor de afstand die het vlak tot de oorsprong heeft.

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

zachte schaduwen

schaduwen in het echte leven stoppen niet onmiddellijk, ze hebben een falloff, aangeduid als een penumbra.

we kunnen dit modelleren door het nemen van verschillende stralen van ons oppervlaktepunt, elk met iets verschillende richtingen.

we kunnen dan het resultaat en het gemiddelde optellen over het aantal herhalingen dat we deden. Dit zorgt ervoor dat de randen van de schaduw

sommige stralen raken en andere missen, wat een duisternis van 50% oplevert.

het vinden van een enigszins pseudo willekeurig getal kan op een aantal manieren worden gedaan, maar we zullen het volgende gebruiken:

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

deze functie retourneert een getal in het bereik [0, 1). We weten dat de uitvoer aan dit bereik gebonden is omdat de buitenste bewerking fract is, die de fractionele component van een floating point getal retourneert.

we kunnen dit dan gebruiken om onze schaduwstraal als volgt te berekenen:

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

Textuurafbeelding

in plaats van een enkele oppervlaktekleur (of een ander kenmerk) gelijkmatig over het gehele oppervlak te definiëren, kan men patronen definiëren om op het oppervlak aan te brengen met behulp van texturen.We behandelen drie manieren om dit te bereiken.

3D-Textuurafbeelding

er zijn volumetexturen die gemakkelijk toegankelijk zijn in shadertoy en die aan een kanaal kunnen worden toegewezen. Probeer een van deze texturen te bemonsteren met behulp van de 3D-positie van het oppervlaktepunt:

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

een manier om ruis te bemonsteren is om meerdere schalen samen te voegen, met behulp van iets als het volgende:

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

de bovenstaande constanten/gewichten worden meestal gebruikt voor een fractal ruis, maar ze kunnen alle gewenste waarden aannemen. Probeer te experimenteren met gewichten/schalen/kleuren en te zien welke interessante effecten je kunt bereiken.

probeer uw object te animeren met iTime en observeer hoe de volumestructuur zich gedraagt. Kan dit gedrag worden veranderd?

2D-textuur mapping

het toepassen van een 2D-textuur is een interessant probleem-hoe projecteer je de textuur op het oppervlak? In normale 3D-graphics, heeft elke driehoek in een object een of meer UV ‘ s toegewezen die de coördinaten van het gebied van textuur dat me zou moeten mappen aan de driehoek (texture mapping). In ons geval hebben we geen UV ‘ s, dus we moeten uitzoeken hoe we de textuur kunnen testen.

een methode is om de textuur te bemonsteren met behulp van een top-down wereldprojectie, door de textuur te bemonsteren op basis van X & Z-coördinaten:

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

welke beperkingen ziet u bij deze aanpak?

Triplanaire afbeelding

een meer geavanceerde manier om texturen in kaart te brengen is door 3 projecties uit de primaire Assen te doen en het resultaat vervolgens te mengen met behulp van triplanaire afbeelding. Het doel van het mengen is om de beste textuur te kiezen voor elk punt op het oppervlak. Een mogelijkheid is om de menggewichten te definiëren op basis van de uitlijning van het normale oppervlak met elke wereldas. Een oppervlak dat naar voren gericht is met een van de assen krijgt een groot blendgewicht:

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

welke beperkingen ziet u bij deze aanpak?

materialen

samen met de afstand die we retourneren van de castRay-functie, kunnen we ook een index retourneren die het materiaal van het getroffen object weergeeft. We kunnen deze index gebruiken om objecten dienovereenkomstig te kleuren.

onze operators moeten vec2s nemen in plaats van drijvers, en de eerste component van elk vergelijken.

nu, bij het definiëren van onze scène zullen we ook een materiaal specificeren voor elke primitieve als de Y component van een 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;

we kunnen deze materiaalindex dan vermenigvuldigen met een aantal waarden in de render functie om verschillende kleuren te krijgen voor elk object. Probeer verschillende waarden uit.

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

laten we het grondvlak kleuren met een dambordpatroon. Ik heb deze mooie analytisch-anti-aliased checkerbox functie van Inigo Quilez’ website genomen.

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

we passeren de XZ-componenten van onze vliegtuigpositie om het patroon te laten herhalen in die dimensies.

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

mist

we kunnen nu mist toevoegen aan de scène op basis van hoe ver elke kruising plaatsvond van de camera.

kijk of u iets kunt krijgen dat lijkt op het volgende:

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

Shape & material blendingom de ruwe vouw te voorkomen die door de min-operator wordt gegeven, kunnen we een meer verfijnde operator gebruiken die de vormen soepel combineert.

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

door de scène vele malen te bemonsteren met licht offset camera richting vectoren, kunnen we een gladgestreken waarde krijgen die aliasing vermijdt.

ik heb de kleurberekening van de scène naar zijn eigen functie gebracht om het bellen in de lus duidelijker te maken.

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;

Step count optimization

als we visualiseren hoeveel stappen we nemen voor elke pixel in het rood, kunnen we duidelijk zien dat de stralen die niets raken verantwoordelijk zijn voor de meeste van onze iteraties.

dit kan voor bepaalde scènes een aanzienlijke verbetering betekenen.

if (t > drawDist) return backgroundColor;

Shape & materiaalinterpolatie

we kunnen interpoleren tussen twee vormen met behulp van de mix-functie en iTime gebruiken om in de tijd te moduleren.

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

domein herhaling

het is vrij eenvoudig om een vorm te herhalen met behulp van een ondertekend afstandsveld, in wezen hoeft u alleen de invoerpositie in een of meer dimensies te moduleren.

deze techniek kan bijvoorbeeld worden gebruikt om een kolom meerdere keren te herhalen zonder de representatiegrootte van de scène te vergroten.

hier heb ik alle drie de componenten van de invoerpositie herhaald en vervolgens de Aftrekken operator ( max() ) gebruikt om de herhaling te beperken tot een bounding box.

Eén gotcha is dat je de helft van de waarde die je moduleert moet aftrekken om de herhaling op je vorm te centreren om het niet in de helft te snijden.

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

Nabewerkingseffecten

vignet

door pixels die verder van het midden van het scherm staan donker te maken, kunnen we een eenvoudig vignet-effect krijgen.

Contrast

donkere en lichtere waarden kunnen worden geaccentueerd, waardoor het waargenomen dynamische bereik toeneemt samen met de intensiteit van het beeld.

col = smoothstep(0.0,1.0,col);

“Ambient occlusion”

als we de inverse van de afbeelding hierboven nemen (in optimalisaties), kunnen we een vreemd ao-achtig effect krijgen.

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

Ad infinitum

zoals u kunt zien, kunnen veel post processing effecten triviaal geïmplementeerd worden; Speel rond met verschillende functies en zie welke andere effecten U kunt creëren.

www.shadertoy.com/view/MtdBzs

wat volgt?

we hebben de basis hier behandeld; er is nog veel meer te onderzoeken op dit gebied, zoals:

  • Subsurface scattering
  • Ambient occlusion
  • geanimeerde primitieven
  • primitieve krommingsfuncties (twist, bend, …)
  • transparantie (breking, bijtende stoffen, …)
  • optimalisaties (begrenzende volumehiërarchieën)

Blader door ShaderToy om wat inspiratie op te doen en snuffel door verschillende shaders om te zien hoe verschillende effecten worden geà mplementeerd. Veel shaders hebben variabelen die u kunt tweaken en direct de effecten van (alt-enter is de snelkoppeling te compileren!).

geef ook de referenties door als je meer wilt leren!

Bedankt voor het lezen! Stuur ons je coole shaders! Als je feedback hebt over de cursus, horen we het ook graag!

neem contact met ons op op twitter @liqwidice & @hdb1

Electric Square neemt in dienst!

aanbevolen uitlezing:

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

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

Ray Tracing in één Weekend: http://in1weekend.blogspot.com/2016/01/ray-tracing-in-one-weekend.html

physical-based rendering bible, PBRT: https://www.pbrt.org/

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.