Als u ontwikkelaar van 3D-games bent, bent u waarschijnlijk de voorwaarden tegengekomen forward rendering en uitgestelde weergave in je onderzoek naar moderne grafische engines. En vaak moet je er een kiezen die je in je game kunt gebruiken. Maar wat zijn ze, hoe verschillen ze en welke moet je kiezen?
Uitgestelde rendering voor veel lichten (Foto met dank aan Hannes Nevalainen)Om te beginnen moeten we een beetje inzicht krijgen in moderne of programmeerbare grafische pipelines.
Vroeger waren we beperkt in wat de grafische kaart van de grafische kaart had. We konden niet veranderen hoe het elke pixel tekende, afgezien van het verzenden van een andere textuur, en we konden geen vertices verdraaien als ze eenmaal op de kaart waren. Maar de tijden zijn veranderd en dat hebben we nu programmeerbare grafische pijplijnen. We kunnen nu code naar de videokaart verzenden om de weergave van de pixels te wijzigen, ze een hobbelige uitstraling te geven met normale kaarten en reflectie toe te voegen (en een groot deel van het realisme).
Deze code heeft de vorm van geometrie, toppunt, en fragment shaders, en ze veranderen in wezen hoe de videokaart je objecten rendert.
Forward-rendering is de standaard, out-of-the-box renderingtechniek die de meeste engines gebruiken. U levert de geometrie aan de grafische kaart, projecteert deze en verdeelt deze naar hoekpunten, en die worden getransformeerd en opgesplitst in fragmenten of pixels, die de uiteindelijke renderingsbehandeling krijgen voordat ze op het scherm worden weergegeven.
Het is vrij lineair en elke geometrie wordt één voor één door de pijp geleid om het uiteindelijke beeld te produceren.
Bij uitgestelde rendering, zoals de naam al aangeeft, wordt de rendering een klein beetje uitgesteld totdat alle geometrieën de pijp zijn gepasseerd; het uiteindelijke beeld wordt vervolgens geproduceerd door schaduw aan het einde toe te passen.
Waarom zouden we dat doen??
Verlichting is de belangrijkste reden om van de ene route naar de andere te gaan. In een standaard voorwaartse weergavepijplijn moeten de verlichtingsberekeningen worden uitgevoerd op elk hoekpunt en op elk fragment in de zichtbare scène, voor elk licht in de scène.
Als u een scène met 100 geometrieën hebt en elke geometrie 1000 hoekpunten heeft, hebt u mogelijk ongeveer 100.000 polygonen (een zeer ruwe schatting). Videokaarten kunnen dit vrij gemakkelijk aan. Maar wanneer die polygonen naar de fragmentshader worden gestuurd, vinden daar de dure lichtberekeningen plaats en kan de echte vertraging optreden.
Ontwikkelaars proberen zoveel mogelijk belichtingsberekeningen in de Vertex-arcering te duwen om de hoeveelheid werk die de fragmentshader moet doen te verminderen.De dure belichtingsberekeningen moeten worden uitgevoerd voor elk zichtbaar fragment van elke veelhoek op het scherm, ongeacht of het overlapt of wordt verborgen door de fragmenten van een andere polygoon. Als je scherm een resolutie van 1024x768 heeft (wat zeker niet erg hoog is), heb je bijna 800.000 pixels die moeten worden weergegeven. Je zou gemakkelijk elk frame een miljoen fragmentbewerkingen kunnen bereiken. Ook zullen veel van de fragmenten nooit op het scherm komen omdat ze werden verwijderd met dieptetests, en dus werd de verlichtingsberekening verspild..
Als je een miljoen van die fragmenten hebt en plotseling moet je die scène opnieuw weergeven voor elk licht, waar je naartoe bent gesprongen [num lights] x 1.000.000
fragmentbewerkingen per frame! Stel je voor dat je een stad vol straatlantaarns had waar elk een puntlichtbron is ...
De formule voor het schatten van deze voorwaartse rendering-complexiteit kan worden geschreven, in grote O-notatie, als O (num_geometry_fragments * num_lights)
. Je kunt hier zien dat de complexiteit direct gerelateerd is aan het aantal geometrieën en het aantal lichten.
Sommige motoren optimaliseren dit door lampen weg te knippen die ver weg zijn, lampen te combineren of lichtkaarten te gebruiken (erg populair, maar statisch). Maar als u dynamische lichten en veel van hen wilt, hebben we een betere oplossing nodig.
Uitgestelde rendering is een zeer interessante benadering die het aantal objecten, en met name de totale fragmententelling, vermindert en de belichtingsberekeningen uitvoert op de pixels op het scherm, waarbij de resolutieafmeting wordt gebruikt in plaats van het totale aantal fragmenten..
De complexiteit van uitgestelde rendering, in grote O-notatie, is: O (schermresolutie * num_lights)
.
Je kunt zien dat het nu niet uitmaakt hoeveel objecten je op het scherm hebt die bepalen hoeveel lichten je gebruikt, dus je kunt je verlichtingstevredenheid graag verhogen. (Dit betekent niet dat u onbeperkte objecten kunt hebben - ze moeten nog steeds naar de buffers worden getrokken om het uiteindelijke renderresultaat te produceren.)
Laten we kijken hoe het werkt.
Elke geometrie wordt weergegeven, maar zonder lichte arcering, naar meerdere schermruimtebuffers met meerdere renderdoelen. In het bijzonder worden de diepte, de normalen en de kleur allemaal geschreven naar afzonderlijke buffers (afbeeldingen). Deze buffers worden vervolgens gecombineerd om voldoende informatie te verschaffen voor elk licht om de pixels te verlichten.
Door te weten hoe ver een pixel verwijderd is, en de normale vector, kunnen we de kleur van die pixel combineren met het licht om onze uiteindelijke render te produceren.
Het korte antwoord is dat als je veel dynamische lichten gebruikt, je uitgestelde rendering zou moeten gebruiken. Er zijn echter enkele belangrijke nadelen:
Als u niet veel lampjes hebt of op oudere hardware wilt kunnen werken, moet u bij voorwaartse rendering blijven en uw vele lampjes vervangen door statische lichtkaarten. De resultaten kunnen er nog steeds geweldig uitzien.
Ik hoop dat dit enig licht heeft gewekt op het onderwerp. Je opties zijn er om je renderingproblemen op te lossen, maar het is erg belangrijk om de juiste te kiezen aan het begin van je game-ontwikkeling om later moeilijke veranderingen te voorkomen.