"Running/Shimmering" pixels?

EpEpEpEp Posts: 5Member
edited August 29 in Shaders

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?

Comments

  • GeffreyGeffrey Posts: 28Member

    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.

  • EpEpEpEp Posts: 5Member

    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?

  • TwistedTwiglegTwistedTwigleg Posts: 1,487Admin

    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.

  • EpEpEpEp Posts: 5Member
    edited September 1

    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.

  • TwistedTwiglegTwistedTwigleg Posts: 1,487Admin

    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.

  • MegalomaniakMegalomaniak Posts: 1,490Admin
    edited September 2

    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?

  • TwistedTwiglegTwistedTwigleg Posts: 1,487Admin

    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.

  • EpEpEpEp Posts: 5Member

    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...

  • MegalomaniakMegalomaniak Posts: 1,490Admin

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

    Might help, too.

  • EpEpEpEp Posts: 5Member

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

  • MegalomaniakMegalomaniak Posts: 1,490Admin

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

Sign In or Register to comment.