www.rolandk.de
- Aktuelle Themen zu .Net -
Achtung: Hier handelt es sich um meine alte Seite.
Die aktuelle ist unter folgendem Link erreichbar: www.rolandk.de/wp/
Home Tutorials DirectX10 Basics Chapter 5: Tiefe




















































Chapter 5: Tiefe
Sonntag, den 19. Juli 2009 um 10:43 Uhr

 

 

Into the deep

Das große Thema dieses Kapitels ist Tiefe. Beim letzten mal haben wir einen 3D-Würfel gezeichnet, doch was passiert, wenn wir mehrere Zeichnen? Oder anders gefragt, was passiert, wenn wir mehrere Zeichnen, welche hintereinander angeordnet sind? Das erste der beiden folgenden Screenshots ist das Ergebnis mit aktuellem Coding:

 


Rendern ohne DepthBuffer

Rendern mit DepthBuffer

 

Das hintere Objekt wird vor das vordere gezeichnet, aber warum? Wir sind doch in der 3D-Welt, das muss die Grafikkarte doch automatisch begreifen, oder? Ne, ganz so einfach ist es dann doch nicht. Dieser Fehlerfall kommt zustande, da ich zuerst das vordere Objekt gezeichnet habe und danach das Hintere. Die Pixel des hinteren Objekts haben somit die des vorderen überschrieben. Verhindert werden kann dieses Verhalten mit einem DepthBuffer (siehe rechtes Bild). Mittels dieses Buffers merkt sich die Grafikkarte für jeden Pixel, wie weit er von der Kamera entfernt ist. Soll nun an einer Stelle ein Pixel geschrieben werden, wird überprüft, ob dieser vor dem Pixel liegt, welcher an der gleichen Stelle schon da ist. Falls nicht, bleibt der alte Pixel im Buffer. Der DepthBuffer ist optional, da er beispielsweise bei transparenten Objekten ausgeschaltet sein muss.

 

Umsetzung

Im Gegensatz zu den letzten Kapiteln benötigen wir keinerlei Änderungen an dem Shader oder dem 3D-Objekt. Die Verwaltung des DepthBuffers ist allein die Aufgabe des Hauptfensters. Aber was genau ist der DepthBuffer eigentlich? Im Prinzip ist er das Gleiche wie ein RenderTarget, bloß werden anstatt der Farben Tiefeninformationen gespeichert. Im Sourcecode erkennt man diese große Ähnlichkeit sofort wieder, da das Anlegen eines DepthBuffers genauso abläuft, wie das Erstellen eines RenderTargets. Fangen wir also einfach mal mit der benötigten Variablen im Hauptfenster an:

  1. private DX10.DepthStencilView m_renderTargetDepth;

Mit dem DepthStencilView Objekt wird der DepthBuffer vom Device angesprochen. Der DepthBuffer selbst ist, genauso wie das RenderTarget, schlicht eine 2D-Textur. In der Methode Initialize3D müssen wir daher diese Textur erstellen. Nur wie funktioniert das jetzt? Bis jetzt haben wir unsere Texturen immer irgendwo erstellen lassen (Das RenderTarget von der SwapChain, Die Textur des 3D-Objekts aus einer Datei). Hier müssen wir zum ersten Mal selbst eine neue Textur anlegen. Hört sich zwar vielleicht kompliziert an, ist es aber eigentlich gar nicht. Zunächst benötigen wir ein Texture2DDescription Objekt, welches die neue Textur beschreibt:

  1. DX10.Texture2DDescription depthBufferDesc = new DX10.Texture2DDescription();
  2. depthBufferDesc.Width = this.Width;
  3. depthBufferDesc.Height = this.Height;
  4. depthBufferDesc.MipLevels = 1;
  5. depthBufferDesc.ArraySize = 1;
  6. depthBufferDesc.Format = DXGI.Format.D32_Float;
  7. depthBufferDesc.Usage = DX10.ResourceUsage.Default;
  8. depthBufferDesc.SampleDescription = new DXGI.SampleDescription(1, 0);
  9. depthBufferDesc.BindFlags = DX10.BindFlags.DepthStencil;
  10. depthBufferDesc.CpuAccessFlags = DX10.CpuAccessFlags.None;
  11. depthBufferDesc.OptionFlags = DX10.ResourceOptionFlags.None;

