How To ... Access the Z-Depth channel

The 3ds Max scanline renderer generates a multitude of additional data channels providing information about colors, texture coordinates, normals, transparency, velocity, coverage etc. All of these channels can be accessed for reading via MAXScript.

The following simple script will render an image including Z-buffer depth info and convert it into what is known as a "voxel landscape" using the depth data to generate geometry representing the single image pixels. This script requires that geometry exists in the scene.

Related Topics:

Deleting objects using wildcard pathname

Rendering the current view with Z-buffer data

Creating a Progressbar

Reading and writing pixels

Accessing the Z-buffer channel as greyscale mask

NATURAL LANGUAGE

Set the current renderer to scanline.

Delete all objects from the last session.

Render the current view with Z-buffer info to a bitmap.

Go through all lines in the rendered image and through all pixels in each line

For every pixel, get the color and the z-depth value and create a box representing that pixel using the color for the object’s wireframe color and the z-depth for the box’s height.

Give each object a unique name to be able to easily delete them when needed.

Display a progress bar to show how long it will take to go through all pixels.

MAXSCRIPT

renderers.current = Default_Scanline_Renderer()
delete $VoxelBox*
rbmp = render outputsize:[32,32] channels:#(#zdepth) vfb:off
z_d = getchannelasmask rbmp #zdepth
progressstart "Rendering Voxels..."
for y = 1 to rbmp.height do
(
 if not progressupdate (100.0 * y / rbmp.height) then exit
 pixel_line = getpixels rbmp [0,y-1] rbmp.width
 z_line = getpixels z_d [0,y-1] rbmp.width
 for x = 1 to rbmp.width do
 (
  b = box width:10 length:10 height:(z_line[x].value/2)
  b.pos = [x*10,-y*10,0]
  b.wirecolor = pixel_line[x]
  b.name = uniquename "VoxelBox"
 )--end x loop
)--end y loop
progressend()

Step-By-Step:

renderers.current = Default_Scanline_Renderer()

First we ensure that the scanline renderer is the current renderer, as the default renderer for 3ds Max (Arnold) does not output zDepth in the same way.

delete $VoxelBox*

We make sure any voxel objects from past sessions have been deleted. We delete all objects starting with "VoxelBox" no matter what other characters the name contains.

PathName Values

rbmp = render outputsize:[32,32] channels:#(#zdepth) vfb:off

Now we render the active viewport at 32x32 resolution and store the result in the variable rbmp . We request an additional Z-depth channel to be generated. The Virtual Frame Buffer will be disabled during rendering.

Controlling the Renderer

z_d = getchannelasmask rbmp #zdepth

After rendering the image, we request a copy of the ZDepth channel as a greyscale mask. It will be stored in the bitma z_d .

getChannelAsMask

progressstart "Rendering Voxels..."

We start a new progress indicator with a respective caption - it will be displayed in the status bar at the bottom of the UI.

Progress Bar Display

for y = 1 to rbmp.height do
(

Now we start looping through all lines in the rendered bitmap. The variable y will change from 1 to the height of the bitmap.

For Loop

progressupdate (100.0 * y / rbmp.height)

The progressupdate method requires a percentage value. We divide the current bitmap line number y by the total number of lines rbmp.height . This yields a result between 0.0 and 1.0

Multiplied by 100.0 it returns a percentage between 0.0 and 100.0.

Progress Bar Display

pixel_line = getPixels rbmp [0,y-1] rbmp.width
z_line = getpixels z_d [0,y-1] rbmp.width

Now we read a whole line of pixels from both the RGBA bitmap and its z-buffer. Note that bitmap indices are 0-based and count from 0 to height minus one. This explains the "minus one" in the vertical position value.

getPixels

for x = 1 to rbmp.width do (

By counting from 1 to the width of the bitmap, we access each pixel of the current scanline.

For Loop

b = box width:10 length:10 height:(z_line[x].value/2) pos:[x*10,-y*10,0]

Now we create a box with width and length equal to 10, and height equal to half the Z-buffer's grayscale value. We also set the X and Y position to every 10 th world unit based on the pixel's coordinates. Note that we have to use the negative y value to get the upper left corner appear up left. A bitmap starts up left, while the positive quadrant of the MAX world space starts down left. This way we mirror the vertical coordinate to get a valid representation of the bitmap in the viewport.

Box : GeometryClass

b.wirecolor = pixel_line[x]

Using the RGB color of the rendered pixel, we assign a new wireframe color to the new box.

Viewport_related_node_properties

b.name = uniquename "VoxelBox"

We also set its box's name to a unique name with the base "VoxelBox"

uniquename

)--end x loop
)--end y loop
progressend()

After the loops are over, we can end the progress display.

Progress Bar Display

Using the Script

To run the script, simply press Ctrl+E or select Tools > Evaluate All in the Scripting Editor's menu. Make sure the current viewport shows the desired view to be rendered. The progress bar will let you know that the script is working. The result will be a group of boxes representing both color and depth of the rendered bitmap with the upper left corner at the world origin.

Where to go from here

Using this script as starting point, you could play with the creation of different object types like Spheres, Cylinders etc. A more advanced version could alter the Z position of vertices in a mesh grid based on the camera Z depth for a real heightmap elevation.

Back to

"How To" Tutorials Index Page