1
$\begingroup$

I want to apply several materials to a landscape mesh. I understand that the normal way to do this is to manually select faces, which is what I've done in the 3D viewport with some place holder materials. However I also have material maps from World Machine. I want to use these material maps to select the faces of the mesh, where white is do select, black is don't select.

Want to apply several different materials to landscape via their own masks.

The reason I want to do it this way instead of just applying a bitmap texture to the entire landscape is because I want to use the materials that are already in the game engine I'll be importing the mesh into. So basically just make place holder material slots in blender and apply the actual materials after import to the game engine. I'm new to blender so I thought maybe this could be done by importing a black/white image in the uv editor and somehow selecting faces from that, but so far no luck.

$\endgroup$
0
$\begingroup$

It's now possible to transfer a bitmap to Material ID with Geometry Nodes.

Assuming you have an nxn bitmap to convert to material ID's in an nxn grid of faces:

Construct a UV map for the grid which will make point-samples of the bitmap

  • Assign and apply a Split Edges modifier to the grid, so all its faces are separated
  • UV unwrap the grid, with 'Project from View (Bounds)'.
  • Set the pivot in the UV Editor to 'Individual Origins'
  • With all faces selected, S scale to 0, or near-0 :

enter image description here

As you see, each face becomes a dot over the bitmap. Once this is done, you can M > By distance weld your grid back together again. You can copy the UV map to other grids with the same number of squares.

Transfer with GeoNodes

Now give the grid a Geometry Nodes modifier, with this simple node-tree..

enter image description here

.. following the steps to make an (image-texture) Texture to sample from. Click the rightmost little button in the Sample Texture node, to jump to the 'Texture' tab where you can create it. In that area, under 'Sampling', disable Interpolation.

As it happens, all the casting between GeoNodes types and domains just works, in this case, so the tree doesn't have to be fiddly. Now the texture, (displayed on the left) has been transferred to Material ID's (on the right).

If you have multi-color bitmaps you want to transfer, that would be possible, too. The tree would just be a bit bigger. Ask in a comment, if you need that.

(Blender 3.0a)

$\endgroup$
0
$\begingroup$

So I reckon that Robin Betts's solution would do the trick, but after thinking about what I really wanted to do a bit more I realized I just needed to buckle down and do some scripting. So this solution does basically the same idea as the previous answer, just in a more efficient in a way. Here's the process:

  1. Export two .bmp maps from world machine. First is the usual heightmap that's needed to use on the displacement modifier, the second is a multi-colored .bmp, let's call it material_ids.bmp, where each color represents a different material. Each pixel should be weighted 100% a single color, no blending. Keep the hex code of each color around for later on.
  2. Same as in the above answer, make a grid that's the same resolution as the two .bmps, unwrap, and apply a displacement modifier using the heightmap
  3. However many materials are represented in material_ids.bmp, add this many materials to the mesh. Since the idea is to texture outside blender, I go with high-contrast, single-color materials for ease.
  4. Open material_ids.bmp in the Texture Properties making sure to change the Mapping -> Extension to Clip.
  5. In the Scripting window and copy-paste this script,
import bpy
import bmesh
import matplotlib.colors as colors
import numpy as np

material_id_bitmap = bpy.data.images["material_ids.bmp"]

hex_colors_to_mat_ids = {'#060044': 0, '#440000': 1,'#444200': 2,'#3c0044': 3,'#007500': 4,'#004344': 5}

bpy.ops.object.mode_set(mode='EDIT')

landscape_mesh = bmesh.from_edit_mesh(bpy.data.objects["grid_mesh"].data)

bitmap_pixels =  np.resize(np.array(material_id_bitmap.pixels), (int(len(material_id_bitmap.pixels)/4), 4))
from_dec_to_hex_color = lambda l: colors.to_hex([l[0],l[1],l[2],l[3]])
hex_colors = list(map(from_dec_to_hex_color, bitmap_pixels))
faces_array = np.array(landscape_mesh.faces)
material_ids = np.array([hex_colors_to_mat_ids[x] for x in hex_colors])

landscape_mesh.faces.ensure_lookup_table()
for i in range(0, len(material_ids)):
    print(i, material_ids[i])
    faces_array[i].material_index = material_ids[i]
    landscape_mesh.faces.ensure_lookup_table()
  1. Change a few things in the script before running it
    • change "material_ids.bmp" to whatever you called the multicolored bitmap
    • change the keys of hex_colors_to_mat_ids to whatever colors are in the material_ids.bmp, and change their values to the ids of the materials you've assigned to the grid mesh; the ids are just integers representing the top-down order of the materials starting from 0
    • change "grid_mesh" to whatever the name of the grid mesh is
    • if you don't have matplotlib installed for blender's python distribution, you'll either have to install it, or replace from_dec_to_hex_color = lambda l: colors.to_hex([l[0],l[1],l[2],l[3]]) with a lambda function that takes as an argument a list of length 4 where each element is respectively a decimal R-G-B-A value and returns its #RRGGBB hexadecimal equivalent
  2. Select the mesh and run the script

And from a .bmp like this

The result is something like this:

I'm sure this can be done with some mixture of geometry nodes like the above answer suggests, but I found dealing with the python directly more straightforward. For processing time, for a 1024x1024 grid mesh the script takes just over a minute to run.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.