• 3D
  • How to get different mesh appearance (f.e. by different material/texture) for same instanced meshes?

Hi,

for level editing (in Godot) I use a bunch of FBX models. Each of them contains one or more submeshes. I opened each of the models as new inherited scene, added collision and saved the new scene. Mostly I assigned a (default) material I created before (just to see how it looks). This is my standard workflow.

Inherited scene of FBX model with submeshes (just opened to show, not edited)

For the FBX models there are up to 16 textures available, each representing a different appearance of the model. For every texture I created a seperate material manually.

Now I want to use these scenes to create some kind of placeable game object scenes, switch their type (visibility of subnodes) and appearance (material of subnode meshes).

Game object scene with multiple inherited scenes (meshes)

For example (sorry for talking about walls and seeing trailers in code/images): There are different wall model (or other) scenes with one or more mesh instances as children and each mesh instance can have one or more materials assigned to it.

My goal is it, to place some of the walls (or other objects) an change their appearance by replacing/overriding their material by/with another one to create different appearing walls (or other objects).

Scene containing game object scenes

I tried it by code (GDScript) as a tool within the editor (by different export variables) and first it seems to work, but there are some issues. When reopening a scene where these "game objects" are placed or adding it to other scenes, all of them are "resetted" to the default material I assigned while creating the inherited model scenes.

Scene with added scene containing game object scenes with unexpected appearance

To change a material within the tool script I get all mesh instance nodes. One way was to get all surface materials and set them to the new one. The second try was to set the "material_override"-Property. Both leads to the issue.

Sample Code (not the complete script):

tool
extends Spatial


# Different types of trailer models
enum TRAILER_TYPE {
	TRAILER_1, TRAILER_2,TRAILER_1_DAMAGED,TRAILER_2_DAMAGED
}


# Different types of materials
enum MATERIAL_TYPE {
	DEFAULT, INTERIOR, GLASS
}


# Dictionary with pathes to mesh nodes of different trailer models
const TRAILERS = {
	"TRAILER_1": {
		"NodeName": "SM_Bld_Trailer_01",
		"DEFAULT": [
			"SM_Bld_Trailer_02",
			"SM_Bld_Trailer_02/SM_Bld_Trailer_Skirt_02"
		],
		"INTERIOR": [
			"SM_Bld_Trailer_02/SM_Bld_Trailer_Interior_02",
			"SM_Bld_Trailer_02/SM_Bld_Trailer_Interior_02/SM_Bld_Trailer_Interior_Door_02"
		],
		"GLASS": [
			"SM_Bld_Trailer_02/SM_Bld_Trailer_Glass_02"
		]
	},
	"TRAILER_2": {
		"NodeName": "SM_Bld_Trailer_02",
		"DEFAULT": [
			"SM_Bld_Trailer_03",
			"SM_Bld_Trailer_03/SM_Bld_Trailer_Skirt_03"
		],
		"INTERIOR": [
			"SM_Bld_Trailer_03/SM_Bld_Trailer_Interior_03"
		],
		"GLASS": [
			"SM_Bld_Trailer_03/SM_Bld_Trailer_Glass_03"
		]
	},
	"TRAILER_1_DAMAGED": {
		"NodeName": "SM_Bld_Trailer_Damaged_01",
		"DEFAULT": [
			"SM_Bld_Trailer_Damaged_02"
		],
		"INTERIOR": [
			"SM_Bld_Trailer_Damaged_02/SM_Bld_Trailer_Damaged_Interior_02"
		],
		"GLASS": [
			"SM_Bld_Trailer_Damaged_02/SM_Bld_Trailer_Damaged_Glass_02"
		]
	},
	"TRAILER_2_DAMAGED": {
		"NodeName": "SM_Bld_Trailer_Damaged_02",
		"DEFAULT": [
			"SM_Bld_Trailer_Damaged_03"
		],
		"INTERIOR": [
			"SM_Bld_Trailer_Damaged_03/SM_Bld_Trailer_Damaged_Interior_03"
		],
		"GLASS": [
			"SM_Bld_Trailer_Damaged_03/SM_Bld_Trailer_Damaged_Glass_03"
		]
	},
}


# Export variables
export (Game.Materials.PA_TYPE) var default_material = Game.Materials.PA_TYPE.PA_01_A setget _set_default_material
#export (Game.Materials.PA_TYPE) var interior_material = Game.Materials.PA_TYPE.PA_01_A setget _set_interior_material
#export (Game.Materials.GLASS_TYPE) var glass_material = Game.Materials.GLASS_TYPE.GLASS_01 setget _set_glass_material
export (TRAILER_TYPE) var trailer_type = TRAILER_TYPE.TRAILER_1 setget _set_trailer_type

