A Unreal Engine 5.6 showcase project implementing a Shape Wipe Transition - like an Iris Wipe, but instead of being locked to a circle, you feed it any grayscale texture and that becomes your wipe shape. Skull, heart, logo, whatever you want.
Grab the packaged demo build from the Releases tab! It's a simple scene - rotating cube, panning skysphere background, and a
ControlWidgetsitting on top of theTransitionWidgetso you can play around with it live.
If this saved you some time or you just think it's neat, you can throw a coffee my way over on Ko-fi. It genuinely helps me keep making stuff like this!
You know how a lot of games do that iris transition - the screen closes in/out from a circle? This is that, but generalized. You supply a grayscale texture, and the material uses the brightness values in that texture to determine what disappears first during the transition. Bright pixels vanish early, dark pixels stick around longer. Crank the transition value from 0 → 1 and the shape "eats" the screen.
I added a few textures to the demo control widget and to the project under Content/Textures/T_TransitionMask_*. A circle, a heart, a star and a skull. Feel free to use those or replace them with your own textures.
These three are the core of the system. Everything else in the project is just for the showcase scene:
| Asset | What it does |
|---|---|
Content/M_Transition.uasset |
The material doing all the heavy lifting - renders the transition overlay |
Content/MF_GetAspectRatio.uasset |
Material function that keeps your shape from stretching on non-square screens |
Content/TransitionWidget.uasset |
Widget that drives the material over time at a configurable speed |
The rest of the Content/ folder (skysphere, control widget, demo level, etc.) is just scaffolding for the demo. Feel free to ignore or delete them when integrating into your own project.
Domain: User Interface | Blend Mode: Masked (Translucent also works, but you may have to bypass the threshold logic.)
Here's a walkthrough of the logic from the outputs back to the inputs:
Final Color = "Background" VectorParameter (default: black)
Opacity = 1 - MaskResult
Opacity Mask = 1 - MaskResult
The color of the overlay is just a solid VectorParameter you can set to whatever. The important bit is the opacity - it's driven by MaskResult, which is described below. Both Opacity and Opacity Mask get the same value, hence why Masked and Translucent both work.
MaskResult = IF (TextureValue > MaskThreshold) → 1.0 (transparent)
ELSE → 0.0 (visible)
MaskThreshold = ScalarParameter "Mask Threshold" (default: 0.001)
The material samples a value from the texture and compares it against a threshold. If the texture is brighter than the threshold, that pixel disappears. If it's darker, it stays visible. Simple cutout logic - but the texture is being warped over time, so the cutout region grows as the transition plays.
TextureValue = TextureRGB(UV_remapped) * InBounds(UV_remapped)
InBounds = (floor(UV_remapped).R == 0 ? 1 : 0)
* (floor(UV_remapped).G == 0 ? 1 : 0)
The texture is sampled at the remapped UVs (more on those below). The InBounds term is a bounds check - it returns 0 whenever the UV falls outside the [0, 1] tile. This kills any texture repetition that would otherwise bleed into the mask and cause visual garbage.
UV_remapped = (NormalizedUV - 0.5) * Warp + 0.5
This keeps the texture anchored at screen center (0.5, 0.5) and scales it outward using Warp. As Warp grows, the texture expands from the center - so at low transition values the shape is small and contained, and at high values it blows up to cover the full screen.
Warp = Value * SmoothCurve(Value) * AspectRatio
SmoothCurve(Value) = SmoothStep curve, Tangent0=200, Tangent1=0
Value- the mainScalarParameteryou drive from 0 → 1 to run the transitionSmoothCurve- a deliberately extreme ease-in. With Tangent0 at 200, the curve is nearly flat for most of the 0–1 range, then shoots up almost vertically near 1. This means the texture expansion is barely perceptible at first, then explodes right at the end - very punchy feelingAspectRatio- aFloat2fromMF_GetAspectRatiothat corrects the warp so the shape doesn't get squished on non-square screens
Here's how Value translates to visible behavior:
| Value | What you see |
|---|---|
| 0.0 | Texture maps 1:1. Bright areas already cut out, dark areas solid |
| ~0.5 | Warp barely moving (smooth curve is still flat here) |
| ~1.0 | Warp explodes. Shape engulfs the entire screen |
This is a small Material Function that outputs a single Float2 XY.
W = ViewSize.R (screen width in pixels)
H = ViewSize.G (screen height in pixels)
Float2 XY = IF (W > H) → float2(W/H, 1 ) // landscape: scale X
IF (W < H) → float2( 1, H/W ) // portrait: scale Y
IF (W == H) → float2( 1, 1 ) // square: no correction
Always returns a vector where the longer axis gets the ratio and the shorter axis stays at 1. When you multiply UVs by this, shapes that would otherwise stretch or squash on non-square screens stay perfectly proportional.
| Screen | Output |
|---|---|
| 1920×1080 (16:9 landscape) | (1.777, 1) |
| 1080×1920 (9:16 portrait) | (1, 1.777) |
| 1024×1024 (square) | (1, 1) |
The widget is what actually animates Value from 0 to 1. It holds a reference to the M_Transition material instance and ticks the Value parameter forward at a configurable speed. Nothing fancy - it's deliberately simple so it's easy to rip out and replace with your own timeline/sequencer setup if needed.
In the demo, a ControlWidget sits on top of it so you can trigger and reverse the transition interactively.
- Migrate
M_Transition,MF_GetAspectRatio, andTransitionWidgetinto your project - Add
TransitionWidgetto your HUD or viewport at whatever Z-order puts it on top of everything - Call the
Fadefunction and set its parameters to whatever depending on where you have it happen. (level load, menu open, death screen, etc.) - To change the shape:
TransitionWidgethas a Texture2D Variable "Texture", that you can set manually or using theFadefunction. - Tweak
Mask Thresholdif you want to adjust how much of the shape is pre-cut atValue = 0
This project is licensed under the MIT License - see the LICENSE file for details.
MIT License
Copyright (c) 2026 SirDiabo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Or in short, do whatever. Go crazy. Credit would be cool, but don't expect to be sued if you don't.
Made with <3 by SirDiabo
