FurShell: Performance-Umbau (Distanz-LOD, Cutout+Early-Z, Tip-to-Root) + Deferred+/HTrace-Fixes
- Distanz-LOD am Renderer (Start/End/Min Shells, pro Kamera), Rendering Layer Mask wie beim MeshRenderer - Cutout in der AlphaTest-Queue statt Transparent-Blend (Early-Z killt Overdraw), Shells tip-to-root submitted - LightMode UniversalForwardOnly (Deferred+ hatte den Opaque-Pass sonst nicht gezeichnet) - FurGBuffer-Pass (ComplexLit-Muster): HTrace WSGI sieht das Fell, kein Durchscheinen mehr - Root-AO-Fix: Grading straehnen-relativ (gradeH) statt absoluter Shell-Hoehe -- schwarze Streifen weg - Distanz-Aggregations-Fade wieder entfernt (auf Wunsch, Fern-Straehnen bleiben voll) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4ed74a5005
commit
b4e4c19e54
2 files changed
+408
-262
No files matched your search
@@ -23,6 +23,14 @@ namespace Chameleon.Components
|
||||
[SerializeField] Material _furMaterial;
|
||||
[SerializeField, Range(1, 64)] int _shellCount = 32;
|
||||
|
||||
[Header("Distance LOD")]
|
||||
// Shell cost is linear in the shell count and nobody can count 32 shells from a few meters away:
|
||||
// full _shellCount up to Start, then a linear fade down to Min Shells at End (per camera, computed
|
||||
// at draw time). Min Shells 0 stops drawing the fur entirely beyond End.
|
||||
[SerializeField, Min(0f)] float _lodStartDistance = 8f;
|
||||
[SerializeField, Min(0f)] float _lodEndDistance = 30f;
|
||||
[SerializeField, Range(0, 64)] int _lodMinShells = 8;
|
||||
|
||||
[Header("Shape")]
|
||||
[SerializeField, Range(0f, 10f)] float _shellLength = 0.15f;
|
||||
[SerializeField, Range(1f, 1000f)] float _density = 250f;
|
||||
@@ -65,10 +73,9 @@ namespace Chameleon.Components
|
||||
[SerializeField, Range(0.01f, 1f)] float _furSoftness = 0.5f;
|
||||
// Per-strand edge anti-aliasing (soft strand silhouette + tip instead of hard clipped discs).
|
||||
[SerializeField, Range(0f, 1f)] float _strandSoftness = 0.3f;
|
||||
// Fragments at/below this coverage are discarded so they don't write depth (the shader has
|
||||
// ZWrite ON to get a correct per-pixel z-order between overlapping fur objects - transparents
|
||||
// otherwise sort per object and pop). Higher = harder strand edges, less halo where two fur
|
||||
// objects overlap.
|
||||
// Alpha-cutout threshold: fragments at/below this coverage are discarded, everything above draws
|
||||
// fully opaque (the fur renders on the AlphaTest queue - opaque + ZWrite gives per-pixel z-order
|
||||
// between fur objects AND early-Z overdraw rejection). Higher = thinner/harder strands.
|
||||
[SerializeField, Range(0f, 1f)] float _depthSortClip = 0.3f;
|
||||
// Opaque base shell. On for the standalone prototype (full coverage); turn OFF on the painted
|
||||
// avatar so the camo body shows between strands instead of being sealed under a solid root.
|
||||
@@ -82,14 +89,16 @@ namespace Chameleon.Components
|
||||
[SerializeField] Vector2 _windDirection = new Vector2(1f, 0f);
|
||||
|
||||
[Header("X-Ray Reveal")]
|
||||
// The fur draws up to _shellCount stacked TRANSPARENT shells, so the mirrored X-ray glow composites
|
||||
// once per overlapping layer and blows out; this scales the per-shell glow down so the total reads
|
||||
// at roughly the body's intensity. Lower = subtler.
|
||||
[SerializeField, Range(0f, 1f)] float _xrayFurStrength = 0.2f;
|
||||
// Fur-vs-body glow intensity match. Since the fur went opaque cutout only ONE fur fragment
|
||||
// survives per pixel (no shell stacking anymore), so this typically wants ~1 to match the body
|
||||
// instead of the old stack-taming ~0.2.
|
||||
[SerializeField, Range(0f, 1f)] float _xrayFurStrength = 1f;
|
||||
|
||||
[Header("Rendering")]
|
||||
[SerializeField] ShadowCastingMode _shadowMode = ShadowCastingMode.Off;
|
||||
[SerializeField] bool _receiveShadows = true;
|
||||
// Same dropdown/semantics as MeshRenderer's Rendering Layer Mask (decal/light layer filtering).
|
||||
[SerializeField] RenderingLayerMask _renderingLayerMask = RenderingLayerMask.defaultRenderingLayerMask;
|
||||
// Hide the plain MeshRenderer on this object so it doesn't double-draw under the fur (the root
|
||||
// shell already covers the surface). Leave off if you want a separately-shaded base mesh.
|
||||
[SerializeField] bool _hideSourceRenderer = true;
|
||||
@@ -197,9 +206,19 @@ namespace Chameleon.Components
|
||||
// to the on-disk state after OnEnable ran, and RenderMeshInstanced throws without it.
|
||||
if (!_furMaterial.enableInstancing) _furMaterial.enableInstancing = true;
|
||||
|
||||
int count = Mathf.Max(1, _shellCount);
|
||||
if (_instances == null || _instances.Length != count)
|
||||
_instances = new Matrix4x4[count];
|
||||
// Distance LOD: scale the shell count down with camera distance (full count inside Start,
|
||||
// Min Shells at/after End). Per camera on purpose - the draw is per camera anyway, so the
|
||||
// Scene View and a distant player camera each get their own appropriate count.
|
||||
int maxCount = Mathf.Max(1, _shellCount);
|
||||
float lodEnd = Mathf.Max(_lodEndDistance, _lodStartDistance + 0.001f);
|
||||
float lodT = Mathf.InverseLerp(_lodStartDistance, lodEnd, Vector3.Distance(cam.transform.position, transform.position));
|
||||
int count = Mathf.RoundToInt(Mathf.Lerp(maxCount, Mathf.Min(_lodMinShells, maxCount), lodT));
|
||||
if (count <= 0) return; // Min Shells 0 => beyond End the fur is culled entirely
|
||||
|
||||
// The array stays at the max size; RenderMeshInstanced only reads the first `count` entries,
|
||||
// so LOD changes don't reallocate per frame.
|
||||
if (_instances == null || _instances.Length != maxCount)
|
||||
_instances = new Matrix4x4[maxCount];
|
||||
|
||||
// all shells share the object's transform; they differ only by SV_InstanceID in the shader
|
||||
Matrix4x4 m = transform.localToWorldMatrix;
|
||||
@@ -214,6 +233,7 @@ namespace Chameleon.Components
|
||||
matProps = _mpb,
|
||||
shadowCastingMode = _shadowMode,
|
||||
receiveShadows = _receiveShadows,
|
||||
renderingLayerMask = _renderingLayerMask,
|
||||
};
|
||||
|
||||
for (int sub = 0; sub < mesh.subMeshCount; sub++)
|
||||
|
||||
Reference in new issue
Block a user