Orthographic vs Perspective
Up until now I’ve had the game camera set up as an orthographic projection. Which gives a similar style to an isometric game but in 3D. I went for this initially because I wanted it to be reminiscent of games like Age Of Empires and RCT. It wasn’t until I started working on the construction tools that I realised how it was becoming difficult to visually understand the terrain and where to place objects. It really doesnt work all that well. Because orthographic cameras have a fixed depth you essentially lose some important information regarding the position and shape of the 3D objects. It makes them slightly harder to understand and also makes it difficult to judge distances in the game world. Games like Monument Valley take advantage of this to create optical illusions and interesting puzzles. For this game though, I don’t want illusions, the most important thing is that the player can understand quickly what is happening in the game.
A slight problem with using perspective camera is that the method for grass rendering I was using did not work as well. As I was using shell rendering It only looks decent when looking downward on the world, with a perspective camera it just didn’t work. Rendering the grass that way was quite costly anyhow, so I had no problem removing it.
New Perspective Camera
I put a little time into getting a working visible day and night cycle. In free floating camera mode you can see the sun in the sky at the correct position. The sky itself now changes color according to the time of day, so for example it will turn orange near sunset. At night the distance fog is changed to give some atmosphere. At night time the focal point of the camera emits light, this makes it so you can still see the surrounding area as well as giving an eerie atmosphere.
I wrote last update about screen space ambient occlusion and how the performance hit was too large to justify the effect. I’ve been playing around with some other methods with some success. I can’t bake occlusion into the mesh because the terrain itself is actually just a displacement map. What I thought of doing however is to calculate the AO by sampling the adjacent pixels from within the displace map for each vertex. You can see the results in the day/night pictures above.
A bit of time was put into the ECS which will manage all the world objects. I put together a placeholder tree model and spawned 10,000 of them around the map. The viewing distance for the trees is not to great in floating camera mode but in normal construction mode It works fine.
Small update this time. I have started working a day job again and so haven’t spent much time on the game. For a bit of fun I’ve been thinking about implementing screen space ambient occlusion, something I haven’t written before. As I’ve been developing and experimenting with the game I’ve noticed issues with my eye being unable identify height and depth of the tiles. Often 2 tiles will sit at different heights but the same orientation, meaning they will be lit exactly the same. Take a look at this example.
The tile in middle is shaded the same as the tile behind. While it’s not difficult to understand what tile is where, It really shouldnt require any thinking. To solve this I started looking into ambient occlusion techniques, I had a form of AO in the game before using an AO value baked into the terrain verts. However, Now I have redone the terrain mesh system making that no longer an attractive option. The most common AO in games is SSAO (Screen Space Ambient Occlusion), there are different implementations but essentially you would use the positions and normals buffer to sample surrounding pixels and calculate if they occlude the current pixels.
I put together a shader which does just that. The results are decent however there is a significant performance hit. I’m using a half size AO buffer and 16 samples, then a 4×4 blur. Framerate goes from about 160 to just about keeping up at 60. I’m sure I could optimize further and improve the method, however I have played round tweaking parameter and even at high sample counts the results are not that great. I think I am going to take another look at baked AO.
SSAO After a lot of tweaking
- Added a way to recompile shaders at runtime so I can make modifications to shader code and see results instantly (Very useful, should have done this ages ago!)
- Improved pipeline for adding 3d models.
- Made headway on ECS for managing game objects.
Nothing too major this update, I’ve been working on some other personal things the last month so I haven’t done anything major on the game.
- Made it so an individual tile will highlight when hovered over with the mouse.
- You can now make square box selections with the mouse by clicking and dragging.
- Added a distance fog for when using the free floating camera
- Replaced black sky color with blue.
- Fixed a z-clipping issue where the ocean mesh intersected the land mesh.
- Optimized terrain rendering for free floating camera. Camera used to sit in the middle of the terrain chunk, now any terrain behind the camera is not calculated.
- Shaders now recompile when using free floating camera and implement a different type of z-buffer.
- In debug mode I can now switch from game camera to free floating camera at click of a button.
- Added more robust terrain tile painting tools
- When a grass tile is sloped above a certain threshold it becomes a dirt tile.
Improvements made to free floating camera, decent enough draw distance.
Drawing onto the terrain
Here is a low framerate fly over of the terrain using the free floating camera.
For me, one of the most important parts of the world generation has to be the rivers. I feel like I rarely see genuine flowing rivers in games, especially not procedurally generated ones. Minecraft and NoMansSky have ‘rivers’ but they don’t really follow any logic, they don’t really flow, and have no clear end and start. This is because the world of minecraft is on an infinite plane and NoMansSky has infinite planets. The terrain has no hard limits, so it’s not possible to do an actual simulation of erosion or rainfall. For me, rivers created from noise generation just don’t cut it, even a very simple simulation of rainfall and erosion is much more interesting.
I always like to get something working as soon as possible. So I started with a dead simple rainfall model. I attach a ‘water level’ value to each tile, then to simulate rain I increment the ‘water level’ for each tile every step of the simulation. Higher points will get slightly more rain. Then, if the water level on an adjacent tile is lower, I move a fraction of water from the current tile to that adjacent tile. I also evaporate water on each tile by a constant amount each step.
Initial simple water simulation
Initial results are quite decent, the noise generation already has decent river like formations and valleys carved into it. So the water just flows into the valleys and looks quite natural.
After running the simulation for 500 steps
Simple beginnings, next step will be to add an erosion model and actually mesh the rivers. Right now, any tile that’s above a water threshold is blue, there is no depth to water, it’s just flat.
I talked before about the improved noise system. I’ve been playing around and experimenting with different parameters, getting some interesting results. The difference between a real island and my fake video game island is that of scale. I want mountains, sweeping rivers, wildly different biomes, all on one island that spans maybe only 1.5 miles coast to coast. So it wouldn’t make sense for my islands to perfectly imitate the natural forming islands of earth. I wouldn’t say the terrain of the islands below look realistic, however these islands might not have even been created using the same tectonic processes. I’ve thought a bit about how far a fantasy world can really deviate from the real world. I think, that If the world looks like it was formed by some sort of natural process then it gets a pass. The real question is weather these maps are fun to play and build on. They definitely provide some nice variation across seeds with each map having some distinctive features.
Below I’ve demonstrated the effect of changing lacunarity on an FBM noise which acts as an input to the frequency of a Ridged Multi noise. So thats one set of noise acting as an input on another noise. Lacunarity starts at 0.0 and increments by 0.1 to a final value of 2.5.
Lacunarity (2000 x 2000)
Below you can see the effect of changing the frequency of the input noise.
The problem with this noise generation is that quite often I get a dud seed, especially when using noise of lower frequency. Sometimes an island will be very small, or overly large. I’m thinking of writing a function which discards maps based on total landmass, then keeps trying new maps until it finds a good fit. I would only need to sample the noise few times, maybe 200 samples, to get a reasonably accurate landmass approximation.
Below is the effect of changing intensity on the input noise. It starts as a sphere because there is a radial gradient subtracted from the noise to act as a falloff. With our input noise having 0 intensity it means that the main noise has a frequency of 0, the noise becomes just a flat color.
Islands with increasing temperature (2000 x 2000)
Urho3d has a nice method for picking objects by casting a ray through the scene octree. This works well enough for picking objects using their AABBs but it was quite slow for picking points on a mesh. The issue now is that with the new terrain generation the mesh doesn’t even exist on the CPU, so it’s not possible now anyway. I thought about doing the picking on the GPU, and because,the world position for every point on screen has already been calculated for the positions buffer the work has pretty much been done already. Urho stores these buffers as textures, so I did a minor modification to the Urho3D internals and by binding the positions texture to a framebuffer I can do a glReadPixel() to get the value under the mouse cursor.
The issue with glReadPixel is that it’s super slow on some hardware, my mac was taking 10ms to get that one singular pixel. This is where ‘pixel buffer objects’ come into play. Using a PBO the call to glReadPixel is non blocking, so it won’t halt the application. Instead of reading directly into client memory the function will read values into a buffer, which you can retrieve values from later. A common set up would be to have 2+ PBO’s initiated, then alternate between reading and writing them each frame. So the data you get would be from the PBO written to on a previous frame.
Unfortunately my development machine is an older macbook pro and I was having real problems getting the glReadPixels to function asynchronously. Despite binding the PBO, when reading the pixels it would still halt the application. Despite lots and lots of testing I could see no solution to this, looks like a bug beyond my control. Very annoying, however I was able to gain some performance by placing the glReadPixel before the scene is rendered. The way opengl works is that it won’t read the pixels until every other opengl call has been executed. This was a large part of why it was so slow before. Also by using a 32bit float for the texture instead of 16 bit half float seemed to help as well. I was able to take the performance to under a millisecond, however it seems to spike randomly, taking 5 to 10ms every 10th frame or so. On better hardware with proper PBO support it shouldn’t be any issue.
I’ve set up my shaders so they write an object ID into the alpha channel of the positions buffer. So figuring out the type of object under the mouse is easy. The idea is that I would use that ID to cull objects from a CPU side raycast.
Here you can see the results, I am now able to use the mouse to draw onto the terrain in real time. Converting grass tiles into sand tiles.
Here is some good reading on PBO’s: