Python Tools - Session 3B
Height-Map Mosaic
January 23, 2020
Session Navigation
Overview
Building on Session 3A's basic mosaic, this session adds UV coordinate sampling to read pixel colors from the source image. Each cube's height is driven by the greyscale value of the sampled pixel, creating a 3D topographic effect. Vertex coloring is then applied so each tile displays the original image color directly on the geometry.
Key Concepts
- UV coordinate sampling — using
pm.colorAtPoint()to read pixel data at specific UV positions - Greyscale conversion — averaging RGB channels to derive a single brightness value
- Height mapping — scaling cube height based on pixel brightness to create a relief effect
- Vertex coloring — applying per-vertex RGB colors with
pm.polyColorPerVertex() - UV math — incrementing U and decrementing V to walk through the texture in the correct direction
Code
This script extends the mosaic by sampling the image at each tile's UV position, computing height from greyscale, and applying the original color as vertex color.
"""
#############################################################################
filename session_3b_code.py
author Matt Osbond
course CS115/CSX510
Brief Description:
Mosaic Project
Session 3B - Jan 23 2020
#############################################################################
"""
# Import the PyMel module, and create the alias "pm" for it to save on typing later
import pymel.core as pm
pm.newFile(force=True)
# Create the variables that we may want to change
# The path to the image - change this to a path on your local drive
image_file = r"C:\path\to\your\image.jpg"
# This is the size of each tile in the mosaic
tile_size = 10
# Set a multiplier for the height of each tile
height_scalar = 2
# Create a file node inside Maya for us to use
file_node = pm.createNode('file')
# Set the image path to be the image set above
file_node.fileTextureName.set(image_file)
# Now we calculate the number of tiles in each direction
# We do this by getting the resolution of the texture from the file node
# .. and dividing it by the tile size
# i.e. a resolution of 400, with tiles of 10 units, will create 40 tiles
width_count = int(file_node.outSizeX.get() / tile_size)
height_count = int(file_node.outSizeY.get() / tile_size)
# Print them out to make sure we get good values
print width_count, height_count
# Now we start the FOR loops
# Because we are counting along each axis as we go..
# .. we need to create a variable outside the loop to increment both the position and UV coords
x_pos = 0.0
u_coord = 0.0
for x in xrange(0, width_count):
# here we add "tile_size" to the x_pos of the current iteration
# += is short hand - the long form of this would be
# x_pos = x_pos + tile_size
x_pos += tile_size
u_coord += 1.0/width_count # Here, we INCREMENT the U direction, starting at 0
# Same here for the Y axis
y_pos = 0.0
v_coord = 1.0
for y in xrange(0, height_count):
y_pos += tile_size
v_coord -= 1.0/height_count # Here we DECREMENT the V coord, starting at 1
# Calculate the height
# First, grab a value from the input multiplier and the tile size
height = height_scalar * tile_size
# colorAtPoint samples the texture on the file node, at the UV coords we specify
# The "output" argument defines what data we get back
rgb = pm.colorAtPoint(file_node, u=u_coord, v=v_coord, output='RGB')
# Now calculate a pseudo greyscale, by getting the average of each channel
greyscale = (rgb[0] + rgb[1] + rgb[2]) / 3.0
# Some images look better if we "invert" the greyscale
# This makes brighter pixels be smaller
greyscale = 1.0 - greyscale
# Multiply both together
height = height * greyscale
# Create a poly cube, setting the width, depth and height to be the size of the tile
# REMEMBER - creating most objects in Maya returns a list
# This contains both the Transform and the Shape
# We only want the transform - the first element
# We access the first element by using "[0]", the "zeroth" element in the list
tile = pm.polyCube(width=tile_size, depth=tile_size, height=height)[0]
# Move the transform node to our new coordinates
pm.move(tile, [x_pos, height/2.0, y_pos])
# Finally, set the vertex color of the tile to be the RGB of the sampled pixel
# Note, the "colorDisplayOption" argument will tell Maya to display the vertex color
# By default, it does not!
pm.polyColorPerVertex(tile, rgb=rgb, colorDisplayOption=True)