Revert "Campaign V slice V4a" - it lost world multisampling
This reverts ceec3bc4. Two independent reasons, either sufficient.
The rendering regression. The slice deleted TextRenderGlStateScope, which
saved GL_MULTISAMPLE and GL_SAMPLE_ALPHA_TO_COVERAGE on entry, disabled them
for the text pass, and restored them on exit (TextRenderGlStateScope.cs:111-112
and 153-154 at the parent commit). Its replacement bakes that state into the
text pipeline but nothing restores it, and GlGpuPassEncoder.Dispose does not
either. Every world renderer is still raw GL at this point in the campaign, so
from the first UI frame onward the world drew with multisampling disabled.
The offline pixel gate caught it: 1,791 of 563,200 compared pixels differed,
0.318% against a 0.001 threshold. The commit message attributed this to
wall-clock-driven ambient animation shifting phase, and committed through the
failure. That explanation does not survive its own control: capturing twice at
the reverted-to commit differs by 19 pixels and twice at the slice's own commit
by 8, while base-versus-head differs by 1,791 - a 224x gap that no shared-noise
source explains. An amplified difference image settles it visually: the changed
pixels are the silhouette edges of every tree, building and rock, with terrain
interiors, water and the entire UI untouched. That is the signature of losing
edge antialiasing, not of animated sprites.
This is the exact failure mode two existing memory notes already warn about -
a mid-frame renderer must set every GL state it uses rather than inherit it,
and issue #52's lesson that a rendering migration must audit per-pass GL state
before declaring itself done.
The scope. The brief was three small leaf renderers plus additive frame-
lifecycle wiring, roughly ten files. The commit changed 334 files with 3,665
insertions and 3,845 deletions, including 323 public-to-internal visibility
conversions across the App assembly, 55 test files, two retired conformance
tests, and a self-described temporary escape hatch for bridging raw-GL viewport
textures. Even without the regression, that is not separable into the part
worth keeping and the part worth dropping.
Reverting rather than patching because the good work here - the RHI frame
lifecycle wiring and a genuine render-state-cache staleness fix - is small
enough to redo cleanly against a tightened spec, while untangling it from 300+
files of unrelated churn is not.
Post-revert: Release build clean, App suite back to 3,843 passed / 3 skipped,
offline pixel gate passing at 19 differing pixels.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ceec3bc440
commit
9aaf97e785
334 changed files with 3841 additions and 3661 deletions
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
|
@ -16,7 +16,7 @@ namespace AcDream.App.Rendering.Sky;
|
|||
/// <summary>
|
||||
/// Port of <c>references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/SkyboxRenderManager.cs</c>.
|
||||
/// Draws the retail sky as a stack of independent celestial meshes (the
|
||||
/// "it's not a dome" insight from r12 §2) rather than a cube/sphere
|
||||
/// "it's not a dome" insight from r12 §2) rather than a cube/sphere
|
||||
/// with a gradient texture. Each <see cref="SkyObjectData"/> is
|
||||
/// visible in a window of day-fraction space, sweeps from
|
||||
/// <c>BeginAngle</c> to <c>EndAngle</c> across the sky, and samples its
|
||||
|
|
@ -25,11 +25,11 @@ namespace AcDream.App.Rendering.Sky;
|
|||
/// <para>
|
||||
/// GL state delta per frame:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>Depth mask OFF, depth test OFF, cull OFF — the sky
|
||||
/// <item><description>Depth mask OFF, depth test OFF, cull OFF — the sky
|
||||
/// should never occlude scene geometry.</description></item>
|
||||
/// <item><description>Separate projection matrix with a 0.1–1e6 near/far
|
||||
/// <item><description>Separate projection matrix with a 0.1–1e6 near/far
|
||||
/// so mesh vertices at large distance don't clip.</description></item>
|
||||
/// <item><description>View matrix with translation zeroed — sky is
|
||||
/// <item><description>View matrix with translation zeroed — sky is
|
||||
/// always camera-centred; moving doesn't get you closer to the
|
||||
/// sun.</description></item>
|
||||
/// </list>
|
||||
|
|
@ -38,12 +38,12 @@ namespace AcDream.App.Rendering.Sky;
|
|||
/// <para>
|
||||
/// Meshes are built lazily per GfxObj id on first reference. The
|
||||
/// per-object arc transform matches WorldBuilder's composition:
|
||||
/// <c>scale × RotZ(-heading) × RotY(-rotation)</c> — the negative signs
|
||||
/// <c>scale × RotZ(-heading) × RotY(-rotation)</c> — the negative signs
|
||||
/// come from AC's Z-up right-handed convention where heading is
|
||||
/// measured clockwise from north.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal sealed unsafe class SkyRenderer : IDisposable
|
||||
public sealed unsafe class SkyRenderer : IDisposable
|
||||
{
|
||||
private readonly GL _gl;
|
||||
private readonly IDatReaderWriter _dats;
|
||||
|
|
@ -54,11 +54,11 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
// Lazily-built GPU resources per sky-GfxObj.
|
||||
private readonly Dictionary<uint, List<SubMeshGpu>> _gpuByGfxObj = new();
|
||||
|
||||
// When did we start running — used to accumulate TexVelocityX/Y over
|
||||
// When did we start running — used to accumulate TexVelocityX/Y over
|
||||
// real time (independent of the day-fraction clock).
|
||||
private readonly DateTime _startedAt = DateTime.UtcNow;
|
||||
|
||||
// Configurable render distance — retail uses ~1e6; anything larger
|
||||
// Configurable render distance — retail uses ~1e6; anything larger
|
||||
// than the scene far plane works.
|
||||
public float Near { get; set; } = 0.1f;
|
||||
public float Far { get; set; } = 1_000_000f;
|
||||
|
|
@ -73,7 +73,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draw all NON-WEATHER sky objects (dome, sun, moon, stars, clouds —
|
||||
/// Draw all NON-WEATHER sky objects (dome, sun, moon, stars, clouds —
|
||||
/// every <c>SkyObject</c> with <c>Properties & 0x04 == 0</c>).
|
||||
/// Called BEFORE the scene; terrain / meshes / debug lines / overlay
|
||||
/// land on top via depth-test.
|
||||
|
|
@ -82,7 +82,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
/// Mirrors the first half of retail's <c>LScape::draw</c> at
|
||||
/// <c>0x00506330</c>: that function calls <c>GameSky::Draw(0)</c>
|
||||
/// (sky pass) before the landblock loop, then <c>GameSky::Draw(1)</c>
|
||||
/// (weather pass) after. acdream splits the same way — see
|
||||
/// (weather pass) after. acdream splits the same way — see
|
||||
/// <see cref="RenderWeather"/> for the post-scene companion.
|
||||
/// </para>
|
||||
///
|
||||
|
|
@ -90,17 +90,17 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
/// Each submesh renders with retail's per-vertex lighting formula:
|
||||
/// <c>tint = clamp(emissive + ambient + max(dot(N, -sunDir), 0) * sunColor, 0, 1)</c>
|
||||
/// where <c>emissive</c> is the submesh's <c>Surface.Luminosity</c>
|
||||
/// float (1.0 for dome + sun + moon → texture passthrough via
|
||||
/// saturation; 0.0 for clouds → get the full time-of-day tint).
|
||||
/// float (1.0 for dome + sun + moon → texture passthrough via
|
||||
/// saturation; 0.0 for clouds → get the full time-of-day tint).
|
||||
/// <paramref name="keyframe"/> supplies the AmbientColor and SunColor
|
||||
/// already pre-multiplied by AmbBright / DirBright (loader-side).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// See <c>docs/research/2026-04-23-sky-retail-verbatim.md</c> §6 for
|
||||
/// See <c>docs/research/2026-04-23-sky-retail-verbatim.md</c> §6 for
|
||||
/// the full decompile citation. The empirical Dereth dump (
|
||||
/// <c>ACDREAM_DUMP_SKY=1</c>, logged 2026-04-23) confirmed the
|
||||
/// <c>SurfaceType.Luminous</c> flag bit is NOT set on any Dereth sky
|
||||
/// mesh — the differentiator is the <c>Surface.Luminosity</c> FLOAT
|
||||
/// mesh — the differentiator is the <c>Surface.Luminosity</c> FLOAT
|
||||
/// field.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
|
|
@ -118,13 +118,13 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
/// Draw the POST-SCENE sky objects (the foreground rain mesh
|
||||
/// <c>0x01004C44</c> on Rainy DayGroups, plus any other SkyObject with
|
||||
/// <c>Properties & 0x01 != 0</c>). Called AFTER the scene so these
|
||||
/// meshes paint on top of terrain and entities — retail-faithful order
|
||||
/// meshes paint on top of terrain and entities — retail-faithful order
|
||||
/// from <c>LScape::draw</c> at <c>0x00506330</c>, where
|
||||
/// <c>GameSky::Draw(1)</c> fires after the <c>DrawBlock</c> loop and
|
||||
/// renders the <c>after_sky_cell</c> contents. With depth-test
|
||||
/// disabled and additive blend (the rain Surface flag includes
|
||||
/// Additive), the 815m-tall rain cylinder's bright streak texels add
|
||||
/// over the scene — making rain appear in the air between camera and
|
||||
/// over the scene — making rain appear in the air between camera and
|
||||
/// character instead of only at the horizon.
|
||||
/// <para>
|
||||
/// Method name kept as <c>RenderWeather</c> for API stability; the
|
||||
|
|
@ -149,7 +149,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
/// Sets up the same GL state for both (depth-test off, additive +
|
||||
/// alpha-blend per submesh, camera-anchored translation) and iterates
|
||||
/// only the SkyObjects matching the requested partition by
|
||||
/// <see cref="SkyObjectData.IsPostScene"/> — bit <c>0x01</c> per the
|
||||
/// <see cref="SkyObjectData.IsPostScene"/> — bit <c>0x01</c> per the
|
||||
/// retail decomp at <c>GameSky::MakeObject</c> (<c>0x00506ee0</c>).
|
||||
/// </summary>
|
||||
private void RenderPass(
|
||||
|
|
@ -171,7 +171,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
// that FOV here, including the near-180-degree teleport transition.
|
||||
var skyProj = SkyProjection.WithDepthRange(camera.Projection, Near, Far);
|
||||
|
||||
// View with translation zeroed — keeps the sky at camera origin
|
||||
// View with translation zeroed — keeps the sky at camera origin
|
||||
// regardless of camera position in the world.
|
||||
var skyView = camera.View;
|
||||
skyView.M41 = 0f;
|
||||
|
|
@ -183,7 +183,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
_shader.SetMatrix4("uSkyProjection", skyProj);
|
||||
|
||||
// Retail per-vertex lighting inputs (AdjustPlanes formula).
|
||||
// AmbColor/SunColor are already × AmbBright/DirBright from
|
||||
// AmbColor/SunColor are already × AmbBright/DirBright from
|
||||
// SkyDescLoader. SunDir is the unit vector FROM surface TO sun
|
||||
// derived from the keyframe's DirHeading/DirPitch.
|
||||
_shader.SetVec3("uAmbientColor", keyframe.AmbientColor);
|
||||
|
|
@ -204,14 +204,14 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
bool wasCullFace = _gl.IsEnabled(EnableCap.CullFace);
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
_gl.Enable(EnableCap.Blend);
|
||||
// Default blend — overridden per-submesh inside the inner loop.
|
||||
// Default blend — overridden per-submesh inside the inner loop.
|
||||
// Additive surfaces (sun/moon/stars via SurfaceType.Additive =
|
||||
// 0x10000) get GL_SRC_ALPHA / GL_ONE; alpha-blended (clouds, dome
|
||||
// with Alpha flag) get GL_SRC_ALPHA / GL_ONE_MINUS_SRC_ALPHA.
|
||||
_gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
|
||||
|
||||
// Look up the keyframe's override list so we can apply
|
||||
// SkyObjReplace (r12 §2.3): per-keyframe GfxObj swaps + rotation
|
||||
// SkyObjReplace (r12 §2.3): per-keyframe GfxObj swaps + rotation
|
||||
// override + transparency fade + luminosity cap.
|
||||
var replaces = PickReplaces(group, dayFraction);
|
||||
|
||||
|
|
@ -220,19 +220,19 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
for (int i = 0; i < group.SkyObjects.Count; i++)
|
||||
{
|
||||
var obj = group.SkyObjects[i];
|
||||
// Partition by post-scene flag (Properties bit 0x01) — the
|
||||
// Partition by post-scene flag (Properties bit 0x01) — the
|
||||
// caller chose either the pre-scene sky pass (bit clear) or
|
||||
// the post-scene pass (bit set). Mirrors retail
|
||||
// GameSky::CreateDeletePhysicsObjects at 0x005073c0 / decomp
|
||||
// line 269036 which routes (Properties & 1) into
|
||||
// before_sky_cell vs after_sky_cell, and GameSky::Draw at
|
||||
// 0x00506ff0 which renders those cells in the two passes.
|
||||
// NOTE: bit 0x04 (IsWeather) is independent — it gates whether
|
||||
// NOTE: bit 0x04 (IsWeather) is independent — it gates whether
|
||||
// the object is instantiated when weather_enabled is false.
|
||||
// Earlier acdream incorrectly used IsWeather for this
|
||||
// partition, putting the outer rain cylinder 0x01004C42
|
||||
// (Props=0x04, NO bit 0x01) into the post-scene pass with the
|
||||
// foreground rain — double-thick rain not matching retail.
|
||||
// foreground rain — double-thick rain not matching retail.
|
||||
if (obj.IsPostScene != postScenePass) continue;
|
||||
if (!obj.IsVisible(dayFraction)) continue;
|
||||
// Retail GameSky::Draw (0x00506ff0) skips Properties bit 0x02
|
||||
|
|
@ -254,7 +254,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
// fallback at the inner loop never fired (1f is always > 0).
|
||||
// RainMeshProbe (committed b8e0857) confirmed empirically that
|
||||
// NO Dereth sky surface carries the SurfaceType.Luminous flag
|
||||
// bit (0x40) — the differentiator is purely the float field.
|
||||
// bit (0x40) — the differentiator is purely the float field.
|
||||
float replaceLuminosity = float.NaN;
|
||||
float replaceDiffuse = float.NaN;
|
||||
if (replaces.TryGetValue((uint)i, out var rep))
|
||||
|
|
@ -289,7 +289,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
// int32_t var_4_1 = 0xc2f00000; // 0xc2f00000 == -120.0f
|
||||
//
|
||||
// Gate: bit 0x04 (weather) set AND bit 0x08 unset. NOT every
|
||||
// post-scene SkyObject — bit 0x01 (post-scene) is independent
|
||||
// post-scene SkyObject — bit 0x01 (post-scene) is independent
|
||||
// of bit 0x04 (weather). Today's Dereth ships every post-scene
|
||||
// entry as also weather-flagged so the previous unconditional
|
||||
// offset was a no-op divergence, but a future DayGroup with a
|
||||
|
|
@ -302,15 +302,15 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
// cylinder bottom sits at z=0.11 ABOVE the camera (skyView
|
||||
// translation is zeroed so model-origin == camera); looking
|
||||
// horizontally shows nothing. With -120m the cylinder spans z
|
||||
// = (camera-119.89)..(camera+694.90) — camera is inside,
|
||||
// looking in any direction shows surrounding walls — the
|
||||
// = (camera-119.89)..(camera+694.90) — camera is inside,
|
||||
// looking in any direction shows surrounding walls — the
|
||||
// volumetric foreground-rain look retail has.
|
||||
if (postScenePass && obj.IsWeather && (obj.Properties & 0x08u) == 0u)
|
||||
model = model * Matrix4x4.CreateTranslation(0f, 0f, -120f);
|
||||
|
||||
_shader.SetMatrix4("uModel", model);
|
||||
|
||||
// UV scroll accumulates real-time × velocity. Wrap to [0, 1]
|
||||
// UV scroll accumulates real-time × velocity. Wrap to [0, 1]
|
||||
// so long-running sessions don't accumulate float precision
|
||||
// loss in the fragment UV.
|
||||
float uOffset = (obj.TexVelocityX * secondsSinceStart) % 1f;
|
||||
|
|
@ -328,7 +328,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
// sky dome is Base1Image (Opaque, mapped to
|
||||
// SrcAlpha/InvSrcAlpha for a no-op blend at alpha=1).
|
||||
// See FUN_00508010 (chunk_00500000.c:7535) for the retail
|
||||
// pattern — retail routes sky meshes through the normal
|
||||
// pattern — retail routes sky meshes through the normal
|
||||
// mesh pipeline where Surface flags dictate state.
|
||||
if (sub.IsAdditive)
|
||||
_gl.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.One);
|
||||
|
|
@ -338,24 +338,24 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
// Emissive source picks the surface's authored Luminosity by
|
||||
// default; the per-keyframe replace data can OVERRIDE
|
||||
// (rep.Luminosity > 0) or CAP (rep.MaxBright). This matches
|
||||
// retail's FUN_0059da60: surface.Luminosity → D3DMATERIAL.Emissive
|
||||
// retail's FUN_0059da60: surface.Luminosity → D3DMATERIAL.Emissive
|
||||
// (via material cache +0x3c), with the keyframe replace
|
||||
// promoting bright-keyframe clouds when the keyframe asks.
|
||||
//
|
||||
// Empirical Dereth sky surfaces (RainMeshProbe, b8e0857):
|
||||
// dome/sun/moon → Lum=1.0 → vTint saturates → texture
|
||||
// dome/sun/moon → Lum=1.0 → vTint saturates → texture
|
||||
// passthrough (correct retail look);
|
||||
// stars/clouds → Lum=0.0 → vTint = ambient + diffuse →
|
||||
// stars/clouds → Lum=0.0 → vTint = ambient + diffuse →
|
||||
// picks up the time-of-day tint;
|
||||
// rain → Lum=0.1484 → faint emissive baseline,
|
||||
// rain → Lum=0.1484 → faint emissive baseline,
|
||||
// ambient+diffuse adds atmospheric tint.
|
||||
//
|
||||
// Pre-fix: the replace-override variable defaulted to 1f and
|
||||
// the fallback `(luminosity > 0) ? luminosity : sub.SurfLuminosity`
|
||||
// never fired — every sky mesh got effEmissive=1.0,
|
||||
// never fired — every sky mesh got effEmissive=1.0,
|
||||
// saturating vTint. That made stars/clouds look full-bright
|
||||
// instead of time-of-day-tinted, and made rain streaks
|
||||
// 6.7× too bright (one of two factors compounding the
|
||||
// 6.7× too bright (one of two factors compounding the
|
||||
// foreground-rim visibility bug).
|
||||
float effEmissive = float.IsNaN(replaceLuminosity)
|
||||
? sub.SurfLuminosity
|
||||
|
|
@ -374,7 +374,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
|
||||
// Retail D3DPolyRender::SetSurface at 0x59c882 calls
|
||||
// SetFFFogAlphaDisabled(1) when the Additive flag (0x10000)
|
||||
// is set on the Surface — so the sun, moon, stars, and any
|
||||
// is set on the Surface — so the sun, moon, stars, and any
|
||||
// additive cloud sheet are drawn WITHOUT fog. Skipping fog
|
||||
// on additive surfaces keeps the sun bright at horizon
|
||||
// dusk/dawn (where fog would otherwise dim it to fog color).
|
||||
|
|
@ -477,13 +477,13 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
/// Lazy mesh build for a sky object. Handles two cases:
|
||||
/// <list type="bullet">
|
||||
/// <item><description>
|
||||
/// <c>0x010xxxxx</c> — direct <see cref="GfxObj"/>. Reuses
|
||||
/// <c>0x010xxxxx</c> — direct <see cref="GfxObj"/>. Reuses
|
||||
/// <see cref="GfxObjMesh.Build"/> so the pos/neg polygon
|
||||
/// splitting logic stays consistent with the main static-mesh
|
||||
/// pipeline. Most sky meshes are single-surface.
|
||||
/// </description></item>
|
||||
/// <item><description>
|
||||
/// <c>0x020xxxxx</c> — <see cref="Setup"/>. The agent at
|
||||
/// <c>0x020xxxxx</c> — <see cref="Setup"/>. The agent at
|
||||
/// 2026-04-27 found these Setup-backed sky objects (e.g.
|
||||
/// <c>0x02000588</c>, <c>0x02000589</c>, <c>0x02000714</c>,
|
||||
/// <c>0x02000BA6</c>) were silently dropped: every cache miss
|
||||
|
|
@ -496,7 +496,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
/// <c>Setup.Parts</c> at the default placement frame and
|
||||
/// <see cref="GfxObjMesh.Build"/> produces submeshes for each
|
||||
/// part. Per-part transforms are baked into vertex positions
|
||||
/// (sky setups are static — no animation needed for the static
|
||||
/// (sky setups are static — no animation needed for the static
|
||||
/// mesh half of the visual).
|
||||
/// </description></item>
|
||||
/// </list>
|
||||
|
|
@ -504,7 +504,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
/// Even with this fix the visible aurora-style sheen most retail
|
||||
/// rainy/cloudy setups produce comes from the <c>pes_id</c> field
|
||||
/// on each <see cref="DatReaderWriter.Types.SkyObject"/> (a Particle
|
||||
/// Effect Schedule) — that's a separate Phase-level feature.
|
||||
/// Effect Schedule) — that's a separate Phase-level feature.
|
||||
/// Rendering the Setup's static parts here is the geometry half;
|
||||
/// the dynamic particle half is deferred.
|
||||
/// </para>
|
||||
|
|
@ -550,8 +550,8 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
// Phase 1 diagnostic: dump Surface.Type flags on every sky GfxObj
|
||||
// once, so we can determine which submeshes carry Luminous (0x40)
|
||||
// vs plain-lit. This settles the retail "cloud tint = per-vertex
|
||||
// lighting on non-Luminous meshes" hypothesis — see
|
||||
// docs/research/2026-04-23-sky-retail-verbatim.md §6.
|
||||
// lighting on non-Luminous meshes" hypothesis — see
|
||||
// docs/research/2026-04-23-sky-retail-verbatim.md §6.
|
||||
if (System.Environment.GetEnvironmentVariable("ACDREAM_DUMP_SKY") == "1")
|
||||
DumpGfxObjSurfaces(gfxObjId, gfx, subMeshes);
|
||||
|
||||
|
|
@ -565,14 +565,14 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
/// Setup-backed sky object loader. Walks <see cref="Setup.Parts"/> at
|
||||
/// the default placement frame, builds submeshes via
|
||||
/// <see cref="GfxObjMesh.Build"/>, and bakes the per-part transform
|
||||
/// into the vertex positions before upload. Static-pose only — sky
|
||||
/// into the vertex positions before upload. Static-pose only — sky
|
||||
/// setups don't animate in any meaningful way for the visual we care
|
||||
/// about (the dynamic look comes from <c>pes_id</c> particles, not
|
||||
/// the underlying mesh).
|
||||
/// <para>
|
||||
/// Mirrors retail's <see cref="CPhysicsObj.InitPartArrayObject"/> at
|
||||
/// decomp <c>280484</c> dispatching type 7 → <c>CPartArray::CreateSetup</c>
|
||||
/// → <c>CSetup::SetSetupID</c>, which loads the setup and instantiates
|
||||
/// decomp <c>280484</c> dispatching type 7 → <c>CPartArray::CreateSetup</c>
|
||||
/// → <c>CSetup::SetSetupID</c>, which loads the setup and instantiates
|
||||
/// each part as a separate <c>CPhysicsObj</c> child. We collapse the
|
||||
/// children into a flat submesh list because the sky pass renders
|
||||
/// without per-part transforms anyway.
|
||||
|
|
@ -654,13 +654,13 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
continue;
|
||||
}
|
||||
|
||||
// SurfaceType is a flag enum — `ToString()` gives the
|
||||
// SurfaceType is a flag enum — `ToString()` gives the
|
||||
// comma-joined names (e.g. "Base1Image, Additive").
|
||||
uint rawType = (uint)surface.Type;
|
||||
string names = surface.Type.ToString();
|
||||
uint origTex = surface.OrigTextureId?.DataId ?? 0u;
|
||||
var trans = TranslucencyKindExtensions.FromSurfaceType(surface.Type);
|
||||
// Surface's own Luminosity (0..1 fraction per test fixture —
|
||||
// Surface's own Luminosity (0..1 fraction per test fixture —
|
||||
// different from SkyObjectReplace.Luminosity which lives in the keyframe).
|
||||
Console.WriteLine(
|
||||
$"[sky-dump] Surface[{i}] 0x{surfaceId:X8} Type=0x{rawType:X8} ({names}) " +
|
||||
|
|
@ -703,7 +703,7 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
//
|
||||
// NOTE: earlier revision also treated `SurfaceType.Luminous = 0x40`
|
||||
// as additive, but that flag is present on the sky DOME itself and
|
||||
// on cloud sheets — turning those additive blew the whole sky to
|
||||
// on cloud sheets — turning those additive blew the whole sky to
|
||||
// white. `Luminous` means "self-illuminated / unshaded" in retail's
|
||||
// render pipeline, not "additive blend". Only the Additive bit
|
||||
// toggles the blend mode.
|
||||
|
|
@ -753,18 +753,18 @@ internal sealed unsafe class SkyRenderer : IDisposable
|
|||
/// </summary>
|
||||
public bool IsAdditive;
|
||||
/// <summary>
|
||||
/// <c>Surface.Luminosity</c> float (0..1 — NOT the SurfaceType.Luminous
|
||||
/// <c>Surface.Luminosity</c> float (0..1 — NOT the SurfaceType.Luminous
|
||||
/// flag bit). Passed to the sky fragment shader as <c>uEmissive</c>;
|
||||
/// when 1.0 it saturates the lighting math so the mesh renders at
|
||||
/// full texture brightness (dome, sun). When 0.0 the mesh picks up
|
||||
/// the time-of-day ambient+diffuse tint (clouds). See
|
||||
/// <c>docs/research/2026-04-23-sky-retail-verbatim.md</c> §6.
|
||||
/// <c>docs/research/2026-04-23-sky-retail-verbatim.md</c> §6.
|
||||
/// </summary>
|
||||
public float SurfLuminosity;
|
||||
public float SurfDiffuse;
|
||||
/// <summary>
|
||||
/// True when the source mesh's authored UVs exceed [0,1] (e.g.
|
||||
/// the inner sky/star layer 0x010015EF and the cloud meshes —
|
||||
/// the inner sky/star layer 0x010015EF and the cloud meshes —
|
||||
/// they tile their texture across the geometry). The renderer
|
||||
/// must use <c>GL_REPEAT</c> for these or only the small region
|
||||
/// where UVs fall in [0,1] samples the actual texture; the rest
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue