Skip to content

Instantly share code, notes, and snippets.

@lyuma
Created November 18, 2020 04:47
Show Gist options
  • Save lyuma/ab525fdf76e3af5951bf99300110a099 to your computer and use it in GitHub Desktop.
Save lyuma/ab525fdf76e3af5951bf99300110a099 to your computer and use it in GitHub Desktop.
decal shader for Godot Engine
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;
uniform vec4 albedo : hint_color;
uniform sampler2D texture_albedo : hint_albedo;
uniform vec3 uv1_scale;
uniform vec3 uv1_offset;
uniform float specular;
uniform float metallic;
uniform float roughness : hint_range(0,1);
uniform sampler2D decal_texture;
uniform vec3 decal_scale = vec3(1.0,1.0,0.0);
uniform vec3 decal_offset = vec3(0.0,0.0,0.0);
uniform float decal_upper_fade = 1.0;
uniform float decal_lower_fade = 0.5;
uniform vec4 decal_modulate : hint_color = vec4(1.0,1.0,1.0,1.0);
uniform float decal_albedo_mix = 1.0;
uniform mat4 decal_xform;
varying vec3 world_vertex;
void vertex() {
UV=UV*uv1_scale.xy+uv1_offset.xy;
world_vertex = (WORLD_MATRIX * vec4(VERTEX, 1.0)).xyz;
}
void fragment() {
vec2 base_uv = UV;
vec4 albedo_tex = texture(texture_albedo,base_uv);
ALBEDO = albedo.rgb * albedo_tex.rgb;
METALLIC = metallic;
ROUGHNESS = roughness;
SPECULAR = specular;
vec3 vertex_ddx = dFdx(world_vertex);
vec3 vertex_ddy = dFdy(world_vertex);
vec3 uv_local = (decal_xform * vec4(world_vertex, 1.0)).xyz;
if (!(any(lessThan(uv_local, vec3(0.0, -1.0, 0.0))) || any(greaterThan(uv_local, vec3(1.0))))) {
//not out of decal
//we need ddx/ddy for mipmaps, so simulate them
vec2 ddx = (decal_xform * vec4(vertex_ddx, 0.0)).xz;
vec2 ddy = (decal_xform * vec4(vertex_ddy, 0.0)).xz;
float fade = pow(1.0 - (uv_local.y > 0.0 ? uv_local.y : -uv_local.y), uv_local.y > 0.0 ? decal_upper_fade : decal_lower_fade);
//has albedo
vec4 decal_albedo = textureGrad(decal_texture, uv_local.xz * decal_scale.xy + decal_offset.xy, ddx * decal_scale.xy, ddy * decal_scale.xy);
decal_albedo *= decal_modulate;
decal_albedo.a *= fade;
ALBEDO = mix(ALBEDO, decal_albedo.rgb, decal_albedo.a * decal_albedo_mix);
}
}
@torchesburn
Copy link

How does this deal with UV Seams?

@lyuma
Copy link
Author

lyuma commented Nov 28, 2023

@torchesburn The key is the ddx and ddy arguments passed into the textureGrad call .

When you perform a traditional texture() call, it actually takes just the texture and the uv.

What really happens behind the scene is when you write

vec4 albedo = texture(tex, uv);

it is transformed as part of the compilation process to

vec4 albedo = textureGrad(tex, uv, dFdx(uv), dFdy(uv));

As you can see, it is taking the uv coordinate you pass in. And if this is a discontinuous function, the result would be disastrous, because that discontinuity would show up as a very large change in the UV which will make the GPU think you are far away and sample the lowest mipmap for the surrounding 2 pixels.

This sort of issue is common if you do environment maps. Let's take this example:

vec4 albedo = texture(tex, fract(uv))

This would be compiled into

vec4 albedo = textureGrad(tex, fract(uv), dFdx(fract(uv)), dFdy(fract(uv)))

and you can see how this would create a problem where the fract function wraps around from 0.9999 to 0.0001.
In this example, you would solve it by instead using

vec4 albedo = textureGrad(tex, fract(uv), dFdx(uv), dFdy(uv))

Hope this kind of answers the question. There is a bit of math involved in terms of using the original world_vertex coordinates to calculate the derivative, and then transforming it into the local space so it can be used.

@torchesburn
Copy link

Thanks for the explanation. Really helps 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment