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$
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

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.