[Godot] 使用 MultiMeshInstance3D 和 Shader 绘制可交互草

581 阅读4分钟

可交互

视频教程:www.youtube.com/watch?v=qcS…

结果:

image.png

视频中的核心部分就是这个 shader,下面给出的是 Godot4 中的写法,有些变量名称不一样了

shader 的核心就是使用一个模型到世界的矩阵变换获得物体顶点在世界中的坐标,之后就可以计算每个草到玩家的距离,根据这个距离做出效果

同时要注意的是,得到了世界中的偏移方向之后,还需要右乘一个模型到世界的矩阵变换,转换回模型坐标

shader_type spatial;
render_mode cull_disabled;

uniform vec4 albedo : source_color;

uniform vec3 player_pos = vec3(0.0);
uniform float interact_power = 0.5;
uniform float radius = 1.0;

uniform float windforce = 0.3;

void vertex() {
	vec3 world_vert = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; // model space to world space
	vec3 direction = world_vert - player_pos;
	direction.y = 0.0;
	direction = normalize(direction);
	float dist = distance(player_pos, world_vert);
	float power = smoothstep(radius, 0.0, dist);
	direction = (vec4(direction, 1.0) * MODEL_MATRIX).xyz; // world space direction to model space
	VERTEX += direction * power * interact_power * (1.0 - UV.y);
}

void fragment() {
	ALBEDO = albedo.rgb;
}

简单随风摆动

类似地,可以添加摆动效果

uniform float windforce = 0.3;

void vertex() {
        ...
	vec3 windDir = vec3(1.0, 0.0, 1.0);
	direction = (vec4(windDir, 1.0) * MODEL_MATRIX).xyz;
	VERTEX += direction * (1.0 + sin(TIME)) * windforce * (1.0 - UV.y);
}

如果觉得太简单的话,可以使用噪声图

这里还是要注意要从进行世界空间和模型空间之间的转换

没转换之间:

	bump_wind = vec3(
		1,
		0,
		0
	);
	//bump_wind = (vec4(bump_wind, 1) * MODEL_MATRIX).xyz;
	VERTEX += bump_wind * (1.0 - UV.y);

image.png

image.png

取消 bump_wind = (vec4(bump_wind, 1) * MODEL_MATRIX).xyz; 的注释,可以看到效果变正常了

使用噪声图的摆动

Shader 如下:

是我从 godotshaders.com/shader/styl… 稍微改来的

shader_type spatial;
render_mode cull_disabled, unshaded;

uniform float wind_speed = 0.2;
uniform float wind_strength = 2.0;
// How big, in world space, is the noise texture
// wind will tile every wind_texture_tile_size
uniform float wind_texture_tile_size = 20.0;
uniform float wind_vertical_strength = 0.3;
uniform vec2 wind_horizontal_direction = vec2(1.0,0.5);

uniform sampler2D color_ramp;
// we need a tiling noise here!
uniform sampler2D wind_noise;
uniform sampler2D wind_noise2;

uniform vec3 character_position;
uniform float character_radius = 3.0;
uniform sampler2D character_distance_falloff_curve;
uniform float character_push_strength = 1.0;

varying float debug_wind;

void vertex() {
	
	vec3 world_vert = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;

	vec2 normalized_wind_direction = normalize(wind_horizontal_direction);
	vec2 world_uv = world_vert.xz / wind_texture_tile_size + normalized_wind_direction * TIME * wind_speed;
	// we displace only the top part of the mesh45
	// note that this means that the mesh needs to have UV in a way that the bottom of UV space
	// is at the top of the mesh
	float displacement_affect = (1.0 - UV.y);
	
	// black = 0 represent have wind
	float noise1 = 1.0 - textureLod(wind_noise, world_uv , 0.0).r;
	float noise2 = 1.0 - textureLod(wind_noise2, world_uv , 0.0).r;
	float wind_noise_intensity = noise1*2.0 + noise2/2.0;
	
	// We convert the direction of the wind into vertex space from world space
	// if we used it directly in vertex space, rotated blades of grass wouldn't behave properly
	
	vec3 bump_wind = vec3(
		-wind_noise_intensity * wind_horizontal_direction.x,
		1.0 - wind_noise_intensity,
		-wind_noise_intensity * wind_horizontal_direction.y 
	);
	normalize(bump_wind);
	bump_wind *= vec3(
		wind_strength,
		wind_vertical_strength,
		wind_strength
	);
	
	bump_wind = (vec4(bump_wind, 1) * MODEL_MATRIX).xyz;
	
	VERTEX += bump_wind * displacement_affect;
	
	// At the moment the blades are pushed away in a perfectly circular manner.
	// We could distort the distance to the character based on a noise, to break a bit the
	// circular shape. We could distort the falloff by sampling in a noise based on the xz coordinates.
	// The task is left to the reader
	
	vec3 dir_to_character = character_position - MODEL_MATRIX[3].xyz;
	// uncomment the following line to have a horizontal only character push
//	dir_to_character.y = 0.0;
	float distance_to_character = length(dir_to_character);
	float falloff = 1.0 - smoothstep(0.0, 1.0, distance_to_character/character_radius);
	// Because we operate in vertex space, we need to convert the direction to the character
	// in vertex space. Otherwise, it wouldn't work for rotated blades of grass.
	// comment the next line to observe how the blades are not all facing away from the character.
	dir_to_character = (inverse(MODEL_MATRIX) * vec4(dir_to_character, 0.0)).xyz;
	dir_to_character = normalize(dir_to_character);

	// sample the curve based on how far we are from the character, in normalized coordinates
	float falloff_curve = texture(character_distance_falloff_curve, vec2(falloff)).x;
	// direction to character is inverted because we want to point away from it
	VERTEX += normalize(-dir_to_character) * falloff_curve * character_push_strength * displacement_affect;
	
}

void fragment() {
	ALBEDO = texture(color_ramp, vec2(1.0 - UV.y, 0)).rgb ;
}

其中要注意的是 wind_texture_tile_size 表示草区域方块的尺寸

噪声图的黑色表示有风

我用了两个噪声图,一个是主要的风力带,一个是 godot 自带的噪声图

image.png

两个噪声图表示的风力是叠加的关系

其他部分跟之前的没区别

效果:

out.gif

控制整个 MultiMeshInstance 的颜色

Shader:

shader_type spatial;
render_mode cull_disabled, unshaded;

uniform float wind_speed = 0.2;
uniform float wind_strength = 2.0;
// How big, in world space, is the noise texture
// wind will tile every wind_texture_tile_size
uniform float wind_texture_tile_size = 20.0;
uniform float wind_vertical_strength = 0.3;
uniform vec2 wind_horizontal_direction = vec2(1.0,0.5);

uniform sampler2D color_ramp;
uniform sampler2D color_sample;

// we need a tiling noise here!
uniform sampler2D wind_noise;
uniform sampler2D wind_noise2;

uniform vec3 character_position;
uniform float character_radius = 3.0;
uniform sampler2D character_distance_falloff_curve;
uniform float character_push_strength = 1.0;

varying float debug_wind;

varying vec2 vertex_uv_world;

vec2 pos_world_to_uv_world(vec2 pos_world){
	pos_world += vec2(wind_texture_tile_size, wind_texture_tile_size)/2.0;
	vec2 uv_world = pos_world/wind_texture_tile_size;
	return uv_world;
}

void vertex() {
	
	vec3 world_vert = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;

	vec2 normalized_wind_direction = normalize(wind_horizontal_direction);
	vec2 world_uv = world_vert.xz / wind_texture_tile_size + normalized_wind_direction * TIME * wind_speed;
	// we displace only the top part of the mesh45
	// note that this means that the mesh needs to have UV in a way that the bottom of UV space
	// is at the top of the mesh
	float displacement_affect = (1.0 - UV.y);
	
	// black = 0 represent have wind
	float noise1 = 1.0 - textureLod(wind_noise, world_uv , 0.0).r;
	float noise2 = 1.0 - textureLod(wind_noise2, world_uv , 0.0).r;
	float wind_noise_intensity = noise1*2.0 + noise2/2.0;
	
	// We convert the direction of the wind into vertex space from world space
	// if we used it directly in vertex space, rotated blades of grass wouldn't behave properly
	
	vec3 bump_wind = vec3(
		-wind_noise_intensity * wind_horizontal_direction.x,
		1.0 - wind_noise_intensity,
		-wind_noise_intensity * wind_horizontal_direction.y 
	);
	normalize(bump_wind);
	bump_wind *= vec3(
		wind_strength,
		wind_vertical_strength,
		wind_strength
	);
	
	bump_wind = (vec4(bump_wind, 1) * MODEL_MATRIX).xyz;
	
	VERTEX += bump_wind * displacement_affect;
	
	// At the moment the blades are pushed away in a perfectly circular manner.
	// We could distort the distance to the character based on a noise, to break a bit the
	// circular shape. We could distort the falloff by sampling in a noise based on the xz coordinates.
	// The task is left to the reader
	
	vec3 dir_to_character = character_position - MODEL_MATRIX[3].xyz;
	// uncomment the following line to have a horizontal only character push
//	dir_to_character.y = 0.0;
	float distance_to_character = length(dir_to_character);
	float falloff = 1.0 - smoothstep(0.0, 1.0, distance_to_character/character_radius);
	// Because we operate in vertex space, we need to convert the direction to the character
	// in vertex space. Otherwise, it wouldn't work for rotated blades of grass.
	// comment the next line to observe how the blades are not all facing away from the character.
	dir_to_character = (inverse(MODEL_MATRIX) * vec4(dir_to_character, 0.0)).xyz;
	dir_to_character = normalize(dir_to_character);

	// sample the curve based on how far we are from the character, in normalized coordinates
	float falloff_curve = texture(character_distance_falloff_curve, vec2(falloff)).x;
	// direction to character is inverted because we want to point away from it
	VERTEX += normalize(-dir_to_character) * falloff_curve * character_push_strength * displacement_affect;
	
	vertex_uv_world = pos_world_to_uv_world(NODE_POSITION_WORLD.xz);
}

void fragment() {
	vec3 color_damp = texture(color_ramp, vec2(1.0 - UV.y, 0.0)).rgb;
	ALBEDO = color_damp * texture(color_sample, vertex_uv_world).rgb;
}

设置:

屏幕截图_20230123_151134.png

效果:

屏幕截图_20230123_151201.png