Author Topic: July 21, 2017 - Grass  (Read 9887 times)

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
July 21, 2017 - Grass
« on: July 21, 2017, 08:12:37 PM »
In Sightseer, a big part of the game is exploring the large procedurally-generated world, so it has to look as nice as possible. Flat terrains are generally not, so adding some grass only seemed logical. Since I was still using Unity's terrain system, for all its faults, I decided to see if it could redeem itself by offering some nice looking grass.

Well... it did, sort of. The final result did look a little better:



But what about looking at it from the top?



If you can't see any grass in that picture, you're not alone. There are two ways of drawing grass using Unity. First is to use the approach you see in the picture, by simply having grass use the world's up vector as its up vector. It looks consistent from the side, but effectively makes the grass invisible when viewed from above. Another approach is to use screen-aligned quads for grass, where the top of the monitor is considered to be "up", regardless of the direction. This approach is even worse -- not only does the grass turn as the camera rotates / tilts, but it also looks very weird when viewed from above. I'll spare you the pic.

Still, neither of those limitations are as bad as the next one: performance hit:



Since the grass update is based on the position of the camera, in a game featuring a 3rd person camera that orbits around the vehicle such as Sightseer, that grass update happens very, very frequently -- often more than once per second! Predictably a 300+ ms hiccup every time that happens is simply unacceptable, and that's with moderately sparse grass, at that!

Ideally I wanted grass to be more dense, like this:



With grass set to that density, the game was spending more time updating grass than everything else combined while playing it.

At this point I found myself wondering just what kind of use case the original developers of this grass system had in mind with it being so stupidly slow. Maybe it was only meant for extremely small worlds, or extremely sparse grass, or both... I don't know. All I know is that it was simply unusable, and that I had to write my own.

And so I did.

So, how can one do super fast grass? Let's start by examining Unity's grass. With it, for each generated terrain, grass information has to be baked in right away for the entire terrain, like a texture -- the same way splat information is passed to it. If a small part of the grass information changes, the entire texture does. What happens with this data is anyone's guess, as it happens somewhere deep inside Unity and the end developer has no control over it.

Does it have to be this way? Certainly not. First, grass information for anything outside the player's immediate area is completely irrelevant. Who cares what grass is supposed to be like 1 km away? It's not visible, so it's irrelevant. Second, I don't know when or why Unity's updates take so damn long to complete, but grass needs to be split into patches (and as far as I could tell, Unity does that -- at least for drawing the grass). As such, patch-based distance checks to determine if the grass should be updated or not should be extremely fast. Similarly, there is no need to update each patch unless the player goes out of range. When it does go out of range, the patch should be repositioned to the opposite side of the visible "bubble" around the player and re-filled. The "bubble" looks like this:



Last but not least, actual placement information for the grass should be based on some data that's available in a separate thread. Since Sightseer sets the terrain heightmap, the base heightmap data can be used as-is. All that the main thread should be doing is updating the VBOs (draw buffers) after the grass has been generated.

Finally, the grass itself shouldn't be based on quads like Unity's. It should be based on meshes. A simple "bush" of grass made up of 3 quads intersecting in a 3D V-like pattern is the most trivial example. Since it's based on meshes, it's possible to have the said meshes to be of different shapes, complexity, and most importantly -- size. Furthermore, since it's shaped in a V-like pattern, it should look good even when viewed from above. Of course since the grass should end up in a single draw call, it's important to have all those meshes use some kind of a grass atlas, letting them share the same material.

In the end, it took only a few hours to write a basic grass system, then a couple more days to perfect it (and a couple more weeks of playing the game to iron out weird glitches <_<). The end result, performance-wise was obvious within the first few hours, however:



You're seeing that right: 0.2 millisecond to update the grass of much greater density than what was taking Unity's grass 300+ milliseconds per frame. I expected as much, which is why I was so surprised that Unity's grass performed so horribly. This is how it looked in the game:



It looks much more dense than what I had with Unity's grass, and is very much visible from above:



In fact, I was immediately curious how the grass would look like if I enabled shadows on it and increased its size to make it look even more dense:



Very nice indeed, although the shadows are a little too obvious from above:



There is one other thing I did with the grass... and it's pretty important. I colored it based on the underlying terrain. Doing so is simple: I render the terrain into a texture using a top-down camera that's updated when the player moves far enough. This texture is sampled by the grass shader, tinting its normally black-and-white albedo texture with the color of the terrain underneath. This makes the grass always blend 100% perfectly, regardless of what's underneath -- whether it's the sand-blasted savanna or the lush grassland -- without any need for developer input. In fact, since Sightseer's terrain is fully procedural and smoothly transitions from one biome to the next, this part was as extremely important to have as performance.

The end result? See for yourself:



All that for 0.2 ms every ~2-3 seconds while driving around.
« Last Edit: July 21, 2017, 08:20:51 PM by ArenMook »

cmifwdll

  • Global Moderator
  • Sr. Member
  • *****
  • Thank You
  • -Given: 0
  • -Receive: 149
  • Posts: 285
  • TNet Alchemist
    • View Profile
Re: July 21, 2017 - Grass
« Reply #1 on: August 06, 2017, 09:40:44 PM »
Where do you even learn this stuff? So much of this and the LOD post is foreign to me. I've been going through rastertek's d3d11 tutorials to try to understand rendering better, but I feel like the tutorials stop explaining stuff very quickly and just supply code, so I'm not really learning anything.

Did college help with this? Specific books? Really good tutorials? A patient mentor?

Anyway, your dev blog is always entertaining, even if I don't understand a word of it. Super fascinating stuff, and it looks fantastic, too :D

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: July 21, 2017 - Grass
« Reply #2 on: August 12, 2017, 01:06:36 PM »
I always learn just by doing it. I actually often have a hard time understanding other people's approaches when looking at their code, but when I see something done in a game, I immediately think of how it could have been done just by paying close attention to what I see. It's usually much easier for me to learn how to do something by examining the end result than it is by examining the code. Likewise, if I see something done poorly, like Unity's grass was, I simply think of what's wrong with it, and how to fix it. This stuff is all easy... There are many areas of game dev where I'm still a newb like most -- planetary / universe scale generation that Star Citizen devs are doing is way beyond me for the time being, for example. I wish I could hire devs of those caliber. I'd happily pay them solid 6 figures. :)
« Last Edit: August 12, 2017, 01:12:23 PM by ArenMook »

cmifwdll

  • Global Moderator
  • Sr. Member
  • *****
  • Thank You
  • -Given: 0
  • -Receive: 149
  • Posts: 285
  • TNet Alchemist
    • View Profile
Re: July 21, 2017 - Grass
« Reply #3 on: August 12, 2017, 05:23:27 PM »
Seems like it might be a case of me lacking a few years in experience with graphics, then :P I hope one day I can get to the point where I can look at improperly fading LOD and think "ah, yes, I need a frag and vert shader so I can calculate and perform dithering to achieve smoother cross fading". I don't even know what I just said.

Thanks for the inspiration, though. Lots and lots of stuff to learn.

Bradamante3D

  • Jr. Member
  • **
  • Thank You
  • -Given: 2
  • -Receive: 0
  • Posts: 79
    • View Profile
Re: July 21, 2017 - Grass
« Reply #4 on: December 28, 2017, 06:07:31 AM »
When I am using Unity for architectural visualisation (often in VR) grass rendering and it's performance is a massive problem. There's no chance you can externalize this into an Asset Store asset? Let me guess, productization is not the problem - ongoing support is? Is it possible to publish an asset on the store explicitely "as is"?
#301224014, #301432336, #302399130

ArenMook

  • Administrator
  • Hero Member
  • *****
  • Thank You
  • -Given: 337
  • -Receive: 1171
  • Posts: 22,128
  • Toronto, Canada
    • View Profile
Re: July 21, 2017 - Grass
« Reply #5 on: December 31, 2017, 10:38:21 PM »
Yeah, I don't want to deal with support, making all the necessary art, preview vids, and all the other hassle related to submitting to the asset store nowadays. I might release it as a public github project later, however.