Aetheria/Assets/Shaders/AetheriaRenderSplatsCore.hlsl
2026-06-28 01:18:21 +01:00

115 lines
3 KiB
HLSL

#ifndef AETHERIA_RENDER_SPLATS_CORE_INCLUDED
#define AETHERIA_RENDER_SPLATS_CORE_INCLUDED
struct AetheriaSplat
{
float4 centerHalfExtent;
float4 rotationChannelFalloff;
float4 layerSource;
float4 sourceFrequencyPhase;
float4 value;
};
StructuredBuffer<AetheriaSplat> _AetheriaSplats;
float4x4 _AetheriaViewportToClip;
int _AetheriaSplatCount;
int _AetheriaChannelFilter;
float _ValueScale;
float hash21(float2 p)
{
p = frac(p * float2(123.34, 456.21));
p += dot(p, p + 45.32);
return frac(p.x * p.y);
}
float valueNoise(float2 p)
{
float2 i = floor(p);
float2 f = frac(p);
float a = hash21(i);
float b = hash21(i + float2(1, 0));
float c = hash21(i + float2(0, 1));
float d = hash21(i + float2(1, 1));
float2 u = f * f * (3 - 2 * f);
return lerp(lerp(a, b, u.x), lerp(c, d, u.x), u.y) * 2 - 1;
}
struct Varyings
{
float4 position : SV_POSITION;
float2 localUv : TEXCOORD0;
nointerpolation uint instanceId : TEXCOORD1;
};
Varyings Vert(uint vertexId : SV_VertexID, uint instanceId : SV_InstanceID)
{
static const float2 corners[6] =
{
float2(-1, -1),
float2(1, -1),
float2(1, 1),
float2(-1, -1),
float2(1, 1),
float2(-1, 1)
};
AetheriaSplat splat = _AetheriaSplats[instanceId];
float2 local = corners[vertexId];
float2 scaled = local * splat.centerHalfExtent.zw;
float c = splat.rotationChannelFalloff.x;
float s = splat.rotationChannelFalloff.y;
float2 world = splat.centerHalfExtent.xy + float2(
scaled.x * c - scaled.y * s,
scaled.x * s + scaled.y * c);
Varyings output;
output.position = mul(_AetheriaViewportToClip, float4(world, 0, 1));
output.localUv = local;
output.instanceId = instanceId;
return output;
}
float ResolveFalloff(float2 localUv, int falloff)
{
float distance01 = saturate(length(localUv));
if (falloff == 0)
return 1;
if (falloff == 1)
return saturate(1 - distance01);
if (falloff == 3)
{
float t = smoothstep(0, 1, distance01);
return t;
}
return 1 - smoothstep(0, 1, distance01);
}
float4 Frag(Varyings input) : SV_Target
{
AetheriaSplat splat = _AetheriaSplats[input.instanceId];
int channel = (int)round(splat.rotationChannelFalloff.z);
if (_AetheriaChannelFilter >= 0 && channel != _AetheriaChannelFilter)
discard;
int falloff = (int)round(splat.rotationChannelFalloff.w);
float alpha = ResolveFalloff(input.localUv, falloff);
clip(alpha - 0.0001);
int sourceKind = (int)round(splat.layerSource.y);
float source = 1;
if (sourceKind == 1 || sourceKind == 2)
{
float2 frequency = splat.sourceFrequencyPhase.xy;
float2 phase = splat.sourceFrequencyPhase.zw;
float timeOffset = sourceKind == 2 ? _Time.y * splat.layerSource.z : 0;
source = valueNoise(input.localUv * frequency + phase + timeOffset);
if (splat.layerSource.w != 0)
source = abs(source);
}
return splat.value * (alpha * source * _ValueScale);
}
#endif