Skip to content

Table-driven FS1 background nebula (FS1 mesh and palettes, generated patterns) - #7853

Open
dustinspace217 wants to merge 14 commits into
scp-fs2open:masterfrom
dustinspace217:graphics/fs1-nebula
Open

dustinspace217 wants to merge 14 commits into
scp-fs2open:masterfrom
dustinspace217:graphics/fs1-nebula

Conversation

@dustinspace217

@dustinspace217 dustinspace217 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

What?

Brings back the FS1-style background nebula as a table-driven feature. Each generated_nebula.tbl entry (or *-gneb.tbm) either names the FS1 data (+Mesh:, +Palette Prefix:, +Palette Suffix:) or describes a generated nebula with clouds and noise. The built-in table ships Nebula01 to 03 and the nine FS1 colors both ways. Of FS1's nebula palette files, retail FS2 ships only Red's and FSPort none, so the other colors fade to their RGB unless a mod ships them; that fade stays within 4 of 255 steps of FS1's ramps.

It draws only when a mod sets $FS1 nebula backgrounds: YES in game_settings.tbl or a mission ends its background section with $FS1 Nebula: YES; a mission's NO overrides the mod. qtFRED and FRED get combos from the table plus Default / On / Off for the mission line.

The table system and generated renderer are MjnMixael's, ported from his branch with his consent and authorship. The mesh path, switches and editor work are mine.

Why?

Nothing has drawn +Nebula: and +Color: since the palette manager went away in 2017, so FSPort's SM1-01 shows plain stars where FS1 had a red nebula. His version makes new nebulae a text edit; mine restores the original look. This keeps both.

How?

A mesh is drawn the way FS1's Direct3D and Glide renderers did: each corner takes the palette entry its brightness rounds down to, and colors blend across the triangle. With post-processing on, the tonemapper would brighten them by about a fifth, so the mesh gets colors the tonemapper turns back into the palette's, with each triangle split into 8x8 pieces so this holds inside it. Colors are capped below the bloom threshold, like the rest of the background. A generated entry bakes his cloud field into a texture colored through the palette when one is named, else by the RGB. Missing files fall back with a debug-log line.

  • The baked texture is released at level close with the environment map.

Screenshots?

In a comment below.

Testing?

Linux, headless harness, OpenGL and Vulkan. Against FS1 under Wine, the brightest nebula color on screen equals the palette's top entry and the shapes match; what remains is FS1's 16-bit output, framing, and its screen-space blending. Switches, fallbacks, a custom pattern, FSPort over its skybox, mission-set-nebula and set-post-effect work, and an older build reads the new line safely. qtFRED saves it in both formats. FRED's dialog was not built here; the Lab and environment maps are untested.

Anything Else?

This supersedes #7855. I used Claude Code for the port, review, FS1 comparison and test runs; I reviewed the code and this text.

Restores the FS1 background nebula that the engine has parsed and loaded
since retail but has not drawn since the palette manager was removed in
2017. The mesh is drawn on the far sphere before the stars, after the
skybox, and only when a mission has no skybox model and is not a full
nebula mission. The colour ramp is the first 20 entries of the FS1 game
palette for the mission's nebula and colour (gamepalette<N>-<CC>.pcx),
uploaded as a one-row texture so the GPU interpolates between ramp
entries. Missing palette or mesh files log a line and draw nothing.

The mesh loader now rejects triangle indices outside the point list and
closes the file on every failure path, since the renderer consumes the
indices. qtFRED's background editor and FRED's nebula colour control
reload the nebula when the fields change, so the editors preview it.
@dustinspace217

dustinspace217 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Screenshots, updated for the current branch (510d47c), headless, post-processing on at the default lighting profile.

Training Mission 1 with Nebula01 Grey blue: FS1 itself on the left (Direct3D under Wine, 16-bit), FSPort's copy of the mission on this branch on the right (OpenGL, with FS1's palette file):

FS1 and FSO side by side

SM1-01 with Nebula01 Red, OpenGL:

SM1-01 Nebula01 Red, OpenGL

The same on Vulkan:

SM1-01 Nebula01 Red, Vulkan

@MjnMixael

MjnMixael commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

This is certainly a fine way to do it. I was working on this a few months ago and my version restored the .neb and pallette files as tabled values in a table so that users can create whole new patterns and colors just with a bit of text editing, removing the need for .neb files entirely. I also exposed various generation values to those tables (samples and other things) so that users could use the system to recreate FS1's style or make something entirely new with the same code. Worth considering.

@dustinspace217

Copy link
Copy Markdown
Contributor Author

I like that idea. I'll investigate. Does your solution still exist?

@MjnMixael

MjnMixael commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

I like that idea. I'll investigate. Does your solution still exist?

https://github.com/MjnMixael/fs2open.github.com/tree/ofp/merged/restore-fs1-nebula

It is feature complete and usable in my own fork. It's not something I'll be PRing to SCP, so feel free to do it however you prefer. Just thought I'd share what I came up with for the same code.

@dustinspace217

Copy link
Copy Markdown
Contributor Author

Thanks, that's generous. I reviewed the branch. I'd most like to keep the ability to edit the patterns and colors that the table provides, and the ability to make new ones without touching code. What I'd like to keep from mine is the easy ability to use the actual FS1 data, so FSPort and other mods can get the exact original look and feel, if desired.

I think they fit together. Both end as a brightness field on a sphere colored by a ramp, so a pattern entry could take an optional +Mesh: line (the retail .neb) and a color entry an optional +Palette: line (the gamepalette pcx), with your clouds and RGB as the other source. The built-in table would ship Nebula01 to 03 as mesh entries (the meshes are already in sparky_fs2.vp; a mod wanting the FS1 colors would ship the palette files, since FS2 only carries the Red one), and your cloud layouts would be the fallback when the files aren't there. One renderer, with your additive blend over the skybox.

Two things I noticed while reading, in case they're useful in your fork: +Generated Nebula: sits inside the block after +Heading:, and when I checked that placement for my own field, builds that don't know the line never enter the $Bitmap List: loop and drop every sun and background bitmap, so I moved mine to the end of the section. And your qtFRED background editor already sits on your undo system, so that half wouldn't apply to SCP's dialog model as-is and would need redoing there.

Are you fine with me porting the table code into this work with you credited on the commits? I'd probably do it as a follow-up PR on top of this one rather than grow this one, but I'm happy either way.

@MjnMixael

Copy link
Copy Markdown
Contributor

Adding a Mesh line is smart. I dig it. My solution was to include the original data as built-in tables so they are always available. But adding the mesh line is a nice add on as well. Bonus is that it supports the off chance someone made a custom .neb file we don't know about.

Yeah you can grab the table code and QtFRED UI for this feature if you want. If they are close enough and SCP merges yours I'll move mine on my fork in your direction the next time I sync SCP commits.

@dustinspace217
dustinspace217 marked this pull request as draft October 13, 2026 17:37
MjnMixael and others added 11 commits October 13, 2026 10:48
Ported from MjnMixael's ofp/merged/restore-fs1-nebula (bb43618..47d8a1c,
engine and FRED parts, squashed; the qtFRED part is redone against the SCP
dialog model in a later commit). Patterns and colors come from
generated_nebula.tbl and *-gneb.tbm; the mission's +Nebula: and +Color:
names resolve through those registries, and unknown names round-trip.
A pattern entry can name an FS1 .neb mesh (+Mesh:) and, with a color's
+Palette Suffix:, the FS1 palette file; when those files exist the mesh is
drawn through the palette's 20-entry ramp, exactly as FS1 did, and otherwise
the entry's generated clouds and RGB fade are used. The built-in Nebula01-03
and the nine colors carry both forms. One draw path, additive, so stars and a
skybox show through where the nebula is dark.

Table system and generated renderer by MjnMixael
(ofp/merged/restore-fs1-nebula).
…: line

The mod-level $FS1 nebula backgrounds: switch decides by default, so missions
that merely carry stale +Nebula: lines stay unchanged and FSPort can turn the
feature on in one line. A mission can override either way with
$FS1 Nebula: YES or NO at the end of its background section, where builds
that do not know the line skip it with the rest of the section. The engine
and the Lab parse it; the saver writes it after $Environment Map: only when
set. The draw decision moves from nebula_init to nebula_render, so an
editor toggle takes effect without reloading the mesh.
The background editor's pattern and color combos list the table's entries
(plus a name the table does not know, kept as read), and a Default/On/Off
combo sets the mission's $FS1 Nebula: line. qtFRED keeps the SCP dialog
model with the nebula reloaded on each change; FRED gets the same combo.
- Saver: drop a stale versioned $FS1 Nebula: line when the switch is
  Default, and rewrite the line in place only while the raw text still
  holds it. A compatibility save after Default prefixed the next section
  header; switching back on in the same session then truncated the file.
- Mesh loader: check every read and reject points off the map, so a
  damaged .neb falls back to the generated pattern instead of drawing
  zeroed geometry.
- Key the baked texture on the mesh or generated path too, and close the
  nebula when the registries are refilled.
- Honor the Planets/Backgrounds detail option and the nebula detail flag
  again, as the mesh-only renderer did.
- Lab: reset the nebula and the mission switch when a background loads.
- qtFRED: keep an unknown pattern or color name when it is picked again
  after a known one; drop the 99-color assert FRED already dropped.
- FRED: give the mission switch its own row inside the group, apply it
  on selection, and keep the controls enabled while an unknown pattern
  name is held.

Table system and generated renderer by MjnMixael
(ofp/merged/restore-fs1-nebula).
FS1's Direct3D and Glide renderers give each mesh corner the palette
ramp entry its brightness rounds down to, (b * 19) / 255, and blend those
colors across the triangle through a plain white texture. The mesh path
here sampled the 20-entry ramp per pixel instead, which kept the
fraction (up to one ramp step brighter) and walked through the
in-between entries inside each triangle. The mesh path now sets the
vertex colors from the ramp and draws untextured; the generated path is
unchanged.

FS1 blended in screen space; this blends with perspective. A screen-space
variant drew hard edges where the large sky triangles cross behind the
camera, which FS1 avoided by clipping in software first, so it is left
out.
With post-processing on, the scene is linearized, scaled by the lighting
profile's exposure, tonemapped and returned to sRGB, which made the FS1
mesh about 20% brighter than its palette colors (measured against FS1
itself on a reference mission). The mesh path now draws colors that
post-processing turns back into the palette's: the default Uncharted
curve inverts as a quadratic and Linear is only the exposure. Bloom,
light shafts and post effects still apply on top. Other tonemappers, and
a zero or invalid exposure, keep the palette colors as they are, logged
once per curve.

The shader linearizes after interpolating, so exact corners alone are
not enough: on a bright palette that left up to 15 steps inside a
triangle. Each mesh triangle is drawn as 8x8 flat pieces whose corners
carry the blend of the triangle's corner colors at that point, so the
picture without post-processing is unchanged and every piece's corners
are compensated. The colors come from a 256-entry table and are redone
only when post-processing, the curve or the exposure changes.

A color the curve cannot reach is clipped rather than pushed above 1,
since the bloom pass picks up anything above 1 and no other background
goes there. At the default exposure (Uncharted, 4) the ceiling is 218 of
255 and the brightest ramp entry of the 27 FS1 nebula palettes is 183,
so nothing clips; at exposure 1 the brightest parts of Blue clip.

Measured per pixel against the same frame without post-processing, on
Nebula01 Blue and Grey blue: worst 2 steps at the default exposure
(OpenGL and Vulkan) and with Linear; it was 16 before the pieces. The
FS1 meshes have 113 to 123 triangles, so this draws about 24,000
vertices; on the software renderer the tests use, the median frame on
the retail mission went from 16.1 to 16.7 ms. A GPU was not measured.
The per-corner mesh build named its corner pointers "color", which
shadows the global type of that name; Debug builds compile with
-Werror=shadow and failed on it.
A generated pattern that names a palette prefix, with a color that names
a suffix, now maps its baked brightness onto that FS1 palette's ramp,
the same 20 entries the mesh path uses, instead of tinting by the RGB.
This covers custom patterns written against the FS1 palettes and the
built-in Nebula01 to 03 when their mesh file is missing or damaged. The
bake blends between ramp entries rather than rounding down as the mesh
path does, since rounding a smooth field to 20 levels would band. A
missing palette file still falls back to the RGB tint with the same
debug-log line, now read through one shared function. Through a palette,
brightness above 1 (+Intensity over 1) stops at the last entry, as on
the mesh path; the table documents this. A brightness that is not a
number (an "nan" or overflowing table value) maps to the first entry.
dustinspace217 added a commit to dustinspace217/fs2open.github.com that referenced this pull request Oct 13, 2026
@dustinspace217 dustinspace217 changed the title Draw FS1 background nebulas behind a game_settings.tbl switch Table-driven FS1 background nebula (FS1 mesh and palettes, generated patterns) Oct 13, 2026
@dustinspace217
dustinspace217 marked this pull request as ready for review October 13, 2026 23:49
The baked texture and its bitmap were only released by the next mission load;
stars_level_close now releases them with the environment map. The unused
Neb2_filenames table and NUM_NEBULAS are gone, and the noise comment names
the ANL noise the engine already has and why this bake does not use it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants