Trying to make a sort of triplanar decal shader

Godot Version

Godot 4.3

Question

I’m trying to add a feature to my game where the player can “cultivate” the ground, which will change the texture of it to something else and seamlessly connect to any other cultivated areas. The way I’m trying to do it right now is by frankensteining together a triplanar material shader with a decal shader However, I’m very inexperienced with shaders, so it mostly works, but there’s some weird artifacts that show up when looking at it from a shallow angle, which I’m not sure how to get rid of. Pictures of what I’m talking about are right below and I’ll post the full shader code in a comment.

above
From directly above, looks great (Noise texture is just so it’s easy to see)


From a shallow angle, weird stuff on the sides.

I’m also open to other solutions that could achieve the same result, the only thing is I’m using the Terrain3D plugin for the terrain so I can’t really do anything that would use a second pass material.
Thanks!

Full shader code:

// NOTE: Shader automatically converted from Godot Engine 4.3.stable's StandardMaterial3D.
shader_type spatial;
render_mode world_vertex_coords, unshaded, cull_back;
uniform sampler2D DEPTH_TEXTURE: hint_depth_texture;
uniform float cube_half_size = 1.0;

varying mat4 INV_MODEL_MATRIX;
uniform vec4 albedo : source_color;
uniform sampler2D texture_albedo : source_color, filter_linear_mipmap, repeat_enable;
uniform float point_size : hint_range(0.1, 128.0, 0.1);

uniform float roughness : hint_range(0.0, 1.0);
uniform sampler2D texture_metallic : hint_default_white, filter_linear_mipmap, repeat_enable;
uniform vec4 metallic_texture_channel;
uniform sampler2D texture_roughness : hint_roughness_r, filter_linear_mipmap, repeat_enable;

uniform float specular : hint_range(0.0, 1.0, 0.01);
uniform float metallic : hint_range(0.0, 1.0, 0.01);
varying vec3 uv1_triplanar_pos;
varying vec3 world_pos;

uniform float uv1_blend_sharpness : hint_range(0.0, 150.0, 0.001);
varying vec3 uv1_power_normal;

uniform vec3 uv1_scale;
uniform vec3 uv1_offset;
uniform vec3 uv2_scale;
uniform vec3 uv2_offset;
vec3 world_pos_from_depth(float depth, vec2 screen_uv, mat4 inverse_proj, mat4 inverse_view) {
	float z = depth;
	
	vec4 clipSpacePosition = vec4(screen_uv * 2.0 - 1.0, z, 1.0);
	vec4 viewSpacePosition = inverse_proj * clipSpacePosition;
	
	viewSpacePosition /= viewSpacePosition.w;
	
	vec4 worldSpacePosition = inverse_view * viewSpacePosition;
	
	return worldSpacePosition.xyz;
}

void vertex() {
	INV_MODEL_MATRIX = inverse(MODEL_MATRIX);

	vec3 normal = MODEL_NORMAL_MATRIX * NORMAL;

	TANGENT = vec3(0.0, 0.0, -1.0) * abs(normal.x);
	TANGENT += vec3(1.0, 0.0, 0.0) * abs(normal.y);
	TANGENT += vec3(1.0, 0.0, 0.0) * abs(normal.z);
	TANGENT = inverse(MODEL_NORMAL_MATRIX) * normalize(TANGENT);

	BINORMAL = vec3(0.0, 1.0, 0.0) * abs(normal.x);
	BINORMAL += vec3(0.0, 0.0, -1.0) * abs(normal.y);
	BINORMAL += vec3(0.0, 1.0, 0.0) * abs(normal.z);
	BINORMAL = inverse(MODEL_NORMAL_MATRIX) * normalize(BINORMAL);

	// UV1 Triplanar: Enabled (with World Triplanar)
	uv1_power_normal = pow(abs(normal), vec3(uv1_blend_sharpness));
	uv1_triplanar_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz * uv1_scale + uv1_offset;
	uv1_power_normal /= dot(uv1_power_normal, vec3(1.0));
	uv1_triplanar_pos *= vec3(1.0, -1.0, 1.0);
}

vec4 triplanar_texture(sampler2D p_sampler, vec3 p_weights, vec3 p_triplanar_pos) {
	vec4 samp = vec4(0.0);
	samp += texture(p_sampler, p_triplanar_pos.xy) * p_weights.z;
	samp += texture(p_sampler, p_triplanar_pos.xz) * p_weights.y;
	samp += texture(p_sampler, p_triplanar_pos.zy * vec2(-1.0, 1.0)) * p_weights.x;
	return samp;
}

void fragment() {
	
	float depth = texture(DEPTH_TEXTURE, SCREEN_UV).x;
	world_pos = world_pos_from_depth(depth, SCREEN_UV, INV_PROJECTION_MATRIX, (INV_VIEW_MATRIX));
	vec4 test_pos = (INV_MODEL_MATRIX * vec4(world_pos, 1.0));
	
	if (abs(test_pos.x) > cube_half_size ||abs(test_pos.y) > cube_half_size || abs(test_pos.z) > cube_half_size) {
		discard;
	}
	//ALBEDO = texture(texture_albedo, (test_pos.xz) + 0.5).rgb * albedo.rgb;
	vec4 albedo_tex = triplanar_texture(texture_albedo, uv1_power_normal, world_pos);
	ALBEDO = albedo.rgb * albedo_tex.rgb;

	float metallic_tex = dot(triplanar_texture(texture_metallic, uv1_power_normal, world_pos), metallic_texture_channel);
	METALLIC = metallic_tex * metallic;
	SPECULAR = specular;

	vec4 roughness_texture_channel = vec4(1.0, 0.0, 0.0, 0.0);
	float roughness_tex = dot(triplanar_texture(texture_roughness, uv1_power_normal, world_pos), roughness_texture_channel);
	ROUGHNESS = roughness_tex * roughness;
}

I think the issue is that you’re taking your normal from the normal of your cube rather than taking it from the normal texture of the screen

I tried this and it worked for that specific problem, but I think I might just have to look for a simpler solution because there’s other problems :pensive:
(Currently it’s unshaded, but turning that off makes the edges cast shadow on itself, trying to make it any shape other than a square messes with things too)

Thanks for the help though!

You should edit the normal to match the normal fo the screen + the normalmap