1

I am writing a spatial shader in godot to pixelate an object.

Previously, I tried to write outside of an object, however that is only possible in CanvasItem shaders, and now I am going back to 3D shaders due rendering annoyances (I am unable to selectively hide items without using the culling mask, which being limited to 20 layers is not an extensible solution.)

My naive approach:

  1. Define a pixel "cell" resolution (ie. 3x3 real pixels)
  2. For each fragment:
    • If the entire "cell" of real pixels is within the models draw bounds, color the current pixel as per the lower-left (where the pixel that has coordinates that are the multiple of the cell resolution).

    • If any pixel of the current "cell" is out of the draw bounds, set alpha to 1 to erase the entire cell.

psuedo-code for people asking for code of the likely non-existant functionality that I am seeking:

int cell_size = 3;

fragment {
  // check within a cell to see if all pixels are part of the object being drawn to

  for (int y = 0; y < cell_size; y++) {
    for (int x = 0; x < cell_size; x++) {
      int erase_pixel = 0;

      if ( uv_in_model(vec2(FRAGCOORD.x - (FRAGCOORD.x % x), FRAGCOORD.y - (FRAGCOORD.y % y))) == false) {
        int erase_pixel = 1;
      }

    }
  }

  albedo.a = erase_pixel
}

tl;dr, is it possible to know if any given point will be called by the fragment function?

Magikarp
  • 171
  • 1
  • 11
  • 1
    For what its worth, you can place a quad that covers the view of the camera (more precisely, make it match the near plane). Which you can accomplish with a simple vertex shader. And then use an spatial shader on it. See [Advanced post-processing](https://docs.godotengine.org/en/stable/tutorials/shaders/advanced_postprocessing.html). Anyway, there might be some workaround, however since fragments run in parallel there is no direct way for fragments to know about each other. By the way, outline shaders often with normals or depth. – Theraot Jun 06 '22 at 15:12
  • I'm unsure of whether this is actually helpful or not. As much as I'd like to use a vertex shader, I've been having much difficulty finding any sort of tutorial explaining how it works (ie. I am aware that the `vertex()` function is called once per vertex, but this makes little sense to me given that a quad only has 4 verts). I wished that I could ask an insightful question, however, I am unable to find resources to expand my knowledge. I am also hesitant to learn GLSL due to Godot only partially exposing its API. – Magikarp Jun 07 '22 at 01:20

1 Answers1

1

On your object's material there should be a property called Next Pass. Add a new Spatial Material in this section, open up flags and check transparent and unshaded, and then right-click it to bring up the option to convert it to a Shader Material.

Now, open up the new Shader Material's Shader. The last process should have created a Shader formatted with a fragment() function containing the line vec4 albedo_tex = texture(texture_albedo, base_uv);

In this line, you can replace "texture_albedo" with "SCREEN_TEXTURE" and "base_uv" with "SCREEN_UV". This should make the new shader look like nothing has changed, because the next pass material is just sampling the screen from the last pass.

Above that, make a variable called something along the lines of "pixelated" and set it to the following expression: vec2 pixelated = floor(SCREEN_UV * scale) / scale; where scale is a float or vec2 containing the pixel size. Finally replace SCREEN_UV in the albedo_tex definition with pixelated.

After this, you can have a float depth which samples DEPTH_TEXTURE with pixelated like this: float depth = texture(DEPTH_TEXTURE, pixelated).r; This depth value will be very large for pixels that are just trying to render the background onto your object. So, add a conditional statement: if (depth > 100000.0f) { ALPHA = 0.0f; }

As long as the flags on this new next pass shader were set correctly (transparent and unshaded) you should have a quick-and-dirty pixelator. I say this because it has some minor artifacts around the edges, but you can make scale a uniform variable and set it from the editor and scripts, so I think it works nicely.

"Testing if a pixel is modifiable" in your case means testing if the object should be rendering it at all with that depth conditional.

Here's the full shader with my modifications from the comments

// NOTE: Shader automatically converted from Godot Engine 3.4.stable's SpatialMaterial.

shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,unshaded;

//the size of pixelated blocks on the screen relative to pixels
uniform int scale;

void vertex() {
}

//vec2 representation of one used for calculation
const vec2 one = vec2(1.0f, 1.0f);

void fragment() {
    //scale SCREEN_UV up to the size of the viewport over the pixelation scale
    //assure scale is a multiple of 2 to avoid artefacts
    vec2 pixel_scale = VIEWPORT_SIZE / float(scale * 2);
    vec2 pixelated = SCREEN_UV * pixel_scale;
    //truncate the decimal place from the pixelated uvs and then shift them over by half a pixel
    pixelated = pixelated - mod(pixelated, one) + one / 2.0f;
    //scale the pixelated uvs back down to the screen
    pixelated /= pixel_scale;
    
    vec4 albedo_tex = texture(SCREEN_TEXTURE,pixelated);
    ALBEDO = albedo_tex.rgb;
    ALPHA = 1.0f;
    float depth = texture(DEPTH_TEXTURE, pixelated).r;
    if (depth > 10000.0f)
    {
        ALPHA = 0.0f;
    }
}
  • Detailed answer, although the artifacts around the edges is a result of leaving the original question unresolved (which I honestly expected as I assume it's not a feature, but I did have hope nonetheless). Assuming the only useful thing I can test depth against is `SCREEN_TEXTURE`, it honestly does not sound too reliable as some parts of the screen may be in front or at the same depth of a model (ie. the floor would be indistinguishable from the character's feet in a depth map). – Magikarp Jun 10 '22 at 03:58
  • One of the reasons I suspect the artefacting is a thing is because the overlaid pass is not covering the outermost pixel of the mesh. You could check how close a pixel is to the edge with a fresnel shader (https://godotshaders.com/snippet/fresnel/) to hide those outer pixels. How does it look otherwise? – Pablo Ibarz Jun 10 '22 at 10:13
  • The artefacting is caused because of cells near the edges not being entirely covered by the model's area that it renders to. While a Fresnel shader would be fine for a sphere (`NORMAL` would be direct), this only works if the only sheer faces are near the edges, and that there are only sheer faces near the edges. For a more complex model, this is not true: the former may be solved for a mid-resolution model; the latter however, would create transparent areas at *any* sheer face in the model, which there are many when you have self-overlap with limbs and clothing. – Magikarp Jun 10 '22 at 11:19
  • The edge artefacting only happens in the editor, running the game doesn't have the artefact because it came from Godot trying to render the wireframe in the editor. With that out of the way, the only artefacting is a set of mysterious streaks going up and down the screen in some screen uv positions. – Pablo Ibarz Jun 10 '22 at 13:40
  • I reworked the shader to use VIEWPORT_SIZE / scale instead of scale on its own and scale as a float instead of a vec2 to get square pixels. In this setup, the streaks do not appear when scale in this context is a multiple of 2 or 10. No idea what's going on there but the multiples of 2 give a nice range of resolutions to work with. – Pablo Ibarz Jun 10 '22 at 13:58