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