The inner solar system at 1 day / second, rendered from Fortran through OpenGL 4.1: procedural Sun with bloom, 8 000-star celestial sphere, and a 15 000-object instanced asteroid belt.
A real-time solar system simulation written in modern Fortran 2018 with a rendering stack built directly on top of OpenGL 4.1 Core and GLFW. The physics core is an N-body gravitational integrator seeded with full J2000.0 Keplerian elements (a, e, i, Ω, ϖ, L) from Meeus Table 31.A and verified for energy and angular-momentum conservation to ~10⁻⁹ % over an Earth year. The rendering side is a full deferred-to-HDR pipeline with procedural Sun, physically-plausible bloom, ACES tonemapping, textured planets, Saturn's rings, a procedural starfield and a 15 000-object instanced asteroid belt.
Everything the program needs — window management, OpenGL function loading,
image I/O, shader compilation, framebuffer setup — is wired up in Fortran
through iso_c_binding. There are no rendering wrapper libraries sitting
between Fortran and the driver.
- Gallery
- Requirements
- Install on Ubuntu / Debian / WSL2
- Quick start
- Helper scripts
- Controls
- Spacecraft
- Cinematic tools
- Configuration (
config.toml) - Project layout
- Why Fortran 2018
- How Fortran talks to OpenGL
- Deep dive:
TECHNICAL.md - Phase history
All four shots come from the same binary via the CLI flags below
(--screenshot, --screenshot-earth, --screenshot-earth-night,
--screenshot-saturn). No post-processing was applied outside the
simulator's own HDR pipeline.
To build from source you need:
gfortranwith Fortran 2018 supportcmake3.18+makebuild-essentialpkg-configlibglfw3-dev
The repo includes an Ubuntu/Debian package manifest at
requirements/ubuntu-apt.txt, and ./install.sh uses that list directly.
git clone https://github.com/NoCoderRandom/SolarsystemFortran.git
cd SolarsystemFortran
./install.sh
./run.sh./install.sh will:
- install the required apt packages on Debian-family systems
- configure a CMake build in
build/ - compile the simulator
- run tests only if a local
tests/directory exists
This project works on WSL2 with Ubuntu. You need a working Linux GUI/OpenGL path:
- On Windows 11, WSLg is usually enough out of the box.
- On older setups, use an X server / OpenGL-capable desktop path before launching the simulator window.
If you only want to install dependencies first:
./install.sh --deps-onlyIf your system packages are already installed:
./install.sh --no-aptIf you are on a non-Debian Linux distro, install the equivalent packages for your package manager, then run:
./install.sh --no-apt# One-shot setup for Ubuntu / Debian / WSL2.
./install.sh
# Launch the simulator from the project root.
# run.sh rebuilds first if sources, shaders, or spacecraft assets changed.
./run.shIf you already have the toolchain, ./install.sh --no-apt skips the package
step.
Alternatively:
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
cmake --build . -j
./solarsimThe repo ships with root-level helpers so people can install, build, and run the simulator without learning the internal build layout first:
./install.shinstalls Debian / Ubuntu / WSL2 dependencies, configures CMake, builds the project, and runs tests when a localtests/directory exists./build.shperforms an explicit CMake build from the repo root; use./build.sh releasefor an optimized build or./build.sh cleanto wipebuild/first./run.shlaunches the simulator from the correct working directory and rebuilds first when the binary is missing or older thansrc/,shaders/, orassets/spacecraft/
The CLI takes optional flags that park the camera on a target, render for 180 frames, save a PNG, and exit — handy for regression shots and CI:
./run.sh --screenshot # full overview
./run.sh --screenshot-earth # Earth close-up (day/night terminator)
./run.sh --screenshot-earth-night # Earth night side (city lights)
./run.sh --screenshot-saturn # Saturn with rings| Key | Action |
|---|---|
0–8 |
Focus camera on Sun / Mercury / … / Neptune |
| LMB drag | Orbit camera |
| RMB drag | Pan |
| Scroll wheel | Zoom (logarithmic) |
R |
Reset camera |
SPACE |
Pause / resume |
+ / - |
Time scale ×2 / ÷2 (1 s/s → 10 yr/s) |
T |
Toggle orbit trails (Shift+T clears) |
H |
Toggle HUD overlay |
B |
Toggle bloom |
[ / ] |
Exposure down / up |
F2 |
Timestamped screenshot → screenshots/solarsim_YYYYMMDD_HHMMSS.png |
F12 |
Overwrite screenshot at the configured preset path |
ESC |
Quit |
A top-bar menu (File / View / Camera / Help) sits above the viewport
and mirrors the hotkeys with clickable checkboxes and buttons. Under
View you'll find toggles for trails, HUD, bloom, and Log-Scale
Distances — a visual-only radial compression around the Sun
(r' = K · log₁₀(1 + r)) that gives the inner planets breathing room so
the whole system fits a textbook-style layout without touching the
physics. Planets, rings, asteroids and trails all share the same remap so
orbits stay coherent; toggle it at runtime to flip between real-scale and
textbook-scale views.
On shutdown the program prints a per-slot performance report (physics, scene render, starfield, planets, asteroids, trails, bloom+tonemap) with average and peak frame times.
When spacecraft are enabled, the Spacecraft top-bar menu exposes:
- enable / disable spacecraft
- select active ship
- spawn at
Earth,Sun, orCurrent Focus - reset / despawn the active ship
- switch between
SystemandFollowcamera modes
Keyboard controls for the active spacecraft:
| Key | Action |
|---|---|
W / S |
Thrust forward / backward |
A / D |
Yaw left / right |
Up / Down |
Pitch up / down |
Q / E |
Roll left / right |
C |
Toggle inspect orbit camera while follow camera is active |
F |
Toggle spacecraft auto-stabilize |
N / M |
Previous / next spacecraft |
The HUD shows the selected spacecraft name, ship speed, camera mode, and auto-stabilize state when spacecraft are enabled and a ship is active.
The spacecraft system is modular and kept separate from the planetary renderer and physics. When enabled, it adds:
- separate asset loading and rendering from planets and spheres
- menu-driven ship selection and spawn presets
- third-person follow camera
- inertial spacecraft controls that do not touch planetary physics
- per-ship follow and visual tuning from the catalog
Current drivable catalog:
Voyager 1USS VoyagerUSS Enterprise NCC-1701
Additional imported assets are already in the repo and can be promoted later through the catalog:
Klingon Bird-of-PreyNegh'VarIntrepid-typeimport
Menu behavior today:
NASAcatalog entries appear automatically under theRealsubmenuStar Trekcatalog entries appear automatically under theTreksubmenu- new franchise groups can be added by extending the menu builder in
src/main.f90
Spacecraft documentation:
- Add new spacecraft tutorial
- Importing new models
- Drive and capture guide
- Shot authoring guide
- Model integration and orientation paper
- Imported asset notes
The repo now includes a separate movie-production workspace under
movies/. It is designed to stay modular so people can
render new social clips, educational reels, and future spacecraft showcases
without disturbing the core simulator flow.
Shipped outputs include:
- Trek-style master clips and a 1-minute reel
- real-space Voyager clips and a 1-minute reel
- a captioned educational Voyager story film
Useful entry points:
# Render one cinematic shot
bash movies/render_one.sh earth_convoy movies/output/singles
# Render a full batch and assemble a reel
bash movies/render_movies.sh movies/output/trek_batch
# Recut a final reel from existing clips
bash movies/compile_best_of.sh movies/output/20260422_trek movies/trek_reel_plan.tsv best_of_1min.mp4Main movie docs:
First launch writes build/config.toml with the defaults the build was
shipped with. Edit and restart to tune:
[window]
width = 1600
height = 900
vsync = true
[simulation]
time_scale = 86400.0 # simulated seconds per real second (1 day/s)
trail_length = 4096
trails_visible = true
hud_visible = true
focus_index = 0 # 0=Sun, 1=Mercury … 8=Neptune
[camera]
azimuth = 0.000
elevation = 0.800
log_dist = 1.778
[bloom]
on = true
threshold = 1.000
intensity = 0.850
mips = 5
[tonemap]
exposure = 1.000
sun_emissive_mul = 3.500
[starfield]
count = 8000
intensity = 1.000
[asteroids]
count = 15000
a_min = 2.200 # AU
a_max = 3.300 # AU
[textures]
earth_night = true
earth_normal = true
earth_specular = true
saturn_rings = true
[spacecraft]
enabled = false
camera_mode = 0 # 0=System, 1=Follow
auto_stabilize = true
default_id = "voyager1"
spawn_preset = "earth" # earth | sun | focusUnknown keys are warned and ignored. The parser is a minimal, handwritten TOML subset with simple named tables and scalar key/value pairs.
SolarsystemFortran/
├── install.sh # Linux / WSL2 dependency install + build helper
├── build.sh # explicit root-level CMake build wrapper
├── requirements/
│ └── ubuntu-apt.txt # apt packages consumed by install.sh
├── run.sh # launch wrapper with stale-build detection
├── CMakeLists.txt # solarsim target + optional ctest + asset copy
├── README.md # you are here
├── TECHNICAL.md # methods, algorithms, Fortran↔GL interop
├── src/
│ ├── core/
│ │ ├── logging.f90 # coloured, timestamped logging
│ │ ├── constants.f90 # physical constants (G, AU, …)
│ │ ├── vector3d.f90 # real64 3-vector with operator overloads
│ │ ├── date_utils.f90 # J2000 → Gregorian conversion
│ │ ├── input.f90 # keyboard / mouse / scroll state + callbacks
│ │ ├── config.f90 # sim_config_t, tunables, clamps
│ │ ├── config_toml.f90 # minimal TOML reader/writer
│ │ └── perf.f90 # CPU timing slots (tic/toc/report)
│ ├── ui/
│ │ └── menu.f90 # top-bar menu + dropdowns + checkbox glyphs
│ ├── physics/
│ │ ├── body.f90 # body_t — name, mass, radius, colour, state
│ │ ├── ephemerides.f90 # J2000 Keplerian elements → Cartesian state
│ │ ├── integrator.f90 # Velocity Verlet, Plummer softening
│ │ └── simulation.f90 # owns bodies, drives integrator
│ ├── render/
│ │ ├── gl_bindings.f90 # iso_c_binding layer for GLFW/GL/GLAD + stb_image
│ │ ├── window.f90 # context init, vsync, callbacks
│ │ ├── mat4.f90 # column-vector mat4 math (proj, view, model)
│ │ ├── mesh.f90 # UV sphere generator + instanced draw setup
│ │ ├── shader.f90 # compile/link/uniform helpers
│ │ ├── framebuffer.f90 # RGBA16F FBO + mipchain attachments
│ │ ├── texture.f90 # stb_image → GL texture with sRGB / linear modes
│ │ ├── material.f90 # per-body material (albedo, normal, night, spec)
│ │ ├── camera.f90 # orbit camera, logarithmic zoom, smooth focus
│ │ ├── display_scale.f90# render-time log radial remap (visual only)
│ │ ├── sun.f90 # procedural Sun surface + corona
│ │ ├── rings.f90 # Saturn's rings (textured disk)
│ │ ├── trails.f90 # GPU-buffered fading orbit ribbons
│ │ ├── starfield.f90 # 8k-star celestial sphere
│ │ ├── asteroids.f90 # instanced asteroid belt
│ │ ├── hud_text.f90 # bitmap font HUD
│ │ ├── post.f90 # bright pass + dual-filter blur + ACES
│ │ └── renderer.f90 # per-frame orchestration
│ └── main.f90 # lifecycle + main loop
├── shaders/ # GLSL 410 core (.vert / .frag pairs)
├── assets/
│ ├── spacecraft/ # source/imported ship assets + runtime meshes
│ └── planets/ # 2k surface maps (see NOTICE in assets/)
├── external/
│ ├── glad/ # OpenGL function loader (vendored)
│ └── stb/ # stb_image.h for PNG/JPG decode (vendored)
├── movies/ # modular cinematic side-project and docs
└── screenshots/ # reference renders per phase
Fortran has an unfair reputation as a "legacy" language. The 2018 standard is modern, strict, and extremely well suited to a project like this:
Numeric rigour. Every real(real64) is 64-bit IEEE 754 with no
surprises from C's implicit promotions. Physics conservation tests pass to
~10⁻⁹ % energy drift over a simulated year at 1-hour Verlet timestep —
the integrator is doing exactly what the math says it should, because the
language carries the math literally.
Strong module system. Each file is a module with explicit private
defaults and curated public lists. use :: foo, only: bar stops
accidental symbol leakage at the compiler level, not at code-review level.
No headers, no textual inclusion, no preprocessor macros — just a graph of
modules the compiler resolves into .mod files.
Derived types with bound procedures. Fortran 2003/2008 OOP is light but
sufficient. simulation_t, camera_t, renderer_t, post_t etc. are
plain structs with type-bound subroutines; no vtables are forced on you
unless you opt into polymorphism. Value semantics + explicit intent(in / out / inout) catches aliasing bugs at compile time.
Array-first syntax. Whole-array operations (a = b + c * 2.0,
sum(arr), pack, spread) generate tight, vectorisable code — the
compiler knows shapes and strides up front because the language does.
C interop via iso_c_binding. The standard-mandated module gives you
c_int, c_float, c_ptr, c_funptr, c_loc, c_funloc, and the
bind(c, name="…") attribute. That's literally everything needed to call
OpenGL, GLFW, GLAD, and stb_image directly. No SWIG, no wrapper generator,
no FFI DSL. You write the C prototype as a Fortran interface and link.
Performance parity with C. The physics loop, matrix math, and starfield
/ asteroid mesh generation all compile to the same SSE/AVX-capable machine
code a C translation would, because both go through the same gcc backend.
For tight inner loops the compiler often does better with Fortran's aliasing
guarantees than it can with C's restrict annotations.
Plays well with GPUs. Once data is laid out as a plain real(c_float), target :: verts(:), c_loc(verts(1)) hands a pointer straight to
glBufferData — there's no marshalling, no copy, no managed heap. The VBO
upload is the same memcpy it would be from C. The CPU side stays in
Fortran; the GPU side stays in GLSL; glGetUniformLocation + a handful of
glUniform* bindings is all the glue you need.
In practical terms: the code reads as plainly as the equations it implements, the compiler catches more mistakes than a C or C++ compiler would, and the runtime is as fast as anything you could write in C.
There is no GL binding library on any package manager for Fortran — so
the simulator builds one. The gl_bindings module is a ~650-line layer
that declares each GLFW / GL / GLAD / stb_image function you use as a
Fortran interface with a bind(c, name="…") attribute, e.g.:
interface
function glCreateShader(shader_type) result(id) &
bind(c, name="glCreateShader")
import :: c_int
integer(c_int), value, intent(in) :: shader_type
integer(c_int) :: id
end function glCreateShader
end interfaceAfter that declaration the rest of the program just calls glCreateShader
as if it were a Fortran function. The same pattern covers buffer objects,
VAOs, textures, framebuffers, callbacks (c_funloc wraps a Fortran
bind(c) procedure into a c_funptr GLFW accepts), and GLAD's function
loader.
See TECHNICAL.md for a full tour: binding patterns, pointer handling, the matrix convention, the HDR pipeline, the Verlet integrator, the starfield / asteroid mesh generators, and the ACES tonemap.
The companion file TECHNICAL.md covers:
- Fortran ↔ C interop patterns (scalars, arrays, pointers, callbacks)
- OpenGL 4.1 pipeline bring-up in Fortran
- The Velocity Verlet N-body integrator and Plummer softening
- J2000 initial conditions and heliocentric → barycentric correction
- The column-vector
mat4convention (and a past bug worth documenting) - HDR pipeline: RGBA16F scene target, bright pass, dual-filter blur, ACES (Narkowicz 2015) tonemap
- Blinn–Phong + normal map + night lights + specular mask for Earth
- Starfield and asteroid belt generation (seeded RNG, Keplerian orbits)
- Trail ring buffers (GPU-side fade via
gl_MultiDrawArrays) - Timestep decoupling (accumulator + interpolation for smooth rendering)
It's written for readers who want to learn either Fortran→GL interop or real-time graphics fundamentals.
| Phase | Scope |
|---|---|
| 1 | Foundation: window, logging, CMake |
| 2 | Physics: bodies, J2000, Velocity Verlet engine |
| 3 | Renderer: instanced spheres, mat4 pipeline |
| 4 | Camera: orbit camera, HUD, input |
| 5 | Trails: GPU-buffered fading orbit trails |
| 6 | HDR pipeline, bloom, ACES tonemap, procedural Sun |
| 7 | Textures: planet surfaces, normals, Earth night/specular, Saturn rings |
| 8 | Polish: starfield, asteroid belt, config.toml, perf timers |
Each phase is a self-contained commit with a verifiable demo.
| Component | Choice |
|---|---|
| Language | Fortran 2018 (-std=f2018 -Wall -Wextra -Werror in Debug) |
| Graphics API | OpenGL 4.1 Core Profile |
| Windowing | GLFW 3.3+ |
| GL Loader | GLAD (vendored, minimal) |
| Image decode | stb_image (vendored, single-header) |
| Build system | CMake 3.18+ |
| Compiler | gfortran ≥ 9 |
| Target platforms | Linux, WSL2, native NVIDIA / Mesa drivers |
This repository is authored by NoCoderRandom.
Planet surface textures bundled under assets/planets/ are 2K maps from
Solar System Scope (solarsystemscope.com/textures),
released under the Creative Commons Attribution 4.0 license. All first-party
code is released under the MIT License — see LICENSE.







