Godot Version
Godot 4.3
Question
I have a Quixel asset pack taken from Fab. There’s quite a lot of assets and manually creating each material and scene containing the mesh would be too time-consuming, so I decided to make an editor script that will automate the process. With the given input folder(s) it will process each asset folder, look for textures and FBX model and create material and scene with the mesh, applying a material to that mesh.
The script mostly works, except for one issue: I’m trying to set material textures to the textures in folder and apparently set_shader_parameter
doesn’t seem to do anything to the texture. (or the texture I set isn’t being saved in material file?)
Here’s my editor script:
@tool
extends EditorScript
# Define texture patterns for detection
const TEXTURE_PATTERNS = {
"texture_ao": ["ao", "ambientocclusion"],
"texture_basecolor": ["basecolor", "albedo"],
"texture_bump": ["bump"],
"texture_cavity": ["cavity"],
"texture_height": ["displacement", "height"],
"texture_gloss": ["gloss"],
"texture_normal": ["normal"],
"texture_roughness": ["roughness"],
"texture_specular": ["specular"],
"texture_opacity": ["opacity", "transparency"]
}
const PICTURE_FORMATS = ["png", "jpg", "jpeg"]
const MODEL_FORMATS = ["fbx"]
const REWRITE = true
const FOLDER_PATHS = ["res://assets/junkyard"]#, "res://assets/warehouse", "res://assets/slate_quarry"]
# Path to the custom shader
const CUSTOM_SHADER_PATH = "res://assets/shaders/SurfaceMaterial.gdshader"
func _run():
print("Auto creating materials and scenes...")
# Process each subfolder in the selected directory
for folder_path in FOLDER_PATHS:
var dir = DirAccess.open(folder_path)
if dir:
for subfolder_name in dir.get_directories():
var subfolder_path = folder_path + "/" + subfolder_name
await process_subfolder(subfolder_path)
print("Materials and scenes auto created.")
# Process each subfolder and generate materials and scenes
func process_subfolder(subfolder_path):
print("Processing subfolder " + subfolder_path)
var texture_paths = {}
var fbx_path = ""
# Open the directory
var dir = DirAccess.open(subfolder_path)
if dir:
for file_name in dir.get_files():
var file_path = subfolder_path + "/" + file_name
var lower_name = file_name.to_lower()
# Check for model format (FBX)
if fbx_path == "" and MODEL_FORMATS.has(lower_name.get_extension()):
fbx_path = file_path
continue # Skip further checks for this file
# Check for picture formats
if PICTURE_FORMATS.has(lower_name.get_extension()):
# Match each texture pattern to identify its type
for param_name in TEXTURE_PATTERNS.keys():
for pattern in TEXTURE_PATTERNS[param_name]:
if lower_name.find(pattern) != -1:
texture_paths[param_name] = file_path
break
# Create a material if base color or other textures are found
var material_path = ""
if "texture_basecolor" in texture_paths:
print(texture_paths)
var material = create_material(texture_paths)
print("Creating material")
material_path = subfolder_path + "/GeneratedMaterial.tres"
if FileAccess.file_exists(material_path) and REWRITE:
dir.remove(material_path)
ResourceSaver.save(material, material_path)
# Create a scene if an FBX file is found
if fbx_path != "":
print("Creating a scene")
create_scene_with_fbx(fbx_path, material_path)
# Function to create a material with optional textures
func create_material(texture_paths):
var shader: Shader = load(CUSTOM_SHADER_PATH) as Shader
if shader == null:
push_error("Failed to load custom shader.")
return null
var material: ShaderMaterial = ShaderMaterial.new()
material.shader = shader
# Assign textures to shader parameters based on detected paths
for param_name in TEXTURE_PATTERNS.keys():
if param_name in texture_paths:
material.set_shader_parameter(param_name, load(texture_paths[param_name]) as Texture2D)
return material
# Function to create a scene for an FBX file with the given material
func create_scene_with_fbx(fbx_path, material_path):
var scene_root = StaticBody3D.new()
var mesh_instance = MeshInstance3D.new()
# Load the FBX file as a scene
var fbx_scene = load(fbx_path)
if fbx_scene == null:
push_error("Failed to load FBX file: " + fbx_path)
return
# Instantiate the FBX scene and search for a MeshInstance3D node
if fbx_scene is PackedScene:
var fbx_root = fbx_scene.instantiate()
var original_mesh_instance = find_mesh_instance(fbx_root)
if original_mesh_instance:
mesh_instance.mesh = original_mesh_instance.mesh
# Load the generated material and assign it to the MeshInstance3D
if material_path != "" and FileAccess.file_exists(material_path):
var material = load(material_path)
if material is Material:
mesh_instance.material_override = material
scene_root.add_child(mesh_instance)
mesh_instance.owner = scene_root
# Create a new scene with the MeshInstance3D as the root node
var scene = PackedScene.new()
scene.pack(scene_root)
# Save the new scene
var output_scene_path = fbx_path.get_base_dir() + "/GeneratedScene.tscn"
ResourceSaver.save(scene, output_scene_path)
# Recursive function to find the first MeshInstance3D node in the hierarchy
func find_mesh_instance(node):
if node is MeshInstance3D:
return node
for child in node.get_children():
var result = find_mesh_instance(child)
if result:
return result
return null
My custom shader contains and uses all the uniforms for the textures, if I set textures manually they are applied as expected.