Solaris Megascans USD Asset Builder + Karma Lookdev | Free HDAs

   2527   1   0
User Avatar
Member
5 posts
Joined: Nov. 2021
Offline
My Houdini Solaris Megascans USD Asset Builder (MaterialX) + Karma Lookdev HDA's is now available on Gumroad for free.

Build a Megascans asset simply by selecting the folder that contains the textures and variants of the asset.
You can set the asset name, specify if the asset includes a proxy, normalize the size (maximum size is 1 meter), set the geometry and material save paths for the USD, and define the geometry and proxy paths to determine how they will be read in the scene graph. All of these parameters can be adjusted without needing to rebuild the asset.

Lookdev the Megascans asset in Karma render. Rotate the asset, offset it, change light exposure and launch an automatic render by just one click.This tool is asset-agnostic and can be used to lookdev any asset in Solaris, not limited to Megascans.

Hope you enjoy it!



https://pepebuendia.gumroad.com/l/csqcv [pepebuendia.gumroad.com]
Edited by pepebuendia - Sept. 13, 2024 02:11:01
Houdini FX/TD
User Avatar
Member
6 posts
Joined: May 2016
Offline
Hello I tried your tool and saw it didn't caught the variations .fbx found in a few 3d plants
Modified the AssetType function
def getAssetType(assetDir):
    check = 0
    for file in os.listdir(assetDir):
        if file.endswith('.json'):
            file_path = os.path.join(assetDir, file)
            try:
                with open(file_path, "r", encoding="utf-8") as f:
                    json_data = json.load(f)
                    asset_type = json_data.get("semanticTags", {}).get("asset_type", "unknown")

                    if asset_type == "3D plant":
                        check = 1
            except Exception as e:
                print(f"Error reading {file_path}: {e}")

    if check == 1:
        assetType = "3DPlant"
    else:
        assetType = "3D"

    return assetType

And

def getGeoAndProxyPath(dirList, geoFormat, lod, lodProxy, assetDir):
geoFiles = []
proxyFiles = []

# Find all geo files in the folder
for file in dirList:
if lod in file and geoFormat in file:
geoFiles.append(file)

# Find all proxy files in the folder if needed
if lodProxy != "None":
for file in dirList:
if lodProxy in file and geoFormat in file:
proxyFiles.append(file)

# Set geo and proxy paths
geoPaths = [os.path.join(assetDir, file) for file in geoFiles]
proxyPaths = [os.path.join(assetDir, file) for file in proxyFiles]

return geoPaths, proxyPaths

Since it returns a list other variables need to be changed elsewhere
In buildGeo()
(if it's 3D the variations are embedded in the fbx or abc)

if assetType == "3D":

proxyPath[0]
geoPath[0]

elif assetType == "3DPlant":

varAssetFolder=assetDir+"/"
varList = os.listdir(assetDir)
geoPath[i]
proxyPath[i]


Also added a way to deal with the albedo dissapearing when subsurf increase (look broken) (adding albedo and translucency and setting subsurf to .5)

def applyTextures(mtlx_subnet, mtlxstandard_surface, mtlxdisplacement, texturesDict, texturesDir):

    mtlxmultiply = mtlx_subnet.createNode("mtlxmultiply")
    mtlxstandard_surface.setInput(1, mtlxmultiply)
    mtlxadd = mtlx_subnet.createNode("mtlxadd")
    mtlxnormalmap = mtlx_subnet.createNode("mtlxnormalmap")
    mtlxstandard_surface.setInput(40, mtlxnormalmap)
    
    # Keep track of Albedo and Translucency nodes
    albedo_node = None
    translucency_node = None
    
    for td in texturesDict:
        if texturesDict[td] != "":
            image = mtlx_subnet.createNode("mtlximage", td)
            path = texturesDir + texturesDict[td]
            image.parm("file").set(path)
            
            if td == "Albedo":
                albedo_node = image
                mtlxmultiply.setInput(0, image)
                mtlxadd.setInput(0, image)
                
            if td == "Metalness":
                mtlxstandard_surface.setInput(3, image)
                image.parm("signature").set("float")
            if td == "AO":
                mtlxmultiply.setInput(1, image)
                image.parm("signature").set("float")
            if td == "Normal":
                mtlxnormalmap.setInput(0, image)
                image.parm("signature").set("vector3")
            if td == "Roughness":
                mtlxstandard_surface.setInput(6, image)
                image.parm("signature").set("float")
            if td == "Displacement":
                min, max = getMinMaxPixelValue(path)
                mtlxrange = image.createOutputNode("mtlxrange")
                mtlxrange.parm("inlow").set(0)
                mtlxrange.parm("inhigh").set(1)
                mtlxrange.parm("outlow").set(-0.5)
                mtlxrange.parm("outhigh").set(0.5)
                mtlxdisplacement.setInput(0, mtlxrange)
                mtlxdisplacement.parm("scale").set(0.01)
                image.parm("signature").set("float")
            if td == "Opacity":
                mtlxstandard_surface.setInput(38, image)
                image.parm("signature").set("float")
            if td == "Transmission":
                mtlxstandard_surface.setInput(11, image)
            if td == "Translucency":
                translucency_node = image
                mtlxadd.setInput(1, image)
                mtlxstandard_surface.parm("subsurface").set(0.5)
                mtlxstandard_surface.setInput(18, mtlxadd)
                mtlxstandard_surface.parm("thin_walled").set(1)
                #mtlxstandard_surface.setInput(17, image)

    mtlx_subnet.layoutChildren()
Sorry if this is a mess but could help someone
And thank you for sharing
Edited by jan.b - Nov. 10, 2025 19:23:59
  • Quick Links