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 061d55c..4f6bb84 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/Fields/EveUnityFieldsVolumeRenderer.cs @@ -28,6 +28,7 @@ namespace GameCult.Eve.UnityScene.Fields private readonly RenderTexture?[] _historyTextures = new RenderTexture?[2]; private int _historyTextureIndex = -1; private Matrix4x4 _previousViewProjection; + private Matrix4x4 _previousView; private EveUnityFieldsVolumePass? _renderPass; private string _activeNodeId = ""; private long _rasterizedFrameId = -1; @@ -160,7 +161,15 @@ namespace GameCult.Eve.UnityScene.Fields finalCloudTexture = _historyTextures[historyWriteIndex]!; SetTexturePort("currentSample", _raymarchTexture); SetTexturePort("history", historyReadTexture!); - SetMatrixPort("previousViewProjection", _historyTextureIndex < 0 ? viewProjection : _previousViewProjection); + SetMatrixPort( + "previousViewProjection", + _historyTextureIndex < 0 + ? viewProjection + : ResolvePreviousViewProjection( + _programMetadata, + _previousViewProjection, + gpuProjection, + _previousView)); SetFloatPort("resetHistory", _historyTextureIndex < 0 ? 1f : 0f); } SetTexturePort("cloud", finalCloudTexture); @@ -186,6 +195,7 @@ namespace GameCult.Eve.UnityScene.Fields { _historyTextureIndex = historyWriteIndex; _previousViewProjection = viewProjection; + _previousView = camera.worldToCameraMatrix; } CompositeCount++; } @@ -454,6 +464,20 @@ namespace GameCult.Eve.UnityScene.Fields { _historyTextureIndex = -1; _previousViewProjection = Matrix4x4.identity; + _previousView = Matrix4x4.identity; + } + + public static Matrix4x4 ResolvePreviousViewProjection( + IReadOnlyDictionary? metadata, + Matrix4x4 previousViewProjection, + Matrix4x4 currentGpuProjection, + 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; } private bool HasTemporalProgram() => ProgramPass("temporal") >= 0; @@ -532,6 +556,10 @@ namespace GameCult.Eve.UnityScene.Fields !ValidPort(metadata, "matrix", "previousViewProjection") || !ValidPort(metadata, "float", "resetHistory")) 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)) + return Fail("Native volume temporal pass has an unsupported previous-view-projection semantic.", out error); return true; } 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 9b8391c..e25a967 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveFieldsUnitySceneProjectionTests.cs @@ -327,6 +327,55 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(EveUnityFieldsVolumeRenderer.TryValidateProgramMetadata(metadata, out var error), Is.True, error); } + [Test] + public void VolumeRendererUsesCurrentProjectionWithPreviousViewWhenAdvertised() + { + var metadata = RequiredVolumeProgramMetadata(); + metadata["unity.volume.matrixSemantic.previousViewProjection"] = + "current-projection.previous-view.v1"; + var previousViewProjection = Matrix4x4.Translate(new Vector3(1f, 2f, 3f)); + var currentProjection = Matrix4x4.Scale(new Vector3(2f, 3f, 4f)); + var previousView = Matrix4x4.Rotate(Quaternion.Euler(10f, 20f, 30f)); + + var resolved = EveUnityFieldsVolumeRenderer.ResolvePreviousViewProjection( + metadata, + previousViewProjection, + currentProjection, + previousView); + + Assert.That(resolved, Is.EqualTo(currentProjection * previousView)); + Assert.That(resolved, Is.Not.EqualTo(previousViewProjection)); + } + + [Test] + public void VolumeRendererDefaultsToTruePreviousViewProjection() + { + var previousViewProjection = Matrix4x4.Translate(new Vector3(1f, 2f, 3f)); + + var resolved = EveUnityFieldsVolumeRenderer.ResolvePreviousViewProjection( + RequiredVolumeProgramMetadata(), + previousViewProjection, + Matrix4x4.Scale(Vector3.one * 2f), + Matrix4x4.Rotate(Quaternion.Euler(10f, 20f, 30f))); + + Assert.That(resolved, Is.EqualTo(previousViewProjection)); + } + + [Test] + public void VolumeProgramRejectsUnknownPreviousViewProjectionSemantic() + { + var metadata = RequiredVolumeProgramMetadata(); + metadata["unity.volume.pass.temporal"] = "1"; + metadata["unity.volume.texturePort.currentSample"] = "_CurrentVolume"; + metadata["unity.volume.texturePort.history"] = "_HistoryVolume"; + metadata["unity.volume.matrixPort.previousViewProjection"] = "_PreviousViewProjection"; + metadata["unity.volume.floatPort.resetHistory"] = "_ResetHistory"; + metadata["unity.volume.matrixSemantic.previousViewProjection"] = "made-up"; + + Assert.That(EveUnityFieldsVolumeRenderer.TryValidateProgramMetadata(metadata, out var error), Is.False); + Assert.That(error, Does.Contain("previous-view-projection semantic")); + } + [Test] public void VolumeProgramRejectsPartialTemporalLifecycle() { diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index 20f654f..e10d407 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.59", + "version": "0.3.60", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {