Godot Version
4.7.1
Question
I’m following this incredible tutorial series: Grass Rendering Series Part 3: Animating and Interacting With Grass in Godot | hexaquo
I’ve been able to get everything set up properly up until the “Making Grass React to an Object” section, my setup is identical to the author’s. With a MeshInstance3D Sphere as a child to the MultiMesh I can see that the Object Position value is being updated and matches that of the MeshInstance3D Sphere when I move it around, problem is the grass has zero reaction to it and the sphere is phasing through the grass rather than pushing it down.
Changing the size of the sphere and the shaders “Object Radius” param does nothing.
Any help would be much appreciated.
shader_type spatial;
render_mode cull_disabled;
uniform float size_small = 0.2;
uniform float size_large = 0.6;
uniform float blade_bend = 0.5;
uniform vec3 color_small: source_color = vec3(0.3, 0.6, 0.1);
uniform vec3 color_large: source_color = vec3(0.9, 0.9, 0.2);
uniform sampler2D patch_noise;
uniform float patch_scale = 5.0;
varying float patch_factor;
uniform sampler2D wind_noise;
uniform float wind_strength = 0.1;
uniform vec2 wind_direction = vec2(1.0, 0.0);
uniform float wind_bend_strength = 2.0;
uniform float wind_ao_affect = 1.5;
uniform float object_radius = 1.0;
uniform vec3 object_position;
varying float bottom_to_top;
varying float current_wind_bend;
varying float cut_height;
void vertex() {
cut_height = 1.0;
// Cutting all grass on the right
if (NODE_POSITION_WORLD.x > 0.0) {
cut_height = 0.5;
VERTEX.y = min(VERTEX.y, cut_height);
UV.y = max(UV.y, 1.0 - cut_height);
}
bottom_to_top = 1.0 - UV.y;
// Wind logic
vec2 wind_position = NODE_POSITION_WORLD.xz / 10.0;
wind_position -= (TIME + 8.0) * wind_direction * wind_strength;
current_wind_bend = texture(wind_noise, wind_position).x;
current_wind_bend *= wind_strength;
current_wind_bend *= bottom_to_top * 2.0;
mat4 inv_model = inverse(MODEL_MATRIX);
vec2 local_direction = (inv_model * vec4(wind_direction.x, 0.0, wind_direction.y, 0.0)).xz;
VERTEX.xz += current_wind_bend * local_direction * wind_bend_strength;
// Bend away from the object
float object_distance = distance(object_position, NODE_POSITION_WORLD);
float bend_away_strength = max(object_radius - object_distance, 0.0) / object_radius;
vec2 bend_direction = normalize(object_position.xz - NODE_POSITION_WORLD.xz);
VERTEX.xz -= (inv_model * vec4(bend_direction.x, 0.0, bend_direction.y, 0.0)).xz
* bend_away_strength * bottom_to_top;
VERTEX.y -= bend_away_strength * bottom_to_top * 0.5;
// General appearance
VERTEX.z += blade_bend * pow(bottom_to_top, 2.0);
patch_factor = texture(patch_noise, NODE_POSITION_WORLD.xz / patch_scale).r;
VERTEX *= mix(size_small, size_large, patch_factor);
NORMAL = mix(NORMAL, vec3(0.0, 1.0, 0.0), bottom_to_top);
}
void fragment() {
if (!FRONT_FACING) NORMAL = -NORMAL;
AO = bottom_to_top - current_wind_bend * wind_ao_affect;
AO_LIGHT_AFFECT = cut_height;
ALBEDO = mix(color_small, color_large, patch_factor);
BACKLIGHT = vec3(0.2);
ROUGHNESS = 0.4;
SPECULAR = 0.2;
}
This script is hooked into the corresponding MultiMesh
@tool
extends MultiMeshInstance3D
func _process(delta: float) → void:
if has_node(“MeshInstance3D”):
material_override.set_shader_parameter(“object_position”, $MeshInstance3D.position)
Pass global_position instead of position to the shader.
Incredible, thank you so much.