Has anyone ever gotten a stencil buffer to copy with CommandBuffer.Blit?

I can 100% confirm that I have been able to successfully do a Graphics.Blit where my custom material/shader was able to read from the stencil buffer.

But I have NOT been able to get CommandBuffer.Blit() to do it. The custom material/shader always skips every pixel if you set the stencil check to “Equals” or anything other than “Always”.

Has anyone ever been able to access the stencil buffer from a shader that’s being fired from a CommandBuffer.Blit command? If so, I’d really like to know how you did it.

I’ve been working on this for 36 solid hours trying to get this to work.

Here are the threads I’ve carefully dissected in my journey to trying to access a stencil buffer form a shader that was run from a CommandBuffer.Blit()…

https://community.unity.com/t5/Shaders/Command-Buffers-and-Depth-Textures-Issue/td-p/2618810

http://answers.unity3d.com/questions/552217/how-does-cameradepthtexture-work-with-cameras-that.html

https://community.unity.com/t5/Shaders/Copy-depth-texture-using-command-buffer/m-p/2375682

…And this appears to be mostly every single thread on the issue.

…And after carefully dissecting these threads, I sadly still haven’t been able to do it.

Also @Tim-C commented on the thread at the top of the list, and he with all certainly said you could do it. So it seems it should be possible but, if anyone out there has ever actually done it, please I’m getting kind of desperate. :face_with_spiral_eyes:

I got it working!

I honestly don’t even know how I got it working, and it’s so dang easy I can’t even particularly figure out WHY it was so hard, or why it’s working now. But all of a sudden after trying this and that a thousand different ways, all of a sudden it just worked.

This is done with nothing but a single camera, a single CommandBuffer, and a single Blit() in CameraEvents.BeforeImageEffects.

It took me nearly 40 SOLID hours of trial and error to get this working.

And no, I do not know why it is working, only that it is.

I’m attaching the whole scene in case anyone else is struggling with this.

2797158–202920–CommandBufferStencils.unitypackage (64.4 KB)

6 Likes

For anyone who’s trying to figure out how to use CommandBuffers to take a screenshot (a rendertexture) of a previous spot in the rendering pipeline, and then use that texture in a later spot in the rendering pipeline, I’ve got that working also.

This uses two command buffers.

The first command buffer takes a “screenshot” (a rendertexture) AFTER opaque objects are drawn, but BEFORE transparent objects are drawn.

The second command buffer activates after everything is finished being drawn. It does its thing only onto the stencil side (the left side), using the stencil buffer. And it takes the previously saves “screenshot” and simply draws it on the left side of the image, which is where the stencil buffer pixels are. So on the left side you see only opaque objects from the previously-saved RenderTexture “screenshot”, and the right side you see everything. The top cube is fully opaque, so it’s seen on both sides. But the bottom cube is using a transparent shader, so it’s not seen on the left side where the previously-saved “screenshot” is being displayed.

I’m attaching a scene showing the above in action, with comments in the script that explain how it works.

NOTE: There is currently a bug in Unity where Blit() is flipping the whole picture upside down. So I have to blit several times in this example, to flip it back right-side-up again. I assume Unity will fix this bug soon. When they do, simply comment out the extra Blits() that I added, as they are entirely unnecessary except to compensate for this bug.

2797304–202936–CommandBufferUsePreviousRenderTexture.unitypackage (95.3 KB)

2 Likes

Thanks for putting this up. I am curious what version of Unity you were researching this with?

5.4.1f1, and incidentally these scenes are also made with 5.4.1f1.

As one and (hopefully) last contribution, I posted an example showing how that CommandBuffer.Blit() is currently bugged, in that it doesn’t pass information correctly to _MainTex in the shader (if working with temporary variables). To work around this bug, I attached an example scene, see: CommandBuffer.Blit() with no custom shader != CommandBuffer.Blit() with Internal_BlitCopy.shader - Unity Engine - Unity Discussions

Hope all this helps someone. And I hope Unity fixes this, but at the moment there are TWO bugs in CommandBuffer.Blit(), one that it flips the picture up-side-down, and two that it doesn’t pass anything to the shader’s _MainTex if you’re working with a temporaryRT. Sigh.

2 Likes

Very good work. I downloaded your first example. I had to set the Camera Rendering Path to “Forward” from “Use Graphics Settings” in order to make it work. Any idea why it doesn’t work in deferred?

My goal is to copy the stencil buffer to a RenderTexture for use later on. Is this possible?

I get an error when I quit your example.
Assertion failed on expression: ‘go.IsActive() && go.GetTag() != 0’
I see that you call:

OnEnable(){
...
Camera.main.AddCommandBuffer(CameraEvent.BeforeImageEffects, cmdBuffer);
}
OnDisable(){
...
Camera.main.RemoveCommandBuffer(CameraEvent.AfterDepthTexture, cmdBuffer);
}

So your insertion point differs, but even when I make them both BeforeImageEffects the error remains.

I think I’ve seen that error before. If it is happening when the game exits, I guess you could try to ensure that all game objects have been fully disabled before the game is allowed to close. Sounds like a problem with cleanup on exit. I figure you could use the OnDisable monobehavior function (or whatever it is called), to do clean up. Cuz the error says go.isactive(), which I assume means “game object isactive”, so something about a game object being active… so… a game object related error during cleanup… see if you can make sure everything cleans up? Or submit a bug report.

Deferred… you need to use CameraEvent.some_event_that_works_in_Deferred. Look at the options under camera event, some of them are specific to deferred. Play around with the options I guess. Should work…

Also I made a bigger thread, title of that thread is “Commandbuffer.blitz isn’t stencil buffer friendly”. It’s got more examples there.

The error arises, because probably the Camera is destroyed before the command buffer is destroyed. When I destroy the command buffer in the update loop, there is no error.

I have tested all deferred options in a loop in a script, but none of them work (the cube remains standard textured). The forward path works from AfterForwardOpaque (11) and further.

When I set the dest target of cmdBuffer.Blit to a temporary render texture (instead of BuiltinRenderTextureType.None), the temporary texture becomes completely filled with the texture instead of just the silhouette. The screen is then not textured any more.

you shoudl try my other thread, i have more complex examples there and one of them might work in deferred.

For anyone else poking at this (or the OP, but he probably knows this already): change the second Blit argument from “BuiltinRenderTextureType.None” to “BuiltinRenderTextureType.CameraTarget” to make this work with MSAA, and probably with other image effects. I think blitting to None is rendering directly to the framebuffer, which gets overwritten by image effects, instead of to the current render texture.

3 Likes

Sorry for reviving this thread 3 years later.

Has anyone ever experienced a solid solution for the described problem, that works on Android?

While the solution with the hint given by NoiseFloorDev works in the editor of my project, it does not if i build the game to Android. On top I am using AR Foundation and i am not sure if that is the problem or what i can do about it.

I am trying to overlay a transparent color over parts of the screen (stencil). In the editor everything works fine, in production the stencil part is correctly modified but the color is not transparent. While asking myself why it even works in the editor with MSAA enabled and does not if MSAA is disabled. I have no clue why it should act different in the production scenario.