Höhenbasierter Nebel per Post

Transcription

Höhenbasierter Nebel per Post
Höhenbasierter Nebel per Post-Processing
Problem
Darstellung eines realistischen Nebels mit unterer
und oberer Begrenzung in einer Echtzeit-3DAnwendung.
Die gängigen 3D-APIs (Direct3D und OpenGL)
unterstützen nur einfachen entfernungsabhängigen
Nebel, d.h. die gerenderten Pixel werden abhängig
von ihrer Entfernung zur Kamera mehr oder
weniger mit der Farbe des Nebels eingefärbt.
Diese Methode wird direkt von allen 3D fähigen
Grafikkarten unterstützt und geschieht ohne
Performanceverlust. Ein höhenbasierter Nebel
lässt sich damit allerdings nicht umsetzen.
Um die 'Vernebelung' eines Pixels bei Abbildung 1 Foto eines realen Nebels
höhenbasiertem Nebel zu berechnen, muss man
die Entfernung ermitteln, die ein Strahl von der Kamera zum Pixel innerhalb des Nebels zurücklegt.
Über einen Vertexshader lässt sich dies ohne weiteres auf Vertexebene umsetzen, dies hat aber
einige Nachteile:
•
Da die 'Vernebelung' nur pro Vertex berechnet wird und für die einzelnen Pixel des Polygons
interpoliert wird, ergeben sich nur befriedigende Ergebnisse, wenn sämtliche Objekte aus
ausreichend vielen Polygonen modeliert sind. Z.B. darf eine Wolkenschicht über dem Nebel
nicht einfach aus zwei Polygonen bestehen, sondern muss in viele einzelne Dreiecke unterteilt
werden.
•
Bei der Darstellung eines jeden Objekts muss der Nebel berücksichtigt werden. D.h. Objekte mit
unterschiedlicher Rendermethode benötigen jeweils einen angepassten Vertexshader zur
Berechnung des Nebels. Objekte, die von sich aus bereits einen eigenen Vertex-/Pixelshader
verwenden, müssen in mehreren Durchgängen gerendert werden. Die Folge davon ist, dass sich
die Programmierung des höhenbasiertem Nebels durch die gesamte Engine zieht und keine
getrennte Einheit ist.
Idee
Um höhenbasierten Nebel umzusetzen und dabei Probleme mit der oben erwähnten Methode zu
vermeiden, bietet sich Post-Processing an. Anstatt den Nebel beim Rendern eines jeden Pixels zu
berücksichtigen, wird der Nebel erst auf das fertige Bild aufgetragen, d.h. das gerenderte Bild wird
nachbearbeitet.
Anhand der Tiefeninformation des Bildes und der beiden Begrenzungsebenen des Nebels lässt sich
durch einfache Rechnung die Nebelstärke jedes Pixels berechnen.
Abbildung 2 Tiefeninformation der
Landschaft
Abbildung 3 Tiefeninformation der
unteren Nebelbegrenzungsebene
Abbildung 4 Tiefeninformation der
oberen Nebelbegrenzungsebene
1. Minimum der Tiefeninformation der Landschaft und der unteren Nebelbegrenzungsebene
errechnen:
Abbildung 5 Minimum aus Tiefe der Landschaft und der
unteren Nebelbegrenzungsebene
2. Differenz aus der Tiefeninformation der oberen Nebelbegrenzungsebene und dem Ergebnis aus
1. bilden. Anschließend das Ergebnis bis zur gewünschten Nebelstärke verstärken:
Abbildung 6 Berechnete Nebelstärke aus den
Tiefeninformationen
Das Ergebnis ist ein Alpha-Layer zum Einfärben des gerenderten Bilds mit einer Nebelfarbe.
Theoretische Umsetzung
Gewinnung der Tiefeninformationen
Eigentlich sollte es kein Problem sein, an die Tiefeninformationen einer Szene zu kommen, da
Grafikkarten diese Informationen intern bereits gespeichert haben, um verdeckte Pixel nicht zu
zeichnen. Dieser z-Buffer (oder Depth-Buffer) ist allerdings auf neueren Grafikkarten kein
einfaches Graustufenbild, sondern eine komplexe Datenstrukur. Ein einfaches Umwandeln des zBuffers in eine Textur ist damit nicht möglich. Auch in einem Pixelshader kann nicht auf den
bestehenden Tiefenwert eines Pixels der Szene zugegriffen werden. Folglich muss die
Tiefeninformation separat gewonnen werden, in dem die gesamte Szene nach dem normalen
Rendern nochmals gerendert und die Tiefe jedes Pixels in einer Textur gespeichert wird. Dies kann
mit zwei verschiedenen Methoden erledigt werden:
1. Eine sehr einfache Methode ist, alle Objekte mit einer schwarzen Farbe zu rendern und einen
weißen, linearen, entfernungsabhängigen Standardnebel zu aktivieren, der von der Kamera bis
zum Horizont reicht. Dieses Verfahren erzeugt ein Bild mit den Tiefeninformationen, hat aber
einen entscheidenden Nachteil: Die Tiefeninformation wird nur als 8-Bit-Grauwert gespeichert
und damit existieren nur maximal 256 verschiedene Tiefen. Das mag zur Darstellung der Tiefe
reichen, versagt aber, wenn die Differenz zur Feststellung der Nebelstärke berechnet werden
muss. Der Nebel wird sehr blockig und fehlerhaft, womit diese Methode bei großen Sichtweiten
nicht anwendbar ist.
2. Sinnvoller, aber auch aufwändiger, ist dieses Verfahren: Anstatt die Tiefeninformation nur als
Grauwert zu speichern, wird der komplette RGB-Farbraum verwendet. Damit stehen 24-Bit zur
Verfügung, die für jede erdenkliche Sichtweite ausreichen sollten. Ein Bild mit einer so
kodierten Tiefeninformation lässt sich leider nicht mit einem einfachen entfernungsabhängigen
Nebel erzeugen, sondern benötigt einen Vertex- und Pixelshader. Diese brauchen aber nur die
Geometrieinformationen eines Objekts, womit die Shader praktisch zu allen Objekten einer
Szene kompatibel sind und keine speziellen Anpassungen benötigen.
Da die erste Methode ausscheidet, wird nachfolgend nur die zweite beschrieben.
Die Aufgabe ist, die Tiefeninformation, die als Fließkommazahl vorliegt, in drei Integerwerte mit je
8-Bit umzuwandeln. Dies kann zum größten Teil in einem Vertexshader geschehen, da Tiefen ohne
weiteres auf einem Polygon interpoliert werden können. Der Pseudocode für den Vertexshader sieht
folgendermaßen aus:
Depth = Tiefe des Pixel (0.0=Kamera, 1.0=Horizont);
TextureCoordinate0.u = Depth * 1.0;
TextureCoordinate1.u = Depth * 256.0;
TextureCoordinate2.u = Depth * 65536.0;
Anhand der drei erstellten Texturkoordinaten kann im Pixelshader auf die Farben von diesen drei
Texturen zugegriffen werden, die einen linearen Farbverlauf der drei Grundfarben enthalten:
Abbildung 7 Roter Farbverlauf
Abbildung 8 Grüner Farbverlauf Abbildung 9 Blauer Farbverlauf
In dem man für den Texturzugriff 'wrap' (Umbruch) einstellt, wird durch den Texturzugriff im
Prinzip ein Modulo von 1.0 berechnet, also nur die Nachkommastellen der drei Texturkoordinaten
verwendet. Pro Rotwert werden 256 Grünwerte verwendet und pro Grünwert 256 Blauwerte. Somit
kommt man zu denen im Vertexshader verwendeten Faktoren.
Abbildung 10 Bild mit kodierter Tiefe
Der Rotkanal ist auf diesem Bild vorhanden, aber auf Grund der geringen Sichtweite nur in sehr
dunklen Tönen. Die einzelnen Farbkanäle dieses Bilds sehen folgendermaßen aus:
Abbildung 11 Kodierte Tiefe
(Rotkanal)
Abbildung 12 Kodierte Tiefe
(Grünkanal)
Abbildung 13 Kodierte Tiefe
(Blaukanal)
Mit folgender einfacher Formel kann die Tiefe wieder aus dem Farbwert eines Pixels dekodiert
werden:
Depth = Pixel.r/1.0 + Pixel.g/256.0 + Pixel.b/65536.0;
Diese Rechnung entspricht einem Punktprodukt des Pixels mit dem Vektor
(1.0, 1.0/256.0, 1.0/65536.0) und kann somit ohne Probleme in einem Pixelshader berechnet
werden.
Mit dem Erzeugen der drei Texturen mit den kodierten Tiefeninformationen der Szene, der unteren
und der oberen Nebelbegrenzungsebene sind die Vorarbeiten zur Erzeugung der höhenbasiertem
Nebels abgeschlossen.
Kombinierung der Szenentiefe mit der Tiefe der unteren Nebelbegrenzungsebene
Um die untere Begrenzung des Nebels zu berücksichtigen, muss sie mit der Tiefeninformation der
Szene kombiniert werden. Für die Berechnung des Nebels sind die Tiefeninformationen der Szene
unterhalb der unteren Nebelbegrenzungsebene unerheblich. Es muss also von beiden Bildern für
jeden Pixel jeweils die kleinere Tiefe übernommen werden. Dies kann komplett in einem
Pixelshader geschehen:
Diff.rgb = ScenePixel.rgb – BackPlanePixel.rgb;
DepthDiff = Diff.r/1.0 + Diff.g/256.0 + Diff.b/65536.0;
if (DepthDiff >= 0) OutputPixel.rgb = BackPlanePixel.rgb;
else OutputPixel.rgb = ScenePixel.rgb;
Berechnung der Nebelstärke
Anschließend kann ähnlich einfach durch Kombination mit der Tiefeninformation der oberen
Nebelbegrenzungsebene die Nebelstärke für jeden Pixel berechnet werden, da nur die Differenz der
beiden Bilder erzeugt werden muss:
Diff.rgb = SceneAndBackPlanePixel.rgb – FrontPlanePixel.rgb;
DepthDiff = Diff.r/1.0 + Diff.g/256.0 + Diff.b/65536.0;
DepthDiff = DepthDiff * FogDensity;
OutputPixel.rgb = FogColor.rgb
OutputPixel.a = DepthDiff;
Die Nebelstärke wird als Alphawert verwendet, womit der Nebel auf die darunterliegende Szene
korrekt aufgetragen wird.
Damit ist der höhenbasierte Nebel per Post-Processing fertig, aber natürlich gibt es noch Probleme
und Sonderfälle zu berücksichtigen.
Probleme
Halbtransparente Objekte in der Szene
Die oben beschriebene Methode zur Gewinnung der Tiefeninformationen der Szene funktioniert nur
bei soliden Objekten. Bei Objekten mit halbdurchsichtigen Flächen (entweder durch Alphatextur
oder bei manuellen Alphawerte) müssen diese Alphawerte ebenfalls berücksichtig werden. Leider
können in den Bildern keine Tiefen von halbdurchsichtigen Flächen gespeichert werden, da pro
Pixel nur eine Tiefe vorhanden sein kann. Foglich muss bei solchen Objekte eine Schwelle
verwendet werden, die festlegt, ob ein Pixel komplett transparent oder komplett sichtbar ist. Je
nachdem wird die Tiefe des vorhandenen oder des neuen Pixels gespeichert.
Damit werden halbtransparente Flächen zwangsläufig falsch 'eingenebelt'. Dies lässt sich nicht
vermeiden, aber dürfte in vielen Fällen nicht auffallen.
Beliebige Kamerapositionen
Das bis jetzt beschriebene Verfahren zur Nebelberechnung funktioniert nur, wenn sich die Kamera
oberhalb der oberen Nebelgrenze befindet. Diese Einschränkung lässt sich leicht aufheben, indem
die Methode an die drei möglichen Kamerazuständen angepasst wird:
1. Kamera befindet sich oberhalb der oberen Nebelgrenze (wie oben beschrieben):
•
Szenentiefen mit Tiefen der unteren Nebelbegrenzungsebene kombinieren (Minimum)
•
Ergebnis mit Tiefen der oberen Nebelbegrenzungsebene kombinieren (Differenz)
2. Kamera befindet sich unterhalb der unteren Nebelgrenze:
•
Szenentiefen mit Tiefen der oberen Nebelbegrenzungsebene kombinieren (Minimum)
•
Ergebnis mit Tiefen der unteren Nebelbegrenzungsebene kombinieren (Differenz)
3. Kamera befindet zwischen der unteren und oberen Nebelgrenze:
•
Szenentiefen mit Tiefen der unteren und oberen Nebelbegrenzungsebene kombinieren
(Minimum)
•
Ergebnis mit einem schwarzen Bild kombinieren (Differenz)
Fehler durch Clipplane der Kamera vermeiden
Jede Kamera in einer üblichen 3DAnwendung besitzt einen Sichtkegel,
dessen Spitze sich an der Position der
Kamera befindet. Innerhalb dieses Kegels
befinden sich zwei Ebenen, die eine
begrenzt die Sichtweite (FarClipPlane), die
andere verhindert Störungen knapp vor der
Kamera (NearClipPlane). Nur Objekte
zwischen diesen beiden Ebenen und
innerhalb des Kegels werden gerendert.
Ein Problem taucht nun auf, wenn sich die
Kamera nah an der Nebelgrenze befindet
und die Ebene die NearClipPlane schneidet. Abbildung 14 Clip-Problem
Im unteren Bereich des Bildes werden
damit keine Tiefeninformationen der Nebelgrenze erzeugt, was zu Fehlern in der Berechnung des
Nebels führt.
Eine einfache (etwas unsaubere) Lösung ist, die Nebelgrenzen stets so zu korrigieren, dass sie nicht
die NearClipPlane schneiden. Da die NearClipPlane im Allgemeinen sehr nah an der Kamera ist,
fällt die Korrektur der Nebelgrenzen in der Praxis kaum auf. Wer darauf achtet, wird es allerdings
bemerken.
Das selbe Problem existiert natürlich auch ander unteren Nebelgrenze.
Praktische Umsetzung
Die nun folgenden Codeausschnitte stammen aus einer C++, Direct3D, Pixelshader1.1,
Vertexshader1.1 Anwendung, sie sollten aber ohne weiteres in andere Programmiersprachen und
3D-APIs übertragbar sein. Diese Ausschnitte sollen nur Anhaltspunkte für die Programmierung sein
und sind mit Sicherheit nicht eins zu eins in anderen Anwendung verwendbar.
Rendern der Tiefeninformationen
Einstellungen und Aufruf des Renderers:
//set constants for depth vertex shader
//factors for converting depth in r, g, b
pD3DDevice->SetVertexShaderConstantF(22,
D3DXVECTOR4(1.0f, 256.0f, 65536.0f, 1.0f/MAX_VIEWDISTANCE), 1);
//world*view*projection matrix to calculate vertex clip space position
D3DXMatrixTranspose( &matTemp, &matWorldViewProj);
pD3DDevice->SetVertexShaderConstantF(0, matTemp, 4);
//set encode textures (1-3) and optional an alpha texture (0)
pD3DDevice->SetTexture(0, NULL);
pD3DDevice->SetTexture(1, pRampRedTexture);
pD3DDevice->SetTexture(2, pRampGreenTexture);
pD3DDevice->SetTexture(3, pRampBlueTexture);
//enable testing... but not blending (MIN_ALPHA = treshold)
pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, true);
pD3DDevice->SetRenderState(D3DRS_ALPHAREF, MIN_ALPHA);
pD3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
//set wrap mode and disable filtering for all ramp textures
pD3DDevice->SetSamplerState(1, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
pD3DDevice->SetSamplerState(1,
pD3DDevice->SetSamplerState(1,
pD3DDevice->SetSamplerState(1,
pD3DDevice->SetSamplerState(1,
pD3DDevice->SetSamplerState(1,
D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
D3DSAMP_ADDRESSW, D3DTADDRESS_WRAP);
D3DSAMP_MINFILTER, D3DTEXF_POINT);
D3DSAMP_MAGFILTER, D3DTEXF_POINT);
D3DSAMP_MIPFILTER, D3DTEXF_NONE);
pD3DDevice->SetSamplerState(2,
pD3DDevice->SetSamplerState(2,
pD3DDevice->SetSamplerState(2,
pD3DDevice->SetSamplerState(2,
pD3DDevice->SetSamplerState(2,
pD3DDevice->SetSamplerState(2,
D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
D3DSAMP_ADDRESSW, D3DTADDRESS_WRAP);
D3DSAMP_MINFILTER, D3DTEXF_POINT);
D3DSAMP_MAGFILTER, D3DTEXF_POINT);
D3DSAMP_MIPFILTER, D3DTEXF_NONE);
pD3DDevice->SetSamplerState(3,
pD3DDevice->SetSamplerState(3,
pD3DDevice->SetSamplerState(3,
pD3DDevice->SetSamplerState(3,
pD3DDevice->SetSamplerState(3,
pD3DDevice->SetSamplerState(3,
D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP);
D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP);
D3DSAMP_ADDRESSW, D3DTADDRESS_WRAP);
D3DSAMP_MINFILTER, D3DTEXF_POINT);
D3DSAMP_MAGFILTER, D3DTEXF_POINT);
D3DSAMP_MIPFILTER, D3DTEXF_NONE);
//set depth pixel and vertex shader
pD3DDevice->SetPixelShader(pDepthPixelShader);
pD3DDevice->SetVertexShader(pDepthVertexShader);
pD3DDevice->SetVertexDeclaration(pDepthVertexDecl);
//begin rendering depth texture
LPDIRECT3DSURFACE9 pDepthSurface = NULL;
if (FAILED(hr=pDepthTexture->GetSurfaceLevel(0,&pDepthSurface)))
return hr;
if (FAILED(hr=pRenderToSurface->BeginScene(pDepthSurface,NULL)))
return hr;
//clear device
pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xFFFFFFFF, 1.0f, 0.0f);
//render all things of szene
/*TODO*/
//end rendering texture
if (FAILED(hr=pRenderToSurface->EndScene(D3DX_FILTER_NONE)))
return hr;
SAFE_RELEASE(pDepthSurface);
Vertexshader:
vs.1.1
dcl_position v0
dcl_texcoord v1
; Transform
dp4 oPos.x,
dp4 oPos.y,
dp4 oPos.z,
dp4 oPos.w,
position to clip space and output it
v0, c0
v0, c1
v0, c2
v0, c3
; calculate depth
dp4 r1.x, v0, c2
; scale down
mul r1.x, r1.x, c22.w
; set value to other coordinates
mov r1.y, r1.x
mov r1.z, r1.x
; calculate look up table coordinates for depth
mul r0.xyz, r1.xyz, c22.xyz
; output as texture coordinate for red, green and blue ramp texture
mov oT1.x, r0.x
mov oT2.x, r0.y
mov oT3.x, r0.z
; output alpha texture coordinate
mov oT0, v1
Pixelshader:
ps.1.1
; get textures
tex t0 ; alpha texture
tex t1 ; red color ramp
tex t2 ; green color ramp
tex t3 ; blue color ramp
; put colors from ramps in output
mov r0, t1
add r0, r0, t2
add r0, r0, t3
; set alpha from alpha texture
mov r0.a, t0
Kombination der Szenentiefen mit den Tiefen der hinteren Nebelbegrenzungsebene
Einstellungen und Aufruf des Renderers:
#define D3DFVF_POSTPROCESSINGVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX3)
struct POSTPROCESSINGVERTEX
{
D3DXVECTOR3 p;
float rhw;
D3DXVECTOR2 t0;
D3DXVECTOR2 t1;
};
//set post processing settings
pD3DDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
pD3DDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
pD3DDevice->SetFVF(D3DFVF_POSTPROCESSINGVERTEX);
//set back pixel shader
pD3DDevice->SetPixelShader(pBackPixelShader);
//set pixel shader constant
pD3DDevice->SetPixelShaderConstantF(1, D3DXCOLOR(0,0,0,0.5f), 1);
pD3DDevice->SetPixelShaderConstantF(3,
D3DXVECTOR4(1.0f, 1.0f/256.0f, 1.0f/65536.0f, MAX_VIEWDISTANCE), 1);
//set textures
pD3DDevice->SetTexture(0, pDepthTexture);
pD3DDevice->SetTexture(1, pBackFogLayerTexture);
//combine back fog layer with depth texture
LPDIRECT3DSURFACE9 pDepthAndBackFogLayerSurface = NULL;
if (FAILED(hr=pDepthAndBackFogLayerTexture->GetSurfaceLevel(0,&pDepthAndBackFogLayerSurface)))
return hr;
if (FAILED(hr=pRenderToSurface->BeginScene(pDepthAndBackFogLayerSurface,NULL)))
return hr;
//clear device
pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xFFFFFFFF, 1.0f, 0.0f);
//set vertex stream
pD3DDevice->SetStreamSource(0, pPostProcessingVB, NULL, sizeof(POSTPROCESSINGVERTEX));
//render screen quad
if (FAILED(hr=pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2)))
return hr;
//end rendering texture
if (FAILED(hr=pRenderToSurface->EndScene(D3DX_FILTER_NONE)))
return hr;
SAFE_RELEASE(pDepthAndBackFogLayerSurface);
pPostProcessingVB ist ein Rechteck mit den Koordinaten (0, 0, Bildschirmbreite, Bildschirmhöhe).
Pixelshader:
ps.1.1
; get textures
tex t0 ; depth image
tex t1 ; back fog layer
; make difference between both distances
add r1, -t1, t0
; calculate real depth
dp3 r0, r1, c3
; compare against 0.5 (pixel shader 1.1 restriction)
add r0.a, r0, c1
; use nearer depth
cnd r0, r0.a, t1, t0
Kombination der Szenentiefen mit den Tiefen der hinteren Nebelbegrenzungsebene
Einstellungen und Aufruf des Renderers:
#define D3DFVF_POSTPROCESSINGVERTEX (D3DFVF_XYZRHW | D3DFVF_TEX3)
struct POSTPROCESSINGVERTEX
{
D3DXVECTOR3 p;
float rhw;
D3DXVECTOR2 t0;
D3DXVECTOR2 t1;
};
//set post processing settings
pD3DDevice->SetRenderState(D3DRS_ZENABLE, FALSE);
pD3DDevice->SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
pD3DDevice->SetFVF(D3DFVF_POSTPROCESSINGVERTEX);
//enable alpha blending
pD3DDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
pD3DDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA);
pD3DDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA);
pD3DDevice->SetRenderState(D3DRS_ALPHATESTENABLE, true);
pD3DDevice->SetRenderState(D3DRS_ALPHAREF, MIN_ALPHA);
pD3DDevice->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
//set final pixel shader
pD3DDevice->SetPixelShader(pPixelShaderFinal);
//set pixel shader constant
pD3DDevice->SetPixelShaderConstantF(3,
D3DXVECTOR4(1.0f, 1.0f/256.0f, 1.0f/65536.0f, MAX_VIEWDISTANCE), 1);
//set textures
pD3DDevice->SetTexture(0, pDepthAndBackFogLayerTexture);
pD3DDevice->SetTexture(1, pFrontFogLayerTexture);
//set vertex stream
pD3DDevice->SetStreamSource(0, pPostProcessingVB, NULL, sizeof(POSTPROCESSINGVERTEX));
//render quad
if (FAILED(hr=pD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2)))
return hr;
pPostProcessingVB ist ein Rechteck mit den Koordinaten (0, 0, Bildschirmbreite, Bildschirmhöhe).
Pixelshader:
ps.1.1
; pixel color from depth + back fog layer texture
tex t0
; get pixel color from front fog layer
tex t1
; get difference of both depth
add t2, t0, -t1
; transform rgb depth to real depth
dp3 t3, t2, c3
; more
add_x4
add_x4
add_x4
power... hr, hr, hr
t3.a, t3, t3
t3.a, t3, t3
t3.a, t3, t3
; set color and density
mov r0, c0
; set alpha = depth
mul r0.a, r0, t3
Ergebnis
Ein mit diesem Verfahren erzeugtes Bild sieht folgendermaßen aus:
Abbildung 15 Ergebnis
Hinweise
Die Beispielbilder und der Code stammen aus der Anwendung
(http://www.scapemaker.de.vu), für die dieses Verfahren entwickelt wurde.
ScapeMaker
Grundlegende Ideen wurden aus der Nvidia-Präsentation 'Direct3D Special Effects' entnommen.
Ersteller und Rechteinhaber für dieses Dokument:
Dirk Plate ([email protected])