Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions source/LibRender2/Shadows/CascadedShadowCaster.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class CascadedShadowCaster
public double DepthMargin { get; set; } = 40.0;

/// <summary>Active shadow map resolution (used for texel snapping).</summary>
public int Resolution { get; set; } = 2048;
public int[] Resolutions { get; set; }

/// <summary>Per-cascade light-space VP matrices.</summary>
public Matrix4D[] LightSpaceMatrices { get; private set; }
Expand All @@ -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;
}
}

Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
27 changes: 18 additions & 9 deletions source/LibRender2/Shadows/CascadedShadowMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ public class CascadedShadowMap : IDisposable
/// <summary>Number of cascades (shadow splits).</summary>
public int CascadeCount { get; private set; }

/// <summary>Shadow map resolution per cascade.</summary>
public int Resolution { get; private set; }
/// <summary>Base shadow map resolution.</summary>
public int BaseResolution { get; private set; }

/// <summary>Resolutions per cascade.</summary>
public int[] Resolutions { get; private set; }

/// <summary>Per-cascade FBO handles.</summary>
public int[] FBOs { get; private set; }
Expand All @@ -26,15 +29,18 @@ public class CascadedShadowMap : IDisposable
/// </summary>
/// <param name="cascadeCount">Number of cascades (typically 3 or 4).</param>
/// <param name="resolution">Resolution of each cascade's depth texture.</param>
public CascadedShadowMap(int cascadeCount = 3, int resolution = 2048)
/// <param name="lowResFarShadows">Whether to downscale resolutions for far cascades.</param>
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);
}
}
Expand All @@ -45,24 +51,27 @@ public CascadedShadowMap(int cascadeCount = 3, int resolution = 2048)
/// </summary>
/// <param name="newCascadeCount">New number of cascades.</param>
/// <param name="newResolution">New resolution per cascade.</param>
public void Resize(int newCascadeCount, int newResolution)
/// <param name="lowResFarShadows">Whether to downscale resolutions for far cascades.</param>
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)
Expand All @@ -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);
Expand Down Expand Up @@ -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]);
}

/// <summary>Unbinds shadow FBO.</summary>
Expand Down
64 changes: 42 additions & 22 deletions source/LibRender2/Shadows/Shadows.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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;

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -194,6 +194,9 @@ private void RenderFaces(IEnumerable<FaceState> faces, ref int lastVAO)
private void RenderFacesFiltered(IEnumerable<FaceState> 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)
{
Expand All @@ -217,33 +220,50 @@ private void RenderFacesFiltered(IEnumerable<FaceState> 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;
Expand Down
31 changes: 31 additions & 0 deletions source/ObjectViewer/formOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions source/OpenBVE/System/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]");
Expand Down Expand Up @@ -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)
{
Expand Down
6 changes: 6 additions & 0 deletions source/OpenBVE/UserInterface/formMain.Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions source/OpenBVE/UserInterface/formMain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions source/OpenBveApi/System/BaseOptions.OptionsKey.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public enum OptionsKey
ShadowBias,
ShadowNormalBias,
ShadowFilterCascades,
LowResFarShadows,
LightAzimuth,

LightElevation,
Expand Down
2 changes: 2 additions & 0 deletions source/OpenBveApi/System/BaseOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ public abstract class BaseOptions
public double ShadowNormalBias = 2.0;
/// <summary>Whether to filter shadow casters per cascade to improve performance.</summary>
public bool ShadowFilterCascades = true;
/// <summary>Whether to downscale far cascade shadow map resolutions to improve performance.</summary>
public bool LowResFarShadows = true;


/// <summary>The sun azimuth in degrees</summary>
Expand Down
Loading