From f440527d44015f8185f3ffa266ace7cf9c6804ef Mon Sep 17 00:00:00 2001 From: adfriz <76892624+adfriz@users.noreply.github.com> Date: Tue, 23 Jun 2026 22:30:13 +0700 Subject: [PATCH] Optimize shadow rendering: static object draw state caching and low-res far shadows option --- .../Shadows/CascadedShadowCaster.cs | 8 ++- .../LibRender2/Shadows/CascadedShadowMap.cs | 27 +++++--- source/LibRender2/Shadows/Shadows.cs | 64 ++++++++++++------- source/ObjectViewer/formOptions.cs | 31 +++++++++ source/OpenBVE/System/Options.cs | 2 + .../OpenBVE/UserInterface/formMain.Options.cs | 6 ++ source/OpenBVE/UserInterface/formMain.cs | 19 ++++++ .../System/BaseOptions.OptionsKey.cs | 1 + source/OpenBveApi/System/BaseOptions.cs | 2 + source/RouteViewer/formOptions.cs | 36 ++++++++++- 10 files changed, 160 insertions(+), 36 deletions(-) diff --git a/source/LibRender2/Shadows/CascadedShadowCaster.cs b/source/LibRender2/Shadows/CascadedShadowCaster.cs index 6c9f63ecc..6cf0eac19 100644 --- a/source/LibRender2/Shadows/CascadedShadowCaster.cs +++ b/source/LibRender2/Shadows/CascadedShadowCaster.cs @@ -23,7 +23,7 @@ public class CascadedShadowCaster public double DepthMargin { get; set; } = 40.0; /// Active shadow map resolution (used for texel snapping). - public int Resolution { get; set; } = 2048; + public int[] Resolutions { get; set; } /// Per-cascade light-space VP matrices. public Matrix4D[] LightSpaceMatrices { get; private set; } @@ -45,10 +45,12 @@ public CascadedShadowCaster(int cascadeCount = 3) LightSpaceMatrices = new Matrix4D[cascadeCount]; SplitDistances = new float[cascadeCount]; CascadeBiases = new float[cascadeCount]; + Resolutions = new int[cascadeCount]; for (int i = 0; i < cascadeCount; i++) { LightSpaceMatrices[i] = Matrix4D.Identity; + Resolutions[i] = 2048; } } @@ -120,7 +122,7 @@ public void Update(Vector3 lightDirection, Matrix4D cameraView, Matrix4D cameraP // === Texel snapping to prevent shadow swimming on camera move === // Snap the light-space center to texel boundaries // Each texel covers (2*orthoSize / Resolution) world units - double worldTexelSize = (orthoSize * 2.0) / (double)Resolution; + double worldTexelSize = (orthoSize * 2.0) / (double)Resolutions[i]; // Transform center into light view space to snap it Vector3 centerLS = TransformPoint(center, lightView); @@ -145,7 +147,7 @@ public void Update(Vector3 lightDirection, Matrix4D cameraView, Matrix4D cameraP // Z-Bias: Convert physical texel size into a Depth Buffer fraction. // This ensures we push the depth exactly enough to cure acne, but no more. - double texelWorldSize = (orthoSize * 2.0) / (double)Resolution; + double texelWorldSize = (orthoSize * 2.0) / (double)Resolutions[i]; double depthRange = zFar - zNear; double baseBias = texelWorldSize / depthRange; CascadeBiases[i] = (float)baseBias; diff --git a/source/LibRender2/Shadows/CascadedShadowMap.cs b/source/LibRender2/Shadows/CascadedShadowMap.cs index 2f2f505e2..dd6752e71 100644 --- a/source/LibRender2/Shadows/CascadedShadowMap.cs +++ b/source/LibRender2/Shadows/CascadedShadowMap.cs @@ -12,8 +12,11 @@ public class CascadedShadowMap : IDisposable /// Number of cascades (shadow splits). public int CascadeCount { get; private set; } - /// Shadow map resolution per cascade. - public int Resolution { get; private set; } + /// Base shadow map resolution. + public int BaseResolution { get; private set; } + + /// Resolutions per cascade. + public int[] Resolutions { get; private set; } /// Per-cascade FBO handles. public int[] FBOs { get; private set; } @@ -26,15 +29,18 @@ public class CascadedShadowMap : IDisposable /// /// Number of cascades (typically 3 or 4). /// Resolution of each cascade's depth texture. - public CascadedShadowMap(int cascadeCount = 3, int resolution = 2048) + /// Whether to downscale resolutions for far cascades. + public CascadedShadowMap(int cascadeCount = 3, int resolution = 2048, bool lowResFarShadows = true) { CascadeCount = cascadeCount; - Resolution = resolution; + BaseResolution = resolution; FBOs = new int[cascadeCount]; DepthTextures = new int[cascadeCount]; + Resolutions = new int[cascadeCount]; for (int i = 0; i < cascadeCount; i++) { + Resolutions[i] = (lowResFarShadows && i > 0) ? Math.Max(128, resolution >> i) : resolution; CreateCascade(i); } } @@ -45,24 +51,27 @@ public CascadedShadowMap(int cascadeCount = 3, int resolution = 2048) /// /// New number of cascades. /// New resolution per cascade. - public void Resize(int newCascadeCount, int newResolution) + /// Whether to downscale resolutions for far cascades. + public void Resize(int newCascadeCount, int newResolution, bool lowResFarShadows = true) { // Dispose old resources Dispose(); // Reallocate CascadeCount = newCascadeCount; - Resolution = newResolution; + BaseResolution = newResolution; FBOs = new int[newCascadeCount]; DepthTextures = new int[newCascadeCount]; + Resolutions = new int[newCascadeCount]; for (int i = 0; i < newCascadeCount; i++) { + Resolutions[i] = (lowResFarShadows && i > 0) ? Math.Max(128, newResolution >> i) : newResolution; CreateCascade(i); } Console.WriteLine( - $"[CSM] Resized to {newCascadeCount} cascades at {newResolution}×{newResolution}"); + $"[CSM] Resized to {newCascadeCount} cascades with downscaling={lowResFarShadows}"); } private void CreateCascade(int index) @@ -72,7 +81,7 @@ private void CreateCascade(int index) GL.BindTexture(TextureTarget.Texture2D, DepthTextures[index]); GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.DepthComponent24, - Resolution, Resolution, 0, + Resolutions[index], Resolutions[index], 0, PixelFormat.DepthComponent, PixelType.Float, IntPtr.Zero); GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear); @@ -118,7 +127,7 @@ private void CreateCascade(int index) public void BindCascadeForWriting(int cascadeIndex) { GL.BindFramebuffer(FramebufferTarget.Framebuffer, FBOs[cascadeIndex]); - GL.Viewport(0, 0, Resolution, Resolution); + GL.Viewport(0, 0, Resolutions[cascadeIndex], Resolutions[cascadeIndex]); } /// Unbinds shadow FBO. diff --git a/source/LibRender2/Shadows/Shadows.cs b/source/LibRender2/Shadows/Shadows.cs index 82391bca8..3decddd8a 100644 --- a/source/LibRender2/Shadows/Shadows.cs +++ b/source/LibRender2/Shadows/Shadows.cs @@ -59,11 +59,11 @@ public void Initialize() { if (Map == null) { - Map = new CascadedShadowMap(cascadeCount, resolution); + Map = new CascadedShadowMap(cascadeCount, resolution, opts.LowResFarShadows); } else { - Map.Resize(cascadeCount, resolution); + Map.Resize(cascadeCount, resolution, opts.LowResFarShadows); } if (Caster == null || cascadeCount != Caster.CascadeCount) @@ -72,7 +72,7 @@ public void Initialize() } Caster.ShadowDistance = shadowDistance; - Caster.Resolution = resolution; + Caster.Resolutions = Map.Resolutions; Caster.SplitLambda = 0.75; Caster.DepthMargin = 150.0; @@ -119,7 +119,7 @@ public void RenderPass() // 2. Update cascade matrices // NOTE: We pass renderer.CurrentViewMatrix here which reflects the camera's rotation. // The Caster will use this to align the shadow frustums with the view direction. - Caster.Resolution = Map.Resolution; + Caster.Resolutions = Map.Resolutions; if (renderer.currentOptions.ShadowDrawDistance == ShadowDistance.ViewingDistance) { Caster.ShadowDistance = renderer.currentOptions.ViewingDistance; @@ -194,6 +194,9 @@ private void RenderFaces(IEnumerable faces, ref int lastVAO) private void RenderFacesFiltered(IEnumerable faces, ref int lastVAO, double maxDistanceSquared) { Vector3 cameraPos = renderer.Camera.AbsolutePosition; + ObjectState lastObject = null; + MeshMaterial? lastMaterial = null; + int lastTextureId = -1; foreach (var face in faces) { @@ -217,33 +220,50 @@ private void RenderFacesFiltered(IEnumerable faces, ref int lastVAO, } } - DepthShader.SetModelMatrix(state.ModelMatrix * renderer.Camera.TranslationMatrix); - DepthShader.SetTextureMatrix(state.TextureTranslation); - var material = face.Object.Prototype.Mesh.Materials[face.Face.Material]; if ((material.Flags & MaterialFlags.NoShadow) != 0 || material.BlendMode == MeshMaterialBlendMode.Additive) { continue; } - if (material.DaytimeTexture != null && renderer.currentHost.LoadTexture(ref material.DaytimeTexture, (OpenGlTextureWrapMode)(material.WrapMode ?? OpenGlTextureWrapMode.ClampClamp))) - { - GL.ActiveTexture(TextureUnit.Texture0); - GL.BindTexture(TextureTarget.Texture2D, material.DaytimeTexture.OpenGlTextures[(int)(material.WrapMode ?? OpenGlTextureWrapMode.ClampClamp)].Name); - DepthShader.SetHasTexture(true); - } - else + + // Cache and skip redundant object matrix/animation uniform updates + if (state != lastObject) { - DepthShader.SetHasTexture(false); + DepthShader.SetModelMatrix(state.ModelMatrix * renderer.Camera.TranslationMatrix); + DepthShader.SetTextureMatrix(state.TextureTranslation); + + if (state.Matricies != null && state.Matricies.Length > 0) + { + DepthShader.SetCurrentAnimationMatricies(state); + GL.BindBufferBase(BufferTarget.UniformBuffer, 0, state.MatrixBufferIndex); + } + lastObject = state; } - DepthShader.SetAlphaCutoff(0.5f); - DepthShader.SetMaterialAlpha(material.Color.A / 255.0f); - DepthShader.SetMaterialFlags(material.Flags); - - if (state.Matricies != null && state.Matricies.Length > 0) + // Cache and skip redundant material and texture state binds + if (!lastMaterial.HasValue || material != lastMaterial.Value) { - DepthShader.SetCurrentAnimationMatricies(state); - GL.BindBufferBase(BufferTarget.UniformBuffer, 0, state.MatrixBufferIndex); + int textureId = -1; + if (material.DaytimeTexture != null && renderer.currentHost.LoadTexture(ref material.DaytimeTexture, (OpenGlTextureWrapMode)(material.WrapMode ?? OpenGlTextureWrapMode.ClampClamp))) + { + textureId = material.DaytimeTexture.OpenGlTextures[(int)(material.WrapMode ?? OpenGlTextureWrapMode.ClampClamp)].Name; + } + + if (textureId != lastTextureId) + { + if (textureId != -1) + { + GL.ActiveTexture(TextureUnit.Texture0); + GL.BindTexture(TextureTarget.Texture2D, textureId); + } + DepthShader.SetHasTexture(textureId != -1); + lastTextureId = textureId; + } + + DepthShader.SetAlphaCutoff(0.5f); + DepthShader.SetMaterialAlpha(material.Color.A / 255.0f); + DepthShader.SetMaterialFlags(material.Flags); + lastMaterial = material; } VertexArrayObject vao = (VertexArrayObject)face.Object.Prototype.Mesh.VAO; diff --git a/source/ObjectViewer/formOptions.cs b/source/ObjectViewer/formOptions.cs index aa93a17b6..4397869ef 100644 --- a/source/ObjectViewer/formOptions.cs +++ b/source/ObjectViewer/formOptions.cs @@ -87,6 +87,26 @@ private formOptions() comboBoxBackwards.SelectedItem = Interface.CurrentOptions.CameraMoveBackward; checkBoxAutoReload.Checked = Interface.CurrentOptions.AutoReloadObjects; checkBoxShadowFilterCascades.Checked = Interface.CurrentOptions.ShadowFilterCascades; + + // Dynamically add LowResFarShadows checkbox + CheckBox checkBoxLowResFarShadows = new CheckBox(); + checkBoxLowResFarShadows.AutoSize = true; + checkBoxLowResFarShadows.Location = new System.Drawing.Point(160, 215); + checkBoxLowResFarShadows.Name = "checkBoxLowResFarShadows"; + checkBoxLowResFarShadows.Size = new System.Drawing.Size(15, 14); + checkBoxLowResFarShadows.Text = ""; + checkBoxLowResFarShadows.Checked = Interface.CurrentOptions.LowResFarShadows; + checkBoxLowResFarShadows.Enabled = (Interface.CurrentOptions.ShadowResolution != ShadowMapResolution.Off); + checkBoxLowResFarShadows.UseVisualStyleBackColor = true; + this.tabPageShadows.Controls.Add(checkBoxLowResFarShadows); + + Label labelLowResFarShadows = new Label(); + labelLowResFarShadows.AutoSize = true; + labelLowResFarShadows.Location = new System.Drawing.Point(6, 215); + labelLowResFarShadows.Name = "labelLowResFarShadows"; + labelLowResFarShadows.Size = new System.Drawing.Size(126, 13); + labelLowResFarShadows.Text = "Low-res Far Shadows:"; + this.tabPageShadows.Controls.Add(labelLowResFarShadows); } private void InitializeSunSliders() @@ -111,6 +131,12 @@ private void UpdateShadowControlsEnabled() trackBarSunElevation.Enabled = enabled; checkBoxShadowFilterCascades.Enabled = enabled; + + CheckBox checkBoxLowResFarShadows = this.tabPageShadows.Controls["checkBoxLowResFarShadows"] as CheckBox; + if (checkBoxLowResFarShadows != null) + { + checkBoxLowResFarShadows.Enabled = enabled; + } } private void comboBoxShadowResolution_SelectedIndexChanged(object sender, EventArgs e) @@ -281,6 +307,11 @@ private void CloseButton_Click(object sender, EventArgs e) Interface.CurrentOptions.ShadowBias = (double)numericUpDownShadowBias.Value; Interface.CurrentOptions.ShadowNormalBias = (double)numericUpDownShadowNormalBias.Value; Interface.CurrentOptions.ShadowFilterCascades = checkBoxShadowFilterCascades.Checked; + CheckBox checkBoxLowResFarShadows = this.tabPageShadows.Controls["checkBoxLowResFarShadows"] as CheckBox; + if (checkBoxLowResFarShadows != null) + { + Interface.CurrentOptions.LowResFarShadows = checkBoxLowResFarShadows.Checked; + } Interface.CurrentOptions.Save(Path.CombineFile(Program.FileSystem.SettingsFolder, "1.5.0/options_ov.cfg")); Program.RefreshObjects(); diff --git a/source/OpenBVE/System/Options.cs b/source/OpenBVE/System/Options.cs index db2bd96ce..be8545897 100644 --- a/source/OpenBVE/System/Options.cs +++ b/source/OpenBVE/System/Options.cs @@ -326,6 +326,7 @@ public override void Save(string fileName) Builder.AppendLine("shadowbias = " + ShadowBias.ToString(Culture)); Builder.AppendLine("shadownormalbias = " + ShadowNormalBias.ToString(Culture)); Builder.AppendLine("shadowfiltercascades = " + (ShadowFilterCascades ? "true" : "false")); + Builder.AppendLine("lowresfarshadows = " + (LowResFarShadows ? "true" : "false")); Builder.AppendLine("fpslimit = " + FPSLimit.ToString(Culture)); Builder.AppendLine(); Builder.AppendLine("[objectOptimization]"); @@ -540,6 +541,7 @@ internal static void LoadOptions() block.TryGetValue(OptionsKey.ShadowNormalBias, ref CurrentOptions.ShadowNormalBias); if (CurrentOptions.ShadowNormalBias < 0.0) CurrentOptions.ShadowNormalBias = 0.0; block.GetValue(OptionsKey.ShadowFilterCascades, out Interface.CurrentOptions.ShadowFilterCascades); + block.GetValue(OptionsKey.LowResFarShadows, out Interface.CurrentOptions.LowResFarShadows); block.GetValue(OptionsKey.FPSLimit, out CurrentOptions.FPSLimit); if (CurrentOptions.FPSLimit < 0) { diff --git a/source/OpenBVE/UserInterface/formMain.Options.cs b/source/OpenBVE/UserInterface/formMain.Options.cs index d6322bd84..d2fa01981 100644 --- a/source/OpenBVE/UserInterface/formMain.Options.cs +++ b/source/OpenBVE/UserInterface/formMain.Options.cs @@ -55,6 +55,12 @@ private void comboboxShadowResolution_SelectedIndexChanged(object sender, EventA updownShadowNormalBias.Enabled = shadowEnabled; checkboxShadowFilterCascades.Enabled = shadowEnabled; + CheckBox checkboxLowResFarShadows = this.groupboxShadows.Controls["checkboxLowResFarShadows"] as CheckBox; + if (checkboxLowResFarShadows != null) + { + checkboxLowResFarShadows.Enabled = shadowEnabled; + } + if (!shadowEnabled) { // Visual hint: grey out the strength label diff --git a/source/OpenBVE/UserInterface/formMain.cs b/source/OpenBVE/UserInterface/formMain.cs index f48c5853a..38470336f 100644 --- a/source/OpenBVE/UserInterface/formMain.cs +++ b/source/OpenBVE/UserInterface/formMain.cs @@ -499,6 +499,20 @@ private void formMain_Load(object sender, EventArgs e) checkboxShadowFilterCascades.Checked = Interface.CurrentOptions.ShadowFilterCascades; // Enable/disable shadow sub-controls based on resolution setting bool shadowEnabled = Interface.CurrentOptions.ShadowResolution != ShadowMapResolution.Off; + + // Dynamically add LowResFarShadows checkbox + this.groupboxShadows.Height = 265; + CheckBox checkboxLowResFarShadows = new CheckBox(); + checkboxLowResFarShadows.AutoSize = true; + checkboxLowResFarShadows.Location = new System.Drawing.Point(8, 238); + checkboxLowResFarShadows.Name = "checkboxLowResFarShadows"; + checkboxLowResFarShadows.Size = new System.Drawing.Size(150, 17); + checkboxLowResFarShadows.Text = "Low-res Far Shadows"; + checkboxLowResFarShadows.Checked = Interface.CurrentOptions.LowResFarShadows; + checkboxLowResFarShadows.Enabled = shadowEnabled; + checkboxLowResFarShadows.UseVisualStyleBackColor = true; + this.groupboxShadows.Controls.Add(checkboxLowResFarShadows); + comboboxShadowDistance.Enabled = shadowEnabled; comboboxShadowCascades.Enabled = shadowEnabled; checkboxShadowFilterCascades.Enabled = shadowEnabled; @@ -1264,6 +1278,11 @@ private void formMain_FormClosing() Interface.CurrentOptions.ShadowBias = (double)updownShadowBias.Value; Interface.CurrentOptions.ShadowNormalBias = (double)updownShadowNormalBias.Value; Interface.CurrentOptions.ShadowFilterCascades = checkboxShadowFilterCascades.Checked; + CheckBox checkboxLowResFarShadows = this.groupboxShadows.Controls["checkboxLowResFarShadows"] as CheckBox; + if (checkboxLowResFarShadows != null) + { + Interface.CurrentOptions.LowResFarShadows = checkboxLowResFarShadows.Checked; + } Interface.CurrentOptions.GameMode = (GameMode)comboboxMode.SelectedIndex; Interface.CurrentOptions.BlackBox = checkboxBlackBox.Checked; Interface.CurrentOptions.LoadingSway = checkBoxLoadingSway.Checked; diff --git a/source/OpenBveApi/System/BaseOptions.OptionsKey.cs b/source/OpenBveApi/System/BaseOptions.OptionsKey.cs index 9d93fac26..c0d2a2c40 100644 --- a/source/OpenBveApi/System/BaseOptions.OptionsKey.cs +++ b/source/OpenBveApi/System/BaseOptions.OptionsKey.cs @@ -57,6 +57,7 @@ public enum OptionsKey ShadowBias, ShadowNormalBias, ShadowFilterCascades, + LowResFarShadows, LightAzimuth, LightElevation, diff --git a/source/OpenBveApi/System/BaseOptions.cs b/source/OpenBveApi/System/BaseOptions.cs index 135f66022..32fa2ed06 100644 --- a/source/OpenBveApi/System/BaseOptions.cs +++ b/source/OpenBveApi/System/BaseOptions.cs @@ -96,6 +96,8 @@ public abstract class BaseOptions public double ShadowNormalBias = 2.0; /// Whether to filter shadow casters per cascade to improve performance. public bool ShadowFilterCascades = true; + /// Whether to downscale far cascade shadow map resolutions to improve performance. + public bool LowResFarShadows = true; /// The sun azimuth in degrees diff --git a/source/RouteViewer/formOptions.cs b/source/RouteViewer/formOptions.cs index 84ff0d240..d1c80ab32 100644 --- a/source/RouteViewer/formOptions.cs +++ b/source/RouteViewer/formOptions.cs @@ -89,6 +89,26 @@ public FormOptions() labelNearClip.Text = Translations.GetInterfaceString(OpenBveApi.Hosts.HostApplication.OpenBve, new[] { "options", "quality_distance_nearclip" }); } checkBoxShadowFilterCascades.Checked = Interface.CurrentOptions.ShadowFilterCascades; + + // Dynamically add LowResFarShadows checkbox + CheckBox checkBoxLowResFarShadows = new CheckBox(); + checkBoxLowResFarShadows.AutoSize = true; + checkBoxLowResFarShadows.Location = new System.Drawing.Point(160, 215); + checkBoxLowResFarShadows.Name = "checkBoxLowResFarShadows"; + checkBoxLowResFarShadows.Size = new System.Drawing.Size(15, 14); + checkBoxLowResFarShadows.Text = ""; + checkBoxLowResFarShadows.Checked = Interface.CurrentOptions.LowResFarShadows; + checkBoxLowResFarShadows.Enabled = (Interface.CurrentOptions.ShadowResolution != ShadowMapResolution.Off); + checkBoxLowResFarShadows.UseVisualStyleBackColor = true; + this.tabPageShadows.Controls.Add(checkBoxLowResFarShadows); + + Label labelLowResFarShadows = new Label(); + labelLowResFarShadows.AutoSize = true; + labelLowResFarShadows.Location = new System.Drawing.Point(6, 215); + labelLowResFarShadows.Name = "labelLowResFarShadows"; + labelLowResFarShadows.Size = new System.Drawing.Size(126, 13); + labelLowResFarShadows.Text = "Low-res Far Shadows:"; + this.tabPageShadows.Controls.Add(labelLowResFarShadows); } private void InitializeSunSliders() @@ -113,6 +133,12 @@ private void UpdateShadowControlsEnabled() trackBarSunAzimuth.Enabled = enabled; trackBarSunElevation.Enabled = enabled; checkBoxShadowFilterCascades.Enabled = enabled; + + CheckBox checkBoxLowResFarShadows = this.tabPageShadows.Controls["checkBoxLowResFarShadows"] as CheckBox; + if (checkBoxLowResFarShadows != null) + { + checkBoxLowResFarShadows.Enabled = enabled; + } } private void comboBoxShadowResolution_SelectedIndexChanged(object sender, EventArgs e) @@ -175,6 +201,7 @@ private void button1_Click(object sender, EventArgs e) double previousShadowBias = Interface.CurrentOptions.ShadowBias; double previousShadowNormalBias = Interface.CurrentOptions.ShadowNormalBias; bool previousShadowFilterCascades = Interface.CurrentOptions.ShadowFilterCascades; + bool previousLowResFarShadows = Interface.CurrentOptions.LowResFarShadows; //Interpolation mode InterpolationMode previousInterpolationMode = Interface.CurrentOptions.Interpolation; @@ -293,9 +320,14 @@ private void button1_Click(object sender, EventArgs e) Interface.CurrentOptions.ShadowBias = (double)numericUpDownShadowBias.Value; Interface.CurrentOptions.ShadowNormalBias = (double)numericUpDownShadowNormalBias.Value; Interface.CurrentOptions.ShadowFilterCascades = checkBoxShadowFilterCascades.Checked; + CheckBox checkBoxLowResFarShadows = this.tabPageShadows.Controls["checkBoxLowResFarShadows"] as CheckBox; + if (checkBoxLowResFarShadows != null) + { + Interface.CurrentOptions.LowResFarShadows = checkBoxLowResFarShadows.Checked; + } + - // Sun direction is already updated in real-time via slider events @@ -313,7 +345,7 @@ private void button1_Click(object sender, EventArgs e) if (previousInterpolationMode != Interface.CurrentOptions.Interpolation || previousAnisotropicLevel != Interface.CurrentOptions.AnisotropicFilteringLevel || GraphicsModeChanged || Interface.CurrentOptions.ViewingDistance != previousViewingDistance || previousShadowResolution != Interface.CurrentOptions.ShadowResolution || previousShadowDistance != Interface.CurrentOptions.ShadowDrawDistance || previousShadowCascades != Interface.CurrentOptions.ShadowCascades || previousShadowStrength != Interface.CurrentOptions.ShadowStrength || previousShadowBias != Interface.CurrentOptions.ShadowBias || previousShadowNormalBias != Interface.CurrentOptions.ShadowNormalBias || - Interface.CurrentOptions.NearClipBase != previousNearClipBase || previousShadowFilterCascades != Interface.CurrentOptions.ShadowFilterCascades) + Interface.CurrentOptions.NearClipBase != previousNearClipBase || previousShadowFilterCascades != Interface.CurrentOptions.ShadowFilterCascades || previousLowResFarShadows != Interface.CurrentOptions.LowResFarShadows) { this.DialogResult = DialogResult.OK; }