Announcement

Collapse
No announcement yet.

SMeshWidget - Hardware Instanced Slate Meshes Thread

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    SMeshWidget - Hardware Instanced Slate Meshes Thread

    I'm trying to work out how I can utilize the new 4.11 SMeshWidget to draw Static Meshes as part of my User Interface. All I actually want to draw at first is a very simple plane, but I need to transform the vertices such that the plane is flat with my radar widget, which currently looks like the screenshot below. All of the blips on the radar are currently UImage widgets, which are added to the Radar Widget dynamically during play.

    Click image for larger version

Name:	cube.JPG
Views:	1
Size:	208.0 KB
ID:	1176083

    Now if an object is far enough away that it goes off of the radar's grid (which is a circular radius around the player) - it get's clamped to the edge, at which point I want the square blip to turn into an arrow, pointing to it's location. The arrow also needs to be distorted such that it's displayed as if on the same plane as the radar grid itself (which is around 30/40 degree incline). Since the radar also rotates with the player rotation, the widgets also need to move around the edge of the radar and keep pointing to their respective objects.

    Since there could be hundreds if not thousands of these blips on the radar at once, it seems like a perfect use-case for the new SMeshWidget which appears to be able to render lots of things like this very quickly. However it seems as as powerful as this new feature is I still haven't really figured out how to use it. For a quick test-case, I wrapped SMeshWidget in a UWidget and placed it in my HUD with UMG. I then gave it a mesh to use and a very simple UI-material, and this is the result:

    Click image for larger version

Name:	UIMesh.jpg
Views:	1
Size:	350.3 KB
ID:	1176084

    If anybody is interested, this is the relatively simple code that makes this possible:

    Custom Mesh UWidget
    Code:
    #include "BZGame_BlipWidget.generated.h"
    
    class USlateVectorArtData;
    class SMeshWidget;
    
    UCLASS()
    class BZGAME_API UBZGame_BlipWidget : public UWidget
    {
    	GENERATED_BODY()
    
    public:
    	UBZGame_BlipWidget(const FObjectInitializer& ObjectInitializer);
    
    	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Mesh")
    	USlateVectorArtData* MeshData;
    
    	virtual void SynchronizeProperties() override;
    	virtual void ReleaseSlateResources(bool bReleaseChildren) override;
    
    #if WITH_EDITOR
    	// Begin UWidget Interface
    	virtual const FSlateBrush* GetEditorIcon() override;
    	virtual const FText GetPaletteCategory() override;
    	virtual void OnCreationFromPalette() override;
    	// End UWidget Interface
    #endif
    	
    protected:
    	// Native Slate Widget
    	TSharedPtr<SMeshWidget> MyMeshWidget;
    
    	// UWidget Interface
    	virtual TSharedRef<SWidget> RebuildWidget() override;
    };
    Code:
    // Fill out your copyright notice in the Description page of Project Settings.
    
    #include "BZGame.h"
    #include "Hud/Widgets/Game/BZGame_BlipWidget.h"
    #include "Runtime/UMG/Public/Slate/SMeshWidget.h"
    #include "Runtime/UMG/Public/UMGStyle.h"
    
    #define LOCTEXT_NAMESPACE "UMG"
    
    UBZGame_BlipWidget::UBZGame_BlipWidget(const FObjectInitializer& ObjectInitializer)
    {
    	//SMeshWidget::FArguments SlateDefaults;
    	
    }
    
    
    TSharedRef<SWidget> UBZGame_BlipWidget::RebuildWidget()
    {
    	MyMeshWidget = SNew(SMeshWidget);
    	return MyMeshWidget.ToSharedRef();
    }
    
    void UBZGame_BlipWidget::SynchronizeProperties()
    {
    	Super::SynchronizeProperties();
    
    	if (MyMeshWidget.IsValid() && MeshData != nullptr)
    	{
    		const uint32 NewMeshIndex = MyMeshWidget->AddMesh(*MeshData);
    	}
    }
    
    void UBZGame_BlipWidget::ReleaseSlateResources(bool bReleaseChildren)
    {
    	Super::ReleaseSlateResources(bReleaseChildren);
    }
    
    #if WITH_EDITOR
    const FSlateBrush* UBZGame_BlipWidget::GetEditorIcon()
    {
    	return FUMGStyle::Get().GetBrush("Widget.ProgressBar");
    }
    
    const FText UBZGame_BlipWidget::GetPaletteCategory()
    {
    	return LOCTEXT("Common", "Common");
    }
    
    void UBZGame_BlipWidget::OnCreationFromPalette()
    {
    
    }
    #endif
    
    #undef LOCTEXT_NAMESPACE
    And here it is placed in UMG. Notice that despite the widgets position at the center of the screen, the mesh is drawing on the top-left still (you can see the edge of it poking out).

    Click image for larger version

Name:	hiya.JPG
Views:	1
Size:	184.6 KB
ID:	1176085

    Now even though my custom mesh widget is placed directly central in my custom UMG widget, the mesh always draws at the top-left of the screen, and in fact it seems to be based not on the viewport size, but on the size of the entire monitor. According to Nick on slack, I need to pass information about the mesh position in via it's UV's, and I sense that this is what the new 'Screen Position' input on the material is for.

    However, nothing I try seems to work. I've used a bunch of default nodes that I think might be the right ones to mirror what I'm already seeing (such as View Size, Screen Position, a vector 2D etc.) - but each one makes it disappear, so I'm obviously not doing something right. I know that these are being used in Paragon for the widgets that are above the minion heads, so I'd really like to see how that was done. I've been trying to figure this out for a few days but it's going way over my head.

    Additionally, if I still can't use this to transform my widgets as if they are in 3D space - I'm open to other suggestions Eventually, I'd like to draw a static mesh 'disc' underneath the existing wire-frame if possible.

    #2
    Awesome!
    That's exactly what we need for our inventory system.


    DennG

    Comment


      #3
      Originally posted by DennG View Post
      Awesome!
      That's exactly what we need for our inventory system.

      DennG
      Just to quote Nick on this, bear in mind that this is NOT a way to draw 3D meshes in the viewport.

      Comment


        #4
        Hey TheJamsh,

        I just noticed you are also trying to use the new widget.

        https://forums.unrealengine.com/show...sed-in-Paragon

        I will be giving this a go also, thanks for the info thus far.
        Abatron Development Thread
        www.abatrongame.com
        Steam Store

        Comment


          #5
          Will re-post this here from the other thread, since this is where I want people to put what they figure out

          I put together the simplest possible example of something practical you could do with it, https://drive.google.com/open?id=0B3...ktpMVlvUFVmTWs

          There's a lot to sift through to really understand everything it's doing. Partly understanding how the ugs SlatePixelShader is sending the data to the material is worth looking at, so you understand what uv channels will contain what data.

          Again - not easy to understand, hard to use, can do cool stuff with it; maybe if others take a look they can help explain everything in this thread ^_^

          The jist is that, the SMeshWidget takes some 2D mesh data, and uses instanced mesh rendering to execute 1 draw call to render all of something. You pack a small amount of data into the instanced mesh buffer, so each instance gets 1 FVector4 worth of data for each draw on the GPU. You then pack whatever you need in there, anything you can't pack, you'll need to pass down an index, and extract extra information from textures you've packed the data into.

          Last edited by Nick Darnell; 04-01-2016, 04:28 PM.

          Comment


            #6
            Okay so here's an update (finally).

            Got back to working on my radar stuff, but decided to work in the example project nick gave first. I finally have this circular mesh drawing as slate geometry!



            Source code (though it's very simple). You can see the shader setup and the mesh in the video. What I want to do next is have better control over the position of the mesh in UMG, using the bounding rectangle ideally to scale and place it.

            Code:
            // Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
            
            #include "MeshWidgetExample.h"
            #include "Slate/SMeshWidget.h"
            #include "Slate/SlateVectorArtInstanceData.h"
            
            #include "ParticleWidget.h"
            
            #include "MeshWidgetExampleCharacter.h"
            
            DECLARE_STATS_GROUP(TEXT("MeshWidget"), STATGROUP_MeshWidget, STATCAT_Advanced);
            DECLARE_CYCLE_STAT(TEXT("Particle Update"), STAT_ParticleUpdate, STATGROUP_MeshWidget);
            
            class SParticleMeshWidget : public SMeshWidget
            {
            public:
            	SLATE_BEGIN_ARGS(SParticleMeshWidget) { }
            	SLATE_END_ARGS()
            
            public:
            	void Construct(const FArguments& Args, UParticleWidget& InThis)
            	{
            		This = &InThis;
            	}
            
            	virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override
            	{
            		SCOPE_CYCLE_COUNTER(STAT_ParticleUpdate);
            
            		const float Scale = AllottedGeometry.Scale;
            
            		// Trail
            		if ( This->TrailMeshId != -1 )
            		{
            			FVector2D TrailOriginWindowSpace = AllottedGeometry.LocalToAbsolute(AllottedGeometry.GetLocalSize() * 0.5f);
            
            			TSharedPtr<FSlateInstanceBufferUpdate> PerInstaceUpdate = BeginPerInstanceBufferUpdateConst(This->TrailMeshId);
            			PerInstaceUpdate->GetData().Empty();
            
            			// Draw Radar Mesh
            			FSlateVectorArtInstanceData RadarData;
            			RadarData.SetPosition(TrailOriginWindowSpace * Scale);
            			RadarData.SetScale(Scale);
            			RadarData.SetBaseAddress(AMeshWidgetExampleCharacter::RotationAngle); // Set to Pawn Rotation
            
            			// Add Radar to Data
            			PerInstaceUpdate->GetData().Add(RadarData.GetData());
            			FSlateInstanceBufferUpdate::CommitUpdate(PerInstaceUpdate);
            		}
            
            		return SMeshWidget::OnPaint(Args, AllottedGeometry, MyClippingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled);
            	}
            
            public:
            	UParticleWidget* This;
            };
            
            UParticleWidget::UParticleWidget()
            	: TrailMeshId(-1)
            {
            }
            
            void UParticleWidget::SynchronizeProperties()
            {
            	Super::SynchronizeProperties();
            
            	if ( TrailMeshAsset )
            	{
            		TrailMeshId = MyMesh->AddMesh(*TrailMeshAsset);
            		MyMesh->EnableInstancing(TrailMeshId, 1);
            	}
            }
            
            void UParticleWidget::ReleaseSlateResources(bool bReleaseChildren)
            {
            	Super::ReleaseSlateResources(bReleaseChildren);
            
            	MyMesh.Reset();
            }
            
            TSharedRef<SWidget> UParticleWidget::RebuildWidget()
            {
            	MyMesh = SNew(SParticleMeshWidget, *this);
            	return MyMesh.ToSharedRef();
            }

            Comment


              #7
              Alright so, I'm almost done with this now. Really happy with how it's turned out, all I want to do at this stage is change the draw-order of the items so that I can get the blips to render on top of the grid lines. Not sure if that's going to be possible without splitting the widget up though, which I'd really rather avoid. Anyway here's the video, recommend full screen 1080 to see it working.



              So I'm doing this using only three instanced meshes and inside one SMeshWidget, but to be honest I could probably have squeezed everything in with two meshes. The base compass is a mesh, drawn in a similar way to how I've done it above, but I've now got it to use the rectangle as the draw location, which prevents any strange issues with scaling or position on screen. The easiest way to do it is use the ClippingRectangle.

              The trickiest part was the blips. I already have a pretty robust spatial hash system and an object manager for getting the list of objects, so that part is already there. Every Game Object with a scanner tells the object manager to 'ping' objects in it's range on a timer, and the object manager determines which ones are visible or in range / on the correct team for that object.

              The coolest part (IMO), is that I've only split the draws into either edge-arrows, or regular blips (aka in-range items). Three floats is what I need to determine colour, opacity, and either Rotation or 'Blip Type' (square or circle) - but these all have to be packed into the BaseAddress, so you only get a certain amount of precision (but it's more than enough for me). The material then unpacks this data and uses it to drive the parameters.

              Here's the way the packing is done:

              Code:
              	FORCEINLINE float PackFloats_3(const float X, const float Y, const float Z) const
              	{
              		const uint8 XInt = X * 255.0f;
              		const uint8 YInt = Y * 255.0f;
              		const uint8 ZInt = Z * 255.0f;
              
              		const uint32 PackedInt = (XInt << 16) | (YInt << 8) | ZInt;
              		return (float)(((double)PackedInt) / ((double)(1 << 24)));
              	}
              And then how it's unpacked in the material:

              Click image for larger version

