www.codeworx.org/directx_tuts/Teil 3: Cartoon Rendering

Teil 3: Cartoon Rendering

Typische Darstellung für Cartoons

Cartoon Rendering besitzt eigentlich keine genaue Darstellung. Man versteht darunter die Darstellung in einem nicht photorealistischen Weg, ähnlich der Darstellung wie in zum Beispiel der Micky Maus. Cartoons besitzen normalerweise eine sehr vereinfachte Schattendarstellung, einfach druch dicke Linien einer Farbe und einer dickeren Linie um das Objekt herum. Hier drei Bilder aus dem Programm für dieses Tutorial.

 

Das linke Bild ist die normale Darstellung. Das mittlere Bild beinhaltet mehr Schatten und das rechte noch zusätzlich mehr des spekulären Bereichs.

Rendern einer Cartoon-Biene

Vorbereitung Teil 1

Ein Vertex unsere Biene sieht so aus:

struct toonvertex 
{ 
   float x, y, z; 
   float nx, ny, nz; 
}; 

Wir benötigen nur die Position und den Normalenvektor. Der ganze Rest, wie zB. die Farbe wird selbst programmiert. Natürlich könnte man auch die Diffuse Farbe in den Mesh (Biene) speichern.

Hier jetzt die VS Deklaration:

DWORD dwToonDecl[] = 
{ 
   D3DVSD_STREAM(0), 
   D3DVSD_REG(0, D3DVSDT_FLOAT3), 
   D3DVSD_REG(1, D3DVSDT_FLOAT3), 
   D3DVSD_CONST(cvs_const,1),*(DWORD*)&c[0],*(DWORD*)&c[1],*(DWORD*)&c[2],*(DWORD*)&c[3],    
   D3DVSD_END() 
}; 

Die Position wird in v0 geladen und der Normalenvektor in v1. Zusätlich laden wir noch nützliche Konstanten. Anstatt aber eine Position einzugeben haben wir aber hier cvs_const angegeben.

Konstanten mit NVASM

Wie wir ja schon wissen verwenden wir nicht DirectX um den VS zu kompilieren, sondern den Shader Assembler von nVidia. Ein Vorteil von diesem ist die Verwendung von Konstanten. Hier jetzt der "normale" Weg ohne Konstanten:

pDev->SetVertexShaderConstant(4, &matTrans, 4) 

Zuerst laden wir irgendeine Matrize (hier die Transformations-Matrize) in den konstanten Memory c4-c7. In der VS-Assembler-Datei müsste man sich nun merken, wo die Matrize beginnt und dies folgendermaßen verwenden:

; Transformiere Position 
dp4 oPos.x, v0, c4 
dp4 oPos.y, v0, c5 
dp4 oPos.z, v0, c6 
dp4 oPos.w, v0, c7

Das heißt, man muss sich merken in welches Register man was schreibt, um später keinen Fehler zu machen. Mit dem NVASM gibt es aber einen Vorteil. Zuerst deklarieren wir einfach Konstanten...

#define cvs_mattrans 4 
#define cvs_mattrans_0 4 
#define cvs_mattrans_1 5 
#define cvs_mattrans_2 6 
#define cvs_mattrans_3 7

..., verwenden diese beim Laden des konstanten Speichers...

pDev->SetVertexShaderConstant(cvs_mattrans, &matTrans, 4) 

...und beim Schreiben des VS:

; Transformiere Position 
dp4 oPos.x, v0, c[cvs_mattrans_0] 
dp4 oPos.y, v0, c[cvs_mattrans_1] 
dp4 oPos.z, v0, c[cvs_mattrans_2] 
dp4 oPos.w, v0, c[cvs_mattrans_3] 

Der Vorteil ist, dass man sich sicher sein kann, kein falsches Register zu verwenden und dass der Code lesbarer wird, da man weiß (durch durchdachte Konstantennamen), was sich in diesem Register befinden.

Vorbereitung Teil 2

Unsere Biene laden wir mit Hilfe der D3DX-Bibliothek zuerst in einen Mesh und speichern dann den Vertex Buffer und Index Buffer separat ab. Dies besprechen wir hier nicht weiter, da dies eigentlich nicht dazugehört. So viel sei gesagt: Die Biene ist in einzelne Bereiche unterteilt, in Sektoren. Mit Hilfe einer Struktur vom Type D3DXATTRIBUTERANGE speichern wir die verschiedenen Abschnitte ab und rendern diese einzeln durch. Dadurch können wir jedem Abschnitt eine eigene Farbe zuordnen.

All diejenigen die schon das DirectX-SDK 8.1 besitzen können mit Hilfe des Programms mview.exe (zu finden im \DX-SDK\bin\dxutils Ordner) den Mesh begutachten und die einzelnen Sektoren betrachten.

Auf zum Rendern

Alles was wir benötigen für unseren VS sind:

  • Vertex Position und Normale
  • Transformations Matrize (von Local zu Projection Space)
  • Licht Richtung
  • Local zu View Matrize
  • Kamera Position (Augenposition)

Zusätzlich sind da noch die Konstanten und Werte die uns hier aber nicht weiter interessieren oder zu denen wir erst später kommen. Wie wir zu diesen Werte kommen, siehe bitte die Quellcodes. Diese sind kommentiert und sollten nicht schwer verständlich sein. Beginnen wir jetzt mit unserem VS.

#include "vs_const.h" 
vs.1.1 

In der Datei vs_const.h haben wir nur die Konstanten definiert um auf den konstanten Speicher zu zugreifen. Dadurch dass nur die Konstanten in dieser Datei sind, können wir sie in den VS und der Quellcodedatei einbinden.

; Transform Pos 
dp4 oPos.x, v0, c[cvs_wvp_0] 
dp4 oPos.y, v0, c[cvs_wvp_1] 
dp4 oPos.z, v0, c[cvs_wvp_2] 
dp4 oPos.w, v0, c[cvs_wvp_3] 

Zuerst transformieren wir die Position ganz normal.

; compute World Space Pos 
dp4 r1.x, v0, c[cvs_matworld_0] 
dp4 r1.y, v0, c[cvs_matworld_1] 
dp4 r1.z, v0, c[cvs_matworld_2] 

Jetzt berechnen wir das ganze noch einmal jedoch ohne Projektions Matrize. Der Vertex ist jetzt in View Space (Kamera/Augenposition ist jetzt in Ursprung).

; Transform Normal 
dp3 r0.x, v1, c[cvs_matworld_0] 
dp3 r0.y, v1, c[cvs_matworld_1] 
dp3 r0.z, v1, c[cvs_matworld_2] 

Auch die Normale in den View Space transformieren.

; Normalize Normal 
dp3 r0.w, r0, r0 
rsq r0.w, r0.w 
mul r0, r0, r0.w

Die Normale normalisieren. Das heißt, der Vektor besitzt jetzt die Länge 1. Achtung: Normalisieren hat nichts mit dem Normalen-Vektor zu zun, auch wenn sich die Namen ziemlich gleichen!

; Vec Point -> Eye 
add r2.xyz, c[cvs_eyepos], -r1 

Jetzt den Vektor von Vertex zur Augenposition berechnen.

; Normalize Vec e 
dp3 r2.w, r2, r2 
rsq r2.w, r2.w 
mul r2, r2, r2.w 

Diesen Vektor auch normalisieren.

Schattenberechnung

So, was wir jetzt haben sind zwei normalisierte Vektoren. Wir bilden jetzt im nächsten Schritt das Punktprodukt zwischen Normale und Vertex->Auge Vektor und verwenden das als X Koordinaten für eine Textur (rechtes Bild). Das Punktprodukt hat die Eigenschaft, wenn die zwei Vektoren normal aufeinander sind, ist es 0. Das heißt desto größer der Winkel zwischen den Vektoren ist, desto kleiner ist der Wert. Wenn der Werte also fast 0 ist, bedeutet dies, dass es sich wahrscheinlich um eine Ecke handelt. 0 als Koordinate für eine Textur wäre ganz links im obigen Bild. Also schwarz als Farbe. Schwarz == Ecke und Weiß != Ecke !!!!!!

; e dot n. edge 
dp3 oT1.x, r0, r2 

Jetzt ein Punktprodukt zwischen Normalenvektor und dem Vertex->Kamera Vektor. Diesen Wert verwenden wir als x Koordinate für die Textur.

Spekulärer Bereich

Hier bilden wir wieder das Punktprodukt und verwenden es wieder als X Koordinate für eine Textur (rechtes Bild). Ist der Winkel zwischen den Vektoren sehr klein, wird der rechte Bereich der Textur für die Farbmischung verwendet. Dadurch entsteht eben dieser spekuläre Bereich auf der Biene.

; l dot n. spec 
   dp3 oT0.x, r0, -c[cvs_lightdir] 

Das gleiche, aber diesmal mit dem Normalenvektor und der Lichtposition.

; y-Coords 
   mov oT0.y, c[cvs_yconst2].x 
   mov oT1.y, c[cvs_yconst].x 

y Koordinaten für die Textur einfach kopieren.

So... wahrscheinlich kennt sich jetzt keiner aus. ("Macht nichts, weiter im Stoff!!!" würde mein Mathematik Professor wohl sagen *g*)

Pixel-Berechnung

Mit Hilfe der Textur-Stage-States haben wir folgendes schon vor dem Rendern gesetzt:

Texturkoordinaten clampen: maximal 1, minimal 0
Erste Textur mit Farbwert multiplizieren.
Diesen Farbwert mit der zweiten Textur multiplizieren.
Und fertig ist unsere Cartoon-Biene. Im Beispielprogramm kann man noch verschiedene Y Koordinaten für die Texture verwenden (darum haben diese solche Abstufungen). Dadurch kann man mit den Schatteneinstellungen und den spekulären Bereichen ein bisschen herumspielen.

Limitierungen

Ein Problem diese Darstellung ist, das große Bereiche schattiert werden, obwohl diese eigentlich normal dargestellt werden sollten. Dies passiert dann wenn zum Beispiel einer der Flügel schon so flach auf die Lichtrichtung steht, das dieser als Schatten interpretiert wird. In der Grafik sieht man das Problem.

Verbesserungen wären die diffuse Farbe direkt in die X-Datei zu speichern (also in den Mesh) und die Verwendung von Texturen, aber fürs erste ist das Ergebnis ganz akzeptabel.

 

(c) by Kongo

Bei Fragen, Beschwerden oder Wünschen E-Mail an: kongo@codeworx.org

Letztes Update: 11.03.2002