Render texture and world-space UI¶
Use Get Rendered Texture when the HTML page needs to appear on a mesh, in a custom material, or in another presentation system that cannot contain the native UMG Magic UI widget.
For an ordinary screen menu or HUD, prefer the UMG widget. It already paints the texture, notices replacement textures, maps aspect ratio, forwards Slate input, handles pointer capture, and invalidates itself when frames change. A custom texture host owns all of those jobs.
The page still comes from a local imported HTML bundle; remote websites are not supported.
Reference checkpoint
These instructions target the Unreal Engine 5.8 source/package checkpoint. MagicUI is Beta, and the current native packages are diagnostic and non-distributable rather than a public release. The platform and legal validation boundary is recorded in Platforms and limitations.
What you will build¶
flowchart LR
A["Imported HTML<br>MagicUI Web File"]
B["Magic UI Component<br>browser + frame owner"]
C["Get Rendered Texture<br>Texture2D"]
D["Dynamic Material Instance"]
E["Static mesh / world panel"]
F["Optional line trace + UV<br>manual input"]
A --> B --> C --> D --> E
F -->|logical coordinates| B
The material receives one public Unreal Texture2D. At a fixed physical size,
MagicUI patches that same object as HTML changes, so the material updates
without assigning a new texture every frame. A physical size change can replace
the object; the resize section shows how to detect that safely.
Before you begin¶
Complete the normal content steps first:
- Create the HTML page in a dedicated source folder.
- Import its entry
.htmlor.htmfile as a MagicUI Web File. - Confirm the imported asset reports type HTML.
- Add a Magic UI Component to an Actor Blueprint.
- Assign the imported asset to the component's HTML Asset property.
See Create the first HTML page and Supported content if that path is not set up yet.
Do not load a website
Load URL accepts only MagicUI's internal magic://bundle/... URLs.
http:, https:, file:, data:, and arbitrary schemes are rejected.
A packaged world-space panel must use a cooked imported HTML asset.
1. Configure a fixed world-space view¶
Create an Actor Blueprint named BP_MagicUIWorldPanel. Add:
BP_MagicUIWorldPanel
├── DefaultSceneRoot
├── PanelMesh (Static Mesh Component)
└── MagicUIComponent (Magic UI Component)
Select MagicUIComponent and use a predictable first setup:
| Property | Suggested value | Why |
|---|---|---|
| HTML Asset | Your imported index asset |
Cookable local page source. |
| Auto Resize To Screen | Off | A world panel should not change resolution with the player's game viewport. |
| Auto Match Viewport DPI | Off | Keeps the panel independent of whichever monitor owns the game window. |
| Manual Device Scale | 1.0 |
Makes logical and physical pixels equal for the first test. Raise deliberately for sharper output. |
| View Width | 1280 |
Logical CSS width. |
| View Height | 720 |
Logical CSS height. |
| Transparent | On or off to match the page | The HTML/CSS background must agree with the desired result. |
| Render Mode | Auto | Safely uses accelerated presentation when available and CPU otherwise. |
The component automatically initializes and loads HTML Asset during
BeginPlay. Do not add Initialize UI or Load HTML From Asset to the
same beginner graph; duplicate calls only make lifecycle debugging harder.
For a sharper panel, increase view dimensions or device scale within the
render budget. The physical texture is constrained to 8,294,400 pixels
(3840 × 2160 total), the per-axis caps, and device scale 0.25–8.0. Larger is
not free: it increases browser raster, upload, texture, and material cost.
2. Create the surface material¶
Create a Material named M_MagicUISurface.
For a transparent unlit world panel, set:
| Material setting | Value |
|---|---|
| Material Domain | Surface |
| Blend Mode | Translucent |
| Shading Model | Unlit |
| Two Sided | Enable only if the panel must be visible from behind |
Add a Texture Sample Parameter 2D and name the parameter exactly:
Give it any harmless placeholder texture so the material compiles, then wire:
Texture Sample Parameter 2D: MagicUITexture
RGB ─────────────────────────────→ Emissive Color
A ─────────────────────────────→ Opacity
MagicUI's public texture is sRGB BGRA8 with straight alpha in both CPU and GPU (Accelerated) presentation. Feed RGB to emissive and A to opacity normally; do not manually un-premultiply it.
If the authored page is guaranteed fully opaque, an Opaque unlit material can ignore alpha. If the page contains transparent regions, use Translucent (or a deliberately authored Masked material) and remember that transparent rendering in the world has the normal Unreal translucency costs and sorting behavior.
Assign M_MagicUISurface to element 0 of PanelMesh.
Using the texture in a UMG material¶
For a custom UMG material instead of a world mesh, set Material Domain to User Interface and connect RGB to Final Color and A to Opacity. This still does not reproduce the native Magic UI widget's automatic input, replacement, aspect-fit, or FPS-overlay behavior.
3. Create a dynamic material instance¶
In BP_MagicUIWorldPanel, create these variables:
| Variable | Type | Initial value |
|---|---|---|
MagicUIMaterial |
Material Instance Dynamic Object Reference | None |
BoundMagicUITexture |
Texture 2D Object Reference | None |
TexturePollTimer |
Timer Handle | Invalid |
Build this BeginPlay flow:
Event BeginPlay
└── PanelMesh: Create Dynamic Material Instance
Element Index = 0
Source Material = M_MagicUISurface
└── Set MagicUIMaterial
└── Set Timer by Event
Time = 0.05
Looping = true
Event = Try Bind MagicUI Texture
└── Set TexturePollTimer
Using a dynamic instance prevents the Actor from changing the shared base material for every other object that uses it.
4. Wait for a completed texture upload¶
Create a custom event named Try Bind MagicUI Texture:
Try Bind MagicUI Texture
├── Is Valid (MagicUIMaterial)?
└── MagicUIComponent: Has Displayed Frame?
False → return and let the timer try again
True
└── MagicUIComponent: Get Rendered Texture
└── Is Valid?
False → return
True
└── Is Not Equal (BoundMagicUITexture)?
False → no rebind needed
True
├── MagicUIMaterial:
│ Set Texture Parameter Value
│ Parameter Name = MagicUITexture
│ Value = returned texture
└── Set BoundMagicUITexture = returned texture
For a fixed-size panel, clear and invalidate TexturePollTimer after the first
successful bind. Normal page animation and damage updates modify the same
texture object in place; the material sees them automatically.
On Page Loaded is not frame ready
On Page Loaded means the document and its scripts completed loading. It
can fire before the first render-thread upload. Calling Get Rendered
Texture only from that event can return None. Use the polling gate above:
Has Displayed Frame must be true and the texture must be valid.
Add On Load Failed to the component and print its URL and Error pins while developing. A missing local resource may still let the main document load, so also inspect the Unreal log when a page looks incomplete.
Texture behavior you can rely on¶
The texture returned to Blueprint is configured as:
- transient
Texture2D, pixel format BGRA8; - sRGB enabled;
- straight alpha;
- UI texture LOD group;
- bilinear filtering;
- clamped U/V address modes; and
- never-streamed.
Do not change those settings at runtime. The object is owned by the Magic UI Component and is cleared by Shutdown UI or component teardown.
There is no Blueprint On Frame Ready event and no public GPU-readback node. Has Displayed Frame reports that at least one ordered upload completed; it is not a per-frame notification. The optional Show UI FPS Counter overlay is drawn by the native Slate widget and will not appear inside this texture.
Handle resize and texture replacement¶
At a fixed physical size, MagicUI applies damage rectangles to the persistent public texture. These do not need a material rebind.
A resize or DPI change can need a new physical extent. MagicUI builds the new
texture privately, uploads a complete frame, and only then replaces the public
pointer. Your old BoundMagicUITexture can remain valid but stop receiving the
new page.
Safe explicit resize flow¶
When the game calls Resize View:
- Save the current
Get Rendered TextureasTextureBeforeResize. - Call Resize View with the new logical width and height.
- Restart the 0.05-second
Try Bind MagicUI Texturetimer. - Wait until Get Displayed View Size matches Get View Size and Has Displayed Frame is true.
- Fetch Get Rendered Texture again.
- If its object reference differs from
BoundMagicUITexture, call Set Texture Parameter Value and update the cached reference. - Stop the timer again for a fixed-size panel.
Resize View (1600, 900)
→ requested size changes immediately/asynchronously
→ old public texture may remain displayed
→ full new-size upload completes
→ Displayed View Size catches up
→ public texture may be replaced
→ re-fetch and rebind
Do not use Has Displayed Frame alone as a post-resize completion signal. It can remain true because the old frame is still valid while the replacement is being prepared. The displayed-size check tells you which logical view the currently exposed texture represents.
If Auto Resize To Screen or automatic DPI is deliberately enabled for a custom host, keep a low-frequency pointer/size check running or restart it whenever the host detects a viewport/DPI change. For a world panel, fixed size is simpler and more predictable.
Add manual input when needed¶
A material only displays pixels. It does not automatically send input to the HTML page.
If the world panel is decorative, stop here. If it is interactive, the host must provide:
ray or cursor hit
→ mesh UV / panel-local position
→ logical MagicUI coordinates
→ mouse move / button / wheel
→ focus + key events + text input
Convert a hit UV to logical coordinates¶
Use a mesh whose UI uses one untiled 0..1 UV rectangle. In Unreal, one common
Blueprint route is:
- Enable Project Settings > Physics > Support UV From Hit Results.
- Line trace against the panel with a trace/collision setup that can return the mesh face and UV.
- Use Find Collision UV for the material's UV channel, commonly channel 0.
- Read Get Displayed View Size from the Magic UI Component.
- Convert UV to logical pixels.
Logical X = clamp(U, 0, 1) × Displayed View Width
Logical Y = clamp(V, 0, 1) × Displayed View Height
Clamp the final coordinate below the exclusive width/height edge if your input
logic requires an integer pixel. If clicks are vertically mirrored, the mesh
or material uses the opposite V direction; map Logical Y from (1 - V) so
input follows the pixels the player actually sees.
Use Get Displayed View Size, not Get Render Size. HTML pointer positions are logical CSS coordinates; the render size is the physical backing texture after device scale.
If your material intentionally aspect-fits, letterboxes, crops, tiles, or distorts the texture, invert that exact material transform first. Reject hits outside the visible content rectangle rather than clamping them onto an HTML edge.
Forward pointer input¶
On hover or aim updates:
valid panel hit
└── Handle Mouse Move(LogicalPosition)
pointer leaves panel
└── Handle Mouse Move((-1, -1))
On press and release:
button pressed
├── Set View Focus(true)
└── Handle Mouse Button(LogicalPosition, Left, true)
matching button released
└── Handle Mouse Button(LastForwardedPosition, Left, false)
Handle Mouse Button returns whether the command was accepted into the bounded runtime queue. It does not report whether JavaScript handled the DOM event. If a press was accepted, retain the last logical position and deliver the matching release even when the pointer leaves the panel; otherwise an HTML control can be left in a pressed/captured state.
Before Handle Mouse Wheel, send Handle Mouse Move for the current hit. The wheel node has only delta inputs and uses the runtime's latest pointer position.
Forward keyboard and text¶
When your interaction policy gives the panel focus:
- call Handle Key Event for key down and key up;
- set Is Repeat for repeated key-down events;
- call Handle Text Input for inserted characters; and
- call Set View Focus(false) when the panel closes or loses focus.
Key commands and inserted text are separate. Do not synthesize the same typed character through both paths. Ordinary Unicode insertion is supported, but complete operating-system IME composition is not implemented in this checkpoint. If the UI needs robust normal screen keyboard/IME behavior, use the native Magic UI UMG widget and test the required languages.
Decide what gameplay receives¶
Manual calls do not automatically consume Enhanced Input, capture the mouse, or switch Player Controller input mode. Your Actor/Controller decides whether the same action also reaches gameplay.
The component exposes Is Mouse Over Interactive Element, Has Keyboard Input Focus, and Is Rendered Frame Opaque, but the cursor/focus states are asynchronous. Do not use a same-frame cursor update as a perfect first-click authority. A modal world terminal can simply consume its chosen controls while focused; a pass-through HUD needs a deliberate game-specific policy.
Never connect both a native Magic UI widget and a custom input host to send the same physical input into one component, or the page can receive duplicate events.
The complete function signatures are in the Blueprint API.
CPU and GPU (Accelerated) modes¶
The material contract is identical in both active modes. MagicUI's web engine still rasterizes on the CPU; GPU (Accelerated) uses Unreal's render/RHI path for host-side alpha conversion/composition. Auto can fall back to CPU when the active RHI lacks the required SM5/BGRA8/render-target capabilities.
Use:
- Get Requested Render Mode for what the component asked for;
- Get Active Render Mode for what is actually presenting;
- Is GPU Accelerated for a convenient active-mode Boolean; and
- On Active Render Mode Changed when diagnostics need to react.
Do not build different material alpha logic for CPU and GPU modes. Both expose the same normal sRGB straight-alpha texture. Read Platforms and limitations before treating acceleration as a platform guarantee.
Troubleshooting¶
Get Rendered Texture returns None¶
- Confirm HTML Asset is an imported HTML MagicUI Web File.
- Bind On Load Failed and print both pins.
- Wait for Has Displayed Frame, not only Is Initialized or On Page Loaded.
- Confirm this is a render-capable game/editor target, not commandlet, dedicated server, or NullRHI-only execution.
- Restart Unreal after replacing/unloading the native plugin.
The mesh stays on the placeholder texture¶
- Confirm the material parameter and Blueprint parameter name are both exactly
MagicUITexture. - Confirm Create Dynamic Material Instance targeted the mesh's correct element index.
- Print the returned texture's display name after the readiness gate.
- Make sure the timer or retry event continues until the first valid upload.
The page is black instead of transparent¶
- Use a Translucent material and connect texture alpha to Opacity.
- Set the component's Transparent property before initialization.
- Make
html,body, and non-panel regions transparent in CSS. - Do not manually premultiply or discard alpha.
The texture stopped updating after resize¶
The material probably still references the previous texture object. Wait until Get Displayed View Size catches up, fetch Get Rendered Texture again, compare object references, and rebind the material parameter.
The page looks blurry¶
- Compare Get Render Size with the number of screen pixels the panel covers.
- Increase fixed logical size or device scale within the physical pixel cap.
- Avoid magnifying a small texture across a large close-up mesh.
- Expect bilinear filtering; point sampling is intentionally not used.
Clicks are offset or mirrored¶
- Use Get Displayed View Size, not the physical texture dimensions.
- Verify the mesh uses one non-tiled
0..1UV island for the panel. - Flip V only when needed to match the rendered orientation.
- Invert any crop, scale, or letterbox transform before calculating HTML coordinates.
- Send a move before wheel and preserve the last accepted position for release.
The FPS number is missing from the mesh¶
That counter is a Slate overlay drawn only by the native Magic UI widget. It is not part of Get Rendered Texture. Display Get UI Frames Per Second with your own world/UI text if a custom host needs a diagnostic counter.
GPU was requested but CPU is active¶
Fallback is expected when the RHI capability check fails. Keep Auto for normal use and diagnose through the active-mode nodes rather than assuming the request is authoritative.
When to return to the native widget¶
Use the UMG Magic UI widget when the page is screen-space and needs normal mouse, keyboard, focus, aspect-fit, transparent pass-through, and automatic texture replacement. Keep the render-texture route for world surfaces or special compositing where manually owning those responsibilities is worth the extra graph.
Continue with Magic UI widget and input for the native path, or JavaScript bridge to send application data after the page is loaded.