Render: FOG restored -- authentic per-map/time/weather fog now renders (task #63)
User: "aren't some levels supposed to have fog?" -- they were, and none showed. BTDPL.INI (the DPL env INI, L4DPLCFG) authors fog=near far r g b (+ often nosearchlightfog=) on EVERY map/time/WEATHER leaf; weather is a fixed egg field (clear/fog/soup). Shipped eggs pin cavern/night/clear -> page dsnitclear (near 90, far 1100, dark blue). The env pipeline resolved + pushed it to D3D, but it rendered INVISIBLE: D3DRS_FOGTABLEMODE=D3DFOG_LINEAR (table fog) derives its factor from the perspective-NONLINEAR z-buffer, so without WFOG the 90..1100 range collapsed to fog-factor ~1 (no fog everywhere). Fix (L4VIDEO.cpp world pass): auto-detect D3DPRASTERCAPS_WFOG -> per-pixel W-fog (smooth, == the arcade dpl_fog_type_pixel_lin); else VERTEX fog (eye-space but per-vertex -> splotchy on coarse terrain tris, so only a fallback). FOGENABLE + the fog mode are re-asserted each world-pass frame (the old one-time set got clobbered per-pass, which is why plain table fog showed nothing). Verified on this GPU: WFOG present -> per-pixel table fog, user-confirmed smooth. Also corrected task #20's false "shipped maps define no fog" comment (it checked .MAP/.RES, not BTDPL.INI). Env hooks: BT_FOGMODE=table|vertex|off, BT_WEATHER= clear|fog|soup, BT_FOG="near far r g b" (now honored on the authored branch too); [fog] resolved/model diagnostics. KB: rendering.md fog section added. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a9c0d2c854
commit
083b148bb0
@@ -52,6 +52,21 @@ DECLOUDS must stay in the sky pass). Authored TEXTURE **SCROLL** (BMF TEXTURE ta
|
|||||||
`L4DPLCFG`): resolves map+time → clip/fog/ambient + a directional sun. Bring-up stubs
|
`L4DPLCFG`): resolves map+time → clip/fog/ambient + a directional sun. Bring-up stubs
|
||||||
(EnsureValidProjection / per-frame ambient / uninit cloud colours) had clobbered it — now they
|
(EnsureValidProjection / per-frame ambient / uninit cloud colours) had clobbered it — now they
|
||||||
RE-ASSERT the env values. Debug: copy BTDPL.INI → *DBG.INI with `debug=True`. [T2]
|
RE-ASSERT the env values. Debug: copy BTDPL.INI → *DBG.INI with `debug=True`. [T2]
|
||||||
|
- **Fog — per map/time/WEATHER (task #63).** `BTDPL.INI` authors `fog=<near> <far> <r> <g> <b>` (+ often
|
||||||
|
`nosearchlightfog=`) on EVERY map/time/weather leaf. WEATHER is a fixed authored egg field
|
||||||
|
(`clear`/`fog`/`soup`, `mission->GetMissionWeather()`; missing = fatal in the arcade); the env pipeline
|
||||||
|
branches location→time→weather to the leaf. Shipped eggs pin `cavern/night/clear` → page `dsnitclear`
|
||||||
|
(near 90, far 1100, dark blue 0.1,0.1,0.12); `fog`→`dsnitfog` (500), `soup`→`dsnitsoup` (300). The
|
||||||
|
arcade fog model is per-PIXEL linear (`dpl_fog_type_pixel_lin`, dpl.h:265). **It rendered INVISIBLE in
|
||||||
|
the port:** `D3DRS_FOGTABLEMODE=D3DFOG_LINEAR` (table fog) reads the perspective-NONLINEAR z-buffer, so
|
||||||
|
without WFOG the authored 90..1100 range collapsed to fog-factor≈1 (no fog). FIX (L4VIDEO.cpp world
|
||||||
|
pass ~8194): auto-detect `D3DPRASTERCAPS_WFOG` → per-pixel W-fog (smooth, == the arcade); else VERTEX
|
||||||
|
fog (eye-space but per-vertex → splotchy on coarse terrain tris); FOGENABLE + mode re-asserted each
|
||||||
|
world-pass frame (the old one-time set got clobbered per-pass). **Task #20's "shipped maps define no
|
||||||
|
fog" was WRONG** — it checked `.MAP`/`.RES`, but fog lives in BTDPL.INI (the DPL env INI), as in the
|
||||||
|
arcade. Night fog is dark BY DESIGN (distance→murk, not light haze); day pages author light haze. Env:
|
||||||
|
`BT_FOGMODE=table|vertex|off`, `BT_WEATHER=clear|fog|soup`, `BT_FOG="near far r g b"` (all branches);
|
||||||
|
`[fog] resolved`/`[fog] model` log lines. [T1 pipeline / T0 arcade model / T2 fix]
|
||||||
- **Projection / stencil (task #55):** the BTDPL.INI `viewangle=60` is the **HORIZONTAL** FOV —
|
- **Projection / stencil (task #55):** the BTDPL.INI `viewangle=60` is the **HORIZONTAL** FOV —
|
||||||
authentic 60×47 frustum at 4:3; fovY derived via `BTFovYFromHorizontal` (L4VIDEO.cpp), applied at
|
authentic 60×47 frustum at 4:3; fovY derived via `BTFovYFromHorizontal` (L4VIDEO.cpp), applied at
|
||||||
all 5 projection sites (it was mis-applied as vertical). The device depth-stencil is now
|
all 5 projection sites (it was mis-applied as vertical). The device depth-stencil is now
|
||||||
|
|||||||
@@ -4697,6 +4697,11 @@ void
|
|||||||
if(master_notation_file->GetEntry(starting_page_name, "fog" ,&TempStringPtr))
|
if(master_notation_file->GetEntry(starting_page_name, "fog" ,&TempStringPtr))
|
||||||
{
|
{
|
||||||
sscanf(TempStringPtr, "%f %f %f %f %f", &fogNear, &fogFar, &fogRed, &fogGreen, &fogBlue);
|
sscanf(TempStringPtr, "%f %f %f %f %f", &fogNear, &fogFar, &fogRed, &fogGreen, &fogBlue);
|
||||||
|
// BT (task #63): BT_FOG="near far r g b" overrides the AUTHORED fog for
|
||||||
|
// tuning/demo (works on the main branch now, not just the no-fog fallback).
|
||||||
|
if (const char *bf = getenv("BT_FOG"))
|
||||||
|
if (!(bf[0]=='0' && bf[1]==0))
|
||||||
|
sscanf(bf, "%f %f %f %f %f", &fogNear, &fogFar, &fogRed, &fogGreen, &fogBlue);
|
||||||
searchLightFogRed = fogRed;
|
searchLightFogRed = fogRed;
|
||||||
searchLightFogGreen = fogGreen;
|
searchLightFogGreen = fogGreen;
|
||||||
searchLightFogBlue = fogBlue;
|
searchLightFogBlue = fogBlue;
|
||||||
@@ -4709,6 +4714,9 @@ void
|
|||||||
noSearchLightFogFar = fogFar;
|
noSearchLightFogFar = fogFar;
|
||||||
currentFogNear = fogNear;
|
currentFogNear = fogNear;
|
||||||
currentFogFar = fogFar;
|
currentFogFar = fogFar;
|
||||||
|
DEBUG_STREAM << "[fog] resolved page='" << starting_page_name << "' near=" << fogNear
|
||||||
|
<< " far=" << fogFar << " rgb=(" << fogRed << "," << fogGreen << ","
|
||||||
|
<< fogBlue << ")\n" << std::flush;
|
||||||
// Force a 0-0 black fog on startup
|
// Force a 0-0 black fog on startup
|
||||||
// dpl_SetViewFog(dplMainView, dpl_fog_type_pixel_lin, 0.0, 0.0, 0.0, 0.01, 0.05);
|
// dpl_SetViewFog(dplMainView, dpl_fog_type_pixel_lin, 0.0, 0.0, 0.0, 0.01, 0.05);
|
||||||
//TODO: for fog testing, just set the values here
|
//TODO: for fog testing, just set the values here
|
||||||
@@ -4722,13 +4730,15 @@ void
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
//
|
//
|
||||||
// BT port (task #20): the SHIPPED maps define no fog entry at all (verified:
|
// CORRECTION (task #63): task #20's claim "the SHIPPED maps define no fog"
|
||||||
// no 'fog' key in any .MAP source nor in BTL4.RES strings) -- so the world
|
// was WRONG -- it looked in .MAP/.RES, but the arcade sourced fog from the
|
||||||
// edge and the void beyond are fully visible, which no pod player ever saw
|
// DPL env INI (L4DPLCFG = content/BTDPL.INI), which authors a fog= (usually
|
||||||
// (cockpit FOV + mission design kept views inward). Provide a period-
|
// nosearchlightfog= too) key on EVERY map/time/weather leaf page. The main
|
||||||
// plausible default curtain: color matched to the horizon so geometry fades
|
// (if) branch above handles the real authored fog; this else branch is only
|
||||||
// into the void seamlessly. env BT_FOG="near far r g b" overrides;
|
// reached by a page that genuinely lacks a fog= key (e.g. a *_default page
|
||||||
// BT_FOG=0 disables.
|
// whose weather compare is commented out AND has no own fog). Keep a period-
|
||||||
|
// plausible fallback curtain there so such a page still fades to the horizon;
|
||||||
|
// BT_FOG="near far r g b" overrides, BT_FOG=0 disables.
|
||||||
//
|
//
|
||||||
float fN = 150.0f, fF = 520.0f, fR = 0.44f, fG = 0.44f, fB = 0.62f;
|
float fN = 150.0f, fF = 520.0f, fR = 0.44f, fG = 0.44f, fB = 0.62f;
|
||||||
int fogOn = 1;
|
int fogOn = 1;
|
||||||
@@ -5167,7 +5177,11 @@ void
|
|||||||
}
|
}
|
||||||
else if(strcmp(target_string,"weather") == 0)
|
else if(strcmp(target_string,"weather") == 0)
|
||||||
{
|
{
|
||||||
compare_strings[token_count] = mission->GetMissionWeather();
|
// BT (task #63): BT_WEATHER overrides the egg's authored weather so
|
||||||
|
// clear/fog/soup fog curtains can be A/B'd without re-authoring eggs
|
||||||
|
// (the shipped eggs pin weather=clear). Authentic default = the egg.
|
||||||
|
const char *wov = getenv("BT_WEATHER");
|
||||||
|
compare_strings[token_count] = (wov && *wov) ? wov : mission->GetMissionWeather();
|
||||||
// std::cout<<target_string<<" = "<<compare_strings[token_count]<<"\n";
|
// std::cout<<target_string<<" = "<<compare_strings[token_count]<<"\n";
|
||||||
}
|
}
|
||||||
else if(strcmp(target_string,"scenario") == 0)
|
else if(strcmp(target_string,"scenario") == 0)
|
||||||
@@ -8191,6 +8205,47 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
|
|||||||
}
|
}
|
||||||
mDevice->SetRenderState(D3DRS_FOGSTART, *((DWORD*)(¤tFogNear)));
|
mDevice->SetRenderState(D3DRS_FOGSTART, *((DWORD*)(¤tFogNear)));
|
||||||
mDevice->SetRenderState(D3DRS_FOGEND, *((DWORD*)(¤tFogFar)));
|
mDevice->SetRenderState(D3DRS_FOGEND, *((DWORD*)(¤tFogFar)));
|
||||||
|
// BT (task #63): the arcade fog was EYE-SPACE linear (dpl_fog_type_pixel_lin).
|
||||||
|
// The port left D3DRS_FOGTABLEMODE = D3DFOG_LINEAR (table/PIXEL fog), whose fog
|
||||||
|
// factor comes from the perspective-NONLINEAR z-buffer -- and without a WFOG cap
|
||||||
|
// FOGSTART/END are z-buffer [0,1] units, so the authored 90..1100 range gave a
|
||||||
|
// fog factor of ~1 (NO fog) across the entire scene: fog was wired but invisible.
|
||||||
|
// Use VERTEX fog: it is computed per-vertex in EYE SPACE, so FOGSTART/END are the
|
||||||
|
// authored world near/far and the haze curtain renders across the real range.
|
||||||
|
// This is the world-geometry pass (SHOULD be fogged; sky/cockpit passes manage
|
||||||
|
// their own). BT_FOGMODE=table = the old z-based mode (A/B); BT_FOGMODE=off.
|
||||||
|
{
|
||||||
|
static const char *s_fm = getenv("BT_FOGMODE");
|
||||||
|
static const int s_fOff = (s_fm && strcmp(s_fm, "off") == 0) ? 1 : 0;
|
||||||
|
// Fog MODEL. The arcade used per-PIXEL linear fog (dpl_fog_type_pixel_lin) --
|
||||||
|
// smooth. D3D fixed-function per-pixel (TABLE) fog is only eye-space (correct
|
||||||
|
// over the authored near/far) when the GPU exposes D3DPRASTERCAPS_WFOG; without
|
||||||
|
// it, table fog reads the NONLINEAR z-buffer and the 90..1100 range collapses
|
||||||
|
// to no fog. Auto-detect: WFOG -> table (smooth, faithful); else VERTEX fog
|
||||||
|
// (eye-space but per-vertex -> splotchy on coarse terrain tris). s_mode:
|
||||||
|
// 1=table, 0=vertex. BT_FOGMODE=table|vertex|off forces.
|
||||||
|
static int s_mode = -1;
|
||||||
|
if (s_mode < 0)
|
||||||
|
{
|
||||||
|
if (s_fm && strcmp(s_fm, "table") == 0) s_mode = 1;
|
||||||
|
else if (s_fm && strcmp(s_fm, "vertex") == 0) s_mode = 0;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
D3DCAPS9 caps; memset(&caps, 0, sizeof(caps));
|
||||||
|
s_mode = (SUCCEEDED(mDevice->GetDeviceCaps(&caps))
|
||||||
|
&& (caps.RasterCaps & D3DPRASTERCAPS_WFOG)) ? 1 : 0;
|
||||||
|
}
|
||||||
|
DEBUG_STREAM << "[fog] model=" << (s_mode ? "table/WFOG (per-pixel, smooth)"
|
||||||
|
: "vertex (per-vertex; splotchy on coarse tris)")
|
||||||
|
<< " (BT_FOGMODE=table|vertex|off overrides)\n" << std::flush;
|
||||||
|
}
|
||||||
|
mDevice->SetRenderState(D3DRS_FOGENABLE, s_fOff ? FALSE : TRUE);
|
||||||
|
if (!s_fOff)
|
||||||
|
{
|
||||||
|
mDevice->SetRenderState(D3DRS_FOGVERTEXMODE, s_mode ? D3DFOG_NONE : D3DFOG_LINEAR);
|
||||||
|
mDevice->SetRenderState(D3DRS_FOGTABLEMODE, s_mode ? D3DFOG_LINEAR : D3DFOG_NONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
mDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);
|
mDevice->SetRenderState(D3DRS_ZWRITEENABLE, true);
|
||||||
mDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
|
mDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
|
||||||
mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
mDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
|
||||||
|
|||||||
Reference in New Issue
Block a user