#cologne #lastborn #82+ #green

Tutorial - Terrestrial Planets - Part 1


Published: 06.11.2025

Step-by-step instructions on how to use Povray to create realistic images of terrestrial planets, for which high-resolution maps are available.

Part 1 explains the development of the script with the necessary code and comments. Part 2 addresses various improvements, possible data sources, and much more.

The 1600x900 pixel images on this page are all original results of the scripts, without any changes. I only had the downscaled versions sharpened a little.

Goal

The goal is to bring rectangular maps into a round shape, and on top of that, to let Povray create realistic shadows. To do this, we need two maps of a celestial body, in our case the moon.

First, a color map of the surface that is as pure as possible. This should not contain shadows, only the color values of the surface. As if every area was illuminated by the sun exactly from above or vertically.

Secondly, we need an elevation map, called DEM (Digital Elevation Map). In it case, black stands for the lowest point, white for the highest, and gray values form the values in between.

Both maps are twice as wide as they are tall and appear distorted in width towards the poles in the rectangular version. This is quite normal, as the rectangular maps are placed around a sphere and the polar regions are compressed together.

Moon Surface
Moon DEM

Download
So that the scripts shown here can be tried out and understood right away, all files are included in the zip file below. In order to keep the file size small, high-resolution images were dispensed with and maps with a maximum size of 4000x2000 pixels were used.

tutorial_povray_planets.zip => 9 Files - 17 MB

The individual steps can be carried out with command lines, which are stored at the top of the respective script. Example:
povray +Ima_tutorial_planet_step1.pov +Otest/step1.png +w1600 +h1200 -GR +A

Step 1 - Image-Map
The basis of all examples is a sphere that is generated at <0,0,0> and whose texture is changed with each step.

#version 3.7;
global_settings { assumed_gamma 1.0 charset utf8 }

#local oPlanet = sphere {
	<0,0,0> 1
	texture {
		pigment {
			image_map {
				jpeg "map_moon_4k_surface.jpg"
				map_type 1		//1 = spherical
			}
		}
		finish { ambient rgb (<2,1,1> * 0.01) }
	}
}

object {
	oPlanet
	rotate y*-90
}
light_source {
	<0,0,100000>
	color <1,1,1>
	rotate y*130
}
camera	{ perspective right x*image_width/image_height sky <0,1,0>
	location	<0,0,-20>
	look_at		<0,0,0>
	angle		10

}

In the example we take a sphere with a radius of 1 units, instead of a colored texture we use an image-map with the name map_moon_surface.jpg.

This image-map places the selected image spherically around the sphere with map_type 1. The finish statement regulates the colouring of the areas that are in the shade.

See also: Povray Dokumentation: Bitmap Modifiers

	texture {
		pigment {
			image_map {
				jpeg "map_moon_4k_surface.jpg"
				map_type 1		//1 = spherical
			}
		}
		finish { ambient rgb (<2,1,1> * 0.01) }
	}

We then output the object and rotate it -90 degrees so that the front of the moon is aligned to Z-. If you want to see the back side, use +90 degrees.

object {
	oPlanet
	rotate y*-90
}

The light source is set to Z+ (behind the sphere) and rotated freely as desired.
rotate y*90 stands for waxing crescent moon, y*180 for full moon, we choose something between.

light_source {
	<0,0,100000>
	color <1,1,1>
	rotate y*130
}

The camera is positioned at Z = -20.

camera	{ perspective right x*image_width/image_height sky <0,1,0>
	location	<0,0,-20>
	look_at		<0,0,0>
	angle		10
}

The result is interesting, but not yet what we want.

Step 2 - Image-Map + Bump-Map

Only with the image map do we get an absolutely round sphere, without any shadows. Except of course that the side facing away from the light is in shadow, but none of the craters and mountains create dark areas.

To create the first shadows, we add a normal statement between the pigment and the finish.

		pigment { ... } //unverändert wie Schritt 1
		normal {
			bump_map {
				png  "map_moon_4k_dem.png"
				map_type 1
				interpolate 2
				bump_size 100 once	//Height of the DEM
			}
		}
		finish { ... } //unverändert wie Schritt 1

The bump-map provides "simulated" shadows. Of course, everything is simulated in Povray, but these shadows a bit more than usual.

