Deferred Shading: Unterschied zwischen den Versionen
(→Vor-/Nachteile) |
|||
Zeile 12: | Zeile 12: | ||
'''Vorteile:''' | '''Vorteile:''' | ||
* Viele Lichtquellen möglich | * Viele Lichtquellen möglich | ||
− | * Postprocessing Effekte (Bloom, Tonemapping, Depth of Field) können einfach angeschlossen werden | + | * Postprocessing Effekte (Bloom, Tonemapping, Depth of Field, etc.) können einfach angeschlossen werden, da bereits alle benötigten Informationen aufbereitet vorliegen. |
'''Nachteile:''' | '''Nachteile:''' | ||
− | * Kein Hardware Anti- | + | * Kein Hardware Anti-Aliasing möglich |
− | * | + | * Transparente Objekte müssen getrennt behandelt werden |
− | * Nur auf neueren Grafikkarten mit MRT (Mutliple Render Target) und Floating Point-Textur Support möglich | + | * Nur auf neueren Grafikkarten mit MRT (Mutliple Render Target) und Floating Point-Textur Support möglich (ab DirectX 9 kompatiblem Grafikchip; OpenGL Version ???) |
== Implementierung == | == Implementierung == |
Version vom 14. März 2010, 23:18 Uhr
Inhaltsverzeichnis
Deferred Shading
Der Befriff Deferred Shading (zu dt. "verzögertes Schattieren") oder auch Deferred Lighting ("verzögerte Beleuchtung") beschreibt eine Technik, mit deren Hilfe eine komplette Lichtquelle mit nur einem einzigen Draw-Call abgebildet werden kann.
Vorgehensweise
Um eine Szene mit einem traditionellen Forward-Renderer zu beleuchten, wird normalerweise jedes Objekt der Szene mit den entsprechenden Beleuchtungsparametern der Lichtquelle gezeichnet. Die verschiedenen Ergebnisse werden durch additives Blenden akkumuliert. Für jede Lichtquelle nl muss also jedes Objekt no gezeichnet werden. Die Anzahl der Rendercalls liegt also bei no * nl. Hierdurch sind nur wenige Lichtquellen gleichzeitig möglich.
Deferred Shading verfolgt einen anderen Ansatz: Anstatt jedes Objekt der Szene für jede Lichtquelle neu zu renderen, werden zunächst alle beleuchtungsrelevanten Daten (Position des Pixels, Normalenvektoren, unbeleuchtete Farbe des Pixels (Albedo), Glanzstärke, etc.) auf Pixelbasis in mehrere Rendertargets (oftmals auch G-Buffer genannt) geschrieben. Nun muss für jede Lichtquelle lediglich die Lichtquellengeometrie gezeichnet werden und in diesem Schritt die Beleuchtung auf die zwischengespeicherten Daten angewendet werden. Die Zahl der Rendercalls liegt also bei no + nl. Mit dieser Methode sind hunderte Lichter gleichzeitig in einer Szene möglich.
Vor-/Nachteile
Vorteile:
- Viele Lichtquellen möglich
- Postprocessing Effekte (Bloom, Tonemapping, Depth of Field, etc.) können einfach angeschlossen werden, da bereits alle benötigten Informationen aufbereitet vorliegen.
Nachteile:
- Kein Hardware Anti-Aliasing möglich
- Transparente Objekte müssen getrennt behandelt werden
- Nur auf neueren Grafikkarten mit MRT (Mutliple Render Target) und Floating Point-Textur Support möglich (ab DirectX 9 kompatiblem Grafikchip; OpenGL Version ???)
Implementierung
Zunächst müssen die Geometriedaten in den (G-Buffer) geschrieben werden. Hierzu benötigen wir drei 16-Bit Float Rendertarget Texturen. Niedrigere Bittiefen sind für Positionsbeschreibung und Normalenvektor zu wenig. Alle Rendertarget-Texturen müssen die gleiche Bittiefe haben. Sind mehrere Rendertargets aktiviert, so wird Hardware-Anti-Aliasing automatisch abgeschaltet. Die folgenden Beispiele sind mit Andorra 2D entwickelt und sollten recht einfach verständlich und nach OpenGL/DirectX umzusetzen sein.
Schritt 1: Erstellen der Rendertargets
FRT_1_Albedo := TAdRenderTargetTexture.Create(FDraw);
FRT_1_Albedo.BitDepth := adF64Bit;
FRT_1_Albedo.SetSize(surfacew, surfaceh);
FRT_1_Albedo.Filter := atPoint;
FRT_2_Position := TAdRenderTargetTexture.Create(FDraw);
[...]
FRT_3_Normal := TAdRenderTargetTexture.Create(FDraw);
[...]
FRT_4_Composite := TAdRenderTargetTexture.Create(FDraw);
[...]
Schritt 2: Der G-Buffer-Fill Shader (HLSL/Cg)
Der hier gezeigte Shader ist nur ein einfaches Beispiel. Unter anderem kann durch das geschickte Weglassen von Daten (Berechnen von X, Y aus den Z und den Screen-Coordinaten im Lighting-Shader) Bandbreite eingespart werden.
//Fragment Shader
struct fs_res {
float4 position: COLOR1;
float4 normal: COLOR2;
float4 albedo: COLOR0;
};
fs_res fs_std_geometry(
float3 viewpos: TEXCOORD0,
float3 normal: TEXCOORD1
)
{
fs_res OUT;
OUT.position = float4(viewpos, 1.0f);
OUT.normal = float4(normal, 1.0f);
OUT.albedo = float4(1.0f, 1.0f, 1.0f , 1.0f);
return OUT;
}
//Vertex Shader
struct vs_res {
float4 pos: POSITION;
float3 viewpos : TEXCOORD0;
float3 normal: TEXCOORD1;
};
vs_res vs_std_geometry(
float3 position : POSITION,
float3 normal: NORMAL,
uniform float4x4 modelview,
uniform float4x4 modelviewproj
)
{
vs_res OUT;
OUT.pos = mul(float4(position, 1.0f), modelviewproj);
OUT.viewpos = mul(float4(position, 1.0f), modelview);
OUT.normal = normalize(mul(float4(normal, 0.0f), modelview));
return OUT;
}
Schritt 3: Daten in G-Buffer Rendern
//Activate the G-Buffers
FDraw.AdAppl.SetRenderTarget(0, FRT_1_Albedo.Texture);
FDraw.AdAppl.SetRenderTarget(1, FRT_2_Position.Texture);
FDraw.AdAppl.SetRenderTarget(2, FRT_3_Normal.Texture);
FDraw.AdAppl.Viewport := AdRect(0, 0, surfacew, surfaceh);
FDraw.AdAppl.ClearSurface(AdRect(0, 0, surfacew, surfaceh), [alColorBuffer, alZBuffer,
alStencilBuffer], Ad_ARGB(0, 0, 0, 0), 1, 1);
//Activate the shaders
FTransformShader.FragmentShader.BindEffect;
FTransformShader.VertexShader.BindEffect;
FScene.Draw(FCamera, nil, 0);
//Deactivate the shaders
FTransformShader.VertexShader.UnbindEffect;
FTransformShader.FragmentShader.UnbindEffect;
FDraw.AdAppl.SetRenderTarget(2, nil);
FDraw.AdAppl.SetRenderTarget(1, nil);
FDraw.AdAppl.SetRenderTarget(0, nil);
Schritt 4: Einfacher Direktionaler Licht-Shader
Das zur Beleuchtung verwendete Quad liegt in diesem (einfachen) Fall bereits projeziert in normierten Koordinaten vor.
struct ps_light_res {
float4 color: COLOR0;
};
ps_light_res ps_light_geometry(
float3 screenpos: TEXCOORD0,
float3 viewpos: TEXCOORD1,
float3 lightdir: TEXCOORD2,
uniform sampler sPosition,
uniform sampler sNormal,
uniform sampler sAlbedo,
uniform float4 lightcolor
)
{
ps_light_res OUT;
float4 col = tex2D(sAlbedo, float2(screenpos.x, -screenpos.y));
float4 normal = tex2D(sNormal, float2(screenpos.x, -screenpos.y));
OUT.color = float4((lightcolor * col * clamp(dot(normal.xyz, lightdir), 0.0f, 1.0f)).rgb, 1.0f);
return OUT;
}
struct vs_light_res {
float4 pos: POSITION;
float3 screenpos: TEXCOORD0;
float3 viewpos: TEXCOORD1;
float3 lightdir: TEXCOORD2;
};
vs_light_res vs_light_geometry(
float3 position : POSITION,
uniform float4x4 modelview,
uniform float4x4 modelviewproj,
uniform float3 lightdir
)
{
vs_light_res OUT;
OUT.pos = float4(position, 1.0f);
OUT.screenpos = position * float3(0.5f, 0.5f, 0.0f) + float3(0.5f, 0.5f, 0.0f);//mul(pos, modelviewproj);
OUT.viewpos = mul(position, modelview);
OUT.lightdir = normalize(mul(float4(lightdir, 0.0f), modelview));
return OUT;
}
Schritt 5: Lichtquellen in das Composite RT rendern
//Render a directional light using a quad
FDraw.AdAppl.SetRenderTarget(0, FRT_4_Composite.Texture);
FDraw.AdAppl.Viewport := AdRect(0, 0, surfacew, surfaceh);
FDraw.AdAppl.ClearSurface(AdRect(0, 0, surfacew, surfaceh), [alColorBuffer, alZBuffer,
alStencilBuffer], Ad_ARGB(255, 0, 0, 0), 1, 1);
FLightShader.FragmentShader.SetParameter('sAlbedo', FRT_1_Albedo.Texture);
FLightShader.FragmentShader.SetParameter('sPosition', FRT_2_Position.Texture);
FLightShader.FragmentShader.SetParameter('sNormal', FRT_3_Normal.Texture);
FLightShader.FragmentShader.BindEffect;
FLightShader.VertexShader.BindEffect;
//Render a blue light
FLightShader.FragmentShader.SetParameter('lightcolor', Ad_ARGB(255, 50, 50, 100));
FLightShader.VertexShader.SetParameter('lightdir', AcVector_Normalize(AcVector3(2.0, 2.0, -1.0)));
FQuad.BlendMode = bmAdd;
FQuad.Draw(nil);
FLightShader.FragmentShader.UnbindEffect;
FLightShader.VertexShader.UnbindEffect;
Schritt 6: Das Ergebnis Zeichnen
Nun muss nur noch das Ergebnis im "Composite"-RT gezeichnet werden - oder weitere Post-Processing Maßnahmen durchgeführt werden.