1

I have a mesh, with 16 faces, that I ultimately show in Blender Game Engine, and want to apply separate textures to the faces via a script.

The reason behind this is I'm trying to show a detailed astrophysics skymap - currently zooming in shows pixelation, I managed to reduce the level of pixelation by increasing the texture size to 16kx16k, but I ended up using far too much RAM. I discovered that manually splitting the textures into 16 smaller chunks reduced the RAM usage (~25% of the texture is not being shown at one time, reducing RAM usage and pixelation at high zoom.

I tried to split the mesh into 32 individually textured faces and found that the rotation was much smoother and the RAM usage was further reduced.

I would obviously like to turn this workflow into a script. So far I have worked out how to texture an entire object as below, but cannot work out how to apply a different texture per face.

new_mat = bpy.data.materials.get('MaterialName')

new_mat.use_nodes = True

nodes = new_mat.node_tree.nodes links = new_mat.node_tree.links

texture = nodes.new('ShaderNodeTexImage') texture.image = bpy.data.images['Earth.png'] texture.location = 0,0

surface = nodes.new('ShaderNodeBsdfPrincipled') surface.location = 300,0

output = nodes.new('ShaderNodeOutputMaterial') output.location = 600,0

link = links.new( texture.outputs['Color'], surface.inputs['Base Color'] ) link = links.new( surface.outputs['BSDF'], output.inputs['Surface'] )

Does anyone have any tips? Should I create separate objects, texture them individually then merge?

brockmann
  • 12,613
  • 4
  • 50
  • 93
DrZaphod
  • 113
  • 5

1 Answers1

3

TLDR: You can create one material per polygon and assign all the materials (strictly speaking: the respective index of each material slot) to MeshPolygon.material_index.


I'd suggest create a dictionary based on the actual face id and the image path beforehand (// prefix is a Blender specific identifier for the current blend file):

images_per_polygon = {
    0: "//city.exr",
    1: "//courtyard.exr",
    2: "//forest.exr",
    ...
}

Then iterate through all Mesh.polygons, create a material based on the current index, add all nodes, load and assign the image to the Image Texture node using BlendDataImages.load() and assign the respective face/polygon id to MeshPolygon.material_index.

enter image description here

Demo on how to setup one image per polygon using the default cube and the built-in hdris based on How to load an image from disc and assign it to a newly created image texture node? and How to assign a new material to an object in the scene from Python? assuming the following folder structure (6 faces hence 6 images):

main_folder/
├── file.blend/
├── textures
    ├── city.exr
    ├── courtyard.exr
    ├── forest.exr
    ...

Make sure the filepaths are correct, the object is selected and run the script:

import bpy

Get a reference to the object in context

-> https://blender.stackexchange.com/a/239102/

obj = bpy.context.object

Create the dictionary

polygon id: "/path/to/current/blend/textures/image.exr"

images_per_polygon = { 0: "//textures/city.exr", 1: "//textures/courtyard.exr", 2: "//textures/forest.exr", 3: "//textures/interior.exr", 4: "//textures/night.exr", 5: "//textures/studio.exr" }

Remove all material slots from the object

-> https://blender.stackexchange.com/a/146719/

obj.active_material_index = 0 for i in range(len(obj.material_slots)): bpy.ops.object.material_slot_remove({'object': obj})

Create (if not present) the materials and assign them per polygon

for c, p in enumerate(obj.data.polygons): image = images_per_polygon.get(c) if image: # Create the material based on the name if not present # -> https://blender.stackexchange.com/a/23434 mat_name = "Mat{}".format(c) mat = bpy.data.materials.get(mat_name) if not mat: mat = bpy.data.materials.new(mat_name)

    # Append the materials to the slots and assign each per polygon
    obj.data.materials.append(mat)
    p.material_index = c

    # Material Properties
    # -> https://blender.stackexchange.com/q/23436/31447
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    nodes.clear()

    # Create Principled Shader node
    node_principled = nodes.new(type='ShaderNodeBsdfPrincipled')
    node_principled.location = 0,0

    # Create Image Texture node
    # -> https://blender.stackexchange.com/a/201414
    node_tex = nodes.new('ShaderNodeTexImage')
    # Assign the image
    node_tex.image = bpy.data.images.load(image)
    node_tex.location = -400,0

    # Create Output node
    node_output = nodes.new(type='ShaderNodeOutputMaterial')   
    node_output.location = 400,0

    # Link all nodes
    links = mat.node_tree.links
    link = links.new(node_tex.outputs["Color"], node_principled.inputs["Base Color"])
    link = links.new(node_principled.outputs["BSDF"], node_output.inputs["Surface"])


Further reading

brockmann
  • 12,613
  • 4
  • 50
  • 93