diff --git a/Data/RenderConfig.cs b/Data/RenderConfig.cs index 397c5ea..8e2a90c 100644 --- a/Data/RenderConfig.cs +++ b/Data/RenderConfig.cs @@ -95,6 +95,13 @@ public class AdvancedConfig /// public bool NonTile { get; set; } = false; + /// + /// When true, the game's bloom (VideoBloom) computes its blur size as if the frame were 720 pixels high, so the + /// blur passes on the 198-pixel-high bloom buffer are the same as in a 720p frame at any render height + /// (VideoBloomPatch). Default false. + /// + public bool HighResBloomCorrection { get; set; } = false; + /// File extension of the default output file name, without the dot (e.g. "mkv", "mp4"); selects the container. public string FileExtension { get; set; } = "mkv"; @@ -120,6 +127,7 @@ public class AdvancedConfig RandomSeed = source.RandomSeed; ProcessAudio = source.ProcessAudio; NonTile = source.NonTile; + HighResBloomCorrection = source.HighResBloomCorrection; FileExtension = source.FileExtension; firePlanetColor = source.firePlanetColor; icePlanetColor = source.icePlanetColor; diff --git a/Localization/Locale/en.json b/Localization/Locale/en.json index 9a126f7..ecae246 100644 --- a/Localization/Locale/en.json +++ b/Localization/Locale/en.json @@ -42,6 +42,7 @@ "ui.render_config.file_extension": "File Extension", "ui.render_config.process_audio": "Process Audio", "ui.render_config.non_tile": "Non-Tile Mode", + "ui.render_config.high_res_bloom_correction": "High-Resolution Bloom Correction", "ui.render_config.random_seed": "Random Seed (Optional)", "ui.render_config.validation_title": "Invalid Settings", "ui.render_config.validation_error": "Please check your input values:\n- Width and Height must be integers > 0\n- Render FPS and Video FPS must be > 0\n- Delay After End must be >= 0", diff --git a/Localization/Locale/ko.json b/Localization/Locale/ko.json index d42bb03..c54434d 100644 --- a/Localization/Locale/ko.json +++ b/Localization/Locale/ko.json @@ -42,6 +42,7 @@ "ui.render_config.file_extension": "파일 확장자", "ui.render_config.process_audio": "오디오 처리", "ui.render_config.non_tile": "논타일 모드", + "ui.render_config.high_res_bloom_correction": "고해상도 블룸 보정", "ui.render_config.random_seed": "랜덤 시드 (선택 사항)", "ui.render_config.validation_title": "설정 오류", "ui.render_config.validation_error": "입력값을 확인해 주세요:\n- 너비와 높이는 0보다 큰 정수여야 합니다.\n- 렌더 FPS와 영상 FPS는 0보다 커야 합니다.\n- 종료 후 대기 시간은 0 이상이어야 합니다.", diff --git a/Patch/Game/VideoBloomPatch.cs b/Patch/Game/VideoBloomPatch.cs new file mode 100644 index 0000000..8a497d6 --- /dev/null +++ b/Patch/Game/VideoBloomPatch.cs @@ -0,0 +1,59 @@ +using ADOFAIRenderer.Core; +using HarmonyLib; +using UnityEngine; + +namespace ADOFAIRenderer.Patch.Game; + +/// +/// Makes the game's bloom compute its blur size as if the frame were 720 pixels high +/// ("High-Resolution Bloom Correction" option). +/// +/// Patched member: VideoBloom.OnRenderImage(RenderTexture source, RenderTexture destination). The game scales the +/// frame down to a bloom buffer 198 pixels high (scale = 198 / source.height) and sets the blur radius in bloom +/// buffer pixels to KernelSize * MediumKernelScale * scale (and KernelSize * LargeKernelScale * scale). +/// BloomBlit turns the radius into the number of 5-tap blur passes and the blend amount of the last pass. +/// +/// +/// While the render is active () and AdvancedConfig.HighResBloomCorrection is +/// enabled, the prefix multiplies KernelSize by source.height / 720 for the duration of the call, so the +/// radius equals KernelSize * MediumKernelScale * 198 / 720 and the blur passes on the bloom buffer are the same +/// as in a 720p frame. The finalizer restores the original KernelSize. The bloom buffer size and the downscale +/// from the source frame keep the game's behavior. +/// +/// +[HarmonyPatch(typeof(VideoBloom))] +public class VideoBloomPatch +{ + /// Frame height in pixels the blur size is computed for. + private const float ReferenceHeight = 720f; + + /// Prefix of VideoBloom.OnRenderImage. + /// The bloom component on the game camera. + /// The camera frame; its height selects the multiplier. + /// Original KernelSize, or null when it was not changed. + [HarmonyPrefix] + [HarmonyPatch("OnRenderImage")] + // ReSharper disable InconsistentNaming + public static void ScaleKernelSize(VideoBloom __instance, RenderTexture source, out float? __state) + // ReSharper restore InconsistentNaming + { + __state = null; + if (!source || !RenderManager.IsActive || !RenderManager.Instance.Configuration.AdvancedConfig.HighResBloomCorrection) + return; + + __state = __instance.KernelSize; + __instance.KernelSize *= source.height / ReferenceHeight; + } + + /// Finalizer of VideoBloom.OnRenderImage: restores KernelSize when the prefix changed it. + /// The bloom component on the game camera. + /// Original KernelSize stored by the prefix, or null. + [HarmonyFinalizer] + [HarmonyPatch("OnRenderImage")] + // ReSharper disable InconsistentNaming + public static void RestoreKernelSize(VideoBloom __instance, float? __state) + // ReSharper restore InconsistentNaming + { + if (__state.HasValue) __instance.KernelSize = __state.Value; + } +} diff --git a/UI/Window/RenderConfigWindow.cs b/UI/Window/RenderConfigWindow.cs index 28a81f9..ef29a38 100644 --- a/UI/Window/RenderConfigWindow.cs +++ b/UI/Window/RenderConfigWindow.cs @@ -139,6 +139,7 @@ internal static class RenderConfigWindow private static O5InputField? _randomSeedInput; private static O5Toggle? _processAudioToggle; private static O5Toggle? _nonTileToggle; + private static O5Toggle? _highResBloomCorrectionToggle; // Planet color overrides: the toggle enables the override, the picker holds the color. A disabled toggle stores // null in AdvancedConfig (no override is applied by PersistencePatch) and blocks the picker. @@ -698,6 +699,7 @@ internal static class RenderConfigWindow // Section 6: Advanced Settings card (RenderConfig.AdvancedConfig) // File Extension | Random Seed // Process Audio | Non-Tile Mode + // High-Resolution Bloom Correction | // "Planet Colors" label, then three columns: Fire | Ice | Wind (toggle + color picker each) // --------------------------------------------------------------------------------------------- // 7. Advanced Settings (Collapsible Card) @@ -739,6 +741,23 @@ internal static class RenderConfigWindow generateLabel: false ); + // High-Resolution Bloom Correction (AdvancedConfig.HighResBloomCorrection): the game's bloom computes its blur + // size as if the frame were 720 pixels high (VideoBloomPatch) + _highResBloomCorrectionToggle = CreateToggleField( + ctx, CreateFieldSlot(advLeft), + labelKey: "", + defaultValue: false, + value: config.AdvancedConfig.HighResBloomCorrection, + toggleTextKey: "ui.render_config.high_res_bloom_correction", + id: "toggle_high_res_bloom_correction", + onChanged: val => + { + config.AdvancedConfig.HighResBloomCorrection = val; + MarkConfigModified(); + }, + generateLabel: false + ); + // Random Seed (AdvancedConfig.RandomSeed): integer passed to UnityEngine.Random.InitState before the game scene // loads; empty text stores null (no seeding). Text that is neither empty nor an integer keeps the previous value. _randomSeedInput = CreateInputField( @@ -980,6 +999,7 @@ internal static class RenderConfigWindow _randomSeedInput?.Set(config.AdvancedConfig.RandomSeed?.ToString() ?? "", false); _processAudioToggle?.Set(config.AdvancedConfig.ProcessAudio, false); _nonTileToggle?.Set(config.AdvancedConfig.NonTile, false); + _highResBloomCorrectionToggle?.Set(config.AdvancedConfig.HighResBloomCorrection, false); // Planet colors: toggle on when an override is stored; the picker shows the stored color or the default and is // blocked without an override @@ -1161,6 +1181,7 @@ internal static class RenderConfigWindow } if (_processAudioToggle != null) target.AdvancedConfig.ProcessAudio = _processAudioToggle.Value; if (_nonTileToggle != null) target.AdvancedConfig.NonTile = _nonTileToggle.Value; + if (_highResBloomCorrectionToggle != null) target.AdvancedConfig.HighResBloomCorrection = _highResBloomCorrectionToggle.Value; // Planet Colors target.AdvancedConfig.firePlanetColor = (_firePlanetToggle?.Value == true && _firePlanetPicker != null)