Hey guys! I’m looking for advice on implementing a specific image effect. Essentially, I want an effect that fades in all the objects rendered by a specific camera, while making sure the Z-buffer occlusion works properly for all objects.
Basically, I have two different cameras which are set to render different objects using their culling masks. The objects from the first camera should always be visible, while the objects from the second camera should fade in. I want both cameras to render to RenderTextures. However, I need the second camera (the one that is drawn last) to start its rendering by keeping the depth and color information of the first camera, just like what would be the case if none of the cameras had any render targets. So in other words, it would keep the drawn pixels from the first camera and only draw new objects which aren’t occluded by objects from the first camera.
If I can achieve this, I can perform the fade-in through a image effect shader that interpolates between the two render textures (one render texture only having the objects from the first camera, and one render texture which has the objects from both cameras with proper occlusion).
There are a few ways to go about this. One is sneaky use of render buffers, and the other is to use a blit pass to fill both color and depth, and the last way is to use custom shaders that clip against a depth texture.
Lets start with the render buffer option.
A RenderTexture isn’t actually a single “thing”, it’s a color buffer and an optional depth buffer. These are both separate things that can be manipulated on their own in certain situations. The basic idea and pseudo code would be something like this:
// create two render textures
RenderTexture renderTargetA = new RenderTexture(x, y, 24, format); // one with depth
RenderTexture renderTargetB = new RenderTexture(x, y, 0, format); // one without depth
// could also use GetTemporary here instead
// set first render texture on the first camera
cameraA.targetTexture = renderTargetA;
// here starts the magic
// use .SetTargetBuffers() instead of .targetTexture to use the color buffer of B and reuse depth of A
// make sure cameraB's Clear Flags setting is set to Don't Clear.
cameraB.SetTargetBuffers(renderTargetB.colorBuffer, renderTargetA.depthBuffer);
// that handles reusing the depth
// for the color, we still need to copy the color
// for that we can use a command buffer
CommandBuffer copyColorCB = new CommandBuffer();
copyColorCB.name = "Copy renderTargetA Color";
copyColorCB.CopyTexture(new RenderTargetIdentifier(renderTargetA), BuiltinRenderTextureType.CameraTarget);
cameraB.AddCommandBuffer(CameraEvent.BeforeForwardOpaque, copyColorCB);
It’s possible that last part won’t work properly if you are using MSAA. It should, but it might not. In which case you could just set the renderTargetA as the targetTexture on both cameras and instead use a command buffer to copy the color to another render texture (or even a dummy Texture2D) that isn’t used as a render target.
The pure blit option is pretty similar, but instead of just copying the color values you have a depth write blit pass to copy the depth. Requires a camera depth texture for the main camera, and for sure doesn’t work well with MSAA. It does have the advantage of not needing to have the same resolution or format for both targets, which is useful for things like offscreen particles.
The custom shader option is also what’s used by a lot of offscreen particle type setups. The objects’ shader itself samples the camera depth texture from the first camera and discards based on it’s depth rather than relying on the built in depth buffer fixed function stuff. Works well for older hardware.