Урна: дух - светящаяся дымка (сложение One/One) вместо Fade с пятнами; necrogenie glow/tint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlexCube
2026-10-16 19:49:20 +03:00
co-authored by Claude Opus 5.5
parent 3ba6572bef
commit 49c7afe273
+121 -20
View File
@@ -14,8 +14,8 @@ namespace NecromancerTome
/// (TEFeatureCinerarium.HasSkull) и пропадает без него. Сдвиги Origin и выгрузку чанка переживает с моделью урны. /// (TEFeatureCinerarium.HasSkull) и пропадает без него. Сдвиги Origin и выгрузку чанка переживает с моделью урны.
/// ///
/// Модель - бандл necrotechnologist (NecroTechnologistSetup.cs): своя сетка на своём скелете NT_rig в позе покоя, /// Модель - бандл necrotechnologist (NecroTechnologistSetup.cs): своя сетка на своём скелете NT_rig в позе покоя,
/// глаза - отдельные рендереры (в имени «eye»). Тело - полупрозрачное, как у торговцев-призраков (alpha 0.3, Standard /// глаза - отдельные рендереры (в имени «eye»). Тело - светящаяся дымка (16.10: Fade давал пятна; сложение One/One,
/// в режиме Fade, как у крысы при проходе); текстура уже серая (сделана под обесцвечивание). Глаза - свой /// холодный оттенок Tint, своё слабое свечение Glow); текстура уже серая (сделана под обесцвечивание). Глаза - свой
/// непрозрачный светящийся материал из бандла, очередь отрисовки 3100 - после тела. /// непрозрачный светящийся материал из бандла, очередь отрисовки 3100 - после тела.
/// </summary> /// </summary>
public static class CinerariumGenie public static class CinerariumGenie
@@ -105,27 +105,55 @@ namespace NecromancerTome
{ {
m.shader = std; m.shader = std;
} }
// 16.10: Fade (SrcAlpha/OneMinusSrcAlpha) давал «прозрачный, но какими-то пятнами» и «не выглядит бесплотным
// духом»: без записи глубины треугольники одной сетки ложатся в случайном порядке. Свечение (One/One) от порядка
// не зависит: дух только добавляет свет к тому, что за ним, - дымка. Альфа здесь не нужна, яркость - цветом.
m.SetFloat("_Mode", 2f); m.SetFloat("_Mode", 2f);
m.SetOverrideTag("RenderType", "Transparent"); m.SetOverrideTag("RenderType", "Transparent");
m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.One);
m.SetInt("_ZWrite", 0); m.SetInt("_ZWrite", 0);
m.DisableKeyword("_ALPHATEST_ON"); m.DisableKeyword("_ALPHATEST_ON");
m.EnableKeyword("_ALPHABLEND_ON"); m.DisableKeyword("_ALPHABLEND_ON");
m.DisableKeyword("_ALPHAPREMULTIPLY_ON"); m.DisableKeyword("_ALPHAPREMULTIPLY_ON");
if (m.HasProperty("_Glossiness"))
{
m.SetFloat("_Glossiness", 0f); // без бликов: блик при сложении - белое пятно
}
if (m.HasProperty("_Metallic"))
{
m.SetFloat("_Metallic", 0f);
}
if (m.HasProperty("_EmissionMap") && m.HasProperty("_MainTex"))
{
m.SetTexture("_EmissionMap", m.GetTexture("_MainTex")); // своё свечение - чтобы ночью не пропадал
m.EnableKeyword("_EMISSION");
m.globalIlluminationFlags = MaterialGlobalIlluminationFlags.None;
}
m.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent; m.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
SetAlpha(m, Alpha); SetLook(m);
_ghostMats[_src] = m; _ghostMats[_src] = m;
return m; return m;
} }
static void SetAlpha(Material _m, float _a) /// <summary>Холодный бледный оттенок духа (умножается на серую текстуру).</summary>
public static Color Tint = new Color(0.55f, 0.75f, 1f, 1f);
/// <summary>Своё свечение тела, доля от Tint.</summary>
public static float Glow = 0.35f;
static void SetLook(Material _m)
{ {
if (_m != null && _m.HasProperty("_Color")) if (_m == null)
{ {
Color c = _m.color; return;
c.a = _a; }
_m.color = c; if (_m.HasProperty("_Color"))
{
_m.color = new Color(Tint.r * Alpha, Tint.g * Alpha, Tint.b * Alpha, 1f);
}
if (_m.HasProperty("_EmissionColor"))
{
_m.SetColor("_EmissionColor", new Color(Tint.r * Glow, Tint.g * Glow, Tint.b * Glow, 1f));
} }
} }
@@ -133,9 +161,58 @@ namespace NecromancerTome
{ {
foreach (Material m in _ghostMats.Values) foreach (Material m in _ghostMats.Values)
{ {
SetAlpha(m, Alpha); SetLook(m);
} }
} }
static readonly Dictionary<Material, Material> _stuckMats = new Dictionary<Material, Material>();
/// <summary>Почерневший дух (класть некуда): свечением чёрное не нарисовать, поэтому обычная Fade-копия, тёмная.</summary>
public static Material Stuck(Material _src)
{
if (_src == null)
{
return null;
}
if (_stuckMats.TryGetValue(_src, out Material m) && m != null)
{
return m;
}
m = new Material(_src) { name = _src.name + "_stuck" };
Shader std = Shader.Find("Standard");
if (std != null && m.shader != std)
{
m.shader = std;
}
m.SetFloat("_Mode", 2f);
m.SetOverrideTag("RenderType", "Transparent");
m.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
m.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
m.SetInt("_ZWrite", 1); // с глубиной: пятен нет, тёмный и почти плотный
m.DisableKeyword("_ALPHATEST_ON");
m.EnableKeyword("_ALPHABLEND_ON");
m.DisableKeyword("_ALPHAPREMULTIPLY_ON");
m.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
if (m.HasProperty("_Color"))
{
m.color = new Color(0.04f, 0.035f, 0.045f, 0.8f);
}
_stuckMats[_src] = m;
return m;
}
/// <summary>Исходный материал тела по его светящейся копии.</summary>
public static Material Source(Material _ghost)
{
foreach (KeyValuePair<Material, Material> p in _ghostMats)
{
if (p.Value == _ghost)
{
return p.Key;
}
}
return _ghost;
}
} }
/// <summary>Покачивание и поворот джинна над урной. Только вид - никакой логики.</summary> /// <summary>Покачивание и поворот джинна над урной. Только вид - никакой логики.</summary>
@@ -150,8 +227,8 @@ namespace NecromancerTome
public const float VisitSeconds = 2.2f; public const float VisitSeconds = 2.2f;
/// <summary>Почернел и замер (готовое класть некуда, решение 06.10).</summary> /// <summary>Почернел и замер (готовое класть некуда, решение 06.10).</summary>
bool _stuck; bool _stuck;
static readonly MaterialPropertyBlock _mpb = new MaterialPropertyBlock(); /// <summary>Светящиеся материалы рендереров - вернуть после почернения.</summary>
static readonly int ColorId = Shader.PropertyToID("_Color"); readonly Dictionary<Renderer, Material[]> _glowing = new Dictionary<Renderer, Material[]>();
/// <summary>_worldUnity - точка в координатах сцены (без Origin): низ блока, где дух работал.</summary> /// <summary>_worldUnity - точка в координатах сцены (без Origin): низ блока, где дух работал.</summary>
public void Visit(Vector3 _worldUnity) public void Visit(Vector3 _worldUnity)
@@ -177,15 +254,23 @@ namespace NecromancerTome
{ {
continue; continue;
} }
if (!_glowing.ContainsKey(r))
{
_glowing[r] = r.sharedMaterials;
}
Material[] glow = _glowing[r];
if (_on) if (_on)
{ {
r.GetPropertyBlock(_mpb); Material[] dark = new Material[glow.Length];
_mpb.SetColor(ColorId, new Color(0.04f, 0.035f, 0.045f, Mathf.Max(CinerariumGenie.Alpha, 0.6f))); for (int i = 0; i < glow.Length; i++)
r.SetPropertyBlock(_mpb); {
dark[i] = CinerariumGenie.Stuck(CinerariumGenie.Source(glow[i]));
}
r.sharedMaterials = dark;
} }
else else
{ {
r.SetPropertyBlock(null); r.sharedMaterials = glow;
} }
} }
} }
@@ -261,7 +346,9 @@ namespace NecromancerTome
"necrogenie fwd <m> - shift along the spirit's own forward (keeps the curled tail inside the neck)\n" + "necrogenie fwd <m> - shift along the spirit's own forward (keeps the curled tail inside the neck)\n" +
"necrogenie turn <deg/s> - idle turning speed\n" + "necrogenie turn <deg/s> - idle turning speed\n" +
"necrogenie bob <m> <s> - bobbing height and period\n" + "necrogenie bob <m> <s> - bobbing height and period\n" +
"necrogenie alpha <0-1> - body opacity"; "necrogenie alpha <0-1> - body brightness (the body adds light, it is not alpha-blended)\n" +
"necrogenie glow <0-1> - body's own glow (visible at night)\n" +
"necrogenie tint <r> <g> <b> - body colour";
} }
public override void Execute(List<string> _params, CommandSenderInfo _senderInfo) public override void Execute(List<string> _params, CommandSenderInfo _senderInfo)
@@ -291,6 +378,19 @@ namespace NecromancerTome
CinerariumGenie.Alpha = Mathf.Clamp01(v); CinerariumGenie.Alpha = Mathf.Clamp01(v);
CinerariumGenie.ApplyAlpha(); CinerariumGenie.ApplyAlpha();
break; break;
case "glow":
CinerariumGenie.Glow = Mathf.Clamp01(v);
CinerariumGenie.ApplyAlpha();
break;
case "tint":
if (_params.Count >= 4 &&
float.TryParse(_params[2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float g) &&
float.TryParse(_params[3], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float b))
{
CinerariumGenie.Tint = new Color(v, g, b, 1f);
CinerariumGenie.ApplyAlpha();
}
break;
default: default:
con.Output("necrogenie: unknown '" + _params[0] + "' - see 'help necrogenie'"); con.Output("necrogenie: unknown '" + _params[0] + "' - see 'help necrogenie'");
return; return;
@@ -298,7 +398,8 @@ namespace NecromancerTome
} }
con.Output("necrogenie: y " + CinerariumGenie.OffsetY.ToString("F2") + ", fwd " + CinerariumGenie.Forward.ToString("F2") + ", turn " + CinerariumGenie.TurnSpeed.ToString("F1") + con.Output("necrogenie: y " + CinerariumGenie.OffsetY.ToString("F2") + ", fwd " + CinerariumGenie.Forward.ToString("F2") + ", turn " + CinerariumGenie.TurnSpeed.ToString("F1") +
", bob " + CinerariumGenie.BobHeight.ToString("F2") + " / " + CinerariumGenie.BobPeriod.ToString("F1") + " s, alpha " + ", bob " + CinerariumGenie.BobHeight.ToString("F2") + " / " + CinerariumGenie.BobPeriod.ToString("F1") + " s, alpha " +
CinerariumGenie.Alpha.ToString("F2") + "; spirits on screen: " + CinerariumGenie.Live.Count); CinerariumGenie.Alpha.ToString("F2") + ", glow " + CinerariumGenie.Glow.ToString("F2") + ", tint " +
CinerariumGenie.Tint.r.ToString("F2") + " " + CinerariumGenie.Tint.g.ToString("F2") + " " + CinerariumGenie.Tint.b.ToString("F2") + "; spirits on screen: " + CinerariumGenie.Live.Count);
} }
} }
} }