Hey guys,

I know the basics of shaders but I have no clue nor yet found any clue of how to achieve what I'm thinking of:

I imagine a screen-space shader, let's say to filter the 3d environment, but certain objects (for example items or characters) should not by affected by this.

The official demo actually covers this: the UI elements are not affected by the shader laying underneath due to the node-tree. However, since the screen-space shader is drawn on the 2d environment, the hierarchy gets ignored when setting up the shader in 3D.

Depth in godot3 doesn't seem to work yet, but this is not what I think is the solution (like DOF works), it's rather just having layers stacked on each other.

I made a rough mockup:

Can you point me to a solution? Thanks

I would try to add the "distortion" shader to everything I want to distort, and use screen space to make the effect. Should be equal to everything. Another option would be set the player to be drawn after (aka "on top of") everything, but it would be problematic when the player is behind anything (like a wall, a column, enemy, etc.).

Another way you could maybe achieve this effect is rendering everything you do not want to distort in another Viewport. Then you could create a black and white bit mask, and then use that bit mask in a screen space shader to decide what to distort.

I did a 2D clipping shader in Godot 2 that used a bit mask when I was trying to remake INK (for educational purposes). In theory rendering to a bit mask should work in 3D as well, it might just be a tad more complicated than a 2D implementation.

Thanks for your replies - using different viewports is something I've thought about but it makes it hard to keep an organized structure since there are a lot of nested scenes and mutiple copies - but maybe I'm doing something wrong. As said I know the very basics of the shader language and still am a bit new to Godot (though I've already quite a lot experience in the blender game engine, vector math is not a problem eg., it helped me to jump right into understanding Godot)

As for hierarchy or "on top" I'm not quite sure to achieve this, since I've tried manipulating z-indeces, visibility layers, node-tree hierarchy etc - nothing worked - maybe it's just a difference in 3D than it's in 2D.

2 years later

@TwistedTwigleg said: Another way you could maybe achieve this effect is rendering everything you do not want to distort in another Viewport. Then you could create a black and white bit mask, and then use that bit mask in a screen space shader to decide what to distort.

I did a 2D clipping shader in Godot 2 that used a bit mask when I was trying to remake INK (for educational purposes). In theory rendering to a bit mask should work in 3D as well, it might just be a tad more complicated than a 2D implementation.

But what if the object where you want to apply the distort effect intersects with another object ?

@panicq said:

@TwistedTwigleg said: Another way you could maybe achieve this effect is rendering everything you do not want to distort in another Viewport. Then you could create a black and white bit mask, and then use that bit mask in a screen space shader to decide what to distort.

I did a 2D clipping shader in Godot 2 that used a bit mask when I was trying to remake INK (for educational purposes). In theory rendering to a bit mask should work in 3D as well, it might just be a tad more complicated than a 2D implementation.

But what if the object where you want to apply the distort effect intersects with another object ?

It would depend on whether the intersecting object(s) are in the Viewport being used to render the bit mask.

If only the object you want to distort is viewable by the mask Viewport, and that Viewport cannot see other objects, then it will apply the distortion even on intersections, even if the object intersecting is not intended to be distorted.

If the object you want to distort is viewable by the mask Viewport and the objects are also visible in the Viewport, then the intersections will not be distorted, as the Viewport will correctly see that the intersected object is obscuring the view of the view of the object you are wanting to distort.


Also, for anyone curious, I wrote a tutorial on RandomMomentania showing how to make bit masks in both 2D and 3D, which may be helpful for anyone looking to achieve the effect in the OP.

How would you do that then, I'm not sure to understand ?

Have you looked at the tutorial? It shows how to use a Viewport texture to achieve bit masking, which I believe would do exactly what you are looking for. The idea is that you use the Viewport texture to tell the shader when to apply the effect, based on whether the color mask texture passed in to the shader is white (1) or black (0).

If that does not help, I can try to make an example, but it will probably take awhile before I have a chance to create it.

@TwistedTwigleg Sorry but I've been reading your tutorial and I still don't see how I could have this done with intersecting objects here is what I've done:

  • Camera is rendering only the sphere (btw to create a mask I had no choice other than overriding the environment of the camera and setting a color background to white)
  • Camear 2 is rendering everything.

A script is sending the texture of the "mask" Viewport to a Canvas_item shader in which a distort effect would be done or something else using that mask. And here you see the issue, the mask was not intersected, so nothing that I can see could be done in the situation where an intersecting object should be masked out ?

You are worried about copying your scene to another viewports, but I believe there's a good solution to that. While the viewports does not own its own world, put a camera in it that will copy the properties of the main camera. Now you see, all Geometry have a layers option. If you set it up so that your scene is in one layer, and your other thing in another, you can set the camera to only render the objects in a certain layer.

@SIsilicon28 I'm not worried about that because I already have two cameras not rendering the same thing :) As you can see here, the issue again is that the intersection without having depth info couldn't be resolved correctly...

AND: Of course I don't want to duplicate objects just for a mask as the mesh could be dense in certain cases !

What would be great is if I could use something like Unity's replacement shaders. In a frame: replace the targeted object by an unshaded shader, render the viewport for the mask and then putting the original shader back.

I see what you mean. To limit by depth, instead of rendering either black or white, render the depth instead (might wanna render with an HDR viewport). Use a screenspace shader inside of that viewport to get the raw DEPTH_TEXTURE. Then compare that depth with the depth of the main scene (by a margin, because floating point precision). If it's similar, then exclude the object from the effect.

@SIsilicon28 said:

I see what you mean. To limit by depth, instead of rendering either black or white, render the depth instead (might wanna render with an HDR viewport). Use a screenspace shader inside of that viewport to get the raw DEPTH_TEXTURE. Then compare that depth with the depth of the main scene (by a margin, because floating point precision). If it's similar, then exclude the object from the effect.

O_O could you tell me more about how I can do that: Use a screenspace shader inside of that viewport to get the raw DEPTH_TEXTURE ?? I thought it was not possible to get the ZBuffer in a viewport ???>

It isn't! At least, not directly. If you make a quad that covers the viewport, and give it a shader that returns the DEPTH_TEXTURE, the viewport will return said texture.

shader_type spatial;
render_mode unshaded;

void vertex() {
	POSITION = vec4(VERTEX.xy, -1.0, 1.0);
}

void fragment() {
	ALBEDO = texture(DEPTH_TEXTURE, SCREEN_UV);
}

The quad must have a width and height of 2 or more.

@SIsilicon28 said: It isn't! At least, not directly. If you make a quad that covers the viewport, and give it a shader that returns the DEPTH_TEXTURE, the viewport will return said texture.

I'll edit this comment with the shader soon.

I totally get what you mean; I just tested it it's not working with cull layers for me or I'm doing something wrong

Did you set the quad to have the same render layer as the sphere?

@SIsilicon28 said: Did you set the quad to have the same render layer as the sphere?

Nope, nice catch. Last issue though, now I cannot render anything else than depth xD it takes over every viewports.

Here for instance Camera3 is rendering depth :( (BTW, yes I tried adding another quad as a child with a shader returning SCREEN_TEXTURE -> not working, also tied to work with render queue...).

You don't really need a viewport for rendering ALL depth. That should be available to you already in Viewport2.

@SIsilicon28 said: You don't really need a viewport for rendering ALL depth. That should be available to you already in Viewport2.

Yes, how without the quad trick ? I don't know, but anyway it's not resolving the fact that now nothing else than depth is being rendered, as the depth shader (writing to the albedo) is taking over every other render)

@panicq said: @TwistedTwigleg Sorry but I've been reading your tutorial and I still don't see how I could have this done with intersecting objects here is what I've done:

  • Camera is rendering only the sphere (btw to create a mask I had no choice other than overriding the environment of the camera and setting a color background to white)

You'll need to have the Camera also render the intersecting objects, but in the background color. So in your case, you'll need the sphere to be black, and the intersecting cube to be white.

It does require every object to be duplicated in the scene though. However, if you have the duplicated objects use a simple unshaded material with a solid color, the rendering cost of making the bit mask should be fairly minimal.

I'll try to quickly whip up a simple example later that illustrates how this could be done.

What would be great is if I could use something like Unity's replacement shaders. In a frame: replace the targeted object by an unshaded shader, render the viewport for the mask and then putting the original shader back.

I had a discussion with a user who managed to, more or less, figure out how to do this in Godot. I'll see if I can find it, and if I can, I will link it here.