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
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()
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.
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.
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
.
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.
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.
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.
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.
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.
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.
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"
)--end x loop
)--end y loop
progressend()
After the loops are over, we can end the progress display.
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.
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