#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