How to pixelate some 3D objects and not others using a screenspace shader? How to render transparent objects into a pixelation screenspace shader?

Godot Version

v4.2.1.stable.official [b09f793f5]

Project Details

Just to make the two topic questions easier to understand, I’m going to lay out my general intention with the visual aesthetics of the project I am working on.

  1. I am using a top-down orthogonal camera with a viewport size of 1920x1080p
  2. I have low-poly environmental and player assets that should be then pixelated on the player’s screen, as if they were rendered from a lower resolution then scaled up ( with no blurring) to fit the screen.
    ( I am drawing massive inspiration from T3ssel8r and Eduardo Schildt if you are curious, I sadly can’t link them because I am a new user but I suggest looking them up on youtube since they do the style I want to emulate amazingly.)
  3. I have sprites, specifically enemy and player projectile sprites, that I do not want to be pixelated, remaining as their original resolution always.
  4. I have VFX and other 3D Mesh material overlays / shader texture work that I want to get pixelated to the same effect as the low-poly assets.
  5. I want to be able to apply other shader FX to the screen like warping, distortion, screen shake, etc. (Not too important though.)

Right now I cannot achieve 3-5 with what I have currently.

Question(s)

So my questions come from mostly all of my roadblocks because this is my first time doing anything ever with shaders in Godot.

So to start, here is the pixelation shader that I am working with right now. It is applied as a mesh shader material. The mesh is placed like this in the node tree:
CameraPivot
>Camera3D
>>MeshInstance3D

shader_type spatial;
render_mode unshaded;

uniform int pixel_size = 4; //Resolution must be divisible by pixel_size
uniform sampler2D SCREEN_TEXTURE : source_color, hint_screen_texture, filter_nearest;

//To make the mesh cover the camera always.
void vertex(){
    POSITION = vec4(VERTEX, 1.0);
}

void fragment(){
    float x = float(int(FRAGCOORD.x) % pixel_size);
    float y = float(int(FRAGCOORD.y) % pixel_size);

    x = FRAGCOORD.x + floor(float(pixel_size) / 2.0) - x;
    y = FRAGCOORD.y + floor(float(pixel_size) / 2.0) - y;

    ALBEDO = texture(SCREEN_TEXTURE, vec2(x, y) / VIEWPORT_SIZE).xyz;
}

I can’t embed more than 1 image but you’re just going to have to take my word for it that everything is getting pixelated as intended, except for the Sprite3D, it shouldn’t be pixelated at all. I have tried fixing this with creating seperate viewports, one that rendered the world object to be pixelated and one that rendered the sprite3D. But that didn’t work, for some reason the screen was black with the pixelation mesh visible. So going back to a single viewport, I want to point out another issue happening at the same time that I can’t understand completely that also gets in the way. I have a .glb / an ArrayMesh + a GPUParticles3D (that emits another .glb) with shader textures on them that aren’t getting rendered at all when I launch the scene. It appears in the editor, but it isn’t pixelated, which from what I understand means that it isn’t getting processed by the pixelation mesh at all. Here’s an image to explain what I am talking about.

In the editor (The swirl is the Slash360 node and the red banana slash is the meshFX_banana node):

In the actual game I can’t see the Slash360 Node nor the meshFX_banana node.

From what I understand reading up on shaders and how screenspace shaders work in Godot is that in 3D, information from meshes or sprites with transparency do not get grabbed by the hint_screen_texture. So my pixelation shader is not able to grab any VFX shader stuff that I want to add to my character’s weapons or abilities.

I am going to experiment with sub-viewports and viewport textures for the next couple of days whenever I can and write a reply here if I figure anything out but for now I kindly ask if anyone can help me with the general strategy of solving these problems in Godot. How can I render transparent objects around or through a screenspace shader? How can I seperate Sprite3Ds and other objects in 3D space to be rendered as their original resolution ontop of a pixelated world / viewport?

1 Like

Hi ! I think I have a partial solution for you.

  • I used the spatial post-processing shader you provided, assigned it to a quad mesh instance child of my camera.
  • I created a plane and a blue cube and assigned to them standard materials, nothing fancy here
  • I then created a red cube but in the material I did set up Render Priority to 1 and Transparency to Alpha

This setup forces Godot to render objects in a certain order:

  • Opaque objects first: the plane, the blue cube, the post process quad (still a 3d mesh !)
  • Transparent obects then: the red cube

It works because in Godot you can’t (as far as I know) change the drawing order of opaque objects, but you can do it with transparent ones. However this is a bit hacky because in this example my cube is fully opaque but sorted as a transparent object. With a more complex scene you might have sorting issues.

Here’s a screenshot:

Hope this helps ! I am not sure I completely understood your particular issue

EDIT: In the red material, just set Depth Draw Mode to Always, this should solve a lot of overlapping issues (but will cause other issues if this object is rendered behind pixelated objects)

1 Like

This helps alot, I can render the Sprite3Ds and the shader textures now. The only problem is that the shader textures / VFX that I am putting on meshes are not getting pixelated, and I would like them to. I’m cramming my head through some documentation right now to see how I can grab transparent objects into some sort of texture that I can then pixelate and mix with what I have now. The current method I have my eyes on is viewport textures. It seems like it might be inefficient but I’ll try it out.

1 Like

Hello again !

I tried the approach you suggested. I was able to draw things this way:

  • Subviewport camera renders the plane, the blue cube, the pixelated particles. This is done by assigning those objects to layer 2 and rendering only this layer.
  • Subviewport camera renders the quad post process (set to layer 2), which has gone through some modifications. Now it displays the subviewport render as you would expect.
  • Then Main camera renders the red cubes (their material has not changed), and the normal particles (all these nodes are on layer 1)

Here’s the screenshot:

The post processing shader must now be set to blend_mix and depth_test_disabled, not exactly sure why. Also I was not able to see anything on Subviewport camera if this camera was not set to Current.

I think this is an awful setup… The scene viewport is all over the place and depth testing is completely off. But if you want to test out some prototype I would go with this solution. I am not sure this is scalable to a more serious project.

Still it was nice to learn about viewports. Hope this helps too !

1 Like

Yeah, this approach would be too difficult to handle. I recreated a similar approach but it was just too annoying to intergrate into the bigger project I’m working on. Since then I’ve been trying to experiment with making my shader textures into chunky pixel art and that gives an okay result, it just looks a bit off since it isn’t in the same pixel grid as the player and environment. So far I’ve been doing good with the previous mentioned strategy (using the initial shader code) of setting all transparent objects to render priority one so they are drawn first. The only thing that I probably need to mention is that there is black appearing in the mesh area of pixelated opaque materials that cross over transparent meshes that aren’t covered by the pixelation. I put the pixel size to 16 to exaggerate it, but It’s hard to put into words, here’s an image:

The opaque cyan quads that are beng emitted and thrown everywhere have black at their edges when crossing in front of a transparent mesh. This does not happen when they enter the mesh itself.

This is barely visible at pixel_size = 4, but it is still annoying to have random black information popping up at the edges of lets say, my player model whenever they walk / appear over a puddle of water or a pane of glass. But this type of scenario I don’t think will happen much in the game I’m working on, most of the shader textures I plan to have right on top of opaque pixelated stuff.

Thank you for the replies by the way, you’ve helped me quite a bit. Even though its frustrating that I can’t figure out how to pixelate transparent materials, I’ll see if I can work around this and find some sort of workable solution.

1 Like