From 114b2543e74111a810244e1143eacc023574af4f Mon Sep 17 00:00:00 2001 From: GlitchedReme <1445424285@qq.com> Date: Fri, 22 May 2026 23:03:07 +0800 Subject: [PATCH 1/2] feat(MaterialUtils): add hue replacement shader and refactor material creation methods --- Utils/MaterialUtils.ShaderSources.cs | 38 +++++++++++++++++ Utils/MaterialUtils.cs | 63 ++++++++++++++-------------- 2 files changed, 69 insertions(+), 32 deletions(-) create mode 100644 Utils/MaterialUtils.ShaderSources.cs diff --git a/Utils/MaterialUtils.ShaderSources.cs b/Utils/MaterialUtils.ShaderSources.cs new file mode 100644 index 00000000..038fa4b3 --- /dev/null +++ b/Utils/MaterialUtils.ShaderSources.cs @@ -0,0 +1,38 @@ +namespace STS2RitsuLib.Utils +{ + public static partial class MaterialUtils + { + private const string ReplaceHueShaderSource = """ + shader_type canvas_item; + + const vec3 LUMA_WEIGHTS = vec3(0.2126, 0.7152, 0.0722); + const float EPSILON = 1e-7; + const float MAX_COLOR_GAIN = 1.12; + + uniform vec3 target_color : source_color = vec3(1.0); + uniform float brightness : hint_range(0.0, 2.0) = 1.0; + + varying vec4 modulate_color; + + void vertex() { + modulate_color = COLOR; + } + + void fragment() { + vec4 col = texture(TEXTURE, UV); + + float max_rgb = max(max(col.r, col.g), col.b); + float min_rgb = min(min(col.r, col.g), col.b); + float value = max_rgb * brightness; + float saturation = (max_rgb - min_rgb) / (max_rgb + EPSILON); + + float target_value = max(max(target_color.r, target_color.g), target_color.b); + vec3 target_hue = target_color / max(target_value, EPSILON); + float color_gain = min(1.0 / max(dot(target_hue, LUMA_WEIGHTS), EPSILON), MAX_COLOR_GAIN); + + vec3 final = mix(vec3(value), target_color * value * color_gain, saturation); + COLOR = vec4(final, col.a) * modulate_color; + } + """; + } +} diff --git a/Utils/MaterialUtils.cs b/Utils/MaterialUtils.cs index 877e3ff8..3da54dca 100644 --- a/Utils/MaterialUtils.cs +++ b/Utils/MaterialUtils.cs @@ -6,7 +6,7 @@ namespace STS2RitsuLib.Utils /// Factory helpers for Godot materials that mirror vanilla game shaders. /// 用于镜像原版游戏着色器的 Godot 材质工厂辅助方法。 /// - public static class MaterialUtils + public static partial class MaterialUtils { private const string HsvShaderPath = "res://shaders/hsv.gdshader"; private const string DoomBarShaderPath = "res://scenes/combat/doom_bar.gdshader"; @@ -14,34 +14,45 @@ public static class MaterialUtils private static NoiseTexture2D? _vanillaDoomBarNoiseTexture; private static ShaderMaterial? _unmodulatedHsvMaterial; - private static Shader? GameHsvShader => (Shader?)GD.Load(HsvShaderPath)?.Duplicate(); + private static Shader? _gameHsvShader; + private static Shader GameHsvShader => _gameHsvShader ??= GD.Load(HsvShaderPath) ?? throw new InvalidOperationException($"Failed to load HSV shader ({HsvShaderPath})."); - private static Shader? GameDoomBarShader => (Shader?)GD.Load(DoomBarShaderPath)?.Duplicate(); + private static Shader? _gameDoomBarShader; + private static Shader? GameDoomBarShader => _gameDoomBarShader ??= GD.Load(DoomBarShaderPath) ?? throw new InvalidOperationException($"Failed to load doom bar shader ({DoomBarShaderPath})."); + + private static Shader? _replaceHueShader; + private static Shader ReplaceHueShader => _replaceHueShader ??= new Shader + { + Code = ReplaceHueShaderSource, + }; private static NoiseTexture2D VanillaDoomBarNoiseTexture => _vanillaDoomBarNoiseTexture ??= CreateVanillaDoomBarNoiseTexture(); + /// + /// Builds a ShaderMaterial using a custom shader that replaces the hue of the input texture + /// with a caller-specified RGB color, while preserving the original brightness and saturation. + /// Suitable for replacing hues when using vanilla card frames. A brightness parameter can be used + /// to adjust output brightness (range 0-2, default is 1). + /// 使用一个自定义着色器构建 ShaderMaterial,该着色器将输入纹理的色调替换为调用方指定的 RGB 颜色, + /// 同时保留原始亮度和饱和度。适用于使用原版卡框时替换色调。可传入亮度参数以调整输出亮度(范围 0-2,默认值为 1)。 + /// + public static ShaderMaterial CreateReplaceHueShaderMaterial(float r, float g, float b, float brightness = 1f) + { + var material = new ShaderMaterial { Shader = ReplaceHueShader }; + material.SetShaderParameter("target_color", new Vector3(r, g, b)); + material.SetShaderParameter("brightness", brightness); + return material; + } + /// /// Builds a ShaderMaterial using the game's HSV shader with the given RGB parameters. /// 使用游戏的 HSV 着色器和给定 RGB 参数构建 ShaderMaterial。 /// + [Obsolete("Prefer MaterialUtils.CreateReplaceHueShaderMaterial instead.")] public static ShaderMaterial CreateRgbShaderMaterial(float r, float g, float b) { - var max = Math.Max(r, Math.Max(g, b)); - var min = Math.Min(r, Math.Min(g, b)); - var delta = max - min; - - float h = 0; - if (delta != 0) - { - if (Mathf.IsEqualApprox(max, r)) h = (g - b) / delta + (g < b ? 6 : 0); - else if (Mathf.IsEqualApprox(max, g)) h = (b - r) / delta + 2; - else h = (r - g) / delta + 4; - h /= 6; - } - - var s = max == 0 ? 0 : delta / max; - return CreateHsvShaderMaterial(h, s, max); + return CreateReplaceHueShaderMaterial(r, g, b); } /// @@ -50,18 +61,10 @@ public static ShaderMaterial CreateRgbShaderMaterial(float r, float g, float b) /// public static ShaderMaterial CreateHsvShaderMaterial(float h, float s, float v) { - var shader = GameHsvShader ?? - throw new InvalidOperationException($"Failed to load HSV shader ({HsvShaderPath})."); - - var material = new ShaderMaterial - { - Shader = shader, - }; - + var material = new ShaderMaterial { Shader = GameHsvShader }; material.SetShaderParameter("h", h); material.SetShaderParameter("s", s); material.SetShaderParameter("v", v); - return material; } @@ -100,11 +103,7 @@ public static ShaderMaterial CreateDoomBarShaderMaterial(GradientTexture1D gradi { ArgumentNullException.ThrowIfNull(gradientTexture); - var shader = GameDoomBarShader; - if (shader == null) - throw new InvalidOperationException($"Failed to load doom bar shader ({DoomBarShaderPath})."); - - var material = new ShaderMaterial { Shader = shader }; + var material = new ShaderMaterial { Shader = GameDoomBarShader }; material.SetShaderParameter("noise_tex", VanillaDoomBarNoiseTexture); material.SetShaderParameter("gradient_tex", gradientTexture); return material; From 64e4aa37efe4c3b2e438bbc684e05b189600d690 Mon Sep 17 00:00:00 2001 From: GlitchedReme <1445424285@qq.com> Date: Sat, 23 May 2026 11:19:35 +0800 Subject: [PATCH 2/2] fix(MaterialUtils): update documentation for CreateReplaceHueShaderMaterial and CreateUnmodulatedHsvShaderMaterial --- Utils/MaterialUtils.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Utils/MaterialUtils.cs b/Utils/MaterialUtils.cs index 3da54dca..72220590 100644 --- a/Utils/MaterialUtils.cs +++ b/Utils/MaterialUtils.cs @@ -32,10 +32,11 @@ public static partial class MaterialUtils /// /// Builds a ShaderMaterial using a custom shader that replaces the hue of the input texture /// with a caller-specified RGB color, while preserving the original brightness and saturation. - /// Suitable for replacing hues when using vanilla card frames. A brightness parameter can be used - /// to adjust output brightness (range 0-2, default is 1). + /// Suitable for replacing hues when using vanilla card frames. + /// parameters r, g, b are in the range 0-1, brightness is in the range 0-2 with a default of 1. /// 使用一个自定义着色器构建 ShaderMaterial,该着色器将输入纹理的色调替换为调用方指定的 RGB 颜色, - /// 同时保留原始亮度和饱和度。适用于使用原版卡框时替换色调。可传入亮度参数以调整输出亮度(范围 0-2,默认值为 1)。 + /// 同时保留原始亮度和饱和度。适用于替换色调,例如给原版卡框换色。 + /// 参数r,g,b的范围是0-1,brightness的范围是0-2,默认值为1。 /// public static ShaderMaterial CreateReplaceHueShaderMaterial(float r, float g, float b, float brightness = 1f) { @@ -83,7 +84,7 @@ public static ShaderMaterial CreateHsvShaderMaterial(float h, float s, float v) public static ShaderMaterial CreateUnmodulatedHsvShaderMaterial() { _unmodulatedHsvMaterial ??= CreateHsvShaderMaterial(0f, 1f, 1f); - return (ShaderMaterial)_unmodulatedHsvMaterial.Duplicate(); + return _unmodulatedHsvMaterial; } ///