1
$\begingroup$

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?

$\endgroup$

1 Answer 1

3
$\begingroup$

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

$\endgroup$
0

You must log in to answer this question.

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