Working on a water shader, I want object to be rendered only when below the surface, i.e. behind a surface stencil mask object. There’s plenty of good tutorials which show how to use stencils to make object visible only when they overlap with a stencil buffer set to some value.
In my case, the stencil mask object writes ‘1’ to the stencil buffer. I then use the URP rendering features to apply a mask which makes only object which overlap with this stencil mask to become visible. I’m very close to achieving my goal, but object are also rendered when they protrude from the water surface. If you can’t picture what I’m trying to say, imagine a cylinder poking out of the water that reach up over the horizon, from you field of view. At this stage, when I apply the mask, the part of the cylinder which goes above the horizon becomes invisible, while everything below the surface, but importantly, everything below the horizon becomes visible. In other words, everything that OVERLAPS with the stencil object is visible, while I want ONLY what is behind it to be visible.
I need to include some kind of depth check in my mask Z-check to see if the object is in front of the stencil mask (i.e. the water surface), and make them invisible where they cross the surface.
Given the tools I found online, I wasn’t able to make the leap to get this to work. Do you have any suggestions?
1 Like
Hm, a picture really would have helped here. It’s not even clear whether your viewpoint is above the water surface, below the water surface or in-between. Also, sometimes you’re saying you want to render things below the water surface and sometimes you’re saying you want to render things above the water surface.
Either way, if you are above the water surface and you only want to render the part that is above, maybe a regular depth test is enough. Same if you are below the surface and only want to render everything below.
Also, be aware that you can specify different stencil buffer operations depending on whether the depth test fails or passes.
Yeah sorry, I was extremely tired when I wrote that, after having spent so many nightly hours stuck on this. Here’s an image showing what I want
The closest I got was using a shader override by using the “Custom Render Features” from URP. This way I could first render the entire scene without the water surface, then add a render pass which renders everything again, including the water, and then checks that if the depth value of every given pixel is the same as before (i.e. the object is not occluded by the water surface), we then overwrite that pixel with a shader. I could leverage this by applying a completely transparent shader to all object above the surface, but the problem is that they have still been rendered by the first render pass, and so adding a transparent shader of the meshes again will not make them go away.
Is it a flat water surface or does it have waves?
If it’s flat, you could use SV_ClipDistance and SV_CullDistance.
At the moment it’s flat but I think it’s for this case going to be safe to retain this assumption. When you say use, can you elaborate a bit? Sorry, I’ve started looking at shader editing and stuff the other day so I’m pretty green.
I’m less enthusiastic about solutions which involve adding a new shader to any object that will abide to the rule I stated above (not rendering above the surface). Mainly because in a game, I want any object, with basically any shader to follow this rule of not rendering above the surface. Maybe that sounds weird but it makes sense because this particular image is rendered by an overlay camera. I’ve read about clip planes, but couldn’t find any that a) woks in URP, b) is applied to all objects and not just the objects with a clip plane shader.
You would need a special shader. Those special shader semantics are calculated in the vertex shader (it’s the distance to a custom plane).
The only other solution I can think of at the moment is killing the pixels in the pixel shader but that would also require special shaders. The advantage of that would be that it would also work for an uneven surface. You’d have to render the depth of the surface to a rendertexture first so that you can sample it in the pixel shader.
Are you sure this is really what you want? Objects would be cut open so that you can see their interior. Usually the interior doesn’t look correct because of backface culling. This would only make sense for flat objects like foliage.
Theoretically, you can do something like this with realtime-screenspace-CSG algorithms - I used to know how that worked but it’s a long time ago
1 Like
So my current, albeit not very elegant solution would be to first render a transparent (not actually transparent, but setting the “Blend Zero one”) copy of every object which we want to be rendered below the surface only. These object copies in turn, have a shader which makes them write to the stencil buffer, let’s say they write ‘1’. These object are however occluded by a plane which sits at planar to the water surface. This render pass thus renders invisible parts of object which stick out of the water, and haves them write to the stencil buffer (not sure that’s actually working). The second render pass renders the standard materials, but not where the stencil buffer is “1”, in other words below the sruface.
Tried to implement this, but I think the issue is that the plane which occludes the invisible copies can’t occlude anything without writing to the z-buffer, but this in turn makes it impossible to render the standard geometry below this plane as the z buffer has already been written by an occluding object there.
Wow that looks like a nightmare to go trough! Thanks though, I’ll see if there’s anything relevant there.
I absolutely don’t mind the inside being visible and weird and glitchy looking, I can’t tell you why because it’s too convoluted, but it has to do with this being the render from an overlay camera.
I know that there’s this “ZTest” thing, which can either pass or not pass depending on “the z-value of that given pixel”. It runs some comparison between “existing geometry” which only makes sense to someone who knows by heart the order that geometries are rendered in… This SOUNDED like the right thing to use, but I could not find a good description with some examples that I could play around with.