From 3135d24bb84f40719b121b51634811c85af0a7af Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 16 Jul 2026 21:41:18 +0100 Subject: [PATCH] Preserve provider temporal projection convention --- .../org.gamecult.eve.unity-scene/README.md | 6 +++++ .../Fields/EveUnityFieldsVolumeRenderer.cs | 19 ++++++++++----- .../EveFieldsUnitySceneProjectionTests.cs | 24 +++++++++++++++++++ .../org.gamecult.eve.unity-scene/package.json | 2 +- 4 files changed, 44 insertions(+), 7 deletions(-) diff --git a/packages/org.gamecult.eve.unity-scene/README.md b/packages/org.gamecult.eve.unity-scene/README.md index 40f8f0b..765188c 100644 --- a/packages/org.gamecult.eve.unity-scene/README.md +++ b/packages/org.gamecult.eve.unity-scene/README.md @@ -82,6 +82,12 @@ the field textures it samples. A `world.scene3d` may also request quality, and sharpening values to URP TAA and restores the previous camera state when the world releases the camera. +Native volume programs also advertise the projection convention consumed by +their history matrix. `non-render-target-projection.previous-view.v1` preserves +the projection convention used by legacy `Graphics.Blit` temporal shaders even +when EveUnity executes the program through a render-target-backed URP pass; +providers must not rely on a runtime guessing that Y-axis convention. + `field.particles3d` may advertise provider material textures, viewport-relative texture scales, and a logical render-frame index port. These are presentation inputs: EveUnity binds the selected provider assets and current display frame diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs b/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs index 6b2760f..4cfd8d1 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs @@ -140,6 +140,7 @@ namespace GameCult.Eve.UnityScene.Fields ApplyQuality(field, _historyTextureIndex < 0); var gpuProjection = GL.GetGPUProjectionMatrix(camera.projectionMatrix, true); + var nonRenderTargetProjection = GL.GetGPUProjectionMatrix(camera.projectionMatrix, false); var viewProjection = gpuProjection * camera.worldToCameraMatrix; SetMatrixPort("cameraInverseViewProjection", viewProjection.inverse); SetMatrixPort("cameraToWorld", CameraTransformToWorld(camera)); @@ -170,6 +171,7 @@ namespace GameCult.Eve.UnityScene.Fields _programMetadata, _previousViewProjection, gpuProjection, + nonRenderTargetProjection, _previousView)); SetFloatPort("resetHistory", _historyTextureIndex < 0 ? 1f : 0f); } @@ -496,13 +498,17 @@ namespace GameCult.Eve.UnityScene.Fields IReadOnlyDictionary? metadata, Matrix4x4 previousViewProjection, Matrix4x4 currentGpuProjection, + Matrix4x4 currentNonRenderTargetProjection, Matrix4x4 previousView) { - return metadata != null && - metadata.TryGetValue("unity.volume.matrixSemantic.previousViewProjection", out var semantic) && - string.Equals(semantic, "current-projection.previous-view.v1", StringComparison.Ordinal) - ? currentGpuProjection * previousView - : previousViewProjection; + if (metadata == null || + !metadata.TryGetValue("unity.volume.matrixSemantic.previousViewProjection", out var semantic)) + return previousViewProjection; + if (string.Equals(semantic, "current-projection.previous-view.v1", StringComparison.Ordinal)) + return currentGpuProjection * previousView; + if (string.Equals(semantic, "non-render-target-projection.previous-view.v1", StringComparison.Ordinal)) + return currentNonRenderTargetProjection * previousView; + return previousViewProjection; } private bool HasTemporalProgram() => ProgramPass("temporal") >= 0; @@ -583,7 +589,8 @@ namespace GameCult.Eve.UnityScene.Fields return Fail("Native volume temporal pass is missing a required history port.", out error); if (metadata.TryGetValue("unity.volume.matrixSemantic.previousViewProjection", out var semantic) && !string.Equals(semantic, "previous-view-projection.v1", StringComparison.Ordinal) && - !string.Equals(semantic, "current-projection.previous-view.v1", StringComparison.Ordinal)) + !string.Equals(semantic, "current-projection.previous-view.v1", StringComparison.Ordinal) && + !string.Equals(semantic, "non-render-target-projection.previous-view.v1", StringComparison.Ordinal)) return Fail("Native volume temporal pass has an unsupported previous-view-projection semantic.", out error); if (metadata.TryGetValue("unity.volume.quality.bootstrap", out var bootstrapQuality) && (!metadata.TryGetValue( diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs index 7a834b4..242112d 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs @@ -341,6 +341,7 @@ namespace GameCult.Eve.UnityScene.Tests metadata, previousViewProjection, currentProjection, + Matrix4x4.identity, previousView); Assert.That(resolved, Is.EqualTo(currentProjection * previousView)); @@ -356,11 +357,34 @@ namespace GameCult.Eve.UnityScene.Tests RequiredVolumeProgramMetadata(), previousViewProjection, Matrix4x4.Scale(Vector3.one * 2f), + Matrix4x4.identity, Matrix4x4.Rotate(Quaternion.Euler(10f, 20f, 30f))); Assert.That(resolved, Is.EqualTo(previousViewProjection)); } + [Test] + public void VolumeRendererUsesNonRenderTargetProjectionWhenAdvertised() + { + var metadata = RequiredVolumeProgramMetadata(); + metadata["unity.volume.matrixSemantic.previousViewProjection"] = + "non-render-target-projection.previous-view.v1"; + var previousViewProjection = Matrix4x4.Translate(new Vector3(1f, 2f, 3f)); + var renderTargetProjection = Matrix4x4.Scale(new Vector3(2f, -3f, 4f)); + var nonRenderTargetProjection = Matrix4x4.Scale(new Vector3(2f, 3f, 4f)); + var previousView = Matrix4x4.Rotate(Quaternion.Euler(10f, 20f, 30f)); + + var resolved = EveUnityFieldsVolumeRenderer.ResolvePreviousViewProjection( + metadata, + previousViewProjection, + renderTargetProjection, + nonRenderTargetProjection, + previousView); + + Assert.That(resolved, Is.EqualTo(nonRenderTargetProjection * previousView)); + Assert.That(resolved, Is.Not.EqualTo(renderTargetProjection * previousView)); + } + [Test] public void VolumeProgramRejectsUnknownPreviousViewProjectionSemantic() { diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index 8e4e5b7..c9bcdb6 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.3.62", + "version": "0.3.63", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {