69. 3D – open windows and doors
When you create 3D buildings, can you make open windows and doors so you can actually see through them to the inside (or to the outside, if you are inside)?
http://instagram.com/p/Zy-WGgBHU4/
This answer is yes, but it is surprisingly difficult. However, solving it brought further insights, so it was worth the effort. The solution is technical, so if you aren’t very interested, just skip this post.
The problem
Creating an open window or door in a wall basically means knocking a hole in the wall, and (as far as I know), there is no way to unpaint part of the wall once it’s drawn.
So, rather than knocking a hole in the wall, you have to leave a hole, as you draw the wall.
Mesh solution
Suppose you have a wall with two windows and a door, and you want to be able to see through all of them.
You need to create a mesh consisting of rectangles which exclude the three holes. I’ve shown one way of doing it, but there are many other ways to arrange them, and this is a problem, because it makes it difficult for a computer to find a solution by itself. In fact, there is no known algorithm to solve this “concave hull problem with holes”. And you don’t want to have to do all the work I just did above (and then code all those rectangles), every time you want a hole in a wall.
So while a set of carefully chosen rectangles will give you a hole, I don’t think it’s a very practical solution.
Shader solution
There is another possible solution, using a shader. If you don’t know what a shader is, you won’t understand the next part, and I’m not going to explain. It’s way too hard.
However, if you are interested, and are reasonably competent at programming, I have a basic explanation here.
We can tell a fragment shader to discard a pixel if it is within the bounds of one of the holes. We are allowed to pass variables through to the shader, so we can give it a vec4 with the x,y,x+w,y+h bounds, and the shader can check if the current x,y position falls within that area.
This will work, but we haven’t solved all our problems.
Window/door frames
A hole is a wall looks pretty ugly, so I like to put a door or window frame in it. I do this by finding a suitable image online, and using a paint program like Paint.net to make the window (or door) transparent, so only the frame shows.
I can put this frame over the hole and this looks good, but it creates a problem.
Open GL, transparency and Z ordering
OpenGL tries to draw efficiently, so if it is asked to draw a pixel that is behind a pixel already drawn on the screen, it won’t do so, because there is no point, as it won’t be seen. So, for example, if you draw a building, and you start by drawing the front wall, then OpenGL won’t bother drawing any of the back wall because it is hidden by the front wall. However, if you draw the back wall first, OpenGL will draw all of it, because (at this stage) there is nothing in front of it. So the order in which you draw objects affects whether OpenGL bothers painting them on screen.
Why does this matter? It doesn’t matter if you just make a hole, as I’ve set out above, because OpenGL recognises that we can see through the hole and will draw anything that can be seen behind it.
However, OpenGL doesn’t realise that transparent pixels are also see through, and it treats them as solid pixels, and doesn’t draw anything that is behind them.
An example will probably help.
Suppose I have a building with two window holes. One hole has a window frame with transparent pixels for glass, the other is just a hole with no pixels drawn in it. There is a dog and a cat outside the house, both of which can be seen by looking through the hole or window from inside.
Suppose I drew in this order:
- dog
- house (including holes and window frame)
- cat
.
What would I see through the windows? You would see both the dog and cat through the bare hole, because it has no pixels and doesn’t block our view of the outside.
However, if you look through the window frame, you would see the dog, but not the cat. You see the dog because when it was drawn, the window frame wasn’t there, and OpenGL didn’t know the house was going to be drawn in front of the dog. But when the cat is drawn, OpenGL figures out that it will be hidden by the wall, and it doesn’t realise some of the pixels in the window frame are transparent, so it assumes it doesn’t need to draw the cat.
This means if you move between the two windows, you can make the cat appear and disappear.
So, given that you will probably want to put something over the holes, how do resolve this viewing problem?
The answer is one we’ve seen before, when talking about drawing a lot of trees, earlier. We had the same problem there, that OpenGL treated the transparent pixels around the trees as blocking the view behind them.
The solution is to sort the meshes so you draw them from furthest to nearest. This even goes for the separate walls of a building. If you want to look into a building from the outside, you have to make sure to draw the back wall before the front wall (and obviously, as you walk around a building, the front and back walls don’t stay the same. This means you need to put each wall in its own mesh so they can be sorted individually.
The same is true of any stand alone objects you put inside a building. They need to be drawn after the wall behind them (from where you are standing), and before the wall whose hole you are looking through. Again, this requires sorting by distance.
There is one thing you could do to resolve the transparent window pixels, and that is to give the window panes a distinctive colour, and then get the shader to discard that colour, so you would have no pixels for the glass part of the window. Then Open GL should behave properly. But it’s a tricky business, and you may hurt your drawing speed.
Overall conclusions
It is much easier to keep buildings closed, so you can’t see into them from the outside, or see outside them from the inside. Making holes just creates a lot of problems and complexity.