# Private variables
var _current_trailer_node: Node #current node containing the mesh instances (could be multiple within the scene)
var _current_default_material: Material #current material mesh instance nodes (default means outside part meshes)
#var _current_interior_material: Material
#var _current_glass_material: Material

# Create materials instance for function calls
var _materials = Game.Materials.new() #contains all the loaded material resources

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	pass

func _set_default_material(mat_type: int) -> void:
	if _current_trailer_node:
		default_material = mat_type
		var trailer_key = TRAILER_TYPE.keys()[trailer_type]
		_current_default_material = _materials.get_material_PA(mat_type) #change the current material to the new one
		for mesh_name in TRAILERS[trailer_key]["DEFAULT"]: #node pathes to the mesh instance nodes
			var mesh = _current_trailer_node.get_node(mesh_name) #get the mesh instance node
			if mesh:
				mesh.material_override = _current_default_material
#				for index in mesh.get_surface_material_count():
#					mesh.set_surface_material(index,_current_default_material)

func _set_trailer_type(tt: int) -> void:
... # handling stuff to change _current_trailer_node (and visibility) whose meshes should be affected by changing material

Am I completely wrong with this approach? Maybe better change the albedo texture of the mesh instance material? Use the mesh material itself or use the override material and how about multiple materials at the mesh?

Will it eventually be a better solution, to create a seperate inherited scene for every appearance (material)? But this will increase the amount of scenes (f.e. one wall mesh with two materials and 16 textures... in combination there would be 32 scenes for just one wall element).

How to deal with this problem in general?

Thanks in advance for reading and maybe helping.

Cheers Z3R0PTR

Did you set resource_local_to_scene flag to true in your material overrides?

I only set this option via the property editor for each material I created to assign later.

These materials are loaded via "load()" in another script

func get_material_PA(mat_type) -> Material:
	var material_key = PA_TYPE.keys()[mat_type] #dictionary with pathes to material resources
	return load(PA[material_key]) as Material 

and used like (see line "current_interior_material = materials.get_material_PA(mat_type)" or code in last posting too)

...
# Create materials instance for function calls
var _materials = Game.Materials.new()
...

func _set_interior_material(mat_type: int) -> void:
	if _current_trailer_node:
		interior_material = mat_type
		var trailer_key = TRAILER_TYPE.keys()[trailer_type]
		_current_interior_material = _materials.get_material_PA(mat_type)
		for mesh_name in TRAILERS[trailer_key]["INTERIOR"]:
			var mesh = _current_trailer_node.get_node(mesh_name)
			if mesh:
				mesh.material_override = _current_default_material
#				for index in mesh.get_surface_material_count():
#					mesh.set_surface_material(index,_current_interior_material)

Or did you mean by code for the loaded material?

Something like this?

_current_default_material.setup_local_to_scene(true)

Hm, it doesn't actually matter if materials are local to the scene as they're not altered per scene.

A tool script that sets material_override properties of meshes in _ready() callback should do the trick. Not sure, but you may have to make this script resource local to the scene.

Does this mean the property of the created material must not be set? They won't be changed within the scenes, just exchanged by other ones.

Another problem with "material_override" is, it won't work with different surface materials at a mesh. I tried another approach. Cropped down everything to just set materials (not handle differen nodes, each with mesh instances). I removed the own type for switching materials (enum for combo box in editor). I set the materials itself as export vars.

It's juts a test script. There is a mesh instance (inherited FBX model) and two materials (outer and inner):


tool
extends Node

export (Material) var inner_material setget _set_inner_material
export (Material) var outer_material setget _set_outer_material

var _mesh_instance: MeshInstance

func _set_inner_material(mat: Material) -> void:
	inner_material = mat
	_mesh_instance = get_child(0) as MeshInstance	
	_mesh_instance.set("material/0",inner_material)


func _set_outer_material(mat: Material) -> void:
	outer_material = mat
	_mesh_instance = get_child(0) as MeshInstance
	_mesh_instance.set("material/1",outer_material)

If I have multiple scenes (inherited FBX models with the script attached to it) in a test scene, for all of them I can now change the material and even in another scene including this test scene I see these changes. After closing and reopening the test scene, all changes are kept.

Maybe there was another error in the first code (switching types and then assign material depending on selected type). Maybe it simply was the "_mesh_instance.set("material/1",outer_material)" call, that makes the difference.

So it looks like it works for now.

Maybe somebody else could verify this or has another approach.