Skip to content

Instantly share code, notes, and snippets.

@DaEgi01
Last active May 3, 2023 00:24
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save DaEgi01/925d5613380f72a89277159836a8945c to your computer and use it in GitHub Desktop.
Save DaEgi01/925d5613380f72a89277159836a8945c to your computer and use it in GitHub Desktop.
Cities Skylines - Shadow Settings Adjuster
using Harmony;
using ICities;
using System;
using System.Reflection;
using UnityEngine;
using UnityEngine.Rendering;
namespace ShadowSettingsAdjuster
{
/// <summary>
/// Relight code ripoff to play around with all shadow settings.
/// </summary>
public class ShadowSettingsAdjusterMod : LoadingExtensionBase, IUserMod
{
public GameObject gameObject;
public ShadowSettingsAdjusterLogic shadowSettingsAdjusterLogic;
public string Name => "Shadow Settings Adjuster";
public string Description => "Tool to adjust all shadow settings.";
public override void OnLevelLoaded(LoadMode mode)
{
base.OnLevelLoaded(mode);
OnEnabled();
}
public override void OnLevelUnloading()
{
base.OnLevelUnloading();
if (gameObject != null)
{
GameObject.Destroy(gameObject);
gameObject = null;
}
}
public void OnEnabled()
{
while (true)
{
var obj = GameObject.Find(this.Name);
if (!obj) break;
GameObject.DestroyImmediate(obj);
}
gameObject = new GameObject(this.Name);
shadowSettingsAdjusterLogic = gameObject.AddComponent<ShadowSettingsAdjusterLogic>();
}
public void OnDisabled()
{
while (true)
{
var obj = GameObject.Find(this.Name);
if (!obj) break;
GameObject.DestroyImmediate(obj);
}
}
}
public enum ShadowDistanceType
{
@default = 0,
@fixed = 1,
}
public class LightShadowValues
{
public LightShadows type;
public float strength;
public LightShadowResolution resolution;
public float bias;
public float normalBias;
public float nearPlane;
}
public class QualityShadowValues
{
public ShadowQuality shadows;
public ShadowResolution resolution;
public ShadowProjection projection;
public ShadowDistanceType distanceType;
public float distanceFactor; //use a factor instead of a fixed value, since the value is based on camera view distance etc.
public float distanceFixed; //for tests with a fixed value.
public float nearPlaneOffset;
public int cascades;
public float globalShadowCascade2Split;
public Vector3 globalShadowCascade4Split;
}
public class ShadowSettingsAdjusterLogic : MonoBehaviour
{
private Rect windowRect = new Rect(200, 200, 460, 600);
private int instanceId;
private bool showUI = false;
private LightShadowValues defaultLightShadowValues;
private LightShadowValues lightShadowValues;
private QualityShadowValues defaultQualityShadowValues;
private QualityShadowValues qualityShadowValues;
void Start()
{
instanceId = this.GetInstanceID();
var mainLight = RenderManager.instance.MainLight;
this.defaultLightShadowValues = new LightShadowValues()
{
type = mainLight.shadows,
strength = mainLight.shadowStrength,
resolution = mainLight.shadowResolution,
bias = mainLight.shadowBias,
normalBias = mainLight.shadowNormalBias,
nearPlane = mainLight.shadowNearPlane
};
this.lightShadowValues = new LightShadowValues()
{
type = defaultLightShadowValues.type,
strength = defaultLightShadowValues.strength,
resolution = defaultLightShadowValues.resolution,
bias = defaultLightShadowValues.bias,
normalBias = defaultLightShadowValues.normalBias,
nearPlane = defaultLightShadowValues.nearPlane
};
this.defaultQualityShadowValues = new QualityShadowValues()
{
shadows = QualitySettings.shadows,
resolution = QualitySettings.shadowResolution,
projection = QualitySettings.shadowProjection,
distanceType = ShadowDistanceType.@default,
distanceFactor = 1f,
distanceFixed = QualitySettings.shadowDistance,
nearPlaneOffset = QualitySettings.shadowNearPlaneOffset,
cascades = QualitySettings.shadowCascades,
globalShadowCascade2Split = QualitySettings.shadowCascade2Split,
globalShadowCascade4Split = QualitySettings.shadowCascade4Split
};
this.qualityShadowValues = new QualityShadowValues()
{
shadows = defaultQualityShadowValues.shadows,
resolution = defaultQualityShadowValues.resolution,
projection = defaultQualityShadowValues.projection,
distanceType = defaultQualityShadowValues.distanceType,
distanceFactor = defaultQualityShadowValues.distanceFactor,
distanceFixed = defaultQualityShadowValues.distanceFixed,
nearPlaneOffset = defaultQualityShadowValues.nearPlaneOffset,
cascades = defaultQualityShadowValues.cascades,
globalShadowCascade2Split = defaultQualityShadowValues.globalShadowCascade2Split,
globalShadowCascade4Split = defaultQualityShadowValues.globalShadowCascade4Split
};
var harmony = HarmonyInstance.Create("egi.citiesskylinesmods.shadowsettingsadjuster");
ApplyHarmonyPatches(harmony);
}
void Update()
{
if (Input.GetKey(KeyCode.LeftShift) && Input.GetKey(KeyCode.LeftAlt) && Input.GetKeyDown(KeyCode.S))
{
showUI = !showUI;
}
}
void OnGUI()
{
if (showUI)
windowRect = GUI.Window(instanceId, windowRect, DrawWindow, "Shadow Settings Adjuster");
}
void DrawWindow(int id)
{
GUI.DragWindow(new Rect(0, 0, 460, 20));
if (GUI.Button(new Rect(430, 20, 25, 20), "x"))
showUI = false;
#region Directional Light - Shadow Settings
var positionY = 0;
AddHeader("Directional Light", ref positionY);
AddOption("Type", ref positionY, ref this.lightShadowValues.type, ref this.defaultLightShadowValues.type, x => (LightShadows)(int)x, x => (float)x, 0f, 2f, 1f);
AddOption("Strength", ref positionY, ref this.lightShadowValues.strength, ref this.defaultLightShadowValues.strength, x => x, x => x, 0f, 1f, 0.01f);
AddOption("Resolution", ref positionY, ref this.lightShadowValues.resolution, ref this.defaultLightShadowValues.resolution, x => (LightShadowResolution)(int)x, x => (float)x, -1f, 3f, 1f);
AddOption("Bias", ref positionY, ref this.lightShadowValues.bias, ref this.defaultLightShadowValues.bias, x => x, x => x, 0f, 2f, 0.01f);
AddOption("Normal bias", ref positionY, ref this.lightShadowValues.normalBias, ref this.defaultLightShadowValues.normalBias, x => x, x => x, 0f, 3f, 0.01f);
AddOption("Near plane", ref positionY, ref this.lightShadowValues.nearPlane, ref this.defaultLightShadowValues.nearPlane, x => x, x => x, 0.1f, 10f, 0.1f);
#endregion
#region Project - Quality Settings
AddHeader("Quality Settings", ref positionY);
AddOption("Shadows", ref positionY, ref this.qualityShadowValues.shadows, ref this.defaultQualityShadowValues.shadows, x => (ShadowQuality)(int)x, x => (float)x, 0f, 2f, 1f);
AddOption("Resolution", ref positionY, ref this.qualityShadowValues.resolution, ref this.defaultQualityShadowValues.resolution, x => (ShadowResolution)(int)x, x => (float)x, 0f, 3f, 1f);
AddOption("Projection", ref positionY, ref this.qualityShadowValues.projection, ref this.defaultQualityShadowValues.projection, x => (ShadowProjection)(int)x, x => (float)x, 0f, 1f, 1f);
AddOption("Distance type", ref positionY, ref this.qualityShadowValues.distanceType, ref this.defaultQualityShadowValues.distanceType, x => (ShadowDistanceType)(int)x, x => (float)x, 0f, 1f, 1f);
if (this.qualityShadowValues.distanceType == ShadowDistanceType.@default)
{
AddOption("Distance (factor)", ref positionY, ref this.qualityShadowValues.distanceFactor, ref this.defaultQualityShadowValues.distanceFactor, x => x, x => x, 0.125f, 4.0f, 0.125f);
}
else if (this.qualityShadowValues.distanceType == ShadowDistanceType.@fixed)
{
AddOption("Distance (fixed)", ref positionY, ref this.qualityShadowValues.distanceFixed, ref this.defaultQualityShadowValues.distanceFixed, x => x, x => x, 0f, 10000.0f, 10f);
}
AddOption("Near plane offset", ref positionY, ref this.qualityShadowValues.nearPlaneOffset, ref this.defaultQualityShadowValues.nearPlaneOffset, x => x, x => x, 0f, 100000, 1f);
AddOption("Cascades", ref positionY, ref this.qualityShadowValues.cascades, ref this.defaultQualityShadowValues.cascades, x => (int)x, x => x, 0f, 4f, 2f);
if (this.qualityShadowValues.cascades == 2)
{
AddOption("1 <|> 2", ref positionY, ref this.qualityShadowValues.globalShadowCascade2Split, ref this.defaultQualityShadowValues.globalShadowCascade2Split, x => x, x => x, 0f, 1f, 0.01f);
}
else if (this.qualityShadowValues.cascades == 4)
{
GUI.Label(new Rect(5, positionY, 115, 30), "1 <|> 2");
var casc4split12 = Mathf.Round(GUI.HorizontalSlider(new Rect(120, positionY + 5, 270, 25), this.qualityShadowValues.globalShadowCascade4Split.x, 0f, 1f) / 0.01f) * 0.01f;
if (GUI.Button(new Rect(395, positionY, 50, 30), this.qualityShadowValues.globalShadowCascade4Split.x.ToString(), GUI.skin.label))
casc4split12 = this.defaultQualityShadowValues.globalShadowCascade4Split.x;
positionY += 30;
GUI.Label(new Rect(5, positionY, 115, 30), "2 <|> 3");
var casc4split23 = Mathf.Round(GUI.HorizontalSlider(new Rect(120, positionY + 5, 270, 25), this.qualityShadowValues.globalShadowCascade4Split.y, 0f, 1f) / 0.01f) * 0.01f;
if (GUI.Button(new Rect(395, positionY, 50, 30), this.qualityShadowValues.globalShadowCascade4Split.y.ToString(), GUI.skin.label))
casc4split23 = this.defaultQualityShadowValues.globalShadowCascade4Split.y;
positionY += 30;
GUI.Label(new Rect(5, positionY, 115, 30), "3 <|> 4");
var casc4split34 = Mathf.Round(GUI.HorizontalSlider(new Rect(120, positionY + 5, 270, 25), this.qualityShadowValues.globalShadowCascade4Split.z, 0f, 1f) / 0.01f) * 0.01f;
if (GUI.Button(new Rect(395, positionY, 50, 30), this.qualityShadowValues.globalShadowCascade4Split.z.ToString(), GUI.skin.label))
casc4split34 = this.defaultQualityShadowValues.globalShadowCascade4Split.z;
positionY += 30;
this.qualityShadowValues.globalShadowCascade4Split = CalculateGlobalShadowCascade4Split(casc4split12, casc4split23, casc4split34);
}
#endregion
}
private void AddHeader(string name, ref int positionY)
{
positionY += 30;
GUI.Label(new Rect(5, positionY, 115, 30), name);
positionY += 30;
}
private void AddOption<T>(string name, ref int positionY, ref T value, ref T defaultValue, Func<float, T> toFieldConverter, Func<T, float> toFloatConverter, float minValue, float maxValue, float stepSize)
{
GUI.Label(new Rect(5, positionY, 115, 30), name);
value = toFieldConverter(Mathf.Round(GUI.HorizontalSlider(new Rect(120, positionY + 5, 270, 25), toFloatConverter(value), minValue, maxValue) / stepSize) * stepSize);
if (GUI.Button(new Rect(395, positionY, 50, 30), value.ToString(), GUI.skin.label))
value = defaultValue;
positionY += 30;
}
private Vector3 CalculateGlobalShadowCascade4Split(float split12, float split23, float split34)
{
if (split23 > split34)
split23 = split34;
if (split12 > split23)
split12 = split23;
return new Vector3(split12, split23, split34);
}
private void ApplyHarmonyPatches(HarmonyInstance harmony)
{
var updateLighting = typeof(DayNightProperties).GetMethod("UpdateLighting", BindingFlags.Instance | BindingFlags.NonPublic);
var updateLightingPostfix = typeof(ShadowSettingsAdjusterLogic).GetMethod(nameof(UpdateLightingPostfix), BindingFlags.Static | BindingFlags.NonPublic);
harmony.Patch(updateLighting, null, new HarmonyMethod(updateLightingPostfix));
var updateTransform = typeof(CameraController).GetMethod("UpdateTransform", BindingFlags.Instance | BindingFlags.NonPublic);
var updateTransformPostfix = typeof(ShadowSettingsAdjusterLogic).GetMethod(nameof(UpdateTransformPostfix), BindingFlags.Static | BindingFlags.NonPublic);
harmony.Patch(updateTransform, null, new HarmonyMethod(updateTransformPostfix));
var lateUpdate = typeof(CameraController).GetMethod("LateUpdate", BindingFlags.Instance | BindingFlags.NonPublic);
var lateUpdatePostfix = typeof(ShadowSettingsAdjusterLogic).GetMethod(nameof(LateUpdatePostfix), BindingFlags.Static | BindingFlags.NonPublic);
harmony.Patch(lateUpdate, null, new HarmonyMethod(lateUpdatePostfix));
}
private static void UpdateLightingPostfix(DayNightProperties __instance)
{
var mainLight = RenderManager.instance.MainLight;
var gameObject = GameObject.Find("Shadow Settings Adjuster");
var shadowSettingsAdjusterLogic = gameObject.GetComponent<ShadowSettingsAdjusterLogic>();
//adjusted in CameraController.LateUpdate
//mainLight.shadows = shadowSettingsAdjusterLogic.lightShadowValues.type;
mainLight.shadowStrength = shadowSettingsAdjusterLogic.lightShadowValues.strength;
mainLight.shadowResolution = shadowSettingsAdjusterLogic.lightShadowValues.resolution;
mainLight.shadowBias = shadowSettingsAdjusterLogic.lightShadowValues.bias;
mainLight.shadowNormalBias = shadowSettingsAdjusterLogic.lightShadowValues.normalBias;
mainLight.shadowNearPlane = shadowSettingsAdjusterLogic.lightShadowValues.nearPlane;
QualitySettings.shadows = shadowSettingsAdjusterLogic.qualityShadowValues.shadows;
QualitySettings.shadowResolution = shadowSettingsAdjusterLogic.qualityShadowValues.resolution;
QualitySettings.shadowProjection = shadowSettingsAdjusterLogic.qualityShadowValues.projection;
//adjusted in CameraController.UpdateTransform
//QualitySettings.shadowDistance = shadowSettingsAdjusterLogic.qualityShadowValues.distance;
QualitySettings.shadowNearPlaneOffset = shadowSettingsAdjusterLogic.qualityShadowValues.nearPlaneOffset;
QualitySettings.shadowCascades = shadowSettingsAdjusterLogic.qualityShadowValues.cascades;
QualitySettings.shadowCascade2Split = shadowSettingsAdjusterLogic.qualityShadowValues.globalShadowCascade2Split;
QualitySettings.shadowCascade4Split = shadowSettingsAdjusterLogic.qualityShadowValues.globalShadowCascade4Split;
}
private static void UpdateTransformPostfix(CameraController __instance)
{
var gameObject = GameObject.Find("Shadow Settings Adjuster");
var shadowSettingsAdjusterLogic = gameObject.GetComponent<ShadowSettingsAdjusterLogic>();
if (shadowSettingsAdjusterLogic.qualityShadowValues.distanceType == ShadowDistanceType.@default)
{
QualitySettings.shadowDistance = QualitySettings.shadowDistance * shadowSettingsAdjusterLogic.qualityShadowValues.distanceFactor;
}
else
{
QualitySettings.shadowDistance = shadowSettingsAdjusterLogic.qualityShadowValues.distanceFixed;
}
}
private static void LateUpdatePostfix(CameraController __instance)
{
var mainLight = RenderManager.instance.MainLight;
var gameObject = GameObject.Find("Shadow Settings Adjuster");
var shadowSettingsAdjusterLogic = gameObject.GetComponent<ShadowSettingsAdjusterLogic>();
mainLight.shadows = shadowSettingsAdjusterLogic.lightShadowValues.type;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment