Erste Schritte mit Shadern unter XNA

Shader werfen bunte Schatten (Teil 3)
Kommentare

Wie rennt das, bitte?
Zu Beginn dieses Kapitels zur Sicherheit gleich eine Warnung: Hier folgt nun sehr hohe Mathematik, bei der auch der Autor ohne ausreichende Vorbereitungszeit ins Schlingern gerät.

Wie rennt das, bitte?

Zu Beginn dieses Kapitels zur Sicherheit gleich eine Warnung: Hier folgt nun sehr hohe Mathematik, bei der auch der Autor ohne ausreichende Vorbereitungszeit ins Schlingern gerät. In der Praxis nimmt man den Shader ja einfach so, wie er ist. Die hier folgenden Erklärungen sind daher vor allem für besonders an den Hintergrundberechnungen Interessierte gedacht. Alle anderen können sie natürlich auch lesen, interessant sind sie allemal.

Die grundlegende Idee hinter Diffuse Lighting besteht in der Lambert’schen Reflektion. Ein Objekt, das diese Reflexionseigenschaften aufweist, unterliegt nicht dem klassischen Reflektionsgesetz: Einfallendes Licht wird so gebrochen, dass den Beobachtern das Objekt aus verschiedenen Winkeln identisch erscheint.

Die Hilfsmatrix entsteht durch die Inversion und die Transposition der lokalen Weltmatrix. Sie enthält die einzelnen „Knochenkoordinaten“ des Modells. Dabei trifft man auf eine Besonderheit der geometrischen Transformation mit Matrizen: Invertiert man eine aus Rotations- und Skalierungsinformationen bestehende Matrix und transponiert sie danach, wird die Skalierung invertiert (also rückgängig gemacht), während die Rotation unverändert bleibt. Diese Matrix wird danach mit dem Normalvektor der Fläche multipliziert, was den Normalvektor an seine Weltkoordinate bringt. Im nächsten Schritt ermittelt man die Lichtintensität, indem man den erhaltenen Vektor mit dem Einfallsvektor der Lichtquelle „dottet“.

Das Dot-Produkt ist auch eine eher komplexe mathematische Transformation, deren Effekt aber leicht verständlich ist. Zeigen die beiden Vektoren in die gleiche Richtung, liefert sie 1. Stehen die Vektoren zu 90 Grad aufeinander, droppt 0. Zeigen die Vektoren gegeneinander, wird -1 retourniert. Dieser Wert wird danach mit der Farbe des einfallenden Lichts multipliziert, wodurch die Lichtintensität abnimmt. Die Funktion saturate beschränkt die Werte auf den Wertebereich 0 bis 1. Dadurch werden auch „negative“ Lichtelemente eliminiert. Wer sich das näher ansehen will, reduziert die einfallende Lichtintensität und betrachtet die Kugeln. Dabei behält man im Hinterkopf, dass die Lichtquelle „links oben“ hinter dem Betrachter sitzt.

Ambient und mehr

Nutzt man nur Diffuse Lighting so entstehen die in Abbildung 5 ersichtlichen schwarzen Bereiche, die überaus häßlich sind. Um sie etwas besser zu kompensieren, kombiniert man in der Praxis oft Ambient und Diffuse Lighting. Dazu reduziert man das für Diffuse Lighting zur Verfügung stehende Lichtbudget: float4 DLColor = float4(0.8f, 0.0f, 0.0f, 0.8f);. Danach addiert man den „Rest“ zu jedem Pixel dazu:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
  // TODO: add your pixel shader code here.

  return input.DiffuseColor + float4(0.2f, 0.0f, 0.0f, 0.2f);

}  

Das Resultat ist in Abbildung 6 zu sehen.

Abb. 6: Die in Abbildung 5 schwarzen Flächen schimmern nun rot
Abb. 6: Die in Abbildung 5 schwarzen Flächen schimmern nun rot
Wo ist die Farbe?

Am Anfang dieses Artikels erschien der Affe weiß, die Kugeln waren rot, und all das ohne manuelles Zuweisen der Farben. Der Grund dafür lag im Vorhandensein von Materialinformationen in den Modellen. Anders als Vertexfarben (die die GPU automatisch über ein Semantic vom Typ COLOR0 an den Vertex-Shader übergeben könnte) liegen diese nicht im Modell vor. Die Content Pipeline wertet die Materialliste beim Einlesen des Modells aus, korreliert die Materialinformationen in den Meshs mit dieser Liste und weist die Farben den BasicEffects der Modellteile zu. Da das ModelMeshPart ärgerlicherweise als Sealed definiert ist, kann man die Klasse auch nicht „einfach so“ um ein Farbfeld erweitern. Allerdings besitzt MeshPart ein Feld namens Tag, das man mit beliebigen Werten befüllen kann. Genau das wird in diesem Beispiel zur Realisierung genutzt. Beim Erstellen des SmartModel durchläuft man alle MeshParts und liest ihre Farben aus dem BasicEffect aus (Listing 9).

Listing 9
public SmartModel(Model _model, Vector3 _pos, Vector3 _rot, Vector3 _scale)
{
  . . .
  foreach (ModelMesh m in myModel.Meshes)
  {
    foreach (ModelMeshPart mp in m.MeshParts)
    {
      BasicEffect x = mp.Effect as BasicEffect;
      mp.Tag = x.DiffuseColor;
    }
  }
}  

Wollte man mehr Farben „retten“, könnte man auch den ganzen BasicEffect in die Tag-Variable schreiben. Sie ist nämlich vom Typ Object und akzeptiert deshalb anstandslos jedes beliebige Objekt. In der Render-Routine regeneriert man diese Daten und weist sie dem optionalen Parameter DLColor zu, wie in Listing 10 zu sehen.

Listing 10
foreach (ModelMeshPart mp in m.MeshParts)
{
  Vector3 tempCol = (Vector3)mp.Tag;
  Vector4 tempVect=new Vector4(tempCol.X, tempCol.Y, tempCol.Z, 1);
  mp.Effect = Game1.myFirstEffect;
  mp.Effect.Parameters["DLColor"].SetValue(tempVect);  

Zuletzt adjustiert man die Routine des Pixel-Shaders. Die aus dem BasicEffect ausgelesenen Farben reichen nämlich von null bis eins, weshalb man die Ausgabe beschränken muss. Außerdem soll auch die Farbe des Ambient Light der „diffusen“ Farbe entsprechen:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
  // TODO: add your pixel shader code here.

  return saturate(input.DiffuseColor + DLColor*0.2);

}  

Damit sind wir fertig und führen die Anwendung erneut aus. Das Resultat zeigt Abbildung 7. Das war jetzt zweifellos ein gutes Stück Arbeit, ist aber nicht permanent notwendig. In der Praxis verwendet man nämlich statt Material- oder Vertexfarben so genannte Texturen, die mehr Informationen enthalten und realistischer aussehen. Dazu mehr im nächsten Artikel.

Abb. 7: Fertig für Heute!
Abb. 7: Fertig für Heute!
Fazit

Die bisher verwendeten Shader sind eher moderat. Sie realisieren Lichteffekte, die theoretisch auch mit BasicEffects machbar wären. Allerdings sind Shader eine relativ komplexe Wissenschaft, die mehr oder minder immer nach diesem Muster abläuft. Die didaktisch erprobte Methode ist bis hierher sinnvoll und gut. Im nächsten Artikel geht es um Texturen, mit denen die Objekte nicht nur Farben, sondern auch „Bilder“ darstellen. Man liest sich!

Tam Hanna befasst sich seit der Zeit des Palm IIIc mit Programmierung und Anwendung von Handcomputern. Er entwickelt Programme für diverse Plattformen, betreibt Onlinenewsdienste zum Thema und steht unter tamhan@tamoggemon.com für Fragen, Trainings und Vorträge gern zur Verfügung.
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -