feat(render): Campaign V slice V6f-1 - converge terrain's view and projection
terrain_modern.vert combined two loose mat4 uniforms per vertex:
gl_Position = uProjection * uView * vec4(terrainPos, 1.0);
Vulkan GLSL cannot express that. There is no default uniform block, so a loose
`uniform mat4` is unspellable however it is written, and the two matrices are
128 bytes against a pinned 96-byte push-constant block (and against Vulkan's
guaranteed 128-byte ceiling). GpuPushConstants already carries exactly one
uViewProjection, which is the shape every other ported shader reads. So the
product moves to the CPU and the shader reads the single matrix.
The transform is identical. System.Numerics uses row-vector convention and
Shader.SetMatrix4 uploads untransposed, so GLSL reads each uploaded matrix as
its transpose. The old expression evaluated Proj^T * View^T; the new one
evaluates (View*Proj)^T, and those are the same matrix. The renderer already had
that product in hand - `viewProjection` at line 422, computed for the visibility
pass - so nothing new is multiplied. It is multiplied once per frame on the CPU
instead of once per vertex on the GPU.
That last sentence is the whole reason this is its own commit. Moving a float
product from GPU to CPU is a real numeric change: different hardware, possibly
different fused-multiply-add behaviour, certainly a different rounding order.
The plan's V4d row requires its pixel effect be attributable alone rather than
folded into a plumbing change, and terrain fills most of the offline gate's
scene, so this is the strongest measurement that gate can make.
Gates. Release build clean. App tests 4,072 passed / 3 skipped, matching the
baseline exactly. Offline pixel gate against 7faaaa34: 22 differing pixels of
563,200 compared (fraction 3.91e-05, maximumChannelDelta 52). A same-commit
control captured immediately afterwards: 15 pixels (2.66e-05, maximumChannelDelta
46). Both sit inside the documented 15-23 pixel noise band and ~26x under the
0.001 threshold, and the change and its own control are drawn from the same
distribution - which is what "no systematic shift" looks like at this
instrument's resolution. The gate run's client log has zero exceptions and an
empty stderr.
The shader manifest is regenerated in the same commit, as its freshness test
requires. terrain_modern.vert now compiles to SPIR-V for the first time; the
pair stays vulkanReady:false and emits no .spv because terrain_modern.frag still
declares `uniform float uTexTiling[36]`, which is the other half of this
shader's port and lands next as the UniformTerrainTiling buffer that
GpuBindingModel already reserves binding 3 for. Per-pair the count is unchanged
at 7/9; per-stage it is 15/18.
No divergence-register row: the transform is identical and no retail-facing
behaviour changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7faaaa347b
commit
5e13b45fae
3 changed files with 20 additions and 10 deletions
|
|
@ -105,9 +105,8 @@
|
|||
"stages": [
|
||||
{
|
||||
"stage": "vert",
|
||||
"sourceSha256": "4de580ce11b8d755d3558dc49bf7ebccec54d307595d91c38b5c5d552d645c7e",
|
||||
"compiled": false,
|
||||
"message": "terrain_modern.vert:221: error: \u0027uProjection\u0027 : undeclared identifier"
|
||||
"sourceSha256": "336880b293e95c9ba13dce7a616a15e941191afec8bdaabe04a4993182eb6811",
|
||||
"compiled": true
|
||||
},
|
||||
{
|
||||
"stage": "frag",
|
||||
|
|
|
|||
|
|
@ -13,8 +13,16 @@ layout(location = 3) in uvec4 aPacked1;
|
|||
layout(location = 4) in uvec4 aPacked2;
|
||||
layout(location = 5) in uvec4 aPacked3;
|
||||
|
||||
uniform mat4 uView;
|
||||
uniform mat4 uProjection;
|
||||
// Campaign V slice V6f-1: uView/uProjection converged into the single
|
||||
// uViewProjection that GpuPushConstants already carries, so terrain can be
|
||||
// expressed in Vulkan GLSL at all — two loose mat4 uniforms are 128 bytes and
|
||||
// cannot both fit the pinned 96-byte push block, and Vulkan GLSL has no default
|
||||
// uniform block to hold them loose. The product is now formed on the CPU
|
||||
// (camera.View * camera.Projection) instead of per vertex here; the two are the
|
||||
// same transform, and System.Numerics' row-vector layout uploaded untransposed
|
||||
// reads in GLSL as the transpose, so (View*Proj)^T == Proj^T * View^T is exactly
|
||||
// the uProjection * uView this replaced.
|
||||
uniform mat4 uViewProjection;
|
||||
|
||||
struct Light {
|
||||
vec4 posAndKind;
|
||||
|
|
@ -165,7 +173,7 @@ void main() {
|
|||
// the un-nudged heightmap via TerrainSurface.SampleZ.
|
||||
// Closes issue #100; supersedes the hiddenTerrainCells cell-collapse hack.
|
||||
vec3 terrainPos = vec3(aPos.xy, aPos.z - 0.01);
|
||||
gl_Position = uProjection * uView * vec4(terrainPos, 1.0);
|
||||
gl_Position = uViewProjection * vec4(terrainPos, 1.0);
|
||||
|
||||
// Phase U.3: terrain clip gate against the single OutsideView region. With
|
||||
// uTerrainClipCount == 0 (U.3 default) the first loop is skipped and the
|
||||
|
|
|
|||
|
|
@ -494,15 +494,18 @@ public sealed unsafe class TerrainModernRenderer : IDisposable
|
|||
|
||||
// Bind shader + uniforms + atlas handles.
|
||||
// Verified Phase W Stage 4 (T4.2): terrain projects from the camera view-proj;
|
||||
// no separate landscape viewpoint to sync. Both uView and uProjection derive
|
||||
// from the ICamera passed into this method — the same camera used for all other
|
||||
// no separate landscape viewpoint to sync. uViewProjection derives from
|
||||
// the ICamera passed into this method — the same camera used for all other
|
||||
// renderers in the unified pipeline. Retail's LScape::update_viewpoint
|
||||
// pre-positions terrain to the outdoor landcell, but acdream uses the
|
||||
// unified camera matrix everywhere, so no separate viewpoint divergence can occur.
|
||||
_shader.Use();
|
||||
UploadTextureTilingOnce();
|
||||
_shader.SetMatrix4("uView", camera.View);
|
||||
_shader.SetMatrix4("uProjection", camera.Projection);
|
||||
// Campaign V slice V6f-1: one uViewProjection, matching the field
|
||||
// GpuPushConstants already carries, instead of the separate uView and
|
||||
// uProjection the shader used to combine per vertex. viewProjection is
|
||||
// the same product the visibility pass above already computed.
|
||||
_shader.SetMatrix4("uViewProjection", viewProjection);
|
||||
|
||||
var (terrainHandle, alphaHandle) = _atlas.GetBindlessHandles();
|
||||
// Campaign V slice V2b: pass each handle's binding=9 table slot
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue