Model matrix relative to GridMap rather then world-space

Godot Version

4.2

Question

Hello, I’m pretty new to Godot and have been using visual shader graphs. Iv been trying to figure this out for a while now but to no avail. I’m hoping someone can help. And I would like to add images to this forum to help display what I mean but I’m not sure how so i will try my best to explain it well.

What im trying to achieve:
I have a gridmap of cubes creating some 3d object. Of course, each tile containing the same index has the same material and texture applied to it. But to break the repeating pattern, I would like to scale the texture up and have the UV’s be world space so that the one texture tiles across multiple cubes in the tile. So just as an example instead of a 512x512p texture being applied to a 1x1 of tiles, I want it to take the space of 3x3 of cubes in the tileset. And for it to tile neatly across the entire gridmap.

The almost a solution:
I can easily get the world position with the model matrix and multiply it using the TransformVectorMult node with the vertex node in the vertex shader. This gives the intended effect of each cube’s UV being a part of the world’s UV making the texture tile correctly with whatever scale I wish.

The problem:
This works fine for the environment since it doesnt move, but every object in my game is made out of these gridmaps, so say a car is made out of these cubes and I have the car move through the world. Since the UV is world-based, the texture is “stuck” to the world position and doesn’t move with the object. What I need is so the UV is not world-based, nor local-based, but based on the location of the gridmap tile relative to the entire Gridmap

What I tried:

  • Passing the object location through script into a parameter of the shader by looping through all of the tiles in the Gridmap and updating each frame its location, that way I add that value to the UV of each cube. In theory, this seemed it would work but I couldn’t even access the material of each tile. Also, this seems very inefficient looping through each tile every frame. To fix both of these I thought I could update a parameter of a global material with the shader, this does work but only if there is one object using that material. As you can imagine I will have many objects using various materials and they all need to work seperatly.

-On ready, pass over the information of the locations relative to the gridmap of each tile to the shader. And use that info within the shader by adding it to the UV’s. But this is blocked by me not being able to access each tile’s material. In addition, I don’t think this would work as (how i understand it) the gridmap treats all tiles of the same index as the same object so I’m not sure I can change a parameter differently for each block in the gridmap.

This is the code I used to try to pass over the locations to the shader:

extends GridMap

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	# Get all the cells that are used in the GridMap
	var used_cells = get_used_cells()  # Returns all the occupied grid cell positions

	# Iterate over each used cell
	for cell in used_cells:
		# Get the item in the current cell
		var cell_item = get_cell_item(cell)
		
		if cell_item != -1:  # If a tile exists at this cell position
			var mesh_instance = get_node_or_null("MeshInstance3D") 
			print(mesh_instance)
			if mesh_instance != null:
				# Get the material from the MeshInstance3D node
				var material = mesh_instance.get_surface_material(0)  # Get the first surface material
				
				if material != null and material.has_parameter("GridLoc"):
					# Set the 'GridLoc' parameter in the material to the grid cell position
					material.set("GridLoc", Vector3(cell.x, cell.y, cell.z))

I don’t like these solutions anyway, as they seem convoluted and messy. I’m open to any other method, doesn’t even have to be a shader, tho I don’t know how else one could achieve this effect.
Thank you in advance!

edit.
After some headscratching and testing, I managed to pass over information to the shader. But as I feared changing the property of one block changes all of the other in every separate gridmap with the same block index. So when i move one object it updates the location to all other. I tried to combat this by duplicating the material through script but its not the material thats the problem, it the block shared MeshLibrary.
Here is the almost working script i got:

@tool
extends GridMap

@export var material : ShaderMaterial

#func _ready():

func _ready() -> void:
	if material == null:
		return
		
	var tile_material = material.duplicate() as ShaderMaterial
	
	var used_cells = get_used_cells()  # Get all occupied cells in the grid
	var mesh_library = get_mesh_library()  # Get the MeshLibrary used by the GridMap
	print("working")
	print(global_transform.origin)
	for cell in used_cells:
		var cell_item = get_cell_item(cell)  # Get the tile ID at the current cell
		if cell_item != -1:  # Ensure the cell has a valid tile
			# Get the mesh for the current tile from the MeshLibrary
			var mesh = mesh_library.get_item_mesh(cell_item)
			
			if mesh != null:
				mesh.surface_set_material(0, tile_material)

func _process(delta: float) -> void:
	var used_cells = get_used_cells()  # Get all occupied cells in the grid
	var mesh_library = get_mesh_library()  # Get the MeshLibrary used by the GridMap
	print("working")
	print(global_transform.origin)
	for cell in used_cells:
		var cell_item = get_cell_item(cell)  # Get the tile ID at the current cell
		if cell_item != -1:  # Ensure the cell has a valid tile
			# Get the mesh for the current tile from the MeshLibrary
			var mesh = mesh_library.get_item_mesh(cell_item)
			
			if mesh != null:
				# Access the material of the mesh
				var material = mesh.material  # Assuming the material is on the first surface
					
				# Set the 'grid_position' uniform to the current cell's position
				if material.get_class() == "ShaderMaterial":
					
					material.set_shader_parameter("grid_position", global_transform.origin)