There are several key changes in the latest update to Voxtric to be discussed starting with the new way the rendering blocks is handled. Instead of using a texture atlas as I had been before and storing the texture position of each block in an array to be accessed by block index, I now use texture arrays. This means I still only need to to one texture binding and only use one texture per mesh, but the way it is accessed is now completely different. It is literally like having an array of textures that can be used. I don’t have to store a texture position at all, and instead just use the block ID of a voxel to access the right index of the texture array which the fragment shader can then sample.
There are several benefits to handling my rendering of meshes this way compared against using a texture atlas. The first major one is that it now means I can have individual images for each texture that can be named specifically for the block that they represent. The second major in game benefit is that texel bleeding as can be seen in my previous videos of Voxtric is no longer an issue. The pixels in the texture surrounding the block the fragment shader needs to sample will not be taken into account at different mipmap levels.
Another key change that I have made to rendering is to implement frustum culling. There is no doubt that this is an absolutely essential part of any game engine, but initially I had not done it as my test scenes didn’t need it to run. Now that I have a better grasp of the Bullet physics engine and the key underlying structure of what I am trying to accomplish with Voxtric is in, I decided now was the time to go back and re-do the rendering pipe-line.
The final important point that has changed is that the centre of mass and total mass of the RegionCollections are no longer broken. Each block is defined in a .csv file containing its ID, name, if it’s visible, if it’s solid, and of course its mass. When each block is checked in the voxel data for building a mesh, it’s position compared to the middle position the data is multiplied by it’s mass which is then added to a total position.
At the end of the mesh generation the total is divided by the number of voxels stored. This position is then considered to be the centre of mass. The average of all the different mesh position centres is then used to decide the RegionCollection centre of mass. As can be seen in the video above, it is a massive improvement when compared to my previous videos.
This post and the video above are short due to the single change that took place in Voxtric for this post to be made, but the change itself is a big one. After a fairly large amount of work I managed to get algorithm for checking for and splitting off of disconnected collections of voxel data.
Method (Has since been far and away improved)
It works by storing a series of points around each block that is removed at once (6 for each block removed), and then checking each point for a solid block at that positing, throwing the point away if there is no solid block at the position. Doing it this way means that large areas of blocks are able to be removed at once and the rest of the algorithm as is about to be explained will still work.
A DataSplitFinder is then created at each point which stores a std::vector of positions found, an std::map of the positions found in the most recent round of checks (explained in a moment), and a std::vector of the positions found in the previous round of checks. It also stores the total number of positions found.
Each DataSplitFinder then performs a breadth first search for the other finders around it. When one is encountered, they are merged together and the process continues. If a finder cannot continue its breadth first search as there is nowhere else for it to search, it means that this finder has detected a collection of voxel data that is no long connected to the main body of voxel data. A new RegionCollection is then created and the voxel data that has been identified as not connected is copied over to the new RegionCollection. This process continues until there is only one finder left as all active finders will have been merged together of removed when copying data.
As it is faster to copy the least data possible from a large voxel data collection, the DataSplitFinder with the least voxel positions found is the one that performs each round of its search more often than the others. This is because, in the case that a split has occurred, in places where the finders can reach each other they will quickly merge into one finder and each round will spread over a much larger area. The smaller section though will have the constraints of being on a boundary of voxel data and will have merged with fewer if any finders.
As this smaller section is the bit we want to copy out, it is the one we perform the breadth first search operation on until it is either confirmed to show a split in the voxel data, it finally meets up with the other finders, or the number of positions it holds becomes more than that of any one of the other active finders. In the latter case, the exact same process is repeated, just with whatever finder now has the least voxel data positions stored.
There were only two changes to Voxtric that can be seen in the video above, but they were both major changes that make Voxtric so much easier to work with.
By the time I had got to the stage that I was at with the previous video, I had hardly implemented any debugging tools. I very, very quickly learned how important debugging tools were when trying to get the Bullet physics engine working in the way that I wanted it to, and so I dedicated a large amount of time to implementing several. In doing so I ended up implementing a way of rendering text to the screen which I had not intended to do at that point originally, though it has made my life much easier now that it has been done.
The second major change was, as the title suggests, the changes to the way that mesh generation is handled. Much like in the original prototype, I wanted to be able to have any mesh generation handled in a different thread so that lots of editing of voxels would not impact the performance of Voxtric. Unlike in the prototype, I wanted to make sure it was done properly.
Watching the original video will show that the changing of voxel data was done in the main thread with thread pools being used to both generate a mesh and test to see if bodies of voxels had disconnected from each other. Of course this meant it was riddled with race conditions and would mean often disconnected areas would not be recognised as such. I set out to make sure that literally everything to do with editing voxels in Voxtric was done in a single thread to avoid any race conditions, but outside of the main thread to avoid any performance impacts.
At this point disconnecting bodies of voxels was not implemented, but by ensuring when I did come to implement it there wouldn’t shouldn’t be any multi-threading issues it meant that when I did come to implement the feature, it would be far more hassle free.
The above video is the first video that I released just documenting where I was up to with Voxtric. This is a little while after the prototype as it took me a fairly large amount of time to rap my mind around OpenGL, Bullet (physics library) and the maths involved in games. I had always been aware that a game engine was a very different undertaking to the production of a game and that Unity3D was doing all the heavy lifting for me, but I never truly appreciated it until this point.
By this point, I had got a very basic rendering pipe-line with OpenGL 3 going and procedural mesh generation to go with it so that the voxel data could be represented. I had the most basic implementation of lighting to give any objects in the scene a bit of definition, but otherwise it was all incredibly basic, and a long shot from what I had working back in Unity3D. That said, it was also working much faster at this stage than it had at the same stage in Unity3D, so it was certainly showing promising results.
Obviously there were lots of issues, a fairly major crash that can be seen in the video, and some fairly poor design decisions, but this was my first foray into creating a game engine, so this was all to be expected. Fast forward to the time of writing and the vast majority of these issues have either been dealt with, or are in the process of being dealt with. I have come to terms with the fact that it’s easy to identify an endless list of things that need to be done, the hard bit is the prioritisation.
This first post for the development of Voxtric is made quite some time after the initial posting of the video shown above, so the detail may be lighter than in later posts which will be made upon completion of the features talked about in them.
As Unity3D is an exceptional game engine when it comes to speedy development, so I decided it would be a safe bet to prototype my initial ideas in it. At one stage I was going to go ahead and try to make a game using Unity3D in conjunction with the early prototype seen above, but unfortunately being fairly good at everything meant that Unity3D excels in nothing. Subsequently I decided to discontinue development of this prototype just weeks after initially posting this video. An example of where it failed to meet potential project needs is in the handling of rigid bodies where meshes without closed edges would cause the engine to throw errors regardless of the context that they were used in.
Another reason I had intended to try and make this prototype into something more than just a prototype is that I had used Unity3D for about a year before, and had become fairly comfortable with C# as a programming language. However two changes occurred to make me disregard that fact. The first is that the course I wanted to take at the time if I went to university (Northumbria which I shall be attending September 2015) would have me programming in C++.
I came to the conclusion that getting ahead and learning C++ before the course began was probably a good idea anyway. Secondly, research suggested that the games industry relied heavily on C++ for its underlying technology due to its high performance capabilities when in the right hands. Again to get ahead of the curve, I decided that learning C++ was worth the temporary reduction in productivity whilst I acclimatised to the language considering the potential future benefit.
Improved Voxel Storage
In the video above it can be seen that Voxtric had many of the features that it has at the time of writing now, but just less well implemented. For example, voxel data used to be stored in an jagged array of 16*16*16 blocks per voxel, which of course lead to memory fragmentation. Now voxel data is stored in a single array with a size of 4096, and a voxel is accessed through a simple equation to find the right index in the array from 3 co-ordinates as seen in the code snippet below.
There are lots of small optimisations like this that make a big overall difference to both the speed and memory consumption of Voxtric that weren’t in the prototype such as storing all block data in a single 2 byte number and using bit shifting to get different elements of information from the block.