Rendering a UMG Widget on a Static Mesh?

So, im working on a 3d " journal " interface, where the UI is drawn on a plane inside the journal model. I exposed the material instance on the UMGWidgetComponent so i can grab it and apply it to my mesh instead…My problem is that while i can apply the material just fine i cant get it to make the ui Somehow " fit " inside the plane, i also tried with a few different meshes with mixed results

Left is the UMG Widget material applied to a mesh, Right is the normal result on a widget component

Applying the Material to a plane, gives fairly good results ( although with some stretching , it " stretches " the UI based on the plane scale , ideally id like the UI to not be stretched )

http://i.imgur.com/59GZFPN.png

Using a scaled down cube works well too

http://i.imgur.com/dTBNcTr.png

And here is where the problem is. When using my own journal model . the pages have their own material slot and they are just planes… the problem is that the UI doesnt fit at all inside these

http://i.imgur.com/CXckq82.png

http://i.imgur.com/jMpQLjO.png

So yeah basically im trying to find a way to make the material / UI “fit” on the specific UV / Material region

How did you expose the material instance? That’s really interesting!

For the UI in the page, I see some options:

  • Remap your pages UVs so that the page fills the UV completely, as the UI texture seems to do. Probably the most practical way of solving your problem.
  • See if the texture from the UI is passed to the material instance as a parameter. If it is, you could get that parameter and use it on a custom material of yours, with your own texture coordinates.
  • Just place the widget right in front of the page, really close, so it seems it’s in the page (this is the silly one XD)

hi,could you show me the way to Render a umg widget on a static mesh.I am a newer,could you show me the code?

This is how I render the UMG into a texture.

/**
* Renders a UMG Widget to a texture with the specified size.
*
* @param Widget		The widget to be rendered.
* @param DrawSize	The size to render the Widget to. Also will be the texture size.
* @return			The texture containing the rendered widget.
*/
UTexture2D* UTextureUtilsBPLibrary::TextureFromWidget(UUserWidget *const Widget, const FVector2D &DrawSize)
{
	if (FSlateApplication::IsInitialized()
		&& Widget != NULL && Widget->IsValidLowLevel()
		&& DrawSize.X >= 1 && DrawSize.Y >= 1)
	{
		TSharedPtr<SWidget> SlateWidget(Widget->TakeWidget());
		if (!SlateWidget.IsValid()) return NULL;
		TSharedPtr<FWidgetRenderer> WidgetRenderer = MakeShareable(new FWidgetRenderer(true));
		if (!WidgetRenderer.IsValid()) return NULL;

		UTextureRenderTarget2D *TextureRenderTarget = WidgetRenderer->DrawWidget(SlateWidget.ToSharedRef(), DrawSize);
		// Creates Texture2D to store RenderTexture content
		UTexture2D *Texture = UTexture2D::CreateTransient(DrawSize.X, DrawSize.Y, PF_B8G8R8A8);
#if WITH_EDITORONLY_DATA
		Texture->MipGenSettings = TMGS_NoMipmaps;
#endif

		// Lock and copies the data between the textures
		TArray<FColor> SurfData;
		FRenderTarget *RenderTarget = TextureRenderTarget->GameThread_GetRenderTargetResource();
		RenderTarget->ReadPixels(SurfData);
		
		void* TextureData = Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
		const int32 TextureDataSize = SurfData.Num() * 4;
		FMemory::Memcpy(TextureData, SurfData.GetData(), TextureDataSize);
		Texture->PlatformData->Mips[0].BulkData.Unlock();
		Texture->UpdateResource();

		// Free resources
		SurfData.Empty();
		TextureRenderTarget->ConditionalBeginDestroy();
		SlateWidget.Reset();
		WidgetRenderer.Reset();

		return Texture;
	}
	return NULL;
}

This will always create a new UTexture2D. You can alter it for your needs and to use always the same texture, for performance.

3 Likes

HUGE Thanks!, thats exactly what i needed

You’re welcome, man! But wasn’t your problem related to the mapping? I thought you had sorted out the “render UMG on any object” problem. XD

Please add includes, I have no idea where you got “MakeSharable”, “WidgetRenderer” or “FWidgetRenderer” from.

#include “SharedPointer.h”
#include “WidgetRenderer.h”

How to use this function? Where can I/should I call it?

This TextureFromWidget C++ function is great, but it seems to not work quite right in UE5.1. For some reason, the image is flipped vertically. Other than that, it looks good.

Does anyone have any idea why this is happening now in UE5, or how it can be fixed?

The problem lies probably in your UV’s (it was my case), but if not, you could use this to parse the Texture and write it the way you want: How can I use ConstructTexture2D in Console Builds?

For example, if you want to flip it Horizontally:
// Lock and copies the data between the textures
TArray SurfData;
FRenderTarget* RenderTarget = TextureRenderTarget->GameThread_GetRenderTargetResource();
RenderTarget->ReadPixels(SurfData);

// Lock the texture so it can be modified
uint8* MipData = static_cast<uint8*>(Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE));

// Create base mip.
int32 Height = FMath::FloorToInt(DrawSize.Y);
int32 Width = FMath::FloorToInt(DrawSize.X);
uint8* DestPtr = NULL;
const FColor* SrcPtr = NULL;
for (int32 y = 0; y < Height; y++)
{
	DestPtr = &MipData[(Height - 1 - y) * Width * sizeof(FColor)];
	SrcPtr = const_cast<FColor*>(&SurfData[((Height - y) * Width)-1]);
	for (int32 x = 0; x < Width; x++)
	{
		*DestPtr++ = SrcPtr->B;
		*DestPtr++ = SrcPtr->G;
		*DestPtr++ = SrcPtr->R;
		*DestPtr++ = SrcPtr->A;
		SrcPtr--;
	}
}

// Unlock the texture
Texture->PlatformData->Mips[0].BulkData.Unlock();
Texture->UpdateResource();

If you want to flip it Vertically, it should be something like this: (I didn’t tried)

for (int32 y = 0; y < Height; y++)
{
DestPtr = &MipData[(Height - 1 - y) * Width * sizeof(FColor)];
int32 Line = FlipVertically ? y : (Height - 1 - y);
int32 LastColumnStart = FlipHorizontally ? Width - 1 : 0;

int32 Index = Line * Width + LastColumnStart;
SrcPtr = const_cast<FColor*>(&SurfData[Index]);
for (int32 x = 0; x < Width; x++)
{
	*DestPtr++ = SrcPtr->B;
	*DestPtr++ = SrcPtr->G;
	*DestPtr++ = SrcPtr->R;
	*DestPtr++ = SrcPtr->A;
	FlipHorizontally ? SrcPtr-- : SrcPtr++;
}

}