How do I make the layers of shell texturing?

Godot Version

4.2.1

Question

I’m trying to make a shell texturing shader for grass. I was wondering if there’s a way within the spatial shader alone to duplicate the vertices of whatever I apply the shader to to make the layers of the texture. Would I need to use a compute shader?

You can’t duplicate vertices or geometry in a shader, you can fake some parallax occlusion in a shader, you can look up that term or the term ‘bump offset’, but even if you can add depth to mimic layers of grass, it’s limited to the surface of the geometry surface you draw it on, so when you see it from the side, there is no depth, it’s all smoke and mirrors.

What is your goal here?

1 Like

You can look at how shell fur is implemented in this Godot 3 add-on: GitHub - Arnklit/ShellFurGodot: Add-on that adds a fur node to the Godot engine, using a shell based approach to imitate fur strands.

1 Like

I wanted to show an example of what I am talking about.

You see I have 9 single planes, the mode you see at the end is the overdraw showing there are only 9, but with the below it fakes a few layers. This will be betrayed when you see the grass from more and more glancing angles, and there are ways of fading it so you can blend to maybe another card pointing the other ways, but that’s for later.

The texture I used has 4 layers, with an alpha channel:


Then the shader code looks like, probably better ways of doing some of this for sure, but this seems to work.

shader_type spatial;
render_mode depth_prepass_alpha;
uniform sampler2D main_texture : source_color,repeat_disable;
uniform float push_back;

varying vec3 p;
varying vec3 vertex_normal_ws;
varying vec3 cam_pos_normalized;

void vertex() 
{
	p = ((MODEL_MATRIX)*vec4(VERTEX,1.0)).xyz;
	vertex_normal_ws = ((MODEL_MATRIX)*vec4(NORMAL,1.0)).xyz;
}

vec2 get_layer_uvs(float layer, float layers, vec2 in_uvs)
{
	vec2 offset = vec2(layer / layers,0.);
	vec2 pushed_uvs = ((in_uvs + (cam_pos_normalized * push_back * layer).xy) / vec2(layers, 1.)) + offset;
	float start_pos = .25 * layer;
	float end_pos = start_pos + .25;
	return vec2(min(max(pushed_uvs.x, start_pos), end_pos), pushed_uvs.y);
}

float clamp_alpha(float alpha)
{
	float contrasted = (alpha -.45) / (.55 -.45);
	return clamp(contrasted, .0, 1.);
}

void fragment() 
{ 
	cam_pos_normalized = normalize(p - CAMERA_POSITION_WORLD);
	vec2 forward_mid_uvs = UV / vec2(4.,1.);
	vec2 pushed_mid_uvs = get_layer_uvs(1., 4., UV);
	vec2 pushed_back_uvs = get_layer_uvs(2., 4., UV);
	vec2 pushed_back_back_uvs = get_layer_uvs(3., 4., UV);
	vec4 forward_layer = texture(main_texture, forward_mid_uvs);
	vec4 mid_layer = texture(main_texture, pushed_mid_uvs);
	vec4 back_layer = texture(main_texture, pushed_back_uvs);
	vec4 back_back_layer = texture(main_texture, pushed_back_back_uvs);
	forward_layer.a = clamp_alpha(forward_layer.a);
	mid_layer.a = clamp_alpha(mid_layer.a);
	back_layer.a = clamp_alpha(back_layer.a);
	back_back_layer.a = clamp_alpha(back_back_layer.a);
	vec4 layers = mix(mix(mix(back_back_layer, back_layer,back_layer.a),mid_layer,mid_layer.a), forward_layer, forward_layer.a) ;
	ALBEDO = layers.rgb;
	ALPHA = layers.a;
}

EDIT: I think this can be done without fetching the same texture 4 times, may be worth a try.

1 Like

Thanks! This isn’t what I was originally looking for but it’s still really interesting so I will use this in addition to a shell texture addon.

1 Like