Skip to content

Render fogs with one draw call per fog instead of re-drawing every surface inside#1888

Open
slipher wants to merge 11 commits intoDaemonEngine:masterfrom
slipher:fogdepth
Open

Render fogs with one draw call per fog instead of re-drawing every surface inside#1888
slipher wants to merge 11 commits intoDaemonEngine:masterfrom
slipher:fogdepth

Conversation

@slipher
Copy link
Member

@slipher slipher commented Feb 3, 2026

Adapt the global fog shader to use a vertex position so it can draw finite-sized fogs. Right now it is only implemented for fogs that the viewer is inside of, but this is enough to fix the artifacts with MSAA as in #1480 (comment). Later I will update the PR with commits to draw fogs the viewer is outside of this way as well.

@slipher slipher force-pushed the fogdepth branch 3 times, most recently from 306c81e to cc477b4 Compare February 7, 2026 20:51
Add a shader keyword that can be used to configure a gradient for fog
density, which tapers to 0 at the edge of the fog. The density of the
fog is scaled by a factor of (1 - exp(-k * t)) where t is the distance
under the fog plane and k a configurable constant.

fogGradient expFalloff <dist>  configures the exponential falloff such
that the density is at 50% of the maximum at <dist> qu from the fog
plane.

fogGradient const  does the same thing as the current default - no
gradient; hard-edged fog.
Make the default gradient like `fogGradient expFalloff 5`. This makes
the fogs have softer edges without changing the appearance too much.
This cheat cvar controlled whether the Quake 3 fog shader is used (while
leaving the ET global fog unaffected). Instead you can use r_noFog which
toggles all fogs.
A shader with a name 58-63 chars long would fail to generate a depth
pass shader due to a length check. Add some extra space in the name
buffer so this can't happen.
When the viewer is inside a fog volume, render the fog with a single
draw call instead of drawing a corresponding fog surface for every
opaque surface. The fogGlobal GLSL shader is modified to use a vertex
position so it can handle finite-sized fogs.

Before:
- Each opaque surface inside a fog volume is drawn a second time using
  the fog shader with GL_EQUAL depth test.
- The underside of the fog plane is drawn with a normal depth test, to
  cover things outside the fog when the viewer is inside.

After:
- If the viewer is inside the fog, draw all inside faces of the fog
  brush, using the depth buffer to reduce the fog distance in the case
  that something is in front of the fog boundary.
- If the viewer is outside the fog, do everything as before.

For the moment this is only used when material system is disabled.

Fixes seams at the edge of fog that appear when MSAA is enabled.
Add a new macro variant of the 'fogGlobal' shader which can be used for
fogs that the viewer is outside of. Now when material system is not
active, this shader is used for all fogs. The "outside" version of the
shader is more complex than the inside one, since it is necessary to
trace against all planes of the fog to find the length in fog.

This change removes the requirement that a fog be enclosed by opaque
walls on all sides except one (the "surface plane"). Now fogs can be
rendered (almost) correctly from any side. The exception is that if eye
position is very close but not inside the fog, within about 5 qu, the
inside fog shader has to be used instead of the outside one to avoid
near plane culling. This causes an artifact when passing into the fog
through a non-surface side which can be noticed if you look closely.
Use the depth-based fog shaders (currently called "fogGlobal") when the
material system is enabled as well. For now they are always run with the
'core' rendering loop.

NUKE the fogQuake3 shader and all the code for propagating fog nums with
surfaces.

Fixes DaemonEngine#1798 (marks drawn over fog with material system) by virtue of not
drawing fogs with the material system.

Also brings improvements described for the core renderer in previous
commits:
- MSAA works with fog without artifacts
- Fog volumes can be open on any side
Also move the Render_fog function with its friends.
Use CPU-side frustum culling to avoid drawing fogs when possible.

Also don't recalculate the near plane distance (for checking if fogs are
too close to the camera) every iteration when looping fogs.
@slipher slipher changed the title WIP: Render fogs with one draw call per fog instead of re-drawing every surface inside Render fogs with one draw call per fog instead of re-drawing every surface inside Feb 8, 2026
@slipher
Copy link
Member Author

slipher commented Feb 8, 2026

This is now ready. But stacked on #1889.

Functional changes to what is rendered:

  • Fixes seams at the fog surface plane when using MSAA and located inside the fog.
  • Fixes some other minor artifacts on models around the fog plane.
  • Fixes Material system: marks are drawn after fog #1798
  • Fog volumes can now be open on any side. The old rule is that fogs must be fully enclosed by opaque surfaces on all sides except one (usually +z), so that the fog can be treated as a half-space in calculations. If you viewed the fog from "under" the fog plane but outside the fog, distance in fog was not calculated correctly. Now it is fine to view fogs from any side, although there is still a barely-perceptible artifact when passing through a non-surface side of the fog.

There are two variants of the fog shader, one for fogs that the viewer is inside of and one for fogs the viewer is outside of. When the viewer is inside, we render the inside faces of the fog brush with depth testing disabled. To find the distance in fog, we just find the minimum of the distance to the fragment and the distance to the point indicated by the depth buffer. When the viewer is outside, the outside faces of the fog brushes are rendered. To find the distance we trace against the other 5 faces of the fog brush as well as checking the depth buffer.

For now all fogs are rendered outside of the material system. Integrating them would be a bit tricky since you have two variants of the shader requiring different GL state, which must be selectively enabled based on the view position.

As for performance, the new fog is sadly slower on an Intel UHD 630 I tested. Turns out this is ~entirely due to reading from the depth buffer. I checked this by editing the GLSL. But also it can be seen that on master, with the ET railgun map which uses the global fog shader (which is very simple but reads from the depth buffer), disabling fog increases FPS by 20%. Anyway I also tried to test with an Nvidia card, but fog didn't make enough difference to FPS to be clearly observed in any of those tests.

@slipher slipher marked this pull request as ready for review February 8, 2026 10:40
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.

1 participant