CptPotato / GodotThings

Some of my snippets and other bits for the Godot game engine.
MIT License
199 stars 12 forks source link

Could this work in a post processing shader? #1

Open EeroMutka opened 5 years ago

EeroMutka commented 5 years ago

My game is a pixel-art game where you can zoom around, but I want to make stuff like fire shaders or dust particles with that same pixel-art style. If I use the window mode 'viewport', it works, but zooming gets messed up. If I use '2d', those things are rendered at too high resolution.

Now this isn't directly related to your shader, but I thought it could be also combined to this and you could maybe help. What if I used a post processing shader that pixelates everything in world space? This way you also wouldn't have to apply your material to every sprite in the game

The first annoyance is that godot canvas item shaders won't give you world coordinates as inputs so you need to use a trick. Here's the rest of my code (I made the screen resolution 256x256 and preview res 1024x1024 for simplicity)

void fragment()
{
    vec2 coord = SCREEN_UV + fract(world_pos)/vec2(-256, 256);
    vec4 color = texture(SCREEN_TEXTURE, coord);

    COLOR = color;
}

This code sort of works, but the sampling isn't perfect, it samples a bit of nearby pixels. I'm not sure why. Also when you zoom in and out there are artifacts which can be fixed by switching to textureLod(). If I use your texturePointSmooth function, the flickering disappears, but there are some artifacts when zooming as well as that nearby pixel blurriness

any help is appreciated, would be awesome to get this to work edit: messing around a bit more as I noticed some issues with my code, sending a project once I'm ready

CptPotato commented 5 years ago

I'm not sure I understand what you're trying to do.

Are you working in 2D or in 3D? Do you want to render at a low resolution (pixel-perfect) and then scale the image up afterwards?

EeroMutka commented 5 years ago

Sorry for being unclear, I'm working in 2d high res, but use a post processing-shader to pixelate the world into constant sized pixels (in world scale) so when you zoom in and out the pixels wont change with the viewport

CptPotato commented 5 years ago

I think this is possible, but it requires some changes to the shader code (instead of using linear texture filtering you have to filter it yourself). I can look into it later this week.

CptPotato commented 5 years ago

Sorry for the delay. You might be able to make this one work:

shader_type canvas_item;

vec4 texturePointSmooth(sampler2D smp, vec2 uv, vec2 pixel_size, vec2 grid_offset)
{
    vec2 ddx = dFdx(uv);
    vec2 ddy = dFdy(uv);
    vec2 lxy = sqrt(ddx * ddx + ddy * ddy);

    vec2 uv_pixels = uv / pixel_size - grid_offset;

    vec2 uv_pixels_floor = round(uv_pixels) - vec2(0.5f);
    vec2 uv_dxy_pixels = uv_pixels - uv_pixels_floor;

    uv_dxy_pixels = clamp((uv_dxy_pixels - vec2(0.5f)) * pixel_size / lxy + vec2(0.5f), 0.0f, 1.0f);

    uv = (uv_pixels_floor + grid_offset) * pixel_size;

    vec4 f0 = textureLod(smp, uv, 0);
    vec4 f1 = textureLod(smp, uv + vec2(pixel_size.x, 0.0f), 0);
    vec4 f2 = textureLod(smp, uv + vec2(0.0f, pixel_size.y), 0);
    vec4 f3 = textureLod(smp, uv + pixel_size, 0);

    return mix(mix(f0, f1, uv_dxy_pixels.x), mix(f2, f3, uv_dxy_pixels.x), uv_dxy_pixels.y);
}

void fragment()
{
    vec2 PixelSize = vec2(6.8f);
    vec2 PixelOffset = vec2(0.5);

    vec4 col = texturePointSmooth(SCREEN_TEXTURE, SCREEN_UV, SCREEN_PIXEL_SIZE * PixelSize, PixelOffset);

    COLOR = col;
}

You'll have to somehow pass PixelSize and PixelOffset to the shader.

EeroMutka commented 5 years ago

No problem at all, I'll try this tomorrow! Thanks

EeroMutka commented 5 years ago

hmm, this stuff is pretty confusing, but I have got something.

var ctrans = get_canvas_transform()
var min_pos = -ctrans.get_origin() / ctrans.get_scale()
#min_pos is the top left camera corner in world coordinates

var PixelOffset = min_pos*Vector2(-1, 1)
material.set_shader_param("PixelOffset", PixelOffset)
material.set_shader_param("camZoom", get_parent().get_node("Camera").zoom.x)

and in the shader

vec2 PixelSize = vec2(4./camZoom); // screen resolution is 1024 while camera res is 256 so 4
vec4 col = texturePointSmooth(SCREEN_TEXTURE, SCREEN_UV, SCREEN_PIXEL_SIZE * PixelSize, PixelOffset);

this works perfectly when panning, but stops working when zooming. If I multiply the PixelOffset by (-1, -1) instead, then zooming works, but panning stops working haha.

CptPotato commented 5 years ago

Have you tried this? vec2 PixelSize = vec2(4.0 * camZoom);