opti : shaders rework test
This commit is contained in:
@@ -1,6 +1,27 @@
|
||||
#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,
|
||||
@@ -8,51 +29,78 @@ float ProjectionMask(
|
||||
float3 RectNormalWS,
|
||||
float3 RectUpWS,
|
||||
float2 RectSize,
|
||||
float Oval)
|
||||
float Oval
|
||||
)
|
||||
{
|
||||
// Build the rectangle local frame
|
||||
/*
|
||||
Build the local projection frame.
|
||||
*/
|
||||
float3 N = normalize(RectNormalWS);
|
||||
float3 U = normalize(RectUpWS);
|
||||
float3 R = normalize(cross(N, U)); // Right axis
|
||||
U = normalize(cross(R, N)); // Recompute Up orthogonally in case RectUpWS is not perfectly perpendicular to RectNormalWS
|
||||
float3 R = normalize(cross(N, U));
|
||||
|
||||
// Ray from camera toward the fragment
|
||||
float3 rayDir = normalize(PositionWS - CamPositionWS);
|
||||
/*
|
||||
Recompute U to guarantee an orthogonal frame.
|
||||
*/
|
||||
U = normalize(cross(R, N));
|
||||
|
||||
// Ray-plane intersection
|
||||
// Plane equation: dot(P - RectPositionWS, N) = 0
|
||||
/*
|
||||
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);
|
||||
|
||||
// Ray is parallel to the plane or facing away — no hit
|
||||
if (abs(denom) < 1e-6)
|
||||
return 0;
|
||||
return 0.0;
|
||||
|
||||
float t = dot(RectPositionWS - CamPositionWS, N) / denom;
|
||||
|
||||
// Intersection must be in front of the camera (t > 0)
|
||||
// and not beyond the fragment (t <= distance from camera to fragment)
|
||||
float tFrag = dot(PositionWS - CamPositionWS, rayDir);
|
||||
if (t < 0.0 || t > tFrag + 1e-4)
|
||||
return 0;
|
||||
/*
|
||||
Reject intersections behind the camera or behind the current fragment.
|
||||
*/
|
||||
if (t < 0.0 || t > distToFrag + 1e-4)
|
||||
return 0.0;
|
||||
|
||||
// Compute the hit point on the plane
|
||||
/*
|
||||
Compute hit point on the projection plane.
|
||||
*/
|
||||
float3 hitPoint = CamPositionWS + rayDir * t;
|
||||
|
||||
// Project hit point into the rectangle local space
|
||||
/*
|
||||
Convert hit point to rectangle local coordinates.
|
||||
*/
|
||||
float3 localVec = hitPoint - RectPositionWS;
|
||||
float localU = dot(localVec, R); // horizontal axis
|
||||
float localV = dot(localVec, U); // vertical axis
|
||||
float localU = dot(localVec, R);
|
||||
float localV = dot(localVec, U);
|
||||
|
||||
float halfW = RectSize.x * 0.5;
|
||||
float halfH = RectSize.y * 0.5;
|
||||
|
||||
// Corner radius: 0 = rectangle, min(halfW, halfH) = oval
|
||||
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);
|
||||
|
||||
// Rounded rectangle SDF
|
||||
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;
|
||||
@@ -60,17 +108,20 @@ float ProjectionMask(
|
||||
|
||||
float SampleProjectionMask(float3 PositionWS, float4x4 M)
|
||||
{
|
||||
if (M[0].w == 0)
|
||||
return 0;
|
||||
/*
|
||||
M[0].w is used as the active flag.
|
||||
*/
|
||||
if (M[0].w <= 0.5)
|
||||
return 0.0;
|
||||
|
||||
return ProjectionMask(
|
||||
PositionWS,
|
||||
M[0].xyz, // CamPositionWS
|
||||
M[1].xyz, // RectPositionWS
|
||||
M[3].xyz, // RectNormalWS
|
||||
M[2].xyz, // RectUpWS
|
||||
float2(M[2].w, M[3].w), // RectSize
|
||||
M[1].w // Shape
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -84,40 +135,82 @@ void Mask_float
|
||||
out float Alpha
|
||||
)
|
||||
{
|
||||
Alpha = 1;
|
||||
|
||||
if (Mask0[0].w == 1 && Mask1[0].w == 1 && Mask2[0].w == 1)
|
||||
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)
|
||||
{
|
||||
float mask = SampleProjectionMask(PositionWS, Mask0);
|
||||
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) ? 1 - mask : mask;
|
||||
|
||||
Alpha = (MaskID <= 1.0) ? (1.0 - mask) : mask;
|
||||
return;
|
||||
}
|
||||
else if (MaskID == 0)
|
||||
|
||||
/*
|
||||
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;
|
||||
}
|
||||
else
|
||||
{
|
||||
float Active = Mask0[0].w + Mask1[0].w + Mask2[0].w;
|
||||
|
||||
if (Active > 0)
|
||||
{
|
||||
float mask = saturate(
|
||||
SampleProjectionMask(PositionWS, Mask0) +
|
||||
SampleProjectionMask(PositionWS, Mask1) +
|
||||
SampleProjectionMask(PositionWS, Mask2)
|
||||
);
|
||||
|
||||
Alpha = (MaskID == 1) ? 1 - mask : mask;
|
||||
}
|
||||
else
|
||||
{
|
||||
Alpha = (MaskID == 1) ? 0 : 1;
|
||||
}
|
||||
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
|
||||
#endif
|
||||
Reference in New Issue
Block a user