So I'm making a "pixel art" game that works by pixelating a 3D scene. At the moment I'm using this shader to get the effect:

shader_type canvas_item;
uniform float pixel_clamp = 0.004;
void fragment() 
{
	vec2 uv = SCREEN_UV;
	uv -= mod(uv, vec2(pixel_clamp,pixel_clamp));
	COLOR.rgb = textureLod(SCREEN_TEXTURE,uv,0.0).rgb;
}

But I have a problem that when moving the camera in any direction but the vertical all the pixels "run" down their edges, as seen in this video. I think this happens in all 3D applications but is too small to notice? Is there a way to prevent this?

It doesn't look bad really. I think that's how it's supposed to look because pixel lines cannot rotate cleanly, only vector lines can rotate cleanly.

6 days later

I know it won't be super clean like vector lines, but when moving the camera form left to right in the video the pixels go crazy along the edges, much much more so than when moving up and down.

I did some more searching, and it seems like I need to find a way to make the pixels sample the same point on the screen texture. One way I found to do this is to snap the camera to a pixel grid, but unless the pixels are really small the snapping is noticeable. There must be a way to shift the UV's, in the shader, onto the correct grid when the camera isn't aligned?

I'm a little confused, where are the pixels running? On the shadows, or just the objects in general?

I've watched the video several times in full-screen, and I'm not sure what I'm supposed to be looking for. The low resolution with the solid colors looks really good, and the only thing I can see that seems to change is the edges of the shadows, but that is likely due to interpolation.

If the issue is the shadows being soft and blending into the ground, then you might be able to make the shadows a little sharper by turning off shadow filtering. By default, Godot uses a PCF5 filter on shadow maps to smooth the shadows, but if you go to the Project settings and look under the Render tab, you can turn shadow filtering off by selecting Disable. That might make the shadows look a little sharper at the edges.

Thanks for the reply, no its not that. You'll see that when I move the camera up and down all the pixels are very "stable" and smooth, kinda like the whole scene was made out of sprites, but when moving side to side all the edges, the shadow edges are most noticeable, become kinda unstable and jittery. Don't really know how else to explain it.

I found a plugin for unity that does something similar to what I want to do this is the showcase video. At about 1:24 he talks about the jittery/running pixels.

Ah, I think I get it now! I'm not totally sure how to work around it, but I have a few ideas. I think the issue may be due to pixel coordinates being rounded when sampled... Though I'm not very experienced with shaders so I am mostly guessing.

I'll tinker around with it for a bit and see if I can figure something out.

Rather than doing it in shader, an option might be to render everything into a lower resolution viewport and display that in turn. In the scene the viewport is rendered into you can then build your GUI over it so that the GUI doesn't need to be lower resolution too.

Not sure if that actually solves you problem, though it may well be worth a try?

After some playing around, I got results that might be slightly better using the following shader:

shader_type canvas_item;

// Credit to Luka712
// https://luka712.github.io/2018/07/01/Pixelate-it-Shadertoy-Unity/
void fragment() 
{
	// Normalized pixel coordinates (from 0 to 1)
	vec2 iResolution = 1.0 / SCREEN_PIXEL_SIZE;
	vec2 uv = FRAGCOORD.xy/iResolution.xy;
	
	float onePixelSizeX = 1.0 / iResolution.x;
	float onePixelSizeY = 1.0 / iResolution.y;
	
	// Change the 4.0 to change the pixel size!
	float cellSizeX =  4.0 * onePixelSizeX;
	float cellSizeY = 4.0 * onePixelSizeY;
	
	float x = cellSizeX * float(int(uv.x / cellSizeX));
	float y = cellSizeY * float(int(uv.y / cellSizeY));
	
	// Output to screen
	COLOR = texture(SCREEN_TEXTURE, vec2(x,y));
}

From what I can tell in my test scene, it reduces the pixel jitter/running effect a little bit. Turning MSAA on to x4 made it much less noticeable for the most part, especially where objects meet. MSAA didn't help with the textures though.

I tried using a low resolution Viewport like @Megalomaniak suggested and it gives pretty good results, especially with MSAA enabled. It is hard to say which looks better, but using a low resolution Viewport might be a tad better for performance and it lets Godot handle a lot of the processing. The results are definitely comparable.

I also tried clamping the camera's position to a grid to see if that would help, as I read it can help for low resolution/pixel-perfect Unity projects. Unfortunately, it didn't really help any and I couldn't find a grid size that worked. I'm guessing there is some more math work done in the shader or something is passed in to the shader, though what it is I do not know.

I tested all of this in a Godot project that I've used for other tests, but I can extract my tests and upload the project if you want.

7 days later

Ok, I'm back. I did try to render everything in lower res, it did have a nice effect, but it did not solve the problem. Also I need it in a shader because I want to do things like lock the pixels to a color palette and maybe change the resolution as zoom changes etc.

That shader is pretty interesting, it still runs when moving in the diagonal though. After some thought I realise it's probably related to the fact that mo original shader did not adjust for aspect ratio. I fixed mine for aspect ratio and had similar results.

Anyway, I sat this whole weekend experimenting with different things and managed to get the shaders (almost ) rock solid with a orthographic camera using this shader, I'll let the comments do the explaining:

shader_type canvas_item; //All uniforms fed from script uniform bool grid = false; uniform vec2 pos = vec2(0,0); //Camera position divided by ortho camera size, adjusted for cam rotation uniform float aspect_ratio = 1f; // X base, used to adjust pixel grid for aspect ratio uniform float pixel_clamp = 0.7; //Pixel grid step void fragment() { float x = pixel_clamp / aspect_ratio; //Adjust pixel grid for aspect ratio float y = pixel_clamp; //Calculate camera offset from pixel grid vec2 offset = mod(vec2(pos.x/aspect_ratio, pos.y), vec2(x,y)); vec2 uv = SCREEN_UV; //Snap UV to grid intersects, has pixelation effect uv -= mod(uv,vec2(x,y)); //Offset UV, has effect of snapping camera to pixel grid uv.y += offset.y; uv.x -= offset.x; COLOR = textureLod(SCREEN_TEXTURE,uv,0);

This is the script feeding the uniforms:

extends ColorRect var screen_size var cam_perspective var ortho_cam var rot_adjustment func _ready(): ortho_cam = get_tree().get_root().get_node("Spatial/Orthogonal") rot_adjustment = Vector2(sin(PI/2-ortho_cam.rotation.y),cos(-PI/2-ortho_cam.rotation.x)) #Adjust shader input for ortho camera rotation func _process(delta): #Set color rect size to cover screen margin_right = get_viewport_rect().size.x margin_bottom = get_viewport_rect().size.y screen_size = get_viewport_rect().size get_material().set_shader_param("pos", Vector2(cam_perspective.translation.x, cam_perspective.translation.z)*rot_adjustment/ortho_cam.size) get_material().set_shader_param("aspect_ratio", screen_size.x/screen_size.y)

As you can probably see, the rotations(rot_adjustment) won't work if there are rotations in multiple axes. But I'm a bit of a noob with 3D so I just did this as a quick fix.

So this will make the pixels(mostly) solid, but as one might imagine as pixel size increases the snapping becomes more noticeable. Now if I this could be achieved without the snapping effect that would be great. Also I actually need to do this in perspective, so that's gonna be fun to try and figure out...

I completely forgot to inquire earlier, but do you have pixel snapping enabled in project settings(under Quality)?

Might help, too.

That doesn't seem to do anything, isn't that just for 2d?

Probably, yes. But it was worth a try I guess.

a year later

@EpEp hey, I'm facing the same issue you had with this. Would you still recommend using this shader method to prevent pixel jitter with objects in sub-pixel spaces? I'm trying to prevent myself from adding code to each object to snap to the nearest pixel position. My game is isometric though, so it might take a bit more work... :\

Also, where do you get the "camera_perspective" data from? It seems like it's declared in the script but it's not initialized despite being used.

2 years later