├── .gitignore ├── Config ├── DefaultEditor.ini ├── DefaultGame.ini ├── DefaultEngine.ini └── DefaultInput.ini ├── Content ├── Fonts │ ├── City.uasset │ ├── CITY-BOI.uasset │ └── CITY-BOL.uasset └── 3DTransform │ ├── M_WidgetBG.uasset │ ├── 3DTransform_Demo.umap │ ├── M_UI_3DTransform.uasset │ ├── T_exampleTexture.uasset │ ├── WBP_3DTransform.uasset │ └── M_UI_3DTransform_Inst.uasset ├── UMG_3DTransform.uproject ├── Readme.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | Video/ -------------------------------------------------------------------------------- /Config/DefaultEditor.ini: -------------------------------------------------------------------------------- 1 | [/Script/AdvancedPreviewScene.SharedProfiles] 2 | 3 | -------------------------------------------------------------------------------- /Content/Fonts/City.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engelmanna/UMG3DTransform/HEAD/Content/Fonts/City.uasset -------------------------------------------------------------------------------- /Content/Fonts/CITY-BOI.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engelmanna/UMG3DTransform/HEAD/Content/Fonts/CITY-BOI.uasset -------------------------------------------------------------------------------- /Content/Fonts/CITY-BOL.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engelmanna/UMG3DTransform/HEAD/Content/Fonts/CITY-BOL.uasset -------------------------------------------------------------------------------- /Config/DefaultGame.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | [/Script/EngineSettings.GeneralProjectSettings] 4 | ProjectID=FD69E7FE48822FD454BAD58249F6E70E 5 | -------------------------------------------------------------------------------- /Content/3DTransform/M_WidgetBG.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engelmanna/UMG3DTransform/HEAD/Content/3DTransform/M_WidgetBG.uasset -------------------------------------------------------------------------------- /Content/3DTransform/3DTransform_Demo.umap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engelmanna/UMG3DTransform/HEAD/Content/3DTransform/3DTransform_Demo.umap -------------------------------------------------------------------------------- /Content/3DTransform/M_UI_3DTransform.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engelmanna/UMG3DTransform/HEAD/Content/3DTransform/M_UI_3DTransform.uasset -------------------------------------------------------------------------------- /Content/3DTransform/T_exampleTexture.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engelmanna/UMG3DTransform/HEAD/Content/3DTransform/T_exampleTexture.uasset -------------------------------------------------------------------------------- /Content/3DTransform/WBP_3DTransform.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engelmanna/UMG3DTransform/HEAD/Content/3DTransform/WBP_3DTransform.uasset -------------------------------------------------------------------------------- /Content/3DTransform/M_UI_3DTransform_Inst.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engelmanna/UMG3DTransform/HEAD/Content/3DTransform/M_UI_3DTransform_Inst.uasset -------------------------------------------------------------------------------- /UMG_3DTransform.uproject: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "EngineAssociation": "5.1", 4 | "Category": "", 5 | "Description": "", 6 | "Plugins": [ 7 | { 8 | "Name": "ModelingToolsEditorMode", 9 | "Enabled": true, 10 | "TargetAllowList": [ 11 | "Editor" 12 | ] 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # UMG 3D Transform 2 | This project demos a technique to deform UMG widgets in such a manner that they look like they are rotating in 3D space. 3 | 4 | [Video Demo of Project](https://youtu.be/Rqgipoc6PYE) 5 | 6 | I was inspired by the Unrealfest 2022 UMG talk: https://youtu.be/dSTdAToJ7Gg. Specifically about the ability to pan a UMG widget around via the material's ScreenPosition input. I wanted to take it a step further and try to get it working with 3D transforms. After some trial and error, I got it working and created a small demo project to share for others that want to use the functionality as well. 7 | 8 | Most of the work is done in the HLSL node within the material. The technique works by getting the vertex's local position, then doing some matrix math to rotate the vertex position around 3D space, and apply some perspective. Then it generates some data into packed UV channels to build perspective corrected UVs. 9 | 10 | This project was built in Unreal 5.1. It'll probably work in older versions, but you will need to manually port the assets over. 11 | 12 | See more of my work at: https://alexengelmann.com -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alex Engelmann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Config/DefaultEngine.ini: -------------------------------------------------------------------------------- 1 | 2 | 3 | [/Script/EngineSettings.GameMapsSettings] 4 | GameDefaultMap=/Game/3DTransform/3DTransform_Demo.3DTransform_Demo 5 | EditorStartupMap=/Game/3DTransform/3DTransform_Demo.3DTransform_Demo 6 | 7 | [/Script/WindowsTargetPlatform.WindowsTargetSettings] 8 | DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 9 | -D3D12TargetedShaderFormats=PCD3D_SM5 10 | +D3D12TargetedShaderFormats=PCD3D_SM6 11 | -D3D11TargetedShaderFormats=PCD3D_SM5 12 | +D3D11TargetedShaderFormats=PCD3D_SM5 13 | Compiler=Default 14 | AudioSampleRate=48000 15 | AudioCallbackBufferFrameSize=1024 16 | AudioNumBuffersToEnqueue=1 17 | AudioMaxChannels=0 18 | AudioNumSourceWorkers=4 19 | SpatializationPlugin= 20 | SourceDataOverridePlugin= 21 | ReverbPlugin= 22 | OcclusionPlugin= 23 | CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) 24 | CacheSizeKB=65536 25 | MaxChunkSizeOverrideKB=0 26 | bResampleForDevice=False 27 | MaxSampleRate=48000.000000 28 | HighSampleRate=32000.000000 29 | MedSampleRate=24000.000000 30 | LowSampleRate=12000.000000 31 | MinSampleRate=8000.000000 32 | CompressionQualityModifier=1.000000 33 | AutoStreamingThreshold=0.000000 34 | SoundCueCookQualityIndex=-1 35 | 36 | 37 | [/Script/HardwareTargeting.HardwareTargetingSettings] 38 | TargetedHardwareClass=Desktop 39 | AppliedTargetedHardwareClass=Desktop 40 | DefaultGraphicsPerformance=Maximum 41 | AppliedDefaultGraphicsPerformance=Maximum 42 | 43 | [/Script/Engine.RendererSettings] 44 | r.GenerateMeshDistanceFields=True 45 | r.DynamicGlobalIlluminationMethod=1 46 | r.ReflectionMethod=1 47 | r.Shadow.Virtual.Enable=1 48 | r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=True 49 | 50 | [/Script/WorldPartitionEditor.WorldPartitionEditorSettings] 51 | CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' 52 | 53 | [/Script/Engine.Engine] 54 | +ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/UIAnimation") 55 | +ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/UIAnimation") 56 | 57 | [/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] 58 | bEnablePlugin=True 59 | bAllowNetworkConnection=True 60 | SecurityToken=19B9D20640E600AD9DA1FDBE10621C48 61 | bIncludeInShipping=False 62 | bAllowExternalStartInShipping=False 63 | bCompileAFSProject=False 64 | bUseCompression=False 65 | bLogFiles=False 66 | bReportStats=False 67 | ConnectionType=USBOnly 68 | bUseManualIPAddress=False 69 | ManualIPAddress= 70 | 71 | -------------------------------------------------------------------------------- /Config/DefaultInput.ini: -------------------------------------------------------------------------------- 1 | [/Script/Engine.InputSettings] 2 | -AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) 3 | -AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) 4 | -AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) 5 | -AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) 6 | -AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) 7 | -AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) 8 | -AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) 9 | +AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 10 | +AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 11 | +AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 12 | +AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 13 | +AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) 14 | +AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) 15 | +AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) 16 | +AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 17 | +AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 18 | +AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 19 | +AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 20 | +AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 21 | +AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 22 | +AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 23 | +AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 24 | +AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 25 | +AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 26 | +AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 27 | +AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 28 | +AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 29 | +AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 30 | +AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 31 | +AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 32 | +AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 33 | +AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 34 | +AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 35 | +AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 36 | +AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 37 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 38 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 39 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 40 | +AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 41 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 42 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 43 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 44 | +AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 45 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 46 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 47 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 48 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 49 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 50 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 51 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 52 | +AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 53 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 54 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 55 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 56 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 57 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 58 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 59 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 60 | +AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) 61 | bAltEnterTogglesFullscreen=True 62 | bF11TogglesFullscreen=True 63 | bUseMouseForTouch=False 64 | bEnableMouseSmoothing=True 65 | bEnableFOVScaling=True 66 | bCaptureMouseOnLaunch=True 67 | bEnableLegacyInputScales=True 68 | bEnableMotionControls=True 69 | bFilterInputByPlatformUser=False 70 | bShouldFlushPressedKeysOnViewportFocusLost=True 71 | bAlwaysShowTouchInterface=False 72 | bShowConsoleOnFourFingerTap=True 73 | bEnableGestureRecognizer=False 74 | bUseAutocorrect=False 75 | DefaultViewportMouseCaptureMode=CapturePermanently_IncludingInitialMouseDown 76 | DefaultViewportMouseLockMode=LockOnCapture 77 | FOVScale=0.011110 78 | DoubleClickTime=0.200000 79 | DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput 80 | DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent 81 | DefaultTouchInterface=/Engine/MobileResources/HUD/DefaultVirtualJoysticks.DefaultVirtualJoysticks 82 | -ConsoleKeys=Tilde 83 | +ConsoleKeys=Tilde 84 | +ConsoleKeys=Caret 85 | 86 | --------------------------------------------------------------------------------