Synesthesia: Improved starfield rendering + satellites

While working on the new pre-rendered skybox system for Synesthesia, I came up with a way to render a cloud mask that would allow masking out objects that are supposed to be located behind the clouds - since skybox is a two-dimensional opaque image, there is no “behind”, things could only possibly render in front of this image.

However, with the cloud mask, objects that are rendered in front can be masked to appear behind, by concealing parts of them which are supposed to be hidden. Because of this, a possibility opened up to render a realistic star sky.

Starfield rendered using the new code

The stars are rendered as Niagara particles and placed slightly in front of the sky sphere. While I did not finish this yet, the stars will be masked by the cloud mask, allowing them to appear correctly behind the cloud.

The stars are defined by a catalog, where right ascension (azimuth) and declination (elevation) are specified. For now, I took a catalog that is relevant to present time, however it would be fun to produce a star catalog for year 2994 (when the game takes place).


The video above shows a timelapse and a few satellites passing over. The satellites are indicated by a red glow for now, however I will add proper calculations for their apparent magnitude.

The ring of satellites that appear to be frozen are an array of 36 geostationary satellites with a 10.0 degree step. They have an orbit defined as geostationary, I used them to verify orbital calculations and alignment between the starfield, the satellites and the geographical location of the observation.

Although there is a specific location encoded internally in the game, the exact location of EDZ-01 (the city that you explore in Synesthesia) should be assumed ambiguous.

The video above shows movement of the satellites at the in-game time scale. Currently, 24 in-game hours take 2.4 hours of real world time. I may revise the scale in the future, however this seems to be optimal for the sort of gameplay that I have in mind. To compare, the same time of day scale is found in S.T.A.L.K.E.R.


To support a large number of satellites the simulation code works like this:

  1. When it is first initialized, the time when satellite will become visible is pre-calculated. The satellite position is estimated at 5 minute intervals until position that is visible is found
  2. If satellite is visible, it is added to the visible satellites list. If a satellite drops below the horizon in this list, it is removed and added to the sorted satellite list.
  3. The sorted satellite list stores all satellites that are not visible in the order of next encounter.
  4. Every tick, the sorted satellite array is scanned until the first satellite that is still too early to encounter is found. In practice, this means that only a small handful of satellites at the start of the list will be checked. These satellites are removed from the sorted list and added to the visible list.

Satellites are defined by a catalog, where six orbital elements and two extra parameters are specified:

  1. Semi-major axis (SMA)
  2. Eccentricity (ECC)
  3. Inclination (INC)
  4. Right ascension of ascending node / longitude of the ascending node (RAAN)
  5. Periapsis argument (PAR)
  6. Initial mean anomaly (MA0)
  7. Tumble period: if the satellite is tumbling, its brightness is modulated with this period
  8. Apparent magnitude: apparent magnitude of the satellite under the best conditions

The satellite system provides extra immersion and flavor to the setting, but also I plan a RF scanning feature, where player can listen in to various radio signals. Many of these satellites are no longer operationally used, however they still likely broadcast a dead carrier or some sort of telemetry data that player would be able to receive (however, most of it would be procedural and meaningless, just for flavor).

Synesthesia: Starfield rendering

See the previous post for more about how the skyboxes work in Synesthesia. One of the issues with the pre-rendered skyboxes is that the image of the stars in the background cannot properly rotate with the movement of sun.

The stars rotate over the sky as time progresses due to the rotation of Earth. However, since the stars must be behind the clouds and the current implementation of the pre-rendering cannot capture a mask of where the clouds are, it’s just embedded in the sky texture.

On the video above, pay attention to the top middle of the frame. There are two prominent stars there which appear to teleport around in a strange way - a consequence of them being embedded in the sky texture as it blends.

Compare this to the way true dynamic sky behaves, shown on the video below. The star map correctly rotates with the movement of Earth.

With some modification of the skybox renderer, it should be up to the task of rendering raw opacity of the cloud. This would allow separating the star map away from sky texture, making it scroll smoothly. It would also significantly improve the way the sun and the moon interact with the clouds.


While writing this post, I experimented a little with some simple modifications and got a result that I think will be practically useful. Here’s an example of a pre-rendered sky and a pre-rendered cloud mask. This cloud mask would allow distinguishing objects in front and behind the clouds accurately.

Hover over the mask below to see the corresponding pre-rendered skybox texture:

Synesthesia: Pre-rendered 2D skybox

In Synesthesia, the sky is displayed by taking a skybox cubemap texture and applying it to the sky sphere surrounding the game world. This texture is pre-rendered and stored on disk, allowing the sky to be displayed at a very small performance cost.

I have now improved the skybox system to support a full day/night cycle, including the smooth movement of the sun and the moon. Because each skybox texture must be a static picture the sun and the moon are superimposed on top of the clouds, allowing them to move smoothly.

The skybox textures are pre-rendered inside the engine using a custom baking tool from a custom volumetric sky shader that uses the Unreal Engine volumetrics to render the clouds. Many skies are pre-rendered to allow the skybox to smoothly blend between different times of day and weathers.

For example, with the default 30 minute time interval, this means that 48 textures will be created for a single weather for the full span of a 24-hour day.

The sky system loads only the sky textures that are currently relevant and then blends between the two textures for the specified time of day, or blends between multiple textures in case the weather is changing.

Since the 2D skybox pre-rendering process cannot properly capture a mask for cloud opacity/transparency right now, to avoid the sun and the moon always appearing to be on top of the clouds their brightness is adjusted based on a pre-calculated curve.

In order to mask the sun when it goes behind the clouds, the bake tool captures two versions of the scene - one with light sources enabled, another one with light sources disabled. It will then compare the brightness of the pixel where sun is located.

If the two brightness values are nearly equal, the sun is blocked by the clouds. If the two brightness values are different, then the sun is not concealed.


This works good enough for both sun and moon. The version of the scene with light sources disabled is the one that gets saved to disk and later used with the 2D skybox.

Here is an example set of the pre-rendered skybox textures for a specific weather. The sun and moon are omitted and will be later superimposed on top of this texture when it is rendered in-game:


This method for skyboxes is highly performant and gives me a lot of artistic control. It does have some flaws, one of which I will demonstrate in the next post.

Some of the limitations of these pre-rendered skies:

  • The clouds within the same weather never move and are fully static. I could potentially fix this by allowing clouds to evolve during the capture process, however the clouds would have to somehow create a looping animation to avoid a discrepancy in cloud positions.
  • The star map blends in discrete steps, rather than smoothly rotating. I will update the star map with a generic texture so this effect wouldn’t be noticeable. However, see the next post for more about this.
  • The sun/moon do not interact with clouds in a satisfying way. They simply get darker or brighter, plus the curve doesn’t perfectly interpolate. But this isn’t very noticeable
  • Weather transitions are simplistic and there are no nice intermediate states. Also a limitation, the only way to transition between two weathers is to directly blend between them.

My intent is to solve most of the visual issues by providing a large pool of different skyboxes. Since they can be very easily generated and created, there can be enough interesting skies to look at so the other problems don’t matter as much.

Synesthesia: General description

Panoramic view of EDZ-01 / Solaris City

Short Blurb

In this open-world roleplaying videogame, you play as a manufactured lifeform (a synth) waking up in a world in crisis. It is a first/third person tactical shooter, your goal is to collect valuable technical artifacts and documentation to help humans revive their golden age and save their civilization.

However, the old cities are now dangerous, full of industrial accidents and decaying infrastructure, making them Environmental Disaster Zones filled with lost treasures from the golden age. You were lost in transit for 200 years, but now it’s up to you whether to help humans or follow your own interests.


Long Blurb

With advancements in science, humanity has finally reached post-scarcity, but over time got complacent with the miracles of technology of the new era. You play as a manufactured lifeform (a synth), waking up in a world in crisis, a world that can no longer maintain its complex infrastructure and systems.

The game is a first/third person tactical shooter with roleplaying elements, focusing on urban exploration and urban combat. The primary goal is to collect technical artifacts and documentation, highly valued by humans with hopes that some of this old world information could help them revive the golden age of the old and stop the fall of their civilization.

As the crisis grew, the old cities became dangerous, full of industrial accidents, decaying infrastructure. Toxic hazards, radiation leaks, electromagnetic dangers all turned some of the largest cities into Environmental Disaster Zones - exclusion zones, full lost treasures and miracles from the golden age.

The players character was lost in transit and forgotten about as the world around descended into disarray, stranding them in a dream-like state for around 200 years. Now, it is up to you on whether you want to help humans in their pursuit of technology that may save their world, or follow your own interests.

Spoiler tags for Hugo

I use Hugo to generate the static HTML pages for this website. One of the projects I’m working on involves a storyline which some of my friends do not want spoiled to them. So for this, I had to add a simple spoiler shortcode.

This spoiler shortcode works like this: . It can be used to individual phrases or whole blocks of text, as seen below.

Here is the source code to the spoiler tag functionality on my website:

// /theme/assets/css/spoiler.css

.spoiler_dummy {
    display: none;
}

.spoiler_dummy:checked + label {
  color: $color;
  background: $background;
  transition: color 0.3s ease, background 0.3s ease;
}

.spoiler {
  position: relative;
  display: inline-block;
  cursor: help;
  
  border-radius: 2px;
  
  color: black;
  background: black;
  transition: color 0.3s ease, background 0.3s ease;
}
<!-- /theme/layouts/shortcodes/spoiler.html -->

<input class="spoiler_dummy" type="checkbox" id="spoilerTag{{ .Ordinal }}"/>
<label class="spoiler" for="spoilerTag{{ .Ordinal }}">
  {{- .Inner | safeHTML -}}
</label>