Following a small break with updates on the rendering technique, holidays and super secret stuff, I could ultimately get back to terrain rendering this week. This meant operate on the final huge component of the terrain technique: Undergrowth. This is basically grass and any sort of little vegetation close to the ground.
As often, I started out carrying out a ton of analysis on the topic to at least have a chance of generating proper decisions at the start off. The dilemma with undergrowth/grass is that even though I could discover a lot of resources, most had been quite specific, describing strategies that only worked in unique cases. This is really typical when doing technical stuff for games even though there are a lot of good data, only a quite modest component is usable in an actual game. This is specially true when dealing with any larger technique (like terrain) and not just some localized unique effect. In these cases reports from other developers are by far ideal, and writing these blog posts is partly a way to spend back what I have learned from other people's work.
Now on with the tech stuff!
Plant Placement Information
The 1st dilemma I was faced with was how to define where the undergrowth need to be. In all of the resources I located, there was some sort of density texture used (meaning a 2D image where every single pixel defines the quantity of plants at that point). I did not like this concept quite significantly although, primarily due to the fact I would be forced to have lots of textures, 1 for each undergrowth kind, or to not permit overlapping plants (which means the same area on the map would not be able to contain two different varieties of undergrowth). There are approaches past this (e.g. the FrostBite engine utilizes sort of texture atlases), but then creating it perform inside the editor would be a discomfort, most most likely demanding pre-processing and a particular editor renderer. I had to do anything else.
What I settled on was to use region primitives, basic geometrical shapes that defined exactly where the undergrowth would be. The way this operates is that each and every primitive define a 2D location have been plants need to be placed. It then also contains variables such as density, enabling 1 to location thick grass at 1 spot, and a sparser area elsewhere.
I ended up implemented circle and convex polygons primitives for this, which throughout tests appear to function just fine.
Producing the Plants
The next dilemma was how generate the actual geometry. My initial idea was to simply draw the grass for each area, but there were some issues with this. One significant was that it would not appear excellent with overlapping regions. If areas of the exact same density overlapped, the cross section would have twice the density of the combined area. This did not seem proper to me. Also, it was problematic to get a good distribution only making use of locations and I was unsure how to save the information.
After once more, the report on the FrostBite engine gave me an thought on how to method this. They way they did this was to fill a grid a with probability values. For every grid point a random quantity in between and 1 is generated and then compared to to the 1 saved in the grid. If the generated number is lower than the saved, a plant is generated at that point, else not. Each and every plant is then offset by random amount, creating a nice uniform but random distribution of the plants!
This program match ideal with the undergrowth places and simplified it also. Using this method, an undergrowth area does not need to worry about generating the actual plants, but only to create numbers on the grid.
The final version works like this:
There is an undergrowth material for each sort of plant that is utilized on a level. This material specifies the max density of every plant and as a result determines how the grid should look. A material with a high density will have a grid with many points and a single with a low density will have few grid points. Each point (not all of course, some culling is employed) on the grid is checked against a region primitive and a value is calculated. This is then repeated for each area, adding contributions from all places that cover the identical grid point.
This solves the issue of overlapping regions, as the density can in no way turn into bigger than max defined by the grid. It permits makes it achievable to have adverse areas, that reduce the amount of undergrowth in a particular place. This way, the two easy region primitives I have implemented can be utilised for just about any type of undergrowth layout.
Cache system
Now it is time to discuss how to create the actual plants. A way to do this is to just produce the geometry for the whole map, but that would take up way as well a lot memory and be very slow. Instead, I use a cache program that only generate grass close to the camera (this is also how FrostBite does it).
The engine divides the whole terrain into a grid of quads, and then generates cache data for every quad that is close adequate to the camera. For each and every quad, it is checked what places intersect with it, and layers are made for every undergrowth material that it contain. Then for every layer, plants are generated based on the technique described above. The undergrowth material also consists of texture and model information as effectively as a bunch of other properties. For instance, the size can be randomized and diverse parts of the texture utilised, all to add some range to the patch of undergrowth. Finally plant is also offset in height according to the heightmap.
This cache generation took quite some perform to get good enough. I had difficulties with the game stuttering as you traveled via a level, and had to do a variety of tricks to make it more quickly. I also made positive that no far more than one patch is generated at every frame (unless the camera is teleported or comparable).
Rendering
As soon as the cache method was in location, rendering the plants were not that considerably of a dilemma. Every generated patch comes with the grass in globe coordinates, so it is as straightforward as it can get. The only fancy stuff taking place is that grass in the distance is dissolved. This signifies that the grass does not finish with a sharp border, but smoothly fades out.
In the above image you can see how the grass dissolves at distance. Here it appears pretty crappy, but with appropriate art, it is meant that grass and ground texture ought to match, as a result producing the transition pretty significantly unnoticeable.
Yet another thing worth mentioning, is that the normal for every grass model is the identical as the ground. This offers a nice appear to many plants, but an individual plant gets fairly crappy shading. Undergrowth is meant to be modest and not observed close-up even though, so I consider this need to function out fine. Also, when creating grass earlier (during development Amnesia), normal normals (ha...) were utilised and the outcome was very negative (sharp shading, and so forth).
Animation
Static grass is boring, so of course some sort of animation is necessary. What I wanted was two diverse types of animation: A international wind animation (distinctive for every single material) and also local animation due to events in a restricted area (somebody walking by means of grass, wind from a helicopter, etc).
My 1st idea was to do all of these on the cpu, meaning that I would need to resend all the geometry to the graphics card each and every frame. This would enable me to use all kinds of fanciness for animation (like my dear noise and fractals) and would effortlessly allow for lots of neighborhood disturbances.
Even so, I did some pondering and decided that this would be a undesirable idea. Not only does the sending of data to the graphics card take up time, but there may possibly be some quite heavy calculations required (like rotating normals) for a lot plants, so the cpu burden would be extremely heavy. Rather I chose to do almost everything on the GPU.
Implementing the global wind animation was fairly basic i was just a matter of sending a handful of new variables to the grass shader. But it was a bit tougher to come up with the actual algorithm. Probably I did not appear hard adequate, but I could discover quite small assist on this area, so I had to do a lot of experimenting as an alternative. The idea was to get anything that was quickly (i.e. no stuff like Perlin noise permitted) and however have a natural random really feel to it. What I ended up with was this:
add_x = vec3(7., three., 1.) * VertexPos.z * wind_freq + vec3(13., 17., 103.)
offset.x = dot( vec3(sin(fT*1.13 + add_x.x), sin(fT*1.17 + add_x.y), sin(fT + add_x.y)), vec3(.125, .25, 1.) )
add_y = vec3(7., three., 1.) * (VertexPos..x + vOffset.x) * wind_freq + vec3(103., 13., 113.)
offset.y = dot( vec3(sin(fT*1.13 + add_y.x), sin(fT*1.17 + add_y.y), sin(fT + add_y.z)), vec3(.125, .25, 1.) )
This is essentially a couple of fractually nested sin curves (fbm fundamentally) that take the present vertex position as input. The essential factor to note is the prime numbers such as vec3(7., three., 1.) without having these the cycles of the sin curves overlap and the end outcome is a quite cyclic, boring and unnatural appear.
The offset generated is then applied differently based on the height of the plant. There will be a lot of swaying at the best and none at the bottom. To do this the base y-coordinate of the plant is saved in a secondary texture coordinate and then looked up in the shader.
Now lastly, the regional animation. To do this entities known as ForceFields are utilized. (Thanks Luis for the name suggestion! It created carrying out the boring parts so considerably much more fun to make.) These are entities that come with a radius and a force worth, and is meant to produce effects on the graphics that they touch. Proper now only grass is impacted, but later on effects on ropes, cloth, larger plants , and so on are meant to be added.
These effect of these are applied in the shader and currently I help a maximum of 4 ForceFields per cache patch. In the shader I either do none, a single entity or four at after. This means that if three entities have an effect on a patch, I nevertheless render the outcome of 4, but fill the final one's data with dummy (null) values. Using four is in fact almost as fast as utilizing a single. Since of how GPUs operate, I can do a lot of function for all four entities in the exact same amount of directions as for a single. This tremendously reduce down the quantity of operate that is required.
Again, just like with the international wind, it was tough work to come up with a great algorithm for this. My very first thought was to basically push every single plant away from the center of the force field, but this looked genuinely crappy. I then tried to add some randomness and animation to this in order to make it nicer. As inspiration, I looked at Titan Quest, which has a really good impact when you walk by means of grass. After nearly a days work the final algorithm appear anything like this:
fForce = 1 - distance(vtx_pos, force_field_pos)/force_field_radius
fAngle = T + rand_seed*6.28
fForce *= sin(force_field_t + fAngle)
vDir = vec2(sin(fAngle), cos(fAngle))
vOffset.xz += vDir * fForce
Rand seed is variable that is saved in the secondary texture coordinate and is generated for every plant. This aids offers a lot more random and natural really feel to it.
Here is how it all appears in action:
Note: Make certain to check in HD!
And in case you are wonder all of this is ugly, made in 10 seconds, graphics.
End notes
Now that undergrowth is lastly completed, it signifies that all fundamental terrain attributes are implemented! In case you have missed out on earlier post here is a summary:
Terrain Geometry
Fractals and Noise
Terrain Texturing
I hope this has been of use and/or interest to somebody! :)
Subsequent up for me is some final terrain stuff (basically just some clean-up) and then I will move on to far more gameplay associated stuff. Much more on that later though...
No comments:
Post a Comment