1
$\begingroup$

I wrote a python code that generates both a height map and texture map (see example below) from USGS topographic data. The idea is to create a coding pipeline that allows the user to generate a rendered image of topography anywhere in the world without a whole lot of data wrangling. I do know of the existence of BlenderGIS, which is awesome.

I then take the height and texture map to displace and texture a plane in blender. I saw an example of this workflow in a youtube tutorial a few years ago.

Below shows the heightmap, texture map, example renders from 4.0 and 4.1+, and the shading node trees from 4.0 and 4.1+. To me, it looks like the renders from 4.1+ are applying the basic shadows, but not from Cycles, but instead Eevee? Does anyone else have any ideas what changed from 4.0 to 4.1 that would break this workflow? Thanks in advance.

Height Map height map Texture Map texturemap Result of Render in Blender 4.0 render in 4.0 Result of Render in Blender 4.1+ render in 4.1+ Shading Node Tree in Blender 4.0 shading node tree in 4.0 Shading Node Tree in Blender 4.1+ shading node tree in 4.1+

Here is a code snippet of the function I used to set up the topography in python. The displacement file is the path to the height map and the color file is the path to the texture map. The dimensions file holds information about the measurements of the topography; it's a simple list of [horizontal x-length, horizontal y-length, height].

def setup_topo(dimensions_file,displacement_file,color_file):
# Create an image from the datafile
displacement_image = bpy.data.images.load(parent + "/" + displacement_file)
color_image = bpy.data.images.load(parent + "/" + color_file)

# make plane
topo_mesh = bpy.ops.mesh.primitive_plane_add(size=plane_size,location=(0.0,0.0,0.0))
bpy.context.active_object.name = 'Topography'
topo_obj = bpy.context.active_object

# Change the shape of the object to match data_aspect
width, height = displacement_image.size
if width / height > 1.0:
    topo_obj.scale = (1, height / width, 1)
else:
    topo_obj.scale = (width / height, 1, 1)

# dimensions if exists
if dimensions_file != "NULL":
    dimensions = np.load(parent + "/" + dimensions_file)
    x_length = dimensions[0]
    y_length = dimensions[1]
    relief = dimensions[2]
else:
    x_length = width
    y_length = height
    relief = 1.0  # if no dimensions given, relief is assumed to be equal to the exaggeration parameter

# add material
topo_mat = bpy.data.materials.new("topo_mat")
topo_mat.cycles.displacement_method = "DISPLACEMENT"
topo_mat.use_nodes = True

# calculate subdivisions
order_of_magnitude = math.floor(math.log10(number_of_subdivisions))
first_digit = int(np.round(number_of_subdivisions / (10.0**order_of_magnitude)))

topo_obj.data.materials.append(topo_mat)
bpy.ops.object.mode_set(mode="EDIT")
for i in range(0, order_of_magnitude):
    bpy.ops.mesh.subdivide(number_cuts=10)
bpy.ops.mesh.subdivide(number_cuts=first_digit)
bpy.ops.object.mode_set(mode="OBJECT")

# add image node - determines the displacement
displacement_image_node = topo_mat.node_tree.nodes.new("ShaderNodeTexImage")
# assign png to image to node
displacement_image_node.image = displacement_image
# change colorspace to b&w
displacement_image_node.image.colorspace_settings.name = "sRGB"

# add image node - determines the color of the lanscape
color_image_node = topo_mat.node_tree.nodes.new("ShaderNodeTexImage")
# assign png to image to node
color_image_node.image = color_image
# change colorspace to b&w
color_image_node.image.colorspace_settings.name = "sRGB"

# add displacement node
displacement_node = topo_mat.node_tree.nodes.new("ShaderNodeDisplacement")
displacement_node.inputs.get("Scale").default_value = (
    exaggeration * plane_size * relief / max(x_length, y_length)
)
displacement_node.inputs.get("Midlevel").default_value = 0.0

# connect nodes
topo_mat.node_tree.links.new(
    displacement_image_node.outputs["Color"], displacement_node.inputs["Height"]
)
topo_mat.node_tree.links.new(
    displacement_node.outputs["Displacement"],
    topo_mat.node_tree.nodes["Material Output"].inputs["Displacement"],
)
topo_mat.node_tree.links.new(
    color_image_node.outputs["Color"],
    topo_mat.node_tree.nodes["Principled BSDF"].inputs[0],
)

# change material properties
topo_mat.node_tree.nodes["Principled BSDF"].inputs.get("IOR").default_value = 1.0
topo_mat.node_tree.nodes["Principled BSDF"].inputs.get("Emission Strength").default_value = 0.0
topo_mat.node_tree.nodes["Principled BSDF"].inputs.get("Roughness").default_value = 1.0

# organize nodes
displacement_image_node.location.x, displacement_image_node.location.y = -500, 500
color_image_node.location.x, color_image_node.location.y = -500, 0
displacement_node.location.x, displacement_node.location.y = -200, 500
topo_mat.node_tree.nodes["Principled BSDF"].location.x, topo_mat.node_tree.nodes["Principled BSDF"].location.y = -100, 0
topo_mat.node_tree.nodes["Material Output"].location.x, topo_mat.node_tree.nodes["Material Output"].location.y = 300, 0

return width, height
$\endgroup$
4
  • $\begingroup$ Hello, looks like you need to check "Displacement And Bump" in the displacement settings for the material for true displacement $\endgroup$
    – Gorgious
    Commented Oct 17 at 15:33
  • $\begingroup$ That's it! Thanks so much. $\endgroup$
    – jkwang
    Commented Oct 17 at 16:01
  • $\begingroup$ It looks like there was a change in the python parameter from bpy.data.materials["mat"].cycles.displacment_method to bpy.data.materials["mat"].displacement_method $\endgroup$
    – jkwang
    Commented Oct 17 at 16:24
  • $\begingroup$ Ah, makes sense since it is now supported by EEVEE $\endgroup$
    – Gorgious
    Commented Oct 18 at 8:45

1 Answer 1

0
$\begingroup$

The solution seems to be to replace :

topo_mat.cycles.displacement_method = "DISPLACEMENT"

to topo_mat.displacement_method = "DISPLACEMENT"

This is due to the fact that EEVEE now supports true displacement so the property is now stored directly in the material, not in Cycles properties.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .