FurShell: scale-aware length/curvature + X-ray glow mirror
The fur is drawn from a CPU-baked unit-scale mesh whose instance matrix carries the avatar's world scale, so the extrusion scaled fully 1:1 with the player (hider 0.5 / hunter 1.5). That read as too-short fur on the small hider and too-long on the big hunter, and the curvature droop was sized off the raw unity_ObjectToWorld which doesn't carry the per-instance scale in the RenderMeshInstanced path. - FurShellRenderer pushes the avatar's lossyScale (_FurObjScale) plus a new Fur Scale Influence (0..1, default 0.5) that blends the fur SIZE between fully proportional (1) and a constant world length (0). The shader pre-divides the object-space displacement so the net world length follows effScale; curl droop and wind use the same effScale, keeping the curl SHAPE invariant across avatar sizes and influence settings. - The body's see-through X-ray reveal (forward half) is mirrored onto the visible fur so strands glow WITH the reveal instead of hiding it under opaque fur. FurShellRenderer pulls the live colour/pulse/flash from the sibling MeshPainter (new getters), in phase with the body. A new X-Ray Fur Strength (default 0.2) tames the per-shell accumulation, since the body writes the glow once (opaque) but the fur stacks N transparent shells that each composite it. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
1 parent
d9e4aec927
commit
9dc8f21528
3 files changed
+132
-5
No files matched your search
@@ -41,6 +41,11 @@ 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
|
||||
@@ -79,6 +84,14 @@ namespace Chameleon.Components
|
||||
// Sway direction in the object's XZ plane; normalized in the shader.
|
||||
[SerializeField] Vector2 _windDirection = new Vector2(1f, 0f);
|
||||
|
||||
[Header("X-Ray Reveal")]
|
||||
// The sibling MeshPainter's body see-through reveal is mirrored onto the fur so the strands glow WITH
|
||||
// the reveal instead of hiding it under opaque fur (colour/pulse are pulled from the painter, in phase
|
||||
// with the body). The fur draws up to _shellCount stacked TRANSPARENT shells, so that additive glow
|
||||
// composites once per overlapping layer and blows out; this scales the per-shell glow down to tame the
|
||||
// stacking so the total reads at roughly the body's intensity. Lower = subtler.
|
||||
[SerializeField, Range(0f, 1f)] float _xrayFurStrength = 0.2f;
|
||||
|
||||
[Header("Rendering")]
|
||||
[SerializeField] ShadowCastingMode _shadowMode = ShadowCastingMode.Off;
|
||||
[SerializeField] bool _receiveShadows = true;
|
||||
@@ -110,6 +123,13 @@ namespace Chameleon.Components
|
||||
static readonly int WindSpeedId = Shader.PropertyToID("_WindSpeed");
|
||||
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");
|
||||
static readonly int XRayPulseShapeId = Shader.PropertyToID("_XRayPulseShape");
|
||||
static readonly int XRayFurStrengthId = Shader.PropertyToID("_XRayFurStrength");
|
||||
|
||||
MeshFilter _meshFilter;
|
||||
SkinnedColliderBaker _baker;
|
||||
@@ -243,6 +263,31 @@ namespace Chameleon.Components
|
||||
_mpb.SetFloat(WindSpeedId, _windSpeed);
|
||||
_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.
|
||||
_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
|
||||
// body uses => in phase. ToRenderColor matches the raw MPB upload to Material.SetColor's gamma
|
||||
// handling (same convention as the fur tint above). Off (alpha 0) when no painter or not revealed
|
||||
// => the shader's additive term is zero, so the fur is unchanged.
|
||||
if (_painter)
|
||||
{
|
||||
_mpb.SetColor(XRayColorId, ToRenderColor(_painter.CurrentXRayColor));
|
||||
_mpb.SetFloat(XRayPulseSpeedId, _painter.XRayPulseSpeed);
|
||||
_mpb.SetFloat(XRayPulseStrengthId, _painter.XRayPulseStrength);
|
||||
_mpb.SetFloat(XRayPulseShapeId, _painter.XRayPulseShape);
|
||||
// Per-shell glow scale: the body writes the reveal once (opaque) but the fur stacks many
|
||||
// transparent shells that each composite it, so scale it down to keep the total near the body.
|
||||
_mpb.SetFloat(XRayFurStrengthId, _xrayFurStrength);
|
||||
}
|
||||
}
|
||||
|
||||
// MaterialPropertyBlock.SetColor uploads raw values (unlike Material.SetColor it does no gamma
|
||||
|
||||
Reference in new issue
Block a user