Sieht jetzt zwar nach viel aus, das meiste ergibt sich aber von selbst. Width und Height setzen wir genauso wie beim RenderTarget auch, also auf die Größe des Ausgabefensters. Bei MipLevels handelt es sich um Detailstufen. Da wir so etwas hier nicht benötigen, setzen wir diesen Wert einfach auf 1. Die ArraySize setzen wir ebenfalls auf 1, weil es sich hier nicht um eine TextureArray handelt. Bei der Eigenschaft Format wird's jetzt interessant, diese setzen wir nämlich auf D32_Float. Dabei handelt es sich um eine 32-Bit Fließkommazahl (=double) und nicht um eine Farbe. Weiter geht's mit dem Usage-Wert, welchen wir natürlich auf Default setzen können. Die CPU benötigt schließlich keinen Zugriff auf den DepthBuffer. Auch die SampleDescription setzen wir auf einen Standardwert, etwas anderes wird nur benötigt, falls Multisampling verwendet wird. Bei der Eigenschaft BindFlags müssen wir angeben, dass es sich um einen DepthBuffer handeln wird. Wie schon gesagt, braucht die CPU keinen Zugriff auf den DepthBuffer, daher setzen wir auch den Wert für CpuAccessFlags auf None. Zu guter letzt natürlich noch das Property OptionFlags. Dieses erhält einen Standardwert von None. 

Jetzt können wir die Textur auch schon anlegen. Das ganze sieht dann so aus:

  1. DX10.Texture2D depthBuffer = new DX10.Texture2D(m_device, depthBufferDesc);
  2. m_renderTargetDepth = new DX10.DepthStencilView(m_device, depthBuffer);

In der ersten Zeile wird die Textur für den Depthbuffer erstellt. Da wir später noch eine View auf den Buffer benötigen, erzeugen wir diese gleich in der nächsten Zeile. Als nächstes folgt eine kleine Änderung:

  1. // m_device.OutputMerger.SetTargets(m_renderTarget);
  2. m_device.OutputMerger.SetTargets(m_renderTargetDepth, m_renderTarget);

Zusätzlich zum RenderTarget müssen wir jetzt auch den DepthBuffer übergeben. Da wir das in diesem Beispiel bereits in der LoadResources Methode machen, brauchen wir uns später beim Rendern nicht mehr darum zu kümmern. 

Mit der LoadResources Methode sind wir durch, was jetzt noch folgt, ist OnPaint. Mit DepthBuffer sieht der Inhalt dieser Methode jetzt so aus:

  1. base.OnPaint(e);
  2.  
  3. if (m_initialized)
  4. {
  5. m_device.ClearRenderTargetView(
  6. m_renderTarget,
  7. new Color4(Color.CornflowerBlue));
  8. m_device.ClearDepthStencilView(
  9. m_renderTargetDepth,
  10. DX10.DepthStencilClearFlags.Depth,
  11. 1f, 0);
  12.  
  13. m_simpleBox.Render(m_device, m_worldMatrix, m_viewProjMatrix);
  14.  
  15. m_swapChain.Present(0, DXGI.PresentFlags.None);
  16. }

Fast alles beim alten, aber eben nur fast. Neu ist hier der Aufruf von ClearDepthStencilView in der 6. Zeile. Ähnlich wie ClearRenderTargetView setzt diese Methode den DepthBuffer zurück auf seinen Ursprungszustand. Diese Methode muss vor jedem Renderdurchgang aufgerufen werden, damit nicht alle Tiefeninformationen vom letzten Bild im Buffer sind.

 

Ein zweites Objekt zeichnen

Im Prinzip sind wir mit dem Kapitel fertig, doch wir sehen bis jetzt keinen Unterschied zu vorher. Damit wir die Wirkung des Depthbuffers auch sehen können, brauchen wir noch ein zweites 3D-Objekt. Um den Aufwand dafür möglichst gering zu halten, werden wir lediglich das bereits vorhandene Objekt 2 mal zeichnen. Dazu benötigen wir eine neue World-Matrix, welche wir noch als Membervariable anlegen müssen:

  1. private Matrix m_worldMatrix2;

Anschließend müssen wir der neuen Matrix in der LoadResources Methode noch einen Wert geben. Die Rotation belassen wir genauso wie beim letzten Mal, wir zeichnen das Objekt lediglich etwas weiter hinten:

  1. Matrix.Translation(0f, 3f, 0f) * Matrix.RotationYawPitchRoll(0.85f, 0.85f, 0f);

Jetzt benötigen wir nur noch eine Zeile in der OnPaint Methode:

  1. m_simpleBox.Render(m_device, m_worldMatrix2, m_viewProjMatrix);

Wichtig ist, dass diese Zeile nach dem Rendern des ersten Objekts ausgeführt wird, ansonsten würde es mit und ohne DepthBuffer genauso aussehen. Das Ergebnis sollte jetzt mit dem Screenshot am Anfang dieser Seite sehr große Ähnlichkeit haben.

table style=/p

 

Kommentar hinzufügen

Ihr Name:
Kommentar: