Files
Ipseity-Demo/Assets/_Content/Shaders/EnkiBilal/Include/Mask.hlsl
T
2026-07-07 19:49:54 +02:00

216 lines
5.3 KiB
HLSL

#ifndef MASK_INCLUDED
#define MASK_INCLUDED
/*
MASK - MEDIUM COMPATIBLE VERSION
Goal:
Keep the original portal / projection behavior while avoiding unnecessary work
in cases where the mask is not supposed to affect the object.
Important compatibility point:
In the original shader, when all three masks are active, MaskID 0 is affected:
Alpha = (MaskID <= 1) ? 1 - mask : mask;
The previous Medium version incorrectly skipped MaskID 0 entirely.
That could break portal logic.
Changes from the original version:
- Preserves the original Alpha logic.
- Keeps rounded rectangle SDF to avoid changing portal shape.
- Adds safer early returns only when they do not change original behavior.
- Samples only active masks outside the "all three active" special case.
*/
float ProjectionMask(
float3 PositionWS,
float3 CamPositionWS,
float3 RectPositionWS,
float3 RectNormalWS,
float3 RectUpWS,
float2 RectSize,
float Oval
)
{
/*
Build the local projection frame.
*/
float3 N = normalize(RectNormalWS);
float3 U = normalize(RectUpWS);
float3 R = normalize(cross(N, U));
/*
Recompute U to guarantee an orthogonal frame.
*/
U = normalize(cross(R, N));
/*
Ray from camera to fragment.
*/
float3 camToFrag = PositionWS - CamPositionWS;
float distToFrag = length(camToFrag);
if (distToFrag <= 1e-5)
return 0.0;
float3 rayDir = camToFrag / distToFrag;
/*
Ray-plane intersection.
*/
float denom = dot(rayDir, N);
if (abs(denom) < 1e-6)
return 0.0;
float t = dot(RectPositionWS - CamPositionWS, N) / denom;
/*
Reject intersections behind the camera or behind the current fragment.
*/
if (t < 0.0 || t > distToFrag + 1e-4)
return 0.0;
/*
Compute hit point on the projection plane.
*/
float3 hitPoint = CamPositionWS + rayDir * t;
/*
Convert hit point to rectangle local coordinates.
*/
float3 localVec = hitPoint - RectPositionWS;
float localU = dot(localVec, R);
float localV = dot(localVec, U);
float halfW = RectSize.x * 0.5;
float halfH = RectSize.y * 0.5;
if (halfW <= 1e-5 || halfH <= 1e-5)
return 0.0;
/*
Original rounded rectangle SDF preserved.
The previous Medium version replaced this with a hard rectangle / ellipse.
That was cheaper, but it could change the portal shape.
*/
float radius = saturate(Oval) * min(halfW, halfH);
float2 uv = float2(localU, localV);
float2 ext = float2(halfW, halfH);
float2 q = abs(uv) - ext + radius;
float sdf = length(max(q, 0.0)) - radius;
return sdf <= 0.0 ? 1.0 : 0.0;
}
float SampleProjectionMask(float3 PositionWS, float4x4 M)
{
/*
M[0].w is used as the active flag.
*/
if (M[0].w <= 0.5)
return 0.0;
return ProjectionMask(
PositionWS,
M[0].xyz, // Camera world position
M[1].xyz, // Rectangle world position
M[3].xyz, // Rectangle normal in world space
M[2].xyz, // Rectangle up vector in world space
float2(M[2].w, M[3].w), // Rectangle size
M[1].w // Rounded shape amount
);
}
void Mask_float
(
float3 PositionWS,
float MaskID,
float4x4 Mask0,
float4x4 Mask1,
float4x4 Mask2,
out float Alpha
)
{
float active0 = Mask0[0].w > 0.5 ? 1.0 : 0.0;
float active1 = Mask1[0].w > 0.5 ? 1.0 : 0.0;
float active2 = Mask2[0].w > 0.5 ? 1.0 : 0.0;
float activeCount = active0 + active1 + active2;
/*
No active mask case.
Preserves original behavior:
- MaskID 1 is hidden.
- Other IDs remain visible.
*/
if (activeCount <= 0.0)
{
Alpha = (MaskID == 1) ? 0.0 : 1.0;
return;
}
/*
Original special case:
when all three masks are active, MaskID 0 is also affected by the mask.
This is probably what your portal depends on.
*/
if (active0 > 0.5 && active1 > 0.5 && active2 > 0.5)
{
float mask = 0.0;
mask += SampleProjectionMask(PositionWS, Mask0);
mask += SampleProjectionMask(PositionWS, Mask1);
mask += SampleProjectionMask(PositionWS, Mask2);
mask = saturate(mask);
Alpha = (MaskID <= 1.0) ? (1.0 - mask) : mask;
return;
}
/*
Safe optimization:
In the original shader, when not all three masks are active,
MaskID 0 is always fully visible.
So only here it is safe to skip all projection work for MaskID 0.
*/
if (MaskID == 0)
{
Alpha = 1.0;
return;
}
/*
Partial active mask case.
Only sample masks that are active.
*/
float maskPartial = 0.0;
if (active0 > 0.5)
maskPartial += SampleProjectionMask(PositionWS, Mask0);
if (active1 > 0.5)
maskPartial += SampleProjectionMask(PositionWS, Mask1);
if (active2 > 0.5)
maskPartial += SampleProjectionMask(PositionWS, Mask2);
maskPartial = saturate(maskPartial);
/*
Original convention preserved:
- MaskID 1 is visible outside the mask.
- MaskID > 1 is visible inside the mask.
*/
Alpha = (MaskID == 1.0) ? (1.0 - maskPartial) : maskPartial;
}
#endif