Adding Trees to the OpenGL Terrain Demo
In my last post I wrote about generating trees with code and the screenshot above shows the results from that being put into my “infworld” demo.
Overall, I really like how it looks and this post will go over some of the details with implementing this feature.
Tree Generation
I have the trees stored in this data structure that I call DecorationTable
because trees are “decorating” the landscape. The code for generating and placing
the trees in the world is shown below:
Firstly, we have a function that assigns a seed value to each chunk based on
the permutation initially generated for creating the world and the chunk’s
coordinates. This should allow for seemingly random but reproducable results.
This seed is then put into std::minstd_rand0
which then generates the number
of decorations that should be placed in that chunk and then proceeds to generate
the positions of those “decorations”. However, the program then filters the
results firstly by using a noise function to cull trees in certain areas so that
the trees are not too uniform and then removes trees that are at heights that
they should not be at (we don’t want trees at the top of snow capped mountains
or at the bottom of the ocean, that’s quite silly).
Instancing
Initially I drew each tree one by one and had a draw call for every tree but that
proved to be rather inefficient and likely would not have scaled for a very large
number of trees. The problem with having a draw call for every tree is that for
each draw call, the CPU needs to send data to the GPU which is fairly time
consuming so limiting draw calls and attempting to group objects that need to
be drawn into one draw call would be more efficient. Since we only have two
tree models and therefore we are drawing a lot of objects that share the same
model, we can simply call glDrawElementsInstanced
using instance buffers to
store the position of each tree. I found about instance buffers from this
learnopengl article
and after some attempts, I was able to produce the following code to update and
generate the instance offset buffer:
while doing this however, I ended up having to come across perhaps one of the
stupidest bugs that I have ever encountered. Originally, I only had the following
code for updating vaoCount
(vaoCount
is simply an unordered map that has
the vao id as a key and the number of instances that need to be drawn as the
corresponding value):
I thought that insert
would update the value at the key if the key already
existed but that was not the case. I spent a little bit too much time trying to
figure out why there were all of a sudden giant gaps that would appear and
disappear whenever the program would generate new trees (it happened because
the vao count was not updated but there might now be more vaos to draw and
therefore the program was drawing less instances than it should have).
Updating the code to be the following ended up making it work:
Overall, instancing has very good performance but it seems that now the infworld demo consumes quite a lot of the integrated GPU on my laptop and while by itself it runs at a mostly smooth 59-60 FPS, when it has to share computing power with a process (like Firefox playing a YouTube video) it seems to suffer frame drops sometimes.
Some other features I implemented were lower detailed tree models so that trees farther away would be drawn with less vertices and therefore consume less resources and allow for more trees to be drawn and I also set limits on how far the trees would be rendered.
Anyway, my only real issue with adding trees is the performance hit that comes with having to render so many models and I might be able to make some improvements if I really tried (like reducing the vertices from one of the tree models with has about 1.4k vertices for the high detail model and ~400 for the low detail model) but after spending almost two? three? (I checked the git log, I started this project on April 1st) months on this, I am getting a little tired. However, I am happy with the current features of this demo and I am ready to move on to another project (although I likely will be looking to reuse the code from this project).
You can download the executable for Windows and Linux here.
Anyway, that’s it, have a nice day.
EDIT 2024-6-29: Okay, it turns out I was a little stupid and found some bugs in the original demo 2 (you can still download the bugged version here) but I updated it and hopefully those bugs are now squashed.