User:Maxbe/Relief mit POVray

From OpenStreetMap Wiki
Jump to: navigation, search

Worum gehts?

Maxbe povray seekar fertig.png
Ich finde es ganz nett, gebirgige Gegenden in 3D darzustellen. Ziel ist es dabei weniger, ein Luftaufnahme zu simulieren, sondern eher das Aussehen dieser tiefgezogenen Reliefkarten aus Kunststoff zu imitieren. Da wird auch erst eine Karte auf Plastik gedruckt (in der Regel ohne spätere Verformungen zu berücksichtigen) und dann über ein Relief gezogen. Das ganze sieht dann so aus wie nebenstehendes Bild: Der rot eingezeichnete Track führt auf einen Berg namens Seekarkreuz (Karte) und startet im Tal bei Hohenburg, nordwestlich davon. Die Kamera steht westlich der Landschaft, die Sonne im Südosten.

Auf Beschriftungen habe ich völlig verzichtet, weil die nach der Verformung nicht mehr lesbar waren und eine nachträgliche Beschriftung mit z.B. Buchstaben, die über Gipfeln schweben eine Nummer zu groß waren. Zumindest automatisiert halte ich das für sehr schwierig. Die Karte ist eine abgewandelte Form meiner Kartenversuche, wo es auch diese Ansicht als Export-Format gibt. Wer dort damit spielen will, kann es gerne tun, er muss halt geduldig sein, das Rendern dauert 10-30 Sekunden und es wird erst ab dem 10. Versuch halbwegs ansehnlich.

Für Leute, die auch mit kleineren Maßstäben zufrieden sind, hätte ich auch ein Relief eines Teils der Alpen. Allerdings ohne Wahlmöglichkeit der Perspektive und Beleuchtung.

In Wirklichkeit ist so ein 3d-Export als Webdienst (zumindest wenn ich so was mache) eine wahllose Ansammlung von zusammengestöpselten Skripten, ich versuche also gar nicht die Details zu erzählen, sondern eher eine grobe Anleitung für die Nachwelt zu hinterlassen...

Höhendaten

Ich arbeite mit den Tools von gdal.org zum Umprojizieren und Ausschneiden der Höhendaten und den Daten von opendem.info. Da ich alles in Mercatorprojektion abgelegt habe, möchte ich auch die Höhendaten so haben. Dazu dient dieses Skript:

wget "http://koenigstuhl.geog.uni-heidelberg.de/~mover/srtm_germany_dtm.zip"
unzip srtm_germany_dtm.zip
mv srtm_germany_dtm.tif srtm.tif
gdal_translate -projwin 11 48.655 13 47 -of GTiff -co "TILED=YES" -a_srs \
   "+proj=latlong" srtm.tif input_adapted.tif
gdalwarp -of GTiff -srcnodata 32767 -t_srs "+proj=merc +ellps=sphere \
   +R=6378137 +a=6378137 +units=m" -multi input_adapted.tif height.tif

Die Datei "height.tif" enthält jetzt die Höhenwerte in Mercatorprojektion. Da mit gdal_translate vorher ein Bereich ausgeschnitten wurde, bin ich auf die Gegend zwischen dem 11. und dem 13. Längengrad und 47 bis 48.655 Nord beschränkt.


Aus dieser Datei kann man sich eine PNG-Datei mit den Höhenwerten seiner Landschaft ausschneiden:

gdal_translate -of PNG -ot UInt16 -projwin <links> <oben> <rechts> <unten> height.tif height.png 

Maxbe povray seekar height.png
Das ergibt dann eine recht kleine Datei, die für das Raster der 90m-Höhendaten je einen Höhenwert pro Pixel enthält. Das Bild ist übrigens ziemlich schwarz, weil die Daten als 16-Bit-Zahl abgelegt sind und selbst ein 3000m hoher Berg nur (3000/65000) 5% Helligkeit hätte. Aufgehellt sieht das Bild etwa so aus:

links, rechts, oben, unten sind die Begrenzungen des Bildes. Da ich alles in Mercatorprojektion habe, muss ich die auch so angeben, das Seekarkreuz steht also bei ca. (1296096 6049146). Für die Höhendaten bei POVray muss ich noch 60m bei links und rechs abziehen, also die Karte nach Westen schieben. Der Wert ist durch Ausprobieren ermittelt und der Grund dafür ist mir nicht klar, die Höhenlinien aus dem gleichen Datensatz stimmen nämlich. Ich vermute, es gibt bei geotiff und bei POVray unterschiedliche Ansichten, für welche Ecke des Pixels (das ist in der echten Welt ein Bereich von der Größe eines Fußballfeldes) die Höhenangabe (das ist ein Punkt) gilt.

Karte

Maxbe povray seekar surface.png
Natürlich braucht man auch eine Karte der Gegend. Die kann man mit irgendeinem Renderer machen, ich mach das mit UMN-Maperserver, aber es geht sicher auch mit Mapnik oder einem Export von openstreetmap.org.

Karte und Höhenbild müssen übrigens nicht die gleiche Größe haben, auch nicht das gleiche Seitenverhältnis. Das Höhenbild kommt ja nur mit einer Auflösung von einem Fußballplatz/Pixel. Es wäre eher schlecht, wenn auch die Karte so ungenau gezeichnet würde. Wichtig ist, dass die geographischen Grenzen die gleichen sind. Um das Übereinanderlegen und das Interpolieren der "Zwischenhöhen" kümmert sich dann später povray. Ich mache die Karten in der Regel doppelt so groß wie die gewünschte Ausgabegröße, dann sehen die verzerrten Stellen an den Gipfeln (da wird das Bild ja stark gedehnt) schöner aus.

POVray

Maxbe povray seekar koordinaten.png
POVray gibts bei povray.org zum runterladen. Die Software ist ungeheuer mächtig, kann beliebig kompliziert werden und hat jede Menge Befehle zur Beschreibung 3-dimensionaler Objekte. Zum Glück brauchen wir nur drei oder vier davon.

Wir brauchen

  • Eine Lichtquelle
  • Eine Kamera
  • Eine Oberflächenform, auf die wir die Karte aufziehen
  • Die Karte

Das ganze wird so in ein Koordinatensystem gequetscht, dass die Landschaft 1x1 Einheiten gross ist und in der x-z-Ebene liegt. 1x1 ist keine Einschränkung von POVray, sondern dient der einfachen Rechnerei. Die Lampe wird irgendwo daneben aufgehängt, für morgendliche Beleuchtung z.B. etwas in positiver x-Richtung verschoben. Die Kamera hängt links und in der Mitte der z-Achse (also bei 0.5), um von Westen auf die Landschaft zu blicken.

Das lässt sich das in einer Textdatei ausdrücken, die man anschließend von POVray abarbeiten lässt:

    1	light_source {  <1.3, 3, -0.3> color rgb <1,.9,.9> }
      
    2	camera { location < -0.4526,0.55,0.5 > 
    3	         direction <0,0,1> 
    4	         look_at  <0.5, 0 , 0.5> }
      
    5	plane  { < 0,1,0>,0 
    6	         pigment {color rgb<0,0,1>} } 
      
    7	height_field {
    8	     png "height.png" smooth
    9	     finish {diffuse .7 ambient .3 brilliance 1.4 phong 0.2 }
   10	     pigment {image_map { png "surface.png" }rotate x*90}
   11	     scale <1,7,1> 
   12	     translate <0,-0.075,0>}
  • Zeile 1: "light_source" ist die Lampe. die hängt weit (1.3) im Osten, hoch am Himmel (3) und etwas im Süden (-0.3) und ist leicht rötlich (wegen Morgenstimmung)
  • Zeile 2: "camera" ist die Kamera. Die steht etwas im Westen (-0.45), etwas über dem Boden (0.55) und in der Mitte der z-Achse (0.5).
  • Zeile 3 gibt die Blickrichtung der Kamera an. Wird hier allerdings nicht verwendet, weil das gleich wieder überschrieben wird. Die Länge des Vektors <0,0,1> ist aber schon wichtig, weil das sowas wie die Brennweite des Objektivs angibt.
  • In Zeile 4 wird die Blickrichtung der Kamera festgelegt. Sie schaut genau auf die Mitte der Landschaft.
  • Zeile 5 ist einfach der Hintergrund. Spielerisch veranlagte Menschen würden hier einen Tisch mit Holzmaserung plazieren.
  • Zeile 6 gibt die Farbe der Tischplatte an: Blau.

Das ganze wesentliche Zeug steht in den Zeilen 7-12.

  • "height_field" bezeichnet eben dieses Höhenfeld aus der Datei height.png.
  • Mit "smooth" werden die Klötze aus dem 90m-Raster geglättet
  • "finish" beschreibt die Mattheit und den Glanz der Oberfläche. Damit kann man rumspielen, bis der korrekte Billiges-Plastik-Effekt eintritt
  • Bei "pigment" wird endlich die Karte aus der Datei surface.png aufgezogen. Um die x-Achse um 90 Grad rotiert, weil wir nicht die x-y-Ebene damit tapezieren wollen, sondern die x-z-Ebene.

Scale

Die Skalierung war das schwierigste: Wir schrumpfen die Karte ja von ca 10x10x1km (länge x breite x Höhe) auf ein 1x1-Gitternetz. Allerdings nur in der Länge und Breite (die Abmessung des Bildes height.png). Die Höhe (die Grauwerte des Bildes) bleibt gleich. Der höchste Punkt des height_field liegt bei 1 auf der y-Achse und entspricht dem Wert 65536 (wegen 16-Bit-Werten).

Unser Berg ist ca 1600m hoch hat also auch einen Grauwert von 1600, was in unserem Koordinatensystem einem y-Wert von 1600/65536=0.024 entspricht. Ein Meter entspricht 1/65536=0.000015

Die Kanten waren vor der Schrumpfung 10km lang. Ein Meter in der Ebene entspricht im neuen Koordinatensystem also 1/10000=0.0001.

Um diese Ungleichheit beim Skalieren auszugleichen, wird die height_map in der y-Richtung wider um den Faktor 7 (0.0001/0.000015) vergrößert.

Soweit die Theorie: Bei dem Bild da oben wird nochmal der Faktor 2 draufgeschlagen. Sieht einfach besser aus, wenn die Berge bergiger sind...

Translate

Unser niedigster Punkt in der Landschaft liegt bei etwa 700m. Würden wir dieses "translate" nicht verwenden, läge die x-z-Ebene in Meereshöhe und unter dem Bild wären 700 Meter (das entspricht etwa 0.075 Einheiten im Koordinatensystem) freier Platz. Deshalb wird die ganze height_map um 0.075 nach unten gezogen (1/65536*700*scale).

Auch hier kann man ein bisschen rumspielen: Falls die Landschaft ein bisschen unter die y=0-Ebene rutscht, erzeugt man mit der blauen Ebene eine Darstellung "was würde passieren, wenn der Meeresspiegel auf 700m steigt". Zu niedrige Korrekturen lassen die Landschaft nach oben verschwinden oder erzeugen einen grossen schwarzen Rand zwischen Tisch und Landkarte.