Ray tracing kontra rastrering

Dagens grafikkort bygger på en annan grundprincip än ray tracing, de använder något som kallas rastrering. Det finns flera sätt att beskriva geometri än trianglar, men för enkelhetens skull utgår alla beskrivningar med trianglar.

Följande tabell pekar på några av de fundamentala skillnaderna mellan hur ray tracing och rendering tar en 3D-beskrivning av världen till det som visas på skärmen

Koncept

Rastrering

Ray tracing

Grundfråga

Vilka pixlar täcker en viss triangel?

Vilka trianglar är synliga utefter en kamerastråle?

Mest frekventa operation

Testa om en pixel befinner sig utanför eller innanför en triangel.

Testa om en stråle passerar genom en viss triangel.

Huvudslinga

För-alla-trianglar-i-världen ställ grundfrågan.

För-alla-pixlar-på-skärmen ställ grundfrågan.

Primärt problem

Kräver hög bandbredd då samma pixel kan läsas/skrivas många gånger (overdraw).

Många trianglar måste testas per stråle, vissa material ger upphov till många sekundärstrålar.

Viktigast optimering

Hålla reda på djupkoordinaten för varje pixel, Z-buffer. Tidig verifiering mot Z-buffer gör att många pixlar i trianglar helt kan skippas.

Många trianglar måste testas per stråle. Objekt i 3D-världen måste grupperas så kollision kan utföras mot hela grupper först.

Nackdel

Väldigt ineffektivt att hantera enskilda pixlar inom en triangel på ett annorlunda sätt, vilket behövs för vissa effekter.

Även strålar nära varandra kan kräva väldigt olika beräkningar, vilket gör processen latenskänslig i stället för bandbreddskrävande.

FLOPS

En term som ofta nämns ihop med grafikkort är deras maximala kapacitet i att göra beräkningar med så kallade flyttal – FLOPS – floating-point operations per second. Flyttal är en datorrepresentation av reella tal, tänk tal med decimaltecken i sig.

ambient_occlusion.png

Nvidias Geforce RTX 2080 Ti på bilden kan utföra över 10 biljoner flyttalsoperationer per sekund, det vill säga är kapabel till över 10 TFLOPS.

Så hur mycket krävs då för att följa en primärstråle genom varje pixel på en skärm med upplösningen 1 920 x 1 080 pixlar (drygt två miljoner pixlar)? Ponera att det åker iväg lika många strålar som skärmens upplösning, en per pixel. Antag att en modern speltitel har cirka en miljon trianglar per scen.

Utan att gå in på detaljer så kommer trianglar i praktiken vara ordnade på ett "smart" sätt så bara en lite fraktion scannas för att avgöra om det är en träff och i så fall vilken triangel som träffas. Antag att hundratals trianglar behöver undersökas, är någonstans i den häraden man hamnar.

Att avgöra om en stråle passerar igenom en enskild triangel kräver ett tiotal multiplikationer och subtraktioner, låt oss säga 100 flyttalsoperationer per triangel för att ha marginal.

2 miljoner strålar
* 100 flyttalsoperationer per stråle och triangel
* hundratals trianglar
* <=100 FPS
= låga miljarder beräkningar per sekund
= någon enstaka TFLOPS

I ett spel som Battlefield V är det relativt få av primärstrålarna som kommer träffa ytor som kräver vidare behandling, så är inte rå beräkningskapacitet som hindrar tidigare grafikkort från att överhuvudtaget inte kunna köra ray tracing i realtid.

Parallellism

Kikar man på huvudslingan för rastrering respektive ray tracing är det värt att notera att de egentligen gör samma sak, fast med omkastad yttre och inre ström av objekt. Rastrering jobbar över alla trianglar medan ray tracing jobbar över alla pixlar på skärmen.

Gemensamt för båda metoder är att den yttre slingan består av ett stort antal, sinsemellan oberoende, uppgifter. "Stort antal" kombinerat med "sinsemellan oberoende" är grundreceptet för att framgångsrikt hantera saker parallellt.

Är då slutsatsen att fler CUDA-kärnor likväl hade kunnat lösa problemet som den väg Nvidia tog med sina specialiserade RT-kärnor? Tyvärr inte, då det finns två huvudklasser av parallellism. Rastrering och ray tracing tillhör varsitt läger.

CUDA-kärnor och processorkärnor

Ett sätt att visualisera skillnaden mellan sekventiella uppgifter ("enkeltrådat"), dataparallella ("CUDA-kärnor" och SSE/AVX på x86-processorer) och uppgiftsparallella ("RT-kärnor" och multipla kärnor på processorer) är att tänka sig två matematiska funktioner, F() samt G().

Det är inte viktigt vad funktionerna gör mer än att de tar någon form av indata, xN, gör en beräkning baserat på detta och ger tillbaka ett resultat, yN.

Enkeltrådat

I det sekventiella fallet får man indata från ena funktionen och utdata till den andra, det vill säga:

y0 = F(G(x0))

Det är i detta läge inte möjligt att beräkna F() innan beräkningen för G(x0) är klar.

Enda praktiska formen av parallellism som kan utnyttjas här är den som kallas "Instruction Level Parallelism" (ILP). Nivån av ILP avgör hur mycket en krets kan utför per cykel, dess "Instructions Per Clock" (IPC).

Grafikprocessorer är riktigt dålig på denna typ av problem, vilka därför istället körs på en processor.

Uppgiftsparallellt

Det uppgiftsparallella fallet är när F() och G() är olika funktioner och de inte är beroende av varandra:

y0 = F(x0) y1 = G(x1)

Orsakerna till att ray tracing hamnar i denna kategori är flera. Hanteringen av primärstrålar är initialt identiskt oavsett vilken pixel på skärmen de motsvarar, men då hanteringen av sekundärstrålar beror på egenskaperna hos ytan som träffats blir effekten att olika strålar i många fall behövs olika sekvenser med instruktioner för att beräkna resultatet.

Kort och gott är olika trådar är oberoende varandra, men arbetet som utförs är typiskt inte identiskt.

ambient_occlusion.png

Nvidia tryckte på "giga rays" vid lansering av RTX-serien. Det går att se det som att kortet på bilden har 10 giga rays för uppgiftsparallellism och över 10 TFLOPS för dataparallellism.

Detta fall benämns ibland "Multiple Instruction Multiple Data", (MIMD). En CPU med flera kärnor är exempel på MIMD.

Dataparallellt

Vissa typer av problem behöver applicera samma funktion på massor med indata. Det vill säga i detta fall gäller att F() = G()

y0 = F(x0) y1 = F(x1)

Det finns en rad optimeringar att göra på kiselnivå i detta fall, vilket är orsaken till att grafikkort har långt mycket högre "giga-/teraflops" (matematisk beräkningar som kan utföras per sekund) värde jämfört med en CPU.

För CPU kallas detta för "Single Instruction Multiple Data", (SIMD) medan GPU-tillverkarna vill benämna det "Single Instruction Multiple Thread", (SIMT), där tråd motsvarar en CUDA-kärna i Nvidias fall. Båda är i grunden samma sak.

På x86-processorer realiseras SIMD via SSE/AVX.

Rastrering är väldigt effektivt då det till stora delar är kraftigt dataparallellt. Vidare hanterar GPU:er ett mycket stort antal samtida dataelement, det vill säga väldigt många samtida xn värden i exemplet ovan.

Då GPU:n kan jobba på tusentals pixlar samtidigt är det inte superkritiskt att varje pixel får sin data med väldigt kort varsel, latensen är därmed rätt oviktig för rastrering medan det krävs rejält med bandbredd.

Prestanda mot korrekthet

Vid rendering skapas skuggor genom att en siluett av det som ska kasta skugga läggs i en textur. Texturen renderas mer eller mindre genomskinlig i den riktning som är rimlig givet primära ljuskällas placering. Det går väldig snabbt och illusionen av halvskugga kan uppnås genom att gradvis göra texturen mer genomskinlig i kanterna.

För bättre resultat används tricks som "ambient occlusion" (AO). AO är verkligen ett "trick" som råkar se riktigt bra ut, tekniken vilar inte på någon verklig fysikalisk beskrivning.

ambient_occlusion.png

Apan till vänster använder sig av AO. Konkava områden samt områden nära utstickande geometri tenderar bli lite mörkare, medan konvexa områden blir lite ljusare jämfört med högra apan som saknar AO.

Ray tracing får i princip alla skuggeffekter på köpet från skuggstrålarna, bara det genereras tillräckligt många skuggstrålar. Reflektion uppnår spel genom tekniker som "environment maps" där omgivningen läggs i en textur och blandas in i vad som exempelvis representerar plåten på en bil. Ger ett trovärdigt resultat, i alla fall på relativt plana ytor.

Ray tracing hanterar alla typer av geometrier. Här fås även självreflektion med, alltså när yttre backspegeln på en bil reflekteras i bilens dörr.

bmw_selfreflect.png

Backspegeln och antennen på taket reflekteras i andra delar av samma modell, självreflektion får man inte med metoderna som används vid rastrering

Häri ligger "heliga gralen" med ray tracing, om det bara kan göras tillräckligt snabbt får man effekter som kräver rätt komplicerad och tidskrävande kodning och anpassning från grafikernas sida från en egentligen trivial algoritm.

Inga gratisluncher

Egenskaperna hos rastrering kan sammanfattas som: i sammanhanget väldigt enkelt att få riktigt bra prestanda, men kräver komplicerade lösningar för att simulera mer avancerade grafiska effekter.

Ray tracing kan sammanfattas som: väldigt enkelt att producera riktigt avancerade resultat, men kräver i praktiken specialdesignat kisel och en hel del kluriga optimeringar för att ge någorlunda acceptabel prestanda.