Dev Update 08-Apr-2017
Well hello again! The past week was probably one of the hardest for me in all the development process. I ran into a problem that occured as a side effect of me wanting to have the levels spread over multiple floors.
You want to always see your player character!
As simple as it sounds – with a top down camera, it’s totally not. Whenever you are in a room which is below another room on a higher floor, you won’t see anything anymore because your camera will show you the upper room instead:
So what you naturally want to do is to somehow hide that room. Thing is, how do you figure when and what to hide? There are a few options available, but not every option is applicable for my case:
- A manager script. Pro: Full control over everything. Con: But what will you do with all the non-static content? Other players or agents might enter that room, new renderers might be spawned over the course of play(like shooting bullets etc) and you won’t really want to constantly track everything, because this would be a) pretty expensive to calculate and b) probably very messy, bulky in implementation.
- Unity Layers. Pro: A Built-In, lightweight solution. I could set all objects layers to their respective floor and just render/not-render based on the current camera position. Con: You still need a manager who sets the objects layers, which makes you run into the same problems. Even worse, as the built in layers are also used for physics occlusion, you would automatically sacrifice that convenient feature just for multi-floor levels.
- Use the cameras near-clipping plane. Pro: Clips everything away with no hassle. Lightweight, no to little additional code required, the GPU does it for you. Con: Out of the box this would only work when your camera would point down with a 90° angle, but mine is more like 65°-70°.
So the third option was the most appealing to me, however I’d need to somehow force it to cull the way I want it to cull, which means I had to:
Customize the cameras projection matrix
Now what the near clipping plane does is clip away anything closer to the camera than a defined distance. Modifiing the distance during runtime looks like this:
So this is not really helpful yet. I need to find a way to have it clip away along the y-axis instead, and what I need to achive this is: Matrices. I was very good with matrices back at the university, but boy is that long ago. So what I naturally did was Google the shit out of it. And Google delivered, I found that I will need the camera have an oblique projection matrix to compensate:
Basically the near and far clip plane should align with the xz-plane. And after a rough evening, I had a decent result:
Only that on the very next day, when I moved from the test scene and tried to implement it into the actual game, I learned that this actually does completely mess up the lighting, at least in deferred rendering. When the GPU renders shadowmaps, it needs the original projection matrix to work properly. So changing the actual projection matrix is a dead end for me. But there is one more option:
Customize the cameras culling matrix
Yes, you can also do this instead. However it turns out that you can’t just use the previously calculated oblique matrix. I can tell that because I tried it 😉 The results are just plain wrong. Some research on the forums point to the culling matrix is somehow not based on the cameras origin but somewhere else within the camera space, most likely its somehow married with the near clipping plane. If you know how to get the matrix right, please let me know! In the meantime, for me it meant it was time to get creative. Fortunatly the Unity documentation pointed to another interesting direction. They use a second cameras matrix on the main camera. So what I did was this:
- Create a second camera in runtime, also disable it as we are not interested in it to actually render.
- Place the camera on room-height, ie 4 units above the current floor.
- Let it point down 90°.
- Set a FOV of 180° – so the camera is basically a super-fisheye and sees all the room.
- Use this cameras projection matrix on the actual main camera!
And thats it!
Addendum: Since the new camera has a FOV of 180° it would actually see far more on the same level, so I also added a light weight manager that additionally culls static room geometry which is more far away, just for more optimization, however thats only the stuff I know from the generation process, so just some lines of code:
Edit: I put some more work in optimization and added selective Frustum culling. This brought a huge boost in render performance:
PS: New Unity version came out a few days ago, with a new command buffer command that could change the projection matrix during rendering, however it did nothing for me. Maybe that could save me the second camera, if anyone knows more, please tell me! 😉
That is all for this week and I return to finish the cyclic level generator! See you next time,
April 17, 2017 at 12:35 pm
I actually really like the clipping plane solution 😀 especially because it can clip the whole area so smoothly. What I usually have seen in such games is turning off visibility of certain areas and objects off completely (i guess it needs levels to be separated objects)
or that everything just went translucent, probably using some sort of custom shader.
But your solution looks like it could be even used in a more abstract way for creating transforming levels 😀 maybe with help of raycasts that only hit vertices that are visible to that specific camera 🙂
April 18, 2017 at 7:51 am
Thank you 🙂 hijacking the cameras near plane to hide content activly, either by clipping or culling, is probably only applicable in this scenario. It would not work whenever I’d wanted to cut non-planar. Actually using a custom shader would be preferable, because I could smoothly blend out or add a more complex ruleset for hiding content – I can imagine using a greyscale 3D-Texture defining the alpha of objects in worldspace. This would allow to selectivly show some higher-than-player areas like bridges you’d walk under. Eventually, I just wanted to avoid moving away from built-in shaders 😉