Downloads, Houdini, Python, Redshift, Substance Painter

Houdini Snippet Vol.12 – Texture Workflow


Here are some kind of ideas for simplifying the texture handling from Substance Painter to Houdini. Therefor i use the gallery preset system and create a little prototype of script to add all baked maps from Substance Painter, with one click. This snippet is a bit low-end but i guess usefull for other automatism, as well.

1. We need some maps from Substance Painter, first!

Here i show the Metal/Roughness-Workflow which gives a very close result in Houdini/Redshift. Starting point is a kind of robotish/cyborgish glove and a ID map.

I use a custom HDA for quick ID-Map backing with UDIM support (picture down below: for presentation purposes, a 3×3 tile UDIM-set)

Some Substance Painter Materials.

For the Metal/Roughness-Workflow you need 5 maps. Diffuse, Metallic, Roughness, Normal and Displacement.

I export these to a unique folder, for instance “P:\…\SubstancePainter\Setup01\_export”. The export folder just contains the exported texture maps.

In the “Export Configuration”, i suffix-ing the type of map in the name convention for later indentification.

2. Preparing the redshift_vopnet and shader setup:

Material Reflection settings to GGX and Metalness.

Referring to the Maya/Redshift Docs: https://support.allegorithmic.com/documentation/spdoc/redshift-for-maya-145653882.html

Gamma settings Texture loader.

3. Automate the map linking process:

Add some OGL Render parms to the redshift_vopnet. So we can see our Texture maps  in the viewport. Those parms we link later to the Texture loader:

Right Click > Parameter and Channels > Edit Paramater  Interface…

Under the “Render Properties”-Tab type in the filter-field: “OpenGl”,  and apply the  Diffuse- , Metallic- , Roughness- , Normal- and Displacment map parms to your vopnet.

Reference the OGL map inputs to the Texture inputs inside the vopnet.

Save it as gallery preset.

Right click > Save > To Gallery…

Under the “Material Pallete” pane, in this case “Shop Materials”, the shader appears and is now drag and droppable to your favourite context.

4. A small script for the map pathes:

Create a Shelf-tool and add the following python code:

import hou
import os
from PySide2 import QtWidgets

## defines the location on your drive the QFileDialog kicks in ##
defaultPath = "C:/YourPath"

nodes = hou.selectedNodes()

if nodes and nodes[0].type().name() == "redshift_vopnet":
    folder = QtWidgets.QFileDialog.getExistingDirectory(None, "Substance Painter Export Path", defaultPath)
    maps = [f for f in os.listdir(folder) if os.path.isfile(os.path.join(folder, f))]
    try:
        i_diff = 0
        i_metallic = 0
        i_rough = 0
        i_norm = 0
        i_heigth = 0
        for i, map in enumerate(maps):
            #################################################################################
            if map.find("_diff") != -1:
                ## UDIM 1001 Pattern Detection ##
                tmp = map.split(".") 
                if len(tmp) > 2:
                    nodes[0].setParms({"ogl_tex1": folder + "/" + tmp[0]+".<UDIM>."+tmp[2]})
                    nodes[0].setParms({"ogl_use_tex1": False}) # off: <UDIM> token not solvable by HOU
                    i_diff += 1
                
                ## UDIM _v1_u1 Pattern Detection ##
                if map.find("_u1_v1") != -1: 
                    nodes[0].setParms({"ogl_tex1": folder + "/" + map.replace("_u1_v1", "<UVTILE>")})
                    nodes[0].setParms({"ogl_use_tex1": False}) # off: <UVTILE> token not solvable by HOU  
                    i_diff += 1
                    
                ## Normal ##
                if i_diff == 0:
                    nodes[0].setParms({"ogl_tex1": folder + "/" + map})
                    nodes[0].setParms({"ogl_use_tex1": True})                
            #################################################################################    
            if map.find("_metallic") != -1:
                ## UDIM 1001 Pattern Detection ##
                tmp = map.split(".") 
                if len(tmp) > 2:
                    nodes[0].setParms({"ogl_metallicmap": folder + "/" + tmp[0]+".<UDIM>."+tmp[2]})
                    nodes[0].setParms({"ogl_use_metallicmap": False}) # off: <UDIM> token not solvable by HOU
                    i_metallic += 1
                
                ## UDIM _v1_u1 Pattern Detection ##
                if map.find("_u1_v1") != -1: 
                    nodes[0].setParms({"ogl_metallicmap": folder + "/" + map.replace("_u1_v1", "<UVTILE>")})
                    nodes[0].setParms({"ogl_use_metallicmap": False}) # off: <UVTILE> token not solvable by HOU
                    i_metallic += 1
                    
                ## Normal ##
                if i_metallic == 0:
                    nodes[0].setParms({"ogl_metallicmap": folder + "/" + map})
                    nodes[0].setParms({"ogl_use_metallicmap": False}) # performance wise off
            #################################################################################            
            if map.find("_rough") != -1:
                ## UDIM 1001 Pattern Detection ##
                tmp = map.split(".") 
                if len(tmp) > 2:
                    nodes[0].setParms({"ogl_roughmap": folder + "/" + tmp[0]+".<UDIM>."+tmp[2]})
                    nodes[0].setParms({"ogl_use_roughmap": False}) # off: <UDIM> token not solvable by HOU
                    i_rough += 1
                
                ## UDIM _v1_u1 Pattern Detection ##
                if map.find("_u1_v1") != -1: 
                    nodes[0].setParms({"ogl_roughmap": folder + "/" + map.replace("_u1_v1", "<UVTILE>")})
                    nodes[0].setParms({"ogl_use_roughmap": False}) # off: <UVTILE> token not solvable by HOU
                    i_rough += 1
                    
                ## Normal ##
                if i_rough == 0:
                    nodes[0].setParms({"ogl_roughmap": folder + "/" + map})
                    nodes[0].setParms({"ogl_use_roughmap": False}) # performance wise off
            #################################################################################                                                
            if map.find("_norm") != -1:
                ## UDIM 1001 Pattern Detection ##
                tmp = map.split(".") 
                if len(tmp) > 2:
                    nodes[0].setParms({"ogl_normalmap": folder + "/" + tmp[0]+".<UDIM>."+tmp[2]})
                    nodes[0].setParms({"ogl_use_normalmap": False}) # off: <UDIM> token not solvable by HOU
                    i_norm += 1
                
                ## UDIM _v1_u1 Pattern Detection ##
                if map.find("_u1_v1") != -1: 
                    nodes[0].setParms({"ogl_normalmap": folder + "/" + map.replace("_u1_v1", "<UVTILE>")})
                    nodes[0].setParms({"ogl_use_normalmap": False}) # off: <UVTILE> token not solvable by HOU
                    i_norm += 1
                    
                ## Normal ##
                if i_norm == 0:
                    nodes[0].setParms({"ogl_normalmap": folder + "/" + map})
                    nodes[0].setParms({"ogl_use_normalmap": False}) # performance wise off                
            #################################################################################
            if map.find("_heigth") != -1:
                ## UDIM 1001 Pattern Detection ##
                tmp = map.split(".") 
                if len(tmp) > 2:
                    nodes[0].setParms({"ogl_displacemap": folder + "/" + tmp[0]+".<UDIM>."+tmp[2]})
                    nodes[0].setParms({"ogl_use_displacemap": False}) # performance wise off
                    i_heigth += 1
                
                ## UDIM _v1_u1 Pattern Detection ##
                if map.find("_u1_v1") != -1: 
                    nodes[0].setParms({"ogl_displacemap": folder + "/" + map.replace("_u1_v1", "<UVTILE>")})
                    nodes[0].setParms({"ogl_use_displacemap": False}) # off: <UVTILE> token not solvable by HOU
                    i_heigth += 1
                    
                ## Normal ##
                if i_heigth == 0:
                    nodes[0].setParms({"ogl_displacemap": folder + "/" + map})
                    nodes[0].setParms({"ogl_use_displacemap": False}) # performance wise off                  
    except:
        hou.ui.displayMessage("Parms in redshift_vopnet missing. Use the rs_substance_metal_rough preset from Gallery")
else:
    hou.ui.displayMessage("Select a redshift_vopnet first!")

As i mentioned, i use a single “_export” folder for my Substance Painter maps, per object. Fire the script, find your folder and voila.

Hint: If your use the other maps to get feedback in viewport (especially the Displacment map), your framerate will drasticly drop and occupies precious VRAM, but its cool for setting up the right values. I recommend to turn the OGL stuff off, in terms of faster GPU-render results.

Ther render result looks pretty much the same like in Substance Painter/IRay.

Win: Copy the gallery folder to “C:\Users\<user>\Documents\houdini17.0”

Get .hdalc: ns_ID_MapMaker.hdalc.zip

Get .gal: gallery.zip