Plants 1

My goal was to make a system for plants to serve a little more than their usual set-dressing, and also require very little work from me. Plants in games typically re-use a few simple models, and are meant to fade into the background. I want my plants to be able to hold the players attention. Not only in an aesthetic sense of each plant being it's own unique interesting thing, but also in a mechanical sense: for the horticulturally inclined player, each plant should spark questions and plans and actions. The process of growing and tending to plants over the course of the plant's lifetime should be more engaging than hitting a "water plant" button and watching some models swap.

To make this more concrete, my plants need to do two things: look pretty over time and respond to player actions over time. This calls for some fun adventures in procedural generation!

Look Pretty: L-Systems
Beginning with the first part of the first requirement (as one does), plants need to look pretty. A nifty way to do that without artistic skills is something called an L-system, which I find cool enough to have been the original motivator for this entire feature. L-systems are a way to use simple rules to procedurally generate plants, basically using character replacement rules. A more in depth explanation and a lot of the examples I worked of off are available here. Basically, this will let me generate different stages of a plant growing, and has plenty of room to add complex behaviour later.

I'm going to bump things up a notch from those examples, and do it in 3D. To do this, instead of drawing line segments, I draw different meshes of the most plant pieces. This revealed a major problems with the L-system examples that I had been working with. Most of them make heavy use of repeating segments in the same place. This works fine in 2D with infinite render time, for me it meant a lot of unnecessary vertices and overlapping faces. This is pretty simple to avoid, it just requires some more carefully orchestrated and more explicit L-system rules.

Grows sideways and kills CPUs.

Grows sideways and kills CPUs.

While boiling down L-systems to behave more nicely and watching a bunch of plant growing time lapses, I discovered a basic rule that I think underlies realistic plant growth. All parts of a plant are growing all the time. Leaves don't pop out when the stem gets to a certain height, they appear immediately and then the stem segment grows underneath them and lifts them up. While this is happening, the next stem segment down is also growing, as is the next one, and so on. This made the next part a bit tricky.

Look Pretty Over Time: Meshs & Matrices & Math, oh my!
So with my bare bones L-systems properly configured, I could procedurally generate a plant, in several stages of growth. Each stage grows quite a bit, so I need some smoothing in between stages. Smoothly transitioning between two meshes with the same number of vertices is fairly easy, just pair up the vertices from the new mesh and the old mesh, and then lerp from one to the other. This was not the case for my plants, each stage added a bunch of new vertices, and even if I did now the final vertex count, I didn't want to pack a whole tree onto a tiny sapling.

So the problem before me was how to smoothly transition from the current mesh to a new target mesh, which has a totally different set of vertices, stored in a different order, with different triangles. My initial solution worked thusly: for each vertex in the target mesh, find the closest vertex in the current mesh, and start it there. Once all the new starting vertices are in the right place, connect all the triangles and as they will be at the end of the transition, and lerp each vertex towards its target position over the next few seconds. This had some weird triangles appearing for a bit, and got a bit muddy with complex geometry, but it worked quite nicely if I do say so myself.

Now I’m sure you’re thinking "that could never work for large meshes, finding the nearest vertex for each vertex is n^2!" which is true. But that doesn't matter, thanks to the magic of Unity's compute shaders. I've never worked with compute shaders before, so this was a great learning experience. As I understand it, they allow you to multithread computation on the GPU, which means doing lots of separate basic math is really fast. Luckily, that's exactly what I'm doing. I use a compute shader to calculate vertex starting positions, the vectors to lerp them along, and to actually move them throughout the transition. This fairly simple implementation raised the maximum amount of vertices I can run in realtime from ~5 to tens of thousands. I am now a big fan of compute shaders.

As nice as it was in simple test cases, this broke down for more planty things. The problem that I was encountering was with leaves that had already been grown, and were being moved up, because of stem growth below them. Instead of simply moving up, they looked like they were being re-extruded higher up. This was happening because the stem up there was closer to their final positions than the initial leaf positions were, and so the new starting mesh put the leaf vertices on the stem, instead of their old positions. The fix for this was to only apply the mesh growing algorithm for "new" growths, and been simply lerp L-system pieces that had already been grown from their old positions. This sounds simple, but it hurt my brain for several days so I thought I should probably mention it.

Next Week
So in the end, I have a system to generate and grow plants out of nothing, which is pretty neat. There is a lot of work left to be done on the mesh generation end, first on the list I think is color, because I have been told that not all trees are solid red (who knew). There are about a thousand little optimizations which will need to happen to be able to run a dense forest full of these things. The system for generating pieces also desperately needs some custom UI work, right now L-systems are defined entirely through code and that's not going to hold up as they get more complex.

Current status of tree generation. Will be less red in the future.

Current status of tree generation. Will be less red in the future.

The more exciting room for expansion addresses the second part of our requirements, plants need to respond to the world and the player. L-systems lend themselves to this quite nicely, because they allow you to distribute effects into many pieces. This will (I hope) let me spread randomness throughout the system such that it isn't specifically predictable, but in general player actions have predictable (i.e. satisfying) effects. Since each part of the plant is always growing, plants that aren't watered (for example) will stop growing realistically, and plants that are moved from a desert might grow in new and interesting ways because of all the new energy available to them. There are a lot of possibilities on this front, and I'm planning on spending a lot of time experimenting with growth algorithms.

Beyond that, I have ideas on procedural speciation, an evolution system, better interactions with the world around them (not growing into the ground, etc.) and each other, interacting with animals, etc.

Previous
Previous

Plants 2: Secondary Growth