Hello there! I am quite new to shaders but I'm trying to match the ALBEDO of my multimeshinstance3d instances to the surface they are populating (including the shadows). Is there any way to acces that info and pass it to the shader attached to the multimesh?
For reference: I am trying to make grass sprites that blend in with the floor (t3ssel8r style):
You are absolutely right and I tried that but that just makes all the instances the same color which doesnât account for the darkening when they are inside a shadow.
Yes I had unshaded on sorry im quite new to all this. But thank you so much man it looks so good. I canât thank you enough
The only thing now is that the higher they are in my game window the more the color differs from the floor. Is there any way to prevent that?
I tried updating a lot of code and got to a point where the instance color varies based on the noise map i use for the floor. However it seems that the variations in color get picked at random from the noise texture instead of picking the color from the point on floor directly below it.
Iâm guessing its because the noise texture is so to speak not âbakedâ perfectly onto the map but just applied over it. I already tried to use a subviewport texture that rendered the floor from above as an image but that always returned a black screen as texture no matter what I did. If anyone has any suggestions I would be really helped!!
Meshinstance3D Script:
@tool
extends MultiMeshInstance3D
@export var main_lightsource : DirectionalLight3D
@export var terrain: MeshInstance3D
@export var placement_area_size: Vector2 = Vector2(150.0, 150.0)
@export var instance_count : int = 1000
@export var grass_mesh : Mesh
@export var grass_base_color: Color = Color(1.0, 0.0, 1.0, 1.0)
@export var use_floor_color : bool = false
@export var terrain_texture_scale : Vector2 = Vector2(150.0, 150.0)
func _ready():
if terrain == null:
push_error("No terrain assigned to MeshInstance3D")
return
var mm = MultiMesh.new()
mm.transform_format = MultiMesh.TRANSFORM_3D
mm.use_custom_data = true
mm.instance_count = instance_count
mm.visible_instance_count = instance_count
mm.mesh = grass_mesh
multimesh = mm
var noise : FastNoiseLite = null
if use_floor_color and terrain.material_override is StandardMaterial3D:
var tex = terrain.material_override.albedo_texture
if tex is NoiseTexture2D and tex.noise is FastNoiseLite:
noise = tex.noise
else:
push_warning("No valid FastNoiseLite found in terrain's albedo texture.")
var space_state = get_world_3d().direct_space_state
for i in range(instance_count):
var x = randf() * placement_area_size.x - placement_area_size.x / 2
var z = randf() * placement_area_size.y - placement_area_size.y / 2
var from_pos = Vector3(x, 1000, z)
var to_pos = Vector3(x, -1000, z)
var ray_params = PhysicsRayQueryParameters3D.new()
ray_params.from = from_pos
ray_params.to = to_pos
ray_params.exclude = []
ray_params.collision_mask = 0x7FFFFFFF
ray_params.collide_with_bodies = true
ray_params.collide_with_areas = false
var result = space_state.intersect_ray(ray_params)
if result:
var hit_pos = result.position
var _transform = Transform3D()
_transform.origin = hit_pos
mm.set_instance_transform(i, _transform)
var color = grass_base_color
if use_floor_color:
var sample_pos = Vector2(hit_pos.x, hit_pos.z) * terrain_texture_scale
var value = (noise.get_noise_2dv(sample_pos) + 1.0) * 0.5
color = Color(value, value, value)
mm.set_instance_custom_data(i, color)
else:
push_error("ERROR: var 'result' is empty")
var fallback_transform = Transform3D().translated(Vector3(x, 0, z))
mm.set_instance_transform(i, fallback_transform)
Reference image (random color variations instead of floor color directly below it):
If you want just a single color to match you can look at the explanations in the comments above, but if you want to match it to a texture keep reading!
So for this to work you need to have a multimeshinstance3d with a custom script on it.
In this script you want to make a private function called something along the lines of _get_terrain_image(). Just put in some safety checks for the type of material the terrain mesh has and then store the albedo_texture in a custom var. Then you can just call and return
var img : Image = your_custom_var.get_image().
For the main functionality you need to make a new function that creates the multimesh.
Letâs call it _create_multimesh() and make sure to set use_custom_data to true.
Then you need to specify the instance_count, mesh, etc and set the multimesh of the multimeshinstance3d node to that new multimesh we just created.
Call _create_multimesh() in the ready function and make and call another final function called e.g. _instantiate_multimesh(). This function will have your logic for placing the multimesh on the surface. For example, you can do this with a for loop by sampling the vertices to place the grass on all the faces of the mesh or using some form of raycasting (this is more expensive though). Make sure to call the _get_terrain_image function and store it in a var. And also call set_instance_transform(i, instance_transform) in the for loop before continuing. While still in the for loop you want to store the size of your mesh in a vector2. Then just calculate the uv coords of the world space pos of the grass instance divided by the mesh size vector2 + 0.5. Weâre almost done now! Make a new var called instance_color and call terrain_image. get_pixelv() on it with the new uvâs multiplied by terrain.image.get_width()/get_height(). Now just call set_instance_custom_data(i, instance_color) on the multimesh and you can use the custom colors in the grass shader!
If you run into problems just comment under this post and Iâll try to help!