From 9d16524ded47483937600dd25ac50e646f7f5946 Mon Sep 17 00:00:00 2001 From: MelekTaus Moribundus Date: Mon, 6 Jul 2026 17:31:05 +0200 Subject: [PATCH] FurShell: remove scale-influence, curvature scales with player scale Drop the _FurScaleInfluence blend (fur stays a constant world length at any avatar size, as tuned to influence 0) and scale the gravity curvature droop by the avatar world scale (_FurObjScale) instead, so the bend tracks the player: hider 0.5 => smaller droop, hunter 1.5 => bigger droop. Co-Authored-By: Claude Opus 4.8 --- Assets/Scripts/Components/FurShellRenderer.cs | 17 ++------- Assets/Shaders/FurShell.shader | 38 ++++++++----------- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/Assets/Scripts/Components/FurShellRenderer.cs b/Assets/Scripts/Components/FurShellRenderer.cs index a5ff932..7dee4ad 100644 --- a/Assets/Scripts/Components/FurShellRenderer.cs +++ b/Assets/Scripts/Components/FurShellRenderer.cs @@ -41,11 +41,6 @@ namespace Chameleon.Components // direction so the flat shell slices merge into continuous strands at the rim instead of squished // dots. 0 = off (footprint stays circular everywhere). [SerializeField, Range(0f, 5f)] float _silhouetteStretch = 2f; - // How much the avatar's world scale drives the fur SIZE. 1 = fully proportional (small avatar => small - // fur, like the body). 0 = constant world size (same absolute fur length at any avatar scale). Fully - // proportional fur reads as too short on the small hider and too long on the big hunter, so dial this - // down to keep the fur closer to a fixed world length; the curl keeps its shape at any setting. - [SerializeField, Range(0f, 1f)] float _furScaleInfluence = 0.5f; [Header("Shading")] // Optional albedo (strand-colour) override for the standalone prototype; a sibling MeshPainter's @@ -124,7 +119,6 @@ namespace Chameleon.Components static readonly int WindFrequencyId = Shader.PropertyToID("_WindFrequency"); static readonly int WindDirectionId = Shader.PropertyToID("_WindDirection"); static readonly int FurObjScaleId = Shader.PropertyToID("_FurObjScale"); - static readonly int FurScaleInfluenceId = Shader.PropertyToID("_FurScaleInfluence"); static readonly int XRayColorId = Shader.PropertyToID("_XRayColor"); static readonly int XRayPulseSpeedId = Shader.PropertyToID("_XRayPulseSpeed"); static readonly int XRayPulseStrengthId = Shader.PropertyToID("_XRayPulseStrength"); @@ -264,14 +258,11 @@ namespace Chameleon.Components _mpb.SetFloat(WindFrequencyId, _windFrequency); _mpb.SetVector(WindDirectionId, new Vector4(_windDirection.x, _windDirection.y, 0f, 0f)); - // Avatar world scale (uniform via PlayerScaler) = the scale baked into the instance matrix used - // for the shell extrusion, so the shader can scale the curvature droop in lockstep and keep the - // curl proportional at any player size (hider 0.5 / hunter 1.5). Read from lossyScale because - // the raw object matrix does NOT carry the per-instance scale in the RenderMeshInstanced path. + // Avatar world scale (uniform via PlayerScaler). The shader keeps the fur a CONSTANT world length + // but scales the gravity droop by this, so the bend tracks the player (hider 0.5 / hunter 1.5). + // Read from lossyScale because the raw object matrix does NOT carry the per-instance scale in the + // RenderMeshInstanced path. _mpb.SetFloat(FurObjScaleId, transform.lossyScale.y); - // Blend the fur size between fully proportional to the avatar (1) and a constant world size (0): - // the shader pre-divides the object-space displacement so the net world length follows this. - _mpb.SetFloat(FurScaleInfluenceId, _furScaleInfluence); // Mirror the sibling painter's live body X-ray reveal onto the fur so the strands glow WITH the // reveal (see-through camo) instead of hiding it under opaque fur. Same colour/pulse/flash the diff --git a/Assets/Shaders/FurShell.shader b/Assets/Shaders/FurShell.shader index 80bc9f9..1f1edb2 100644 --- a/Assets/Shaders/FurShell.shader +++ b/Assets/Shaders/FurShell.shader @@ -60,13 +60,10 @@ Shader "Custom/FurShell" _WindFrequency("Wind Spatial Frequency", Range(0.0, 10.0)) = 1.0 _WindDirection("Wind Direction (object XZ)", Vector) = (1, 0, 0, 0) - // Avatar world scale, pushed by FurShellRenderer (transform.lossyScale). Drives the curvature droop - // so the curl stays proportional at any player size; 1 = authored size (see the vertex shader). + // Avatar world scale, pushed by FurShellRenderer (transform.lossyScale). The fur keeps a CONSTANT + // world length at any player size, but the gravity droop scales with this so the bend tracks the + // avatar: small hider => small droop, big hunter => big droop. 1 = authored size (vertex shader). [HideInInspector] _FurObjScale("Object Scale (driven by FurShellRenderer)", Float) = 1 - // How strongly the avatar scale drives the fur SIZE: 1 = fully proportional (fur shrinks/grows with the - // avatar), 0 = constant world size (same absolute fur length at any avatar scale). Proportional fur - // reads as too short on the small hider and too long on the big hunter, so this blends between them. - [HideInInspector] _FurScaleInfluence("Scale Influence (driven by FurShellRenderer)", Range(0, 1)) = 0.5 [Header(XRay Reveal)] // See-through reveal glow mirrored from the sibling MeshPainter's body X-ray so the fur strands glow @@ -150,7 +147,6 @@ Shader "Custom/FurShell" float _WindFrequency; float4 _WindDirection; float _FurObjScale; - float _FurScaleInfluence; half4 _XRayColor; half _XRayPulseSpeed; half _XRayPulseStrength; @@ -202,16 +198,14 @@ Shader "Custom/FurShell" float count = max(_ShellCount, 1.0); float h = (count <= 1.0) ? 0.0 : (float)shellIndex / (count - 1.0); - // Effective fur-size scale. The instance matrix already multiplies the object-space fur - // displacement by the avatar's world scale (_FurObjScale), so at full influence the fur is - // fully proportional (small avatar => small fur, like the body). _FurScaleInfluence blends - // that toward a CONSTANT world size (influence 0 => same absolute fur length at any avatar - // size), because fully proportional fur reads as too short on the small hider and too long on - // the big hunter. dispComp pre-divides the object-space offset so that after the matrix's - // *_FurObjScale the NET world displacement equals effScale, not _FurObjScale. + // The instance matrix multiplies the object-space fur displacement by the avatar's world scale + // (objScale), so a naive extrusion shrinks/grows 1:1 with the player - too short on the small + // hider, too long on the big hunter. To keep the fur a CONSTANT world length (same absolute + // length at any size) we pre-divide the object-space offset by objScale; after the matrix's + // *objScale the net world displacement is then 1x. Wind sway shares this so it stays a constant + // world size too. (The gravity droop below intentionally does NOT - it scales with objScale.) float objScale = max(_FurObjScale, 1e-4); - float effScale = lerp(1.0, objScale, _FurScaleInfluence); - float dispComp = effScale / objScale; + float dispComp = 1.0 / objScale; // extrude along the normal; bend the tips down with h^2 for a gravity/curl droop. // The (h + 0.02) lifts the whole stack a hair off the surface: the root (h=0) would else @@ -237,12 +231,12 @@ Shader "Custom/FurShell" // RenderMeshInstanced path - when it falls back to identity the droop follows the mesh's own // local down, so on the rotated player the fur hung sideways instead of toward true down. float3 positionWS = TransformObjectToWorld(posOS); - // Curl droop in world down. It uses the SAME effScale as the extrusion above (not the raw - // _FurObjScale), so the curl keeps its SHAPE relative to the fur length at any avatar size and - // at any influence setting - dialing the length toward constant carries the droop with it - // instead of leaving it too strong on the shrunk hider / too weak on the grown hunter. Applied - // after moving to world space, so the droop follows true world down regardless of player rotation. - positionWS.y -= _Curvature * _ShellLength * h * h * effScale; + // Curl droop in world down, scaled by the avatar's world size (objScale) so the bend tracks the + // player: a bigger avatar gets a proportionally bigger droop, a smaller one a smaller one. The + // fur length above is a CONSTANT world size, so scaling only the droop keeps the bend reading + // right relative to the body at every player scale (hider 0.5 / hunter 1.5). Applied after + // moving to world space, so the droop follows true world down regardless of player rotation. + positionWS.y -= _Curvature * _ShellLength * h * h * objScale; VertexNormalInputs vn = GetVertexNormalInputs(input.normalOS);