Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length

Advanced search

1382157 Posts in 66017 Topics- by 58430 Members - Latest Member: DrKrakov

September 18, 2020, 10:16:14 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsCommunityDevLogsÉclaircies - Experimental running and racing sim set in a generated forest
Pages: [1]
Author Topic: Éclaircies - Experimental running and racing sim set in a generated forest  (Read 478 times)
Level 0

View Profile WWW
« on: May 17, 2020, 04:52:40 AM »

Hi everyone,

I'm Simon, a French game developer from France making an experimental first person racing game set inside a generated forest. It's called Éclaircies.

I started working on this game during the 7 day FPS and ProcJam at the end of 2018. I spent a good part of 2019 working on other projects and could not spend as much time as I wanted on this game. Until fairly recently I was not even sure what it was truly about. Anyway, I'm starting this devlog a bit late I guess but I have some cool things to share I think and many more things to do. I hope to finish it by the end of the year.

The recordings and overall sound design is the work of Marie Muller but everything else is being made by myself using Unity and Blender. The devlog will thus cover all the technical aspects of the game, from rendering to procedural generation and 3D modeling as well as audio integration.

The three main features of the game worth mentioning right away are:

The forest generation

Generating the forest is probably the most time consuming task as well as the most challenging. So I guess this will be a big part of this devlog. I wanted a big forest that could not be generated beforehand (it's currently taking a radius of 3000 meters but could be bigger in the end) and that featured different areas of varying densities and tree kinds.

The alternative first person controller

Éclaircies is actually a kind of walking and running simulation that offers a different way to move. Instead of the classic mouse and keyboard inputs it uses only two keys: one for going left and another to go right. The goal was to try and enhance the feeling of movement and the degree to which it can be played with. I felt that the traditional control scheme emphasized looking rather than moving and Éclaircies was really not about looking at all.

The experimental racing gameplay

Finally, and this is the part that wasn't clear to me at first, this game is actually a racing game. Might feel weird to say this but even though I was making a running simulation I had no idea it would end up being a racing game Smiley. Éclaircies's gameplay revolves around the idea of a rogue-lite, race against the clock kind of approach that makes it pretty unique I guess because players first have to create the path they will have to take to leave the forest.


The game's website: http://www.eclaircies-game.com
My twitter, where I try to post regularly about the game: https://twitter.com/SimonChauvin

And that's it for now! The next blog posts should be about the forest generation.

Thank you for reading Smiley

Level 0

View Profile WWW
« Reply #1 on: June 08, 2020, 07:34:09 AM »

Hi everyone!

Since I'm about to finish overhauling and optimizing the forest generation algorithm I thought that this would be a good starting point for this devlog.

Éclaircies is a first person game that allows players to freely roam a vast forest. I am a big fan of procedural generation in general and never really considered creating the forest by hand or by using terrain painting tools. I usually prefer programming than placing props around so I started, very early on, to build an intricate system to make this vast forest a reality. I also felt that this would be a better fit to my overall intentions. I wanted players to connect with the environment and make it progressively very familiar and unique while allowing them to come back to the game from time to time and experience a new forest.

Streaming system

The forest being quite vast, the time it took to generate it entirely on start was beyond reasonable. Additionally, it would have been a waste of time because most of the forest would never have to be generated in the first place since it would take forever to explore it and was not something the game would push for.

Thus I chose to build a kind of streaming system to allow tiny bits of forests to be generated in real time depending on where the player went. The whole area was divided into as many 50 meters wide chunks were necessary to fill it. Right now it's 6000 meters wide and made of a total of 14 400 chunks. The size of 50 meters came about mostly because it felt like a nice enough size for a forest chunk. Each chunk being generated independently from others it had to be sufficiently big to be a distinct area of its own without taking too long to be generated.

Forest layers

Each chunk is only generated if the player is at a specified distance and it led me to create a custom system of LOD to generate certain kind of trees before others because they could be seen from farther away.

Visibility of layers, from emergent (yellow) to floor (green).

As such, each chunk is divided into 4 layers. From my research on forests I found out that they tend to feature different classes of trees and shrubs depending on their height and overall sunlight access. Emergent layers, which are most often present in rainforests, regroup all the biggest and tallest trees. Even though the forest I aim for is a more humble European forest the emergent layer is still a good addition to divide the generation burden and to create a sense of perspective with tall trees in the background, which is something I happen to like in the forests I live close by. Below the emergent layer we find the canopy layer that actually covers most of the lower understory and floor layers.

Each tree belongs to a layer that determines its height (yellow for emergent, blue for canopy and purple for understory).

This division means that every chunk goes through a 5 steps generation. First, the ground itself is generated, it's made of a texture and the mesh it will be applied on. Then are generated all the emergent trees, the canopy trees and the understory trees. The last step is often the most time consuming and is responsible for generating the many shrubs, logs and plants that lay on the forest floor. Each generation step is using a custom Fast Poisson Disc sampling algorithm that I should cover in the next blog post.

Chunks gets generated then filled as the player gets nearer.

Nothing very fancy but a quite satisfying piece of code that makes use of a fundamental law of the architectural organization of forests!
« Last Edit: June 09, 2020, 12:45:45 AM by Chauchau » Logged

Level 0

View Profile WWW
« Reply #2 on: July 18, 2020, 02:57:07 AM »

Hi everyone!

It's been a while I think Smiley.

To conclude on my previous post and before I go on to talk about the generation of the forest itself I wanted to dig a little bit into the approach used to generate the ground.

As presented in the previous post, the whole forest is partitioned into squared chunks. This allows me to generate only the nodes that surround the player. The first step is to generate the ground itself as well as its texture. I took the lazy approach and used a simple fragment shader to generate a heightmap and a diffuse map whereas I could have dug into compute shaders for this I think. But I was interested in a low resolution kind of look and was not too worried about the performance. The mesh is made of 600 vertices and the diffuse has a width of 128. Once the heightmap is generated I then read every pixels and apply their values to a simple grid mesh's vertices. The diffuse itself is simply attached to the object's material.

Turns out, generating many chunks per seconds could be a problem at times so I ended up relying on a cool feature called AsyncGPUReadback. It is used to delay the reading of a render texture and thus avoids stalling the pipeline. After blitting my fragment shader to the render texture I then create a readback request that ends up being processed a few frames later when it's ready. It's a bit more involved than the use of ReadPixels because of the NativeArray data returned but it works flawlessly and is a perfect way to transfer data from the GPU to the CPU without wasting precious milliseconds. A good thing to know though is that, as the documentation says, the result is only available the frame it is received so that it is necessary to go through the whole queue of requests and process all the done ones. Without it I had random fails because I accessed the request one frame too late.

From above it is easy to see the many chunks that make up the ground.

While the heightmap is pretty straightforward and use a single Perlin noise, the diffuse map was a bit more tricky to generate. As can be seen on the screenshot above, each chunk corresponds to a specific kind of forest area. For instance, sparse areas have fewer trees and feature more grass on the ground. To achieve some level of diversity I chose to assign a different Perlin noise and probability to each kind of ground texture. Those can be leaves, grass or dirt for instance. Some areas are covered in moss whereas others like the sparse areas are made of big patches of grass. I also included a modifier that allows me to change the heightmap and make certain areas more hilly for instance.

The settings used on one of the areas (dirt is those yellowish patches).

Each forest area is configured by a scriptable object in which it is possible to select the ground kinds, their probabilities as well as their noise parameters. All the settings related to the generation of the forest are then stored in a collection of interrelated scriptable objects that tend to take the shape of a database in the end.

The configuration of the the forest through scriptable objects.
« Last Edit: August 22, 2020, 02:44:54 AM by Chauchau » Logged

Level 0

View Profile WWW
« Reply #3 on: August 23, 2020, 06:46:21 AM »

Hi everyone,

It's finally time to deal with the generation of the forest layers themselves. This was a big issue throughout the development because generating a dense chunk full of trees can take dozens of milliseconds if not properly done. And to avoid having trees pop in front of the player I had to make sure that many chunks were generated in advance.

Naive approach

A chunk is a 50 meters wide square of ground and typically features 60 floor elements, 120 understory trees, 15 canopy trees and a few emergent trees. Some special chunks can even contain up to 350 floor elements or 250 understory trees. The major issue was to prevent too much penetration between the crowns of the trees while allowing smaller trees below the bigger ones.

My first attempt at this was naive and used a dart throwing method where I would just try to spawn a tree somewhere on the chunk and process all the existing trees to check if the constraints are respected (height of the new tree is below the crown of the overlapping tree). This was innefficient, but I backed it up with an array of structs that allowed me to save the state of every possible positions on a chunk. Each time a new tree is created I update the  state of the positions covered by the trunk and crown to make sure nothing spawn on the trunk's position and only trees smaller to the crown base height are possible. Then, when generating a new tree I just check the state of the selected position to know if this particular tree can be spawn here.

Each position's state (yellow means anything can be spawned, blue forbids emergent trees, pink forbids emergent and canopy and the green color only allows for floor elements. White is unavailable.).

The switch to Fast Poisson Disc sampling

This approach worked surprisingly well with chunks moderately dense (less than 5ms to generate a 100 tree layer). But when it came to spawn a very dense layer (like 250 understory trees) it could take up to 20ms. Doing a single layer per frame was ok but it was still too much. In the meantime I came accross a popular algorithm called Fast Poisson Disc Sampling which, as the name implies, is a fast way to sample points in a circle.

I still had to adapt it to my specific constraints such as the fact that the points sampled (the trees) all have different radii. But overall, the switch to the new method was pretty easy. The logic behind it is very simple. When generating a new tree I select an existing tree and try to spawn around it at a distance that is greater than the sum of the radii of the existing tree and the tree to be spawned. If it can't be done I remove the position from the list but if it is in fact possible I then add this new position to the list. The algorithm ends when the list of possible positions is empty. This methods allows the generation of 250 trees in less than 5ms. The only issue with this is that it is less convincing when generating low density areas as it tends to create clusters of trees instead of diversly dense areas. By adding an additional spread radius parameter and predetermined starting positions I managed to make it look more random.

Overall statistics about the current generated forest (mean[min, max]).

In order to compare both methods I had to gather data about each chunk generated. This ended up as an inspector addon to visualize all the information relative to the generated forest. It tells me how many areas of each kind has been generated as well as the number of trees (elements) were spawn for each layer and the time it took. I'm pretty happy about this but I realize this is the kind of information I should have gathered as soon as I started building my generator. It's impossible to have a good overview of such a huge and rich environment without relying on objective data.

Pages: [1]
Jump to:  

Theme orange-lt created by panic