Sunday, August 18, 2013

106 – Trinity

The plan for snapshot 6 did not go 100% as planned. I wanted to do a full bug-fixing run, but halfway though it tuned into a full graphics and graphics bug fixing run. Anyway, the changes are so substantial that I'm bumping up the version number in snapshot 6.

The first reason for the version bump is major internal refactoring that while only about 60% done, it has already payed off in spades. As the project size increases it becomes harder and harder to maintain. Back in the day I split the project in two, a general purpose game library and the actual game and this helped a lot to maintain things under control. But lately, the Game class was getting too large. Splitting it up into a hierarchy of classes only helped spread the complexity over multiple files, not reduce it.

So I introduced the holly trinity:
  • The Rederer. This component babysits all the almost 20 classes responsible for pushing pixels to the screen and takes care of loading resources, render targets, rendering profiles, post-processing, etc. Delegating all the rendering tasks to this class helps make the game render engine agnostic. The migration will be done in two phases. Phase one is already done and consists of creating the class and making it sure that it offers all the rendering services. The Game class uses these services to render the world. For phase two, I will inherit from the Renderer class and create a child class that renders my world using the services of the parent, thus achieving true decoupling between game and rendering. Phase two is not done yet.
  • The ModRuleSet. This is a collection of modules and rules that define how a world is set up and what assets can be used to build what. You give it the list of modules that you want and it will try to merge them into a master rule set. It also handles loading materials and other assets that might be needed. And it also includes some system variables, like the way the wold looks under lighting, how long the day is, what is the run speed, etc. While I have no intention to vary these from world to world, the engine is completely capable of it. The ModRuleSet is fully implemented.
  • The Level. I named it level to be more generic, but it is actually a world. This class holds the real data related to a world, like your character, terrain, grass, placed objects physics, etc. Think of an instance of ModRuleSet as the generic class for a world, and an instance of Level being a specific instance of that class. The Level class also handles loading and saving. This class is not yet fully implemented.

So with these classes, the Game class need to instantiate a Renderer and tell it in broad strokes what it wants to do, then you create a ModRuleSet and give it a list of modules and finally you load a Level from disk. And you tell these classes do their thing! And they do. Now, the Game class is only responsible of input, GUI, managing your interaction with the world and the game mechanics.

The second reason for the version bump is the major improvement in mesh tangent space continuity and everything that depends on this.

Implementation wise, I have three problems with my lighting scheme. First sometimes the normal mapping effect gets inverted and looks very strange. This is particularly bad with the barrel and you can see this in a lot of screenshots and videos. Heck, you can see it in some videos I did in other engines using the same assets, which lead me to believe that maybe not my shaders are to blame. And you can see it in last post's screenshots. Anyway, I'm getting ahead of myself. Second, there is nasty bug with point light attenuation. I have a fix for this which I used since day one, but it is hack. I would like to find a real solution for it. Third, there are some nasty seams in the meshes where the UV mapping continuity breaks. These seams combined with normal mapping create very ugly results. This is partially due to the textures not being seamless, but also there is something else wrong. Something very bad!

So I went forth and tried to fix as much as possible. The new debug mode visualizations helped a lot. Also, I started being active on the GameDev forums (mostly asking for help :P ). Using the vertex normal visualization I determined that vertex normals are correct on the meshes. Pixel normals are not, but the normal map is fine. You compute the pixel normals using the normal, tangent and binormal of the vertex, and since the bi-normal is computed, it was a safe bet to try and inspect the tangents:

Bingo! You can't see from this angle, but the barrel also has a discontinuity in the tangent space on the back side. These meshed have some craptastic tangents.

I googled it and I found dozens of threads on several forums. It seem that having normal mapping seams is an extremely common problem and those forums were mostly without a proper solution. The problem is caused by the way 3D modeling programs have a continuous vertex list across smoothing groups, even if there is a break in the UV mapping continuity. If you export this mesh without tangents, since the vertices at UV continuity breaks will be saved as two different vertices, with the same normals (if the exporter is any good) but with two different UV coordinates. Then when you compute the tangents, you get this result. Most formats do not get exported with tangents. So problems abound. Ideally you would export normals, tangents and binormals, to make sure that the mesh looks identical in your renderer and the 3D modeling program. These bad tangents were the reason why I got the same problems with the rings across different XNA engines.

The first step was to get rid of the crappy computed values provided by XNA and compute my own:

The seam is still there of course. And it will remain while there is an UV continuity is there. It is about taking control away from XNA when generating tangents and taking over that. Special care can be had when modelling the mesh to avoid such problems, but it is not always possible. And I'm a programmer, not a modeler. So now I'm in control of the tangent generation. Let's improve this generation a little:

Tangents are now just a little bit smother. But besides the obvious seam, the tangents should be theoretically correct, so maybe they can help with my barrel ring normal mapping problem. Let's find out!



OK, take a look at those rings! I tested from different angles and the rings now extrude the right way. The tangents finally point the right way! I've had this problem for over a year without knowing it. Here is a closeup of the normal mapping with correct tangents:

The normals can still look a little bit weird, especially around the rings, where it seems that the wood is popping out, not the metal, but this is due to my poor auto-generated normal maps. With some proper normal maps, this can be fixed.

So let's see what we did. We fixed the tangent space and thus fixed the normal mapping issues. This has done nothing for the seams nor for the attenuation bug.

I have no idea how to fix the attenuation bug, but I write a code to detect the seams and a debug visualization mode to render the results:

This might look like the normal render of the tangents with the seams, but it actually highlights the seam that it detected procedurally. Now that I know where the seam is, I try to fix it. The fix is not 100% mathematically sound and I did pull some epsilon values in some comparisons straight out of my ass, but it seems to work just fine. I won't upload the full bunch of screenshots that I have, they can be found on the forum. The major very annoying seam is gone and I am left with a minor seam. Let's study this minor seam:

It can be seen if you look for it, but otherwise you probably won't notice it. The old seam was dire an immediately noticeable. But what causes it? Is it the textures that are not seamless or the fix is not good enough? Well, this is the corrected tangent view of the mesh:

Looks pretty smooth to me. Maybe in the future the seam fixing algorithm can do a better job, but I'm happy for now with it. Here is the same shot only with the normal mapping effect:

A minor seam, at least partially due to the texture work. And now the diffuse texture:

OK, I think it is clear that most of the seam is caused by the diffuse texture. If the diffuse texture and normal map get fixed, I think the seam will go away now that tangents are proper. I still need to test my algorithm on more complex meshes.

So to reiterate, fixing the tangent space fixed normal mapping direction, except on the seams. And now I fixed the seam. All imported meshes get their seams automatically corrected.

This is definitely the high point of graphics correctness in my engine! I'm looking forward to the day I'm done with the graphics once and for all.

But not today!

Another thing I tried is adding light shafts:

I based my implementation on J. Coluna's method, but I had to heavily modify it to get it to work and my results are not as good as his. Still, not bad. Even with that post and the working code in front of me, it took me almost 6 hours to get this thing to work.

So lighting is mostly fixed and we have light shafts. What next? I tried my hand at SSAO, but this one did not turn out that good:

It is dark, grainy, full of artifacts, there is an ugly fixed pattern on the screen that is very disturbing when moving the camera around and the distant lands look even worse.

I did try and fix this and came up with possibly the most ghetto ass SSAO implementation out there:

Needless to say, I don't like how it looks and I won't be including it in snapshot 6.

No comments:

Post a Comment