Name:	Unpack.JPG
Views:	1
Size:	53.3 KB
ID:	1105781

              Blip and Arrow materials. Byte1 is used to drive a Colour LUT to determine the colour, Byte 2 is used to determine opacity, and Byte 3 is the 'Data' byte.

              Click image for larger version

Name:	ColourLUT.JPG
Views:	1
Size:	132.6 KB
ID:	1105782
              Click image for larger version

Name:	Arrow.JPG
Views:	1
Size:	125.8 KB
ID:	1105783

              Phew... yeah so that's what I did this weekend!
              Last edited by TheJamsh; 07-25-2016, 10:15 AM. Reason: Had to remove some source code for... reasons.

              Comment


                #8
                Dear [MENTION=155]TheJamsh[/MENTION],

                This is an incredible sharing! Thank you so much for sharing your research!

                I love your 3D mesh rendering of the landscape in your video!

                Thanks also for taking the time to put up all those pictures!

                Great work!



                Rama
                100+ UE4 C++ Tutorials on the UE4 Code Wiki, including UE4 Multi-Threading!

                UE4 Marketplace: Melee Weapon Plugin & Compressed Binary Save System Plugin | Rama's C++ AI Jumping Videos | Vertex Snap Editor Plugin

                Visit www.ue4code.com to see lots of videos about my C++ Creations! ♥ Rama

                Comment


                  #9
                  No probs [MENTION=552]Rama[/MENTION] Hope it's of use to people!

                  Comment


                    #10
                    Originally posted by TheJamsh View Post
                    No probs [MENTION=552]Rama[/MENTION] Hope it's of use to people!
                    Just a question on your stats segment of the UI, how are you achieving the snipped off angled corner of the stats boxes? looks really good with the gradient opacity.

                    Comment


                      #11
                      Originally posted by NigeySUK View Post
                      Just a question on your stats segment of the UI, how are you achieving the snipped off angled corner of the stats boxes? looks really good with the gradient opacity.
                      Thanks, they're a relatively simple material using a mask texture I created in Photoshop. I control the colour / size through code

                      Click image for larger version

Name:	Image.JPG
Views:	1
Size:	157.4 KB
ID:	1108044

                      Comment


                        #12
                        Oh wow dude that's awsome, thanks for sharing the set ups, much appreciated!

                        Comment


                          #13
                          Just one simple quick question, how would I get "AMeshWidgetExampleCharacter::RotationAngle", it is not in the provided script.

                          Comment


                            #14
                            Originally posted by Nick Darnell View Post
                            This stuff looks very promising.

                            In that example you posted, there are 5 Particle widgets, which generate a bunch of small rotating squares as seen on that screenshot.

                            How many drawcalls are generated in this example?

                            Is it 1 drawcall per ParticleWidget? ie: 5 drawcalls in this example.

                            Or 1 Draw Call per unique mesh per widget? ie: 1 drawcall since all 5 different Particle Widgets use the same unique mesh.

                            Comment


                              #15
                              1 Draw Call per ParticleWidget.

                              Comment

                              Working...
                              X