Shader for pixelated shadows? (3D)

Godot Version

v4.4.1.stable

Question

I want to write a shader that would align shadows on 3D surfaces to a pixel grid. I’ve never written a shader before, but I know the theory from school.

What I’m trying to do sounds like it shouldn’t be hard in theory — for every pixel, for every light (so just how the light() function is called), just take the position of the pixel, which is used in the calculation of whether the light is occluded from its perspective or not, and snap it to a grid, in effect calculating the occlusion from the centre of the large (fake) pixel, for each real screen pixel inside it.

So I took the shader that Godot automatically generated for me from my non-shader material, found a recreation of Godot’s default light() function, and tried to modify it accordingly. However, I soon found out that Godot doesn’t give you access to the shadow calculation directly (or at least in the light() function?), instead just giving you the ATTENUATION value which tells you how brightly the given light illuminates that pixel. But since I want the calculation to be made from a different 3D position, that value is useless to me, unless I can modify the way it’s calculated. Which I haven’t been able to find how to do (if it’s even possible).

Does anyone have any tips or a solution to this problem? Can I modify the way the occlusion is calculated?

PS: I know about the trick about using a Sub-viewport that makes everything pixelated, and it’s not the effect I’m going for. I need smooth movement across the screen. You could say that the effect I want is sorta similar to how Minecraft’s new Vibrant Visuals do shadows — it wasn’t the inspiration, but it looks pretty much how I would like the shadows in my game to look.

shadows in godot are hardcoded in the engine in order to get more performance.
maybe you can tweak the shadow quality settings in project settings to achieve a similar effect.

you could also fake shadows in some parts by using decals or light cookies.

for changing the shadows you need to alter the godot source code and compile it, and it’s more difficult that just writing a shader. you also need to do this for forward+, then mobile and compat.

Oh damn, that is a big disappointment. I thought Godot would at least give you the option to customise them, this seems really out of character for the engine.. I might try other workarounds but do you have any tips for faking the shadows? I kind of don’t get how I can calculate the shadow myself if I don’t have access to the lightsource and the space between it and the pixel.

I hope they at least add an option to mess with the shadows in a future version…

Thank you a lot for the reply though! It’s been really helpful!

(re-replying because I think I used the wrong reply button last time? Still not used to this forum, sorry) (EDIT: oops, I didn’t, I hit the right one last time as well, I’m just silly)

shadows are better this way because we don’t have a limit to the number of lights that can be in a scene, the shadows are placed in an atlas and are very fast.
yes it has these problems, but tweaking shadow-buffers is a very difficult task anyways and it’s not as simple in other engines either.
you still have the option to tweak the source code and compile it, if you really need to.

they are however working on improving the rendering pipeline to make it easier to create custom shaders, or at least that’s what they said a year ago.

it sounds a bit like integer depth buffers, maybe you can round the vertices of the geometry, or use a second model with shadows only. same with the map.

with a shader you have access to the geometry that is rendered, nothing more, if you have a sphere, you can only use the surface that that sphere occupies on the screen. the shadows are handled somewhere else because the entire level has to be rendered into a depth buffer from the point of view of the light, and then compared with a different buffer, and that tells the engine what parts are lit or shaded.
and you have access to the depth buffer of the screen in a shader, but not of the lights.

1 Like

Thanks a lot for the reply!

Could you please elaborate a bit on what this means and how I can find out more? I unfortunately don’t really know what you’re talking about here, sorry.

Also, I’m sorry if I’m asking for too much help, absolutely feel free to abandon this topic if I’m bothering you too much. I would love to find out more, but at the same time I feel kinda bad for asking so much.

Hi, i think you can use this shader

shader_type spatial;

render_mode specular_disabled, depth_draw_opaque;

uniform sampler2D tex: filter_nearest, source_color;
uniform vec4 albedo : source_color = vec4(1.0f);
uniform float tiling = 1.0;
uniform bool triplanar = true;

void fragment()
{
	
	vec2 uvs = UV * tiling;
	if(triplanar){
		vec4 projected_coords = INV_VIEW_MATRIX * vec4(VERTEX, 1.0);
		vec3 albedoX = albedo.rgb * texture(tex, projected_coords.zy).rgb;
		vec3 albedoY = albedo.rgb * texture(tex, projected_coords.xz).rgb;
		vec3 albedoZ = albedo.rgb * texture(tex, projected_coords.xy).rgb;
		vec3 world_normal = abs(INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz;
		vec3 normal_weights = world_normal / (world_normal.x + world_normal.y + world_normal.z);
		ALBEDO = albedoX * normal_weights.x + albedoY * normal_weights.y + albedoZ * normal_weights.z;
	}else{
		ALBEDO = albedo.rgb * textureLod(tex, uvs, 0).rgb;
	}
	vec2 tex_size = vec2(textureSize(tex, 0));
	vec2 nearest_texel = (floor(uvs * tex_size) + 0.5) / tex_size;
	vec2 offset = nearest_texel - uvs;
	vec2 x_derivative = dFdx(uvs);
	vec2 y_derivative = dFdy(uvs);

	offset /= x_derivative.s*y_derivative.t - y_derivative.s*x_derivative.t;
	vec2 frag_delta = vec2
	(
	offset.x*y_derivative.t - offset.y*y_derivative.s,
	offset.y*x_derivative.s - offset.x*x_derivative.t
	);
	vec3 view_delta = dFdx(VERTEX)*frag_delta.s + dFdy(VERTEX)*frag_delta.t;
	LIGHT_VERTEX += view_delta;
}
1 Like

Hi! Thank you so much for your reply! Unfortunately this shader doesn’t seem to work for me - here’s before I applied it:


And here’s after:

The shadows seem to be exactly the same.

I converted the standard material I had, to a ShaderMaterial, and then just replaced the content of the shader with this one. Did I do that right?

I unfortunately don’t really understand what’s going on inside the shader, so I can’t tell anything about that.

Anyway, thank you for taking the time to help me out!

ok, I made it sort of work.
you need a meshInstance3D for the shadow and one that will be visible.

the one for the shadow must have a shader with this line in vertex:

void vertex() {
	UV = UV * uv1_scale.xy + uv1_offset.xy;
	VERTEX = ((round(vec4(VERTEX * 10.0, 1) * inverse(MODELVIEW_MATRIX)) * 0.1) * MODELVIEW_MATRIX).xyz;
}

maybe someone can make it better, I did it in the last 5 minutes.

then, in the “shadow” meshInstance3D you set it to shadows only:

and then in you “visible” meshinstance3D you set it to off:

they must move at the same time. it works better for moving objects, so try it with characters.

Thanks! This is unfortunately not the effect I’m going for, and I am afraid that the effect I’m going for might really be impossible. I need the shadow to snap to the texels as the shadow source moves smoothly.

One theoretical solution I thought of now, that I again don’t know if it’d be possible — that’d actually work even better than shifting the point in the shadow calculation if that were possible — is having the game calculate the shadows, calculate the colour of each pixel, and then after that, override the displayed colour for each texel with the average calculated colour on it. But I suppose that would run into issues with texels that are partially hidden from the camera.

No, it’s possible ^^ (Nothing is impossible.)
Take my code, create a new gdshader with this code, and create ShaderMaterial with this gdshader.


I’ll provide screenshots of how it works for me.

1 Like

1 Like

But my shader have a few problems. For example - unclear artifacts when the light is directed at an object with this shader from a certain angle. If you have idea - how to solve this problems, write me

why not set a low resolution for the shadowmap and disable filtering?

Because that’s not what the author of the topic wants. And this is not a solution to the problem, but an imitation. Because soft shadows are always enabled by default, and even if you set the strictest filter, the shadows will remain soft. Do you understand what I mean?

Thank you so much, you’re a life saver! Your original code works perfectly after all, I was just a little dumb and didn’t realise that it wouldn’t work in Compatibility render mode and that I would have to switch to Forward+. This is EXACTLY the effect I want, thanks again! ^^

2 Likes

I was glad to help

sadly this is not as simple of problem as it first seems. there’s a really great Unity shader thread (The Quest for Efficient Per-Texel Lighting - Unity Engine - Unity Discussions) that goes over the complications of doing this, and by the end of the thread they were able to solve it (both in code and in shader graph).

i’m not sure if their techniques would be able to be implemented in godot as godot does not have as mature of a scriptable rendering pipeline.

Luckily user mlkpnck was able to solve it somehow, I have no idea how their genius little shader works but it works flawlessly for me!

i have the same shadow effect of your shader only using a low res shadowmap and no filter also its working in mobile and compatibility renderer.


This is a similar effect that works on mobile and compatibility renderer