Help with screen shader ordering

Godot Version

4.4.1

Context

Hi, I’m hoping to come up with a way to control what objects are processed by a screen-reading post process effect.

My setup is as follows:

  • 3D scene with 3D objects and an orthogonal camera. Characters populating the scene using Sprite 3D, billboarded.
  • Using the typical post process setup of a quad in front of the camera to do full screen effects.
  • A second viewport and camera rendering only select layers. Piping this to the shader on the quad.

I want to be able to have a scene wide effect applied to everything but the sprite3D in the characters. I have tried splitting the layers into two viewports with linked cameras, and that gets me nearly there. However, the sprites then don’t respect occluding as they did before. Here’s an example:


The second image is what I would expect to happen. In this example, it’s very subtle, but the effect that is being masked is that faint outline shader. In the full implementation I plan on doing a full pixel art shader pass and I’d ideally have a lot of control over how that’s applied.

Question

Am I going about this the wrong way? Would I need to reimplement some kind of depth testing manually using the depth pass I get in the shader? I have seen that compositor effects are kinda new and maybe the way to do this in the future, but it also seems like I’ll need to wait for stencil buffers to be implemented (which seems soon!) to achieve what I’m going for.

I am new to Godot, loving it so far, and would appreciate anyone’s input or suggestions!
Thanks.

As a followup, I was able to create a ‘mask’ from the normal pass by looking at only billboard textures. This eliminates rendering twice which is great. This works for me for the time being, though I will eventually need to figure out how I want to handle different pixel resolutions. I think this will be less of a problem in the future as I build out the actual assets for the game.

Happy to accept anyone’s input on better ways to do this, but until then, here’s where I ended up and a code snippet for anyone looking to do something similar. Please be aware this was refactored by an llm so double check this code, I’m not confident it’s handling values correctly.

vec3 camera_facing_mask(vec3 normal, vec3 view_dir) {
    // Convert normal from [0,1] range to [-1,1] range
    vec3 world_normal = normal * 2.0 - 1.0;
    
    // Calculate dot product between normal and view direction
    float facing_amount = dot(world_normal, -view_dir);
    
    // Create binary mask: white if exactly facing camera, black otherwise
    float mask = step(0.999, facing_amount);
    
    return vec3(mask);
}