.heightMap III

Shadowmap
.zurück zu heightMap II .weiter zu heightMap IV

Obwohl ein Phongshading zunächst wichtiger wäre, habe ich mich heute doch daran gesetzt zunächst die Shadowmap zu implementieren. Wie immer hab ich die Ergebnisse mit aufbereitet, um hier einen kurzen Abriß darüber geben zu können...
 

 
Idee der Shadowmap
Die Idee hinter den Shadowmaps ist eigentlich recht simpel: Man benötigt zunächst lediglich zwei Winkel, die angeben in welcher Position die Lichtquelle (Sonne) steht - also sogenannte Polarkoordinaten. Folgende Skizze sollte das verdeutlichen:

Rot, grün, blau sind dabei die Achsen, weiß zeigt die Richtung der Lichtquelle an und cyan bzw gelb sollte dann über die trigonometrieschen Funktionen zu bestimmen sein. Der Cyan Vektor (= die Projektion des Lichtrichtungsvektors auf die x-y-Ebene) ist natürlich in seine Komponenten in x und y Richtung zu zerlegen...
Die x und y Anteile gehen über Sinus und Cosinus, die Höhe wird über den Tangens errechnet... das kommt - denke ich jedenfalls - im Quelltextausschnitt ganz gut raus.

Während des Debugs bzw. auch hier zur Verdeutlichung verwende ich zunächst folgende überschaubare Heightmap:

Der Algorithmus bestimmt nun aus den Winkeln die Richtung in der die Lichtquelle steht und prüft von jedem Punkt der Hightmap aus ab, ob in dieser Richtung ein Hindernis die Sicht auf die Lichtquelle blockiert. Also eine Art Raytracing, man kann den Algorithmus auch durchaus mit Techniken des Raytracings beschleunigen, aber für meine Zwecke habe ich einen Textpunkt einfach diskret über die Hightmap verschoben, das sollte erst einmal langen.
Ebenfalls zur Verdeutlichung des Vorgehens soll die nächste Skizze dienen:

Ausser den Standard-Fehlern beim Umrechnen der Winkel oder falsche Floatingpoint casts, ist die Implementation eigentlich recht einfach und sollte keine größeren Probleme bereiten.
Bei mir sieht der Code in etwa wie folgt aus:

       

Bitmap* HeightMap::buildShadowMap(int pixelSize, GLfloat rot_plane, 
                                  GLfloat rot_height, GLfloat shadow_attenuation) {
  GLfloat dx  = cos(rot_plane *PI/180.0);
  GLfloat dy  = -sin(rot_plane *PI/180.0);
  GLfloat dz  = (rot_height < 89)? tan(rot_height*PI/180.0) : tan(89*PI/180.0);
  GLfloat adx = fabs(dx);
  GLfloat ady = fabs(dy);
  GLfloat adz = fabs(dz);

  GLfloat max_d = (ady > adx)? ((adz>ady)? adz : ady) : ((adz>adx)? adz : adx);
  dx = dx/(float)max_d;
  dy = dy/(float)max_d;
  dz = dz/(float)max_d;

  GLfloat act_x, act_y, act_z, light;
  Bitmap* result      = new Bitmap(pixelSize, pixelSize);
  GLfloat maxHeight   = this->getMaxHeightValue();
  if (dz < 0) return result;
  
  for (int y = 0; y < pixelSize; y++) {
    for (int x = 0; x < pixelSize; x++) {
      light = 1.0;
      act_x = (x/(float)pixelSize)*this->mapSize;
      act_y = (y/(float)pixelSize)*this->mapSize;
      act_z = this->getHeightAt(act_x, act_y);

      while ((act_x >= 0) && (act_x < this->mapSize) &&
             (act_y >= 0) && (act_y < this->mapSize) &&
             (act_z < maxHeight)) {
        act_x += dx;
        act_y += dy;
        act_z += dz;
        if (act_z < this->getHeightAt(act_x, act_y)) {
          light *= shadow_attenuation;
          if (light < 0.01) break;
        }
      }

      result->setPixel(x, y, (light*255), (light*255),  (light*255));
    }
  }
  return result;
}
Beachte: Egal wie - aber die while - Schleife terminiert

Der shadow_attenuation Faktor sorgt dafür, dass der Schatten nicht 100% hart ist, also schon ein leichter "Streifschuss" an einer Klippe entlang einen Kernschatten produziert.

Meine Ergebnisse nach diesem Schritt sahen als BMP-Dump wie folgt aus:
(Beide Winkel 0, dann mit jeweils 45 Grad und schließlich mit abgemildertem Attenuationfaktor)


 

 
Heightmap - Schadowmap - Textur
Nun wird lediglich noch die generierte Shadowmap mit den Farbwerten der prozedural erzeugten Textur multipliziert und das Ergebnis ist die an den Schattenstellen korrekt abgedunkelte Oberflächentextur.
An dieser Stelle nocheinmal die graphische Version:


 

 
Und wieder: Das vorläufiges Ergebnis
Nachdem alles soweit debugged und einsatzbereit war, habe ich das dann alles auf die Insel-Heightmap aus der vorigen Version angewand und folgendes Ergebnis erhalten:


.zurück zu heightMap II .weiter zu heightMap IV zurück