Fixing the beaches
At the end of my long post on adding beaches, we ended up with this:
It was simple and very high performance (low poly graphics, will run fine on 7+ year old computers) and looked OK, but had a slightly squashed look around the edges. Until viewed from the side, when it looked horrible, and the reason behind the squashed look became clear:
- The beach is 100% flat
- The grass/beach interface is also flat, but shouldn’t be – I’d intended it to be a small slope/cliff
- The interface is squashed thin otherwise the shore takes too much of the hex
This made it easier to calculate heights – they’re all “zero”.
I also thought it would make beaches look more beach-like, especially next to steep slopes – the slope would suddenly become gentle.
This was accident; the interface zone is cut-out from the beach zone, so it inherits the height (of zero).
Fixing it correctly will take some effort.
Shore mustn’t be too much of the hex
Civ 5 has a visual artifact where it makes land appear to be sea. All three of these tiles are officially “sea”, despite their appearance:
These happen every game, many times. There are much nastier examples, but they’re rare – the rare ones render most of the tile as sea, but class it as land, which is very confusing.
This is bad from a gameplay perspective – after the first time it deceives you, you turn on the ugly “hexagonal grid” and never risk turning it off. It’s a side effect of their attempt to make the maps look “less hexagonal, more natural”.
Changes to beaches
So I changed the heights of the strips: the inland edge of the beach (which is also the outer edge of the grass/beach interface) and the inland edge of the grass/beach interface (which coincides with the main grass area).
First attempt I set the heights, going Sea/beach -> beach/interface -> interface / grass:
This is really how I should have left it last time. There is a noticeable slope, and the interface texture ensures that the change from grass to beach isn’t visually a hard, straight line.
I also tried setting the numbers to give a non uniform slope. At first I thought this looked better, but now I think it sucks:
There are gaps between the neighbouring hexes because I calculated the “percents” based on the center-height of each hex. This was lazy, quick test. The whole point of my terrain algorithm is that I have an official, global, persistent value of height at every single co-ordinate (generated by a separate algorithm.
This enables me to calculate things at the edge of one hex, and know that the same calculation in the neighbouring hex will give the same result. If I use it. Which I did not. Yet. Anyway, moving on …
Changing the camera angle made the interface with variable slope look too sudden:
…so I changed the amount of beach that is converted to interface, versus kept as pure sand, from 50/50 to 80/20:
All looked good, until using an angled camera-view (which is the main one in-game):
Ugh. More tweaks: set the interface/beach ratio to 70/30 (more beach; it gets too narrow when perspective comes into play), and increase the amount of the hex given over to beach from 35% of radius up to 45% of radius:
Re-thinking the Mesh algorithm
All this was a side-track: what I really wanted to do was change the rendering of rivers. Currently, they’re drawn as sea (i.e. fully occupy a hex), but with a downhill slope:
…when what I really want is something that cuts-through the middle of a hex, while leaving grass (or hills, or rock … etc) to either side. I especially want to control it programmatically, so that it gets visually wider the further downhill the river flows. Oooo!
I’d created a quick, easy, editor-friendly way of selecting the code that generates meshes for each hex. This is sort-of forced upon me by Unity3D (polymorphism is a bit messed up in Unity thanks to their disabling of types, inheritance, and constructors) – but I’ve been using Unity for many years, and I know the easy workarounds. Here’s my editor for choosing which class to generate which mesh, in what circumstances:
It’s great! Each terrain-type has its own class! Or … can share with others. Simply drag-and-drop in the Editor.
But … what about Rivers? Rivers:
- Flow through ALL terrain types (except Sea)
- Must use the same algorithm everywhere, so they meet up at the edges
- Generally ignore the terrain-type, except to use its texture (grass vs sand etc)
It would appear I have to throw-away my simple approach of generating meshes based on the underlying Terrain, and write something where mesh-generators can be used anywhere, and are more cross-compatible.
Once I’ve done that, I can write rules for “use the River-drawing class here, but use the Default Ground-drawing class everywhere else” etc. This will only work if there are standardised rules for how meshes connect at their edges.
Rules for neighbouring hex meshes
So far, my only rule has been:
Each corner must be positioned at the height from the fine-grained heightmap, and each edge must be a straight line between the two corners
That has allowed me to do anything I want internally, and I bent the rules a little for rivers: I override the definition of “corner height” globabally and where any of the hexes at that corner are water, I set the corner-height to be the same as the center of that hex, i.e flat across the surface.
Coincidentally while I was thinking about all this, CatLikeCoding published a new tutorial on his generic approach to making meshes for simple hex-based games, focussing on … rivers! Go read the tutorial (or the whole series, starting from Hexes 1) – it’s a very different approach, and creates a very different style of map.
But the key thing is that he sets rules for the “edge zone” of every mesh. His edge-zones exist OUTSIDE the hexes – which I don’t want, it would ruin most of my graphics (and possibly my gameplay) – but it seems very similar to what I did with my “beach/grass interface strip” already. Maybe I could generalise the “interface strips” approach, and deploy it inside my hexes?
Ideal meshes for rivers
I spent a lot of time looking at rivers, river cross-sections, diagrams, photos, etc. Ultimately I want rivers that look like this (image on the left from here), with texturing to give distinct colours and materials like (image on the right):
There are eight distinct materials needed to draw a river like this:
- Left bank surface (e.g. grass – depends on the terrain)
- Left bank interface
- Left bank riverbank (e.g. mud, stone)
- Underwater / riverbed
- Right bank riverbank (e.g. sand)
- Right bank interface
- Right bank surface (e.g. grass – depends on the terrain)
- Water surface
After a lot of photo-surfing, I realised I want my rivers to change water-height/depth over time, possibly during the game as a response to in-game actions. I already know that some rivers will have steep slopes – a side-effect of the random generation – so I need to handle the water-surface with code; it cannot be a simple flat plane, it will have to follow the contours of the land.
The right bank / left bank surfaces will probably be identical, since I’m saying that each river should have only a single terrain-type. For a more general solution, one that allowed a river to be border between e.g. grass and mountain, or desert and flood-plain, they’d be separate. Even when the terrain is identical both sides, the colouring and texturing is often different due to the deposition/erosion shown in the diagrams above:
Sidetracked with triplanar shaders
At this point, I tried to plan the generic rules for meshes that would mean a River mesh would always safely sit next to a Hills mesh and a Desert mesh, with no gaps in-between, etc. I had a half-complete plan, and then I realised how ugly my Hills were.
I wanted a Triplanar shader so that the hills would automagically be grassy on top, and rocky wherever they became steep. It didn’t quite work out as planned.
- Using Shaderforge to make a triplanar shader took a couple of minutes
- After a lot of tweaking (and working around bugs in the Shaderforge triplanar example (!), and bugs in Shaderforge (grr!)), it looks … interesting?
- It took hours of tweaking to get anything vaguely nice-looking
- There are SO MANY BUGS in ShaderForge; given how old, famous, and money-making this Unity Asset is, this is surprising
- Some of the tiles have world-space going left-right instead of up-down; this is impossible and I still have no idea what’s gone wrong 🙂
- It doesn’t look like I intended
…giving me this:
The shader I finished up with does this:
- Takes the Normal of each point
- Abs() : converts negatives to positive, so it’s from 0,0,0 to 1,1,1 possible values
- (re-)Normalize it : makes it unit length (otherwise next steps go weird b/c they assume normals)
- Split into 3 parts – X/Y/Z (uses Comp.Mask in Shaderforge; in Shader language, it would just be literally typing “.x”, “.y” and “.z”. Shaderforge overcomplicated here compared to typing it out!)
- Take the Y part and remap it so that 0..1 becomes -5..1 : this means only the VERY flat parts of the object will count as “top” for the triplanar materials
- Multiply that by 4 : increases the sharpness of the cut-off; in the screenshots this is how much yellow there is at the border between grass and rock,and how thick / thin, gradual/sudden it is
- Clamp it back to 0..1 : again: (otherwise next steps go weird, b/c they assume normals)
- Append the new Y onto the original X
- Append the original Z onto both : so now we have (X,Y [changed], Z) again
- (re-)Normalize : those appends can slightly de-normalize it, so fix it up again
- Do the triplanar part:
- Use a Channel Blend (Shaderforge’s way of doing what’s trivial in GLSL code) to combine 3 textures
- Each texture has a custom UV stage:
- Append (i.e. combine, like earlier) two of the three axes from the world-position
- Texture 1 combines Y,Z
- Texture 2 combines X,Z
- Texture 3 combines X,Y
- Feed that into the output “Diffuse”
- NB: I’m not using Unity’s Standard shader because the performance is SHOCKINGLY bad in-editor when you have a large number of meshes.
- Some bug they need to fix
- …if they don’t I’ll do my own mesh-combining / optimization later, but for now: legacy shader is easier solution
Here it is in ShaderForge, if you want to copy (why doesn’t ShaderForge have a one-click copy/paste Hash system? ARGH!)
Where was I?
Oh, that’s right – Rivers!
Well … completely out of time now for this fortnight, so I’ll have to leave it here and pick up from here next time, with details on how I end up implementing rivers (still undecided, frankly). I know exactly how I want the rivers meshes to be built, but I need to sort out in my head the general solution for making:
… hills meet plains meet desert meet grass meet beach …