logo
Kurashizu Blogwhere ideas flow
  • Home
  • News
  • Blog
  • About
  • Home
  • News
  • Blog
  • About

© 2026 Kurashizu. All rights reserved.

Admin·New Post·Service Status
Powered by Cloudflare·30.10.26
← Back to news

Rendering pretty sky in Around The World game

Original article
frozenfractal.com|85 points|6 comments|by ibobev|Oct 16, 2026

Rendering a Pretty Sky in Around The World

Around The World, Part 34: A pretty sky
Published Mon, 12 Oct 2026 by Thomas ten Cate
Game Development | Around the World

This entry is part of an ongoing series documenting the creation of Around The World, a game centered on maritime exploration and discovery.

Despite previous progress, playtesting revealed that the visual synthesis of the sea, sky, and clouds wasn't quite hitting the mark. The overall composition felt "off."


☁️ Light Scattering in Clouds

In the previous installment, I utilized a lookup table (LUT) generated via povray. This table mapped the amount of light scattered out of clouds based on two variables:

  1. Horizontal Axis: Light angle.
  2. Vertical Axis: Cloud density.

However, I had lingering doubts about the accuracy of this data:

"I am beginning to suspect that povray doesn’t actually simulate multiple scattering at all; the documentation does not say yes or no either way."

Regardless of how I adjusted the intervals or samples, the base of the spheres remained too dark. While I initially resisted the urge to build a custom tool, curiosity eventually won. I developed a multithreaded, recursive Monte Carlo ray tracer using Rust in just a few hundred lines of code.

While this tool was "dumb"—hard-coded specifically to render a single, uniform-density spherical cloud—it served its purpose: it confirmed that povray does not simulate multiple scattering.

Comparison of Scattering Methods

Featurepovray LUTCustom Rust Tracer LUT
Scattering TypeSingle ScatteringMultiple Scattering
Light DistributionConcentrated/Dark bottomsUniform/Brighter bottoms
Visual Result"Metallic" appearanceSoft, natural clouds
Low DensitySimilar resultsSimilar results

As shown in the comparison, at low densities (thin clouds), the difference is negligible because there are fewer opportunities for a light ray to scatter more than once. However, at higher densities, the difference is stark.

Comparison of old vs new LUT clouds

By implementing the new LUT, the "metallic" sheen vanished, the cloud bottoms brightened, and the overall lighting became significantly smoother.


🪄 One Weird Trick: Indirect Lighting

In the real world, clouds aren't solely illuminated by direct sunlight; they also scatter indirect sunlight from the surrounding sky. Because this ambient light is typically blue, clouds should exhibit a blueish tint.

I avoided adding a static blue value because the clouds must dynamically react to the sky's color during sunrise, sunset, or nighttime. The solution lay in how Godot handles light rendering.

The Logic of Indirect Light

Godot samples indirect light from cube maps containing radiance and irradiance. For diffuse materials, the engine typically samples these maps in the direction of the surface normal.

  • The Problem: At the bottom of a cloud, the normal points down toward the ground, resulting in very little indirect light.
  • The Reality: Clouds receive indirect light from above, which then scatters through the volume.

The Hack: What if we force the normal to point upward toward the sky?

The results were subtle but transformative:

  • Daytime: Clouds gained a blueish hue, blending seamlessly with the atmosphere and appearing more distant.
  • Sunset: Instead of just having orange bottoms (from direct Rayleigh outscattering), the clouds turned pink.

This pink hue is the result of mixing red (direct sunlight)+blue (indirect sky light)\text{red (direct sunlight)} + \text{blue (indirect sky light)}red (direct sunlight)+blue (indirect sky light), with the green channel being suppressed.


🌌 Atmospheric Scattering

I noticed an unnatural yellowing of the sky near the horizon, even when the sun was at its zenith.

Yellow Horizon Issue

Initially, I thought the outscattering was overpowering the inscattering and tried adding a fake multiple inscattering term. Ultimately, the fix was simpler: changing the order of operations during the raymarch.

Raymarch Order Comparison

Old Method (Incorrect):

  1. Add (blueish) light scattered into the ray segment.
  2. Attenuate by outscattering over the ray segment (reddening).

New Method (Correct):

  1. Attenuate by outscattering over the ray segment (reddening).
  2. Add (blueish) light scattered into the ray segment.
// Conceptual Raymarch Logic
for (float t = 0; t < max_dist; t += step) {
    // New Order:
    color = attenuate(color, outscattering); 
    color += calculate_inscattering();
}

Theoretically, this difference should vanish if the number of integration steps is increased (as it's a numerical integration artifact), but in practice, it didn't. I suspect there is still a deeper underlying issue, but the visual result is much improved.


🌍 Horizon Bending

To achieve realism, I used physical parameters from Earth, including:

  • Atmospheric density
  • Atmospheric height
  • Planet size

However, there is a scaling discrepancy: the planet in my game is approximately 75×75\times75× smaller than Earth.

Implementation Checklist

  • Implement Multiple Scattering LUT via Rust
  • Flip surface normals for indirect sky light
  • Reorder raymarch attenuation/addition
  • Resolve numerical integration discrepancy
  • Adjust horizon bending for 1/751/751/75 scale