Bump/Normal Mapping und Shader-Performance verstehen

Der Herr der Hügel
Kommentare

Das im letzten Teil der Serie vorgestellte und naiv als Bump Mapping bezeichnete Shader-Verfahren erlaubt das weitgehend aufwandsfreie Anreichern von Objekten mit zusätzlichen geometrischen Details.

Windows Developer

Der Artikel „Der Herr der Hügel“ von Tam Hanna ist erstmalig erschienen im Windows Developer 7.2012

Die Mathematik, die hinter den geometrischen Details zur Anreicherung von Objekten steht, haben wir bis jetzt bewusst ausgeblendet, ja sogar durch ein leicht verständliches, aber mathematisch inkorrektes Modell ersetzt. Bevor wir uns in die Tiefen des Codes begeben, eine erfreuliche Nachricht für alle nicht an Shader-Programmierung Interessierten: Dieser Teil der Serie ist der letzte zum Thema Shader, ab jetzt haben Sie für mindestens drei Monate Ruhe. Aber sehen wir uns zunächst einmal den Prozess in seiner vollen Komplexität an. Beginnen wir gleich mit der Klärung des Begriffs, um den Prozess genauer spezifizieren zu können. Im Internet werden die Bezeichnungen Bump Mapping und Normal Mapping nämlich bunt gemischt verwendet. Das ist fachlich gesehen nicht richtig, da es sich dabei um zwei verschiedene Versionen des Verfahrens handelt.

Addieren, aber was?

Als erstes wollen wir die grundlegende Theorie rekapitulieren. Die Idee hinter dem Prozess ist das Erweitern der Modellgeometrie, ohne dabei aber mehr Rechenleistung für zusätzliche Dreiecke verwenden zu müssen. Die „hinzuzufügenden“ Daten werden dabei in einer weiteren Textur eingeliefert, die im Pixel Shader zu Farbinformationen verwurstet werden. Daraus folgt der erste wichtige Unterschied zwischen Bump/Normal Mapping und echter Geometrie: Die eingepflegten geometrischen Daten sind virtuell, stehen also für Kollisionserkennung etc. nicht zur Verfügung. Der englische Patentanwalt Geoff Dallimore hat das in Abbildung 1 gezeigte Bild mittels POVRAY erstellt, um damit die Unterschiede zwischen echter Geometrie und Mapping-Verfahren zu demonstrieren.

Abb. 1: Links Kugel mit Normal Mapping, rechts verformter Körper ohne Mapping
Abb. 1: Links Kugel mit Normal Mapping, rechts verformter Körper ohne Mapping

Die linke Kugel wird dabei durch Mapping realisiert, während die rechte Kugel eine echte, also aus Geometriedaten bestehende Kugel ist. Betrachtet man sowohl den Rand der Kugeln als auch die geworfenen Schatten, so sieht man sofort den Unterschied zwischen echter und falscher Geometrie. Die Unterschiede zwischen Bump Mapping und Normal Mapping ergeben sich durch die in den Texturen enthaltenen Werte. Beim Bump Mapping gibt die Textur an, um wie viel der Oberflächenpunkt in Richtung des Normalvektors verschoben werden soll. Aus diesem Grund sind die für Bump Mapping verwendeten Texturen meist monochrom; sie verschieben den Oberflächenpunkt ja nur um einen bestimmten Wert, der von der Richtung des Normalvektors festgelegt ist. Im Vergleich dazu ist Normal Mapping um einiges komplizierter. Eine derartige Map enthält nämlich nicht nur einen, sondern gleich drei Werte. Diese drei Werte ergeben einen Vektor, der auf die Position des Oberflächenpunkts gesetzt wird und ihn „transponiert“. Da farbige Grafiken in der Regel aus drei Koordinaten pro Punkt bestehen (rot, grün und blau), liegt ihre Verwendung für Normal Maps nahe. Der Unterschied zwischen den beiden Verfahren ist in Abbildung 2 grafisch dargestellt.

Abb. 2: Bump Mapping verschiebt nach oben und unten, während Normal Mapping dreidimensional arbeitet
Abb. 2: Bump Mapping verschiebt nach oben und unten, während Normal Mapping dreidimensional arbeitet

Leider haben Normal Maps einen insbesondere für kleine Studios ärgerlichen Nachteil: Anders als Bump Maps lassen sie sich nicht ohne Weiteres in einem Grafikprogramm erstellen. Will man die Objektgeometrie mittels Normal Map verändern, braucht man einen spezialisierten Editor oder zumindest ein Plug-in für eine Grafiksoftware wie GIMP, das die einzelnen Ebenen zusammenrechnet.

Code im Blick

Doch damit genug der Theorie. Betrachten wir in Listing 1 den im letzten Teil des Artikels erstellten Code für den Pixel Shader, im Vertex Shader passiert nichts, was in diesem Zusammenhang interessant wäre. Zum Vergleich wollen wir uns zwei weitere, schon von früher bekannte Shader ansehen. Als erstes betrachten wir in Listing 2 den Pixel Shader des reinen Textur-Shaders aus Teil 6 dieser Artikelserie, der Modelle mit Texturinformationen verarbeitet. Als zweites betrachten wir in Listing 3 den Diffuse Lighting Shader aus dem fünften Teil der Serie, hier brauchen wir auch den Vertex Shader.

Listing 1

float4 TPixelShaderFunction(TVertexShaderOutput input) : COLOR0
{
  float4 textureColor = tex2D(textureSampler, input.TextureCoordinate);
  textureColor.a = 1;

  //Bump Map
  float3 bump =  (tex2D(bumpSampler, input.TextureCoordinate) - (0.5, 0.5, 0.5))*2;
  float3 bumpNormal = input.Normal + (bump.x * input.Tangent + bump.y * input.Binormal);
  bumpNormal = normalize(bumpNormal);
  float diffuseIntensity = dot(normalize(DLVector), bumpNormal);
  if(diffuseIntensity < 0)diffuseIntensity = 0;

  return textureColor * (diffuseIntensity);

}  
Listing 2

float4 TPixelShaderFunction(TVertexShaderOutput input) : COLOR0
{
  // TODO: add your pixel shader code here.
  float4 textureColor = tex2D(textureSampler, input.TextureCoordinate);
  textureColor.a = 1;
  return textureColor;
}  
Listing 3

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
  VertexShaderOutput output;

  float4 worldPosition = mul(input.Position, World);
  float4 viewPosition = mul(worldPosition, View);
  output.Position = mul(viewPosition, Projection);

  // TODO: add your vertex shader code here.
  float4 normal = normalize(mul(input.NormalVector, WorldInverseTranspose));
  float lightI = dot(normal,DLVector);
  output.DiffuseColor = saturate(DLColor * lightI);

  return output;
}  

Die ersten beiden Zeilen des Shaders unseres Normal-Mapping-Beispiels könnten 1:1 aus dem Textur-Shader stammen - der Textur-Sampler wird angewiesen, die Farbe des Punkts auf der Oberfläche des Objekts zu ermitteln. Die letzte Zeile des Shaders entstammt dem Diffuse Lighting Shader. Sie multipliziert die aus der Textur ermittelte Farbe mit einem Abschwächungswert, der die Farbe abdunkelt oder aufhellt. Der Unterschied besteht darin, dass der Diffuse Lighting Shader die Berechnung schon im Vertex Shader erledigt und statt der aus der Textur ermittelten Farbe die Materialfarbe des Objekts heranzieht.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -