Island trees viewed from above. Note that trees are self-shadowing. |
View from above the pine trees, looking out across the terrain at the other tree types in the foggy distance. |
Tree types are selected based on elevation. Pine trees are placed at the highest elevation, palm trees are placed at low elevations near water, and deciduous trees are placed in intermediate areas. Grass and flowers are also placed at low to mid elevation, with grass density based on the sky/sun visibility from each point on the ground. Visibility is determined from precomputed mesh and tree occlusion maps stored in each terrain tile.
Palm trees are new to tiled terrain mode. I added some experimental palm trees to some of the other scenes a few months ago, including the office building scene which appeared in earlier blog posts. I had to rewrite the palm tree rendering to be more efficient by storing the tree vertex data on the GPU instead of the CPU. This allowed the placement of over a thousand visible palm trees in the scene with minimal additional frame time.
Palm trees are drawn using the same shaders and a similar rendering path to pine trees. However, I still don't have a good low-detail distant model for palm trees. Billboards work well for pine trees since they're mostly rotationally symmetric, but they don't work well for palm trees. There is too much popping and too many rendering artifacts on palm tree leaves when transitioning from a geometry model to a billboard image model. This will be future work.
Deep in the forest, among the trees. A (new!) bush has been placed to the lower left of the center of the image. |
Deciduous trees are generated from five different species, where a set of random noise weight maps is used to select the species of each tree. The species that has the highest weight is selected for placement at each chosen location. This produces clumps of trees of the same type, similar to what appears in a real forest. The original placement was too regular though, so I added another layer of random noise to blur the boundary between the tree types. Now there are some sparsely places trees of a different species mixed into the clumps of trees of one species. This looks much more natural, and makes an amazing difference for such a small change to the tree placement algorithm.
3DWorld procedurally generates 10 variants of each tree species, giving a total of 50 unique trees to be instanced in the scene. In addition, some bushes are generated by changing the tree parameters to remove the trunk and translate the tree down into the ground. The number of unique trees is mostly limited by GPU memory usage (on my old computer), and can be made higher for less detailed trees. This appears to be enough unique trees that it's nearly impossible to spot two duplicates next to each other. My new computer has 2 GB of graphics memory, so I could increase the number to 100 unique trees, but it seems unnecessary.
View of the bay with grass and palm trees. |
More palm trees by the bay, with birds in the sky, reflective water, and distant fog. |
I also improved the elevation-based tree placement to remove the hard lines between pine/deciduous/palm trees that caused the transition between tree type to exactly follow elevation contours. This looked very unnatural. The solution was to use the low bits of the floating-point number representing tree height to adjust the placement parameters. These low bits are deterministic but random enough that the pattern isn't noticeable. The random bits are multiplied by a large constant and added to the elevation value, producing a more randomized tree type contour that allows multiple types to be mixed near the elevation boundary. Now there are five different elevation regions: palm, palm+deciduous, deciduous, deciduous+pine, and pine. You can see pine + deciduous tree types mixed together in the first screenshot at the top of the page, and palm + deciduous trees mixed in the screenshot below.
Grassy, sandy beach with distant mountains. |
Lighting, shadows, and fog work on all tree and plant types. Everything casts and receives shadows. Ambient occlusion is calculated and combined with shadows to create darker areas under dense tree coverage. The image below shows darker shadowed areas in the front and right, contrasted to a bright open area in the back left. Note that grass density is higher in bright areas.
A tree covered, shadowy hillside. |
Here is a video of me walking (or running?) around on the ground, from a medium elevation point on the mountain side down to the water, and along the beach. Everything you see is procedurally generated with no load time and rendered in realtime at around 60 FPS. If this video looks too small or blurry, you can try the direct link to my video on YouTube here. I'll have to see if I can find a video hosting site that allows me to post longer videos that have less compression artifacts, so that you can see more details - for example, how all of the leaves move in the wind.
Note that, unlike most other forest videos I've seen online, there are almost no visual artifacts or "popping" of trees or terrain. There are a few places in the video (mostly near the end) where some "dissolve" level-of-detail transitions are present, but they are distant and minor. This was difficult to achieve with thousands of visible trees containing up to 20 thousand individual leaf polygons.
Can you spot any duplicate instanced trees? It's unlikely. There are 50 unique deciduous tree models, and each pine tree, palm tree, and plant is uniquely generated.