The value behind bump_size sets the height of the shadows, which are set to overcast here.

This already gives us a better result.

Let's enlarge an area near the Terminator (the day/night boundary), adjust the camera setting - our destinations are the Mare Humorum and the crater Gassendi.

camera	{ perspective right x*image_width/image_height sky <0,1,0>
	location	<0,0,-20>
	look_at		<-.5,-.4,-.5>
	angle 2
}

One of the shortcomings of bump-maps is about to become apparent:
The round sphere is still absolutely round.

There are no prominent heights/bumps for mountains and no cuts for valleys and craters. The illuminated part of the sphere has a sharp edge, even hugely high areas behind it don't even get light at the tip.

The shadows are simulated, Povray pretends and only changes the color of the surface, so that it looks like a shadow cast from a distance.

Step 3 - Spherical Heightfield
Realistic shadows cannot be created with bump-maps, for that we need spherical heightfields.

We change the entire script. We only take care of the shadows, leave out the surface map for the time being and use a simple color texture in its place.

//start as before

#local nRadius		= 1;
#local nFactor		= 0.35;
#local nHeightDiff	= 50;  // the more the higher the contrast between valleys and hills

#local PlanetSphere_F		= function {internal(61)}
#local PlanetHeightfield_F	= function {
	pigment {
		image_map {
			png "map_moon_4k_dem.png"
			map_type 1
			interpolate 2
		}
	}
}
#local oPlanet = isosurface {
	function {
		PlanetSphere_F(x,y,z, nRadius)
		- PlanetHeightfield_F(x,y,z).gray*nHeightDiff/1000*nRadius
		+ nFactor*nRadius
	}
	contained_by { sphere{0, nRadius} }
	max_gradient 1.3
	accuracy 0.0001
	texture {
		pigment {
			color rgb (<1,1,1>*.5) //Instead the surface-map
		}
	}
	finish { ambient rgb (<2,1,1> * 0.004) }
	scale 1/(1-nFactor)
}

//output, camera, and light as before

There are our real shadows.

Even at first glance, the area around the Terminator looks frayed, as it should be.

What happens in the new lines?

Frankly... I didn't understand it myself exactly.

I pretty much tried and tried, was on the right track, tried back and forth, sometimes it got better and sometimes worse. Finally, I came across the right lines in a script from another povrayer.

The fact is, an Isosurface is created with the help of the provided functions. The PlanetHeightfield_F function provides a height value for each point of the sphere, which is further processed. How exactly, I'll have to find out somewhen.

It doesn't matter, the lines work and we have real shadows.

Step 4 - radial errors
However, our result so far has an annoying flaw.

Let's zoom in, again to the Mare Humorum in the lower area, change the settings of the camera in the file step-3.

camera	{ perspective right x*image_width/image_height sky <0,1,0>
	location	<0,0,-20>
	look_at		<-.51,-.41,-.5>
	angle 		2
}

Looks very nice at first, brightly lit mountain peaks on dark ground.

At second glance (after clicking on the picture), strange shadows appear directly on the Terminator. Radially around small craters, which have nothing to do with the maps or (for me) rationally explainable Povray settings.

Everything done right, should work, and yet they are there.

I also worked on it and tinkered with it quite a while until the Povray-Forum gave me the right idea.

At this point, a very clear message:

SIZE - DOES - MATTER

The radius of our planet must be significantly increased. Not from 1 to 10 or 100, let's go straight to 1000.

Disadvantage: The render time increases also significantly. You shouldn't overdo it too much with the size of the object.

Of course, the camera must then also be moved by a similar factor.

camera	{ perspective right x*image_width/image_height sky <0,1,0>
	location	<0,0,-20000>
 	look_at		<-510,-410,-500>
	angle 2
}
	

Looks much better right away.

Finally, we replace the pigment with an image map to bring the original surface colors to bear.

		pigment {
			//color rgb (<1,1,1>*.5) //test-color
			image_map {
				jpeg "map_moon_4k_surface.jpg"
				map_type 1
			}
		}

That's it!

Apart from the low resolution, which is definitely due to the small maps that are only 4000x2000 in size, we have a finished planet or moon.

- - -

Job done, the moon is alive, part 1 of the tutorial is completed.

For final images and more information, see Part 2.