HowTo:High precision heightfields

From POV-Wiki
Jump to navigation Jump to search

Overview

The following is a tentative method of generating 24- and 32-bit heightfields in POV-Ray using standard raster image output formats such as PNG. It is based on some discussion that occurred in povray.general toward the end of 2008.

The technique involves encoding different levels of heightfield precision in each color channel. The first 8 bits of precision are in the blue channel; the second 8 bits in the green channel; and the third 8 bits in the red channel. The channels are then concatenated to produce a single 24 bit number.

The benefit of this technique is that it results in standard 24-bit bitmaps that can be imported into regular image editing software. (By regular image editing software, I actually mean Photoshop and a few other select programs. More on that below.) I think that Leveller uses this format to export its heightfields. Of course, this technique can be extended to 32 bits as well (as you'll see below).

Sample code

A sample scene with the 24-bit function applied as a pigment to a one-unit-high cone is as follows. You should be able to render the scene and import the resulting image back into POV-Ray to generate a new heightfield.

camera
{
	orthographic
	location	-z * 10
	direction	+z
	up		+y * 2
	right		+x * 2 * image_width/image_height
	rotate		+x * 90
}

#local hf_pigment_function_pow = function
{
	y * pow(256,3)
}
#local hf_pigment_function_b = function
{
	mod(hf_pigment_function_pow(x,y,z), pow(256,1))
}
#local hf_pigment_function_g = function
{
	mod(hf_pigment_function_pow(x,y,z)
	- hf_pigment_function_b(x,y,z), pow(256,2))
}
#local hf_pigment_function_r = function
{
	mod(hf_pigment_function_pow(x,y,z)
	- hf_pigment_function_b(x,y,z)
	- hf_pigment_function_g(x,y,z), pow(256,3))
}

#local hf_pigment_b = pigment
{
	function {hf_pigment_function_b(x,y,z) / pow(256,1)}
	color_map {[0 rgb 0][1 rgb z * 3]}
}
#local hf_pigment_g = pigment
{
	function {hf_pigment_function_g(x,y,z) / pow(256,2)}
	color_map {[0 rgb 0][1 rgb y * 3]}
}
#local hf_pigment_r = pigment
{
	function {hf_pigment_function_r(x,y,z) / pow(256,3)}
	color_map {[0 rgb 0][1 rgb x * 3]}
}

// This is the one you want
#local hf_pigment_rgb = pigment
{
	average
	pigment_map
	{
		[hf_pigment_b]
		[hf_pigment_g]
		[hf_pigment_r]
	}
}

cone
{
	0, 1, y, 0
	texture
	{
		pigment	{hf_pigment_rgb}
		finish {ambient 1}
	}
}

The 32-bit version of the function (which I haven't tested by importing back into POV-Ray yet) is as follows. Make sure to turn on the alpha channel in the output image by modifying the corresponding INI setting.

camera
{
	orthographic
	location	-z * 10
	direction	+z
	up		+y * 2
	right		+x * 2 * image_width/image_height
	rotate		+x * 90
}

#local hf_pigment_function_pow = function
{
	y * pow(256,4)
}
#local hf_pigment_function_t = function
{
	mod(hf_pigment_function_pow(x,y,z), pow(256,1))
}
#local hf_pigment_function_b = function
{
	mod(hf_pigment_function_pow(x,y,z)
	- hf_pigment_function_t(x,y,z), pow(256,2))
}
#local hf_pigment_function_g = function
{
	mod(hf_pigment_function_pow(x,y,z)
	- hf_pigment_function_t(x,y,z)
	- hf_pigment_function_b(x,y,z), pow(256,3))
}
#local hf_pigment_function_r = function
{
	mod(hf_pigment_function_pow(x,y,z)
	- hf_pigment_function_t(x,y,z)
	- hf_pigment_function_b(x,y,z)
	- hf_pigment_function_g(x,y,z), pow(256,4))
}

#local hf_pigment_t = pigment
{
	function {hf_pigment_function_t(x,y,z) / pow(256,1)}
	color_map {[0 rgbt 0][1 rgbt <0,0,0,4,>]}
}
#local hf_pigment_b = pigment
{
	function {hf_pigment_function_b(x,y,z) / pow(256,2)}
	color_map {[0 rgbt 0][1 rgbt <0,0,4,0,>]}
}
#local hf_pigment_g = pigment
{
	function {hf_pigment_function_g(x,y,z) / pow(256,3)}
	color_map {[0 rgbt 0][1 rgbt <0,4,0,0,>]}
}
#local hf_pigment_r = pigment
{
	function {hf_pigment_function_r(x,y,z) / pow(256,4)}
	color_map {[0 rgbt 0][1 rgbt <4,0,0,0,>]}
}


// This is the one you want
#local hf_pigment_rgbt = pigment
{
	average
	pigment_map
	{
		[hf_pigment_t]
		[hf_pigment_b]
		[hf_pigment_g]
		[hf_pigment_r]
	}
}

cone
{
	0, 1, y, 0
	open
	texture
	{
		pigment	{hf_pigment_rgbt}
		finish {ambient 1}
	}
}


Issues

You may notice that when importing the rendered image back into POV-Ray as a heightfield, the heightfield looks less smooth than it really should. This is not due to a deficiency in the technique as far as I can tell, but probably rather due to the gamut POV-Ray (as well as most image editing programs, but excluding Photoshop and a few others) uses when outputting bitmap images. This issue is discussed in more detail here. Heightfield images outputted by Leveller don't seem to suffer from this problem, and I suspect the 16-bit "hf_gray_16" format already natively supported by POV-Ray gets around this issue somehow as well.

Criticisms of this technique include:

  • While such images are easy to view in standard imaging software, they are more difficult to modify than a simple grayscale image.
  • Such a high level of precision is unnecessary. 16-bit heightfields should be more than sufficient.