Z-buffering in a non-prerendered scene
I will use the term pre-rendered background for what you describe as a 2.5D scene, as I assume this is what you are looking for. Ideally, you would want your character to be able to walk around the environment, as if it were not rendered as a flat image. Your character should be able to move behind and in front of objects as he moves away from or towards the camera. The way "drawing only the front-most objects" works is by using a Z-buffer or depth buffer. When rendering a fragment, the depth of the fragment is written to this buffer. When a new fragment is drawn on the same onscreen position, the depth buffer is consulted to see whether this previous fragment was in front of the new one. If so, the new fragment is not drawn.
Solution 1: Z-buffering for pre-rendered backgrounds
Ideally, you would want to exploit this mechanism for your pre-rendered backgrounds as well. Technically, you would do this by rendering two passes of your background: the actual color data and a depth map.
- The color pass would be a simple color image. It should obviously be rendered with a camera identical to the one used in your game.
- The depth map is a grayscale image that represents the distance of pixels to the camera. White pixels are very close to the camera's near plane, whereas black pixels are very far away from the camera (close to the far plane). Again, this map should be rendered with a camera identical to the one used in your game. Pay special attention to the near and far plane settings here! (You can do a Google search for Depth Map to see an example of these maps)
What you want to do in the draw phase of your game loop, is render the background first. This means, you'd write the color image to the screen. Additionally, you want to copy the depth map to the depth buffer, so that it is filled with the same depth data, as if it were truly rendered as 3D geometry. Note that after these steps, the screen and depth buffer will be in exactly the same state, as if you has truly drawn the state in 3D. You however can render as much detail as you want, as the color and depth data are pre-rendered, thus they don't have any real-time constraints. After that you can simply draw all of your objects on top. Objects that appear behind segments of your pre-rendered background will be occluded correctly if depth buffering is enabled.
Now, this is where you might run into a problem. I'm not very familiar with Unity myself, but I do not know if it is possible to write to the depth buffer directly. If it is not, you could try the following.
Solution 2: Splitting of your pre-rendered background into depth planes
If direct write access to the z-buffer is impossible, you'll need to fill it with the correct depth data "the right way" (i.e. by rendering polygons). A solution would be to split your backgrounds up into multiple layers of different depths. Within a single layer all objects in your pre-rendered background will be considered to be at the same depth plane. For example, if you need to render a scene with a floor, a sky and two church buildings, you would use separate layers for:
- The ground + sky: the backmost layer, as the player is always in front of these
- The backmost church: an inbetween layer, so that the player can walk both behind and in front of it
- The frontmost church: a more nearby layer, so that the player can walk inbetween both churches, as well as in front of the front-most one
Note that this is an approximation! For example, a wall that is not perfectly parallel with the camera should have a gradually increasing depth value as it comes closer to the camera. You will need to be clever in designing your backgrounds, and assigning a single depth value to every layer. This is how it is done in some older PS1 games however, so you should be able to use the same clever tricks.
To actually render your pre-rendered background, you will render all layers separately on full-screen quads. In your shader, make sure to discard fragments (using the discard keyword in GLSL, or something similar) if they are fully transparant. For example, in the depth plane of the frontmost church, you only want to render the church. The area around it should be fully transparant (think of it as a cut-out, or a PNG with a transparant background). By using the discard keyword, you make sure no data is written to the depth buffer. What you will end up with, after rendering all depth planes, is:
- The color data of the pre-predered background background drawn onscreen
- Your depth buffer filled with multiple depth planes
As in the first solution, you can now draw your objects on top of this setup. Fragments of objects that should appear behind background depth planes will be occluded correctly thanks to automated depth testing with the z-buffer!
To summarize, you will want to make use of the depth buffer (= z-buffer) to achieve your effect. If Unity allows you to write data to the depth buffer directly, pre-rendered depth maps are the way to go. They will provide pixel-perfect depth data, which will make your scene indistinguishable from a true 3D rendered scene. If you can't write data to the buffer directly, the classic approach using depth planes is the way to go. Segment your background into multiple planes, assign depths to them, and render them before drawing anything else.