UE4/UE5 c++场景直方图
- UE4/UE5 C++绘制编辑器场景直方图
- 绘制原理:
- 元素绘制
- 坐标轴绘制
- 源码处理
UE4/UE5 C++绘制编辑器场景直方图
注:源码包含场景中的像素获取、菜单添加ToolBar
实现效果:
这个是用于美术统计场景中像素元素分布,类似于PS里面的直方图,比如,R值是255的像素,场景中的个数是1000个,那么就是(255,1000)。
绘制原理:
元素绘制
根据不同高度绘制了不同的Box
坐标轴绘制
箭头,一段段的标尺,坐标轴,均由绘制线构成
源码处理
首先创建一个Editor模块:
创建SCompoundWidget
:
.h
// Fill out your copyright notice in the Description page of Project Settings.#pragma once
#include "CoreMinimal.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/SCompoundWidget.h"/*** */
class SLASHEDITOR_API SPixHistogramWidget : public SCompoundWidget
{
public:SLATE_BEGIN_ARGS(SPixHistogramWidget){}SLATE_END_ARGS()virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override;virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect,FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle,bool bParentEnabled) const override;virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;virtual FReply OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;virtual void OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;void DrawPixelOnScreen(const TMap<int, float>& InData, const float InBarWidth,const float InBarGap,const FGeometry& InAllottedGeometry, FSlateWindowElementList& InOutDrawElements,const int32 InLayerId,const FVector2D InEdgeSize,const FLinearColor InColor) const;virtual bool SupportsKeyboardFocus() const override;void SelectionChanged(TSharedPtr<FString> NewSelection, ESelectInfo::Type SelectInfo);void GetScreenPixelValue();void RefreshData();void ResetMap();void OnScalingFactorChanged(float InScalingFactor);/** Constructs this widget with InArgs */void Construct(const FArguments& InArgs);private: TArray<TSharedPtr<FString>> Pass;int CurrentIndex = 0;FVector2D LinearScale = FVector2D(3.f, 3.f);FVector2D BottomSize = FVector2D(40.f,40.f);float HeightScale = 1.f;const float HeightScalingFactor = 500;TArray<FColor> PixelsColor;TMap<int,float> GrayData;TMap<int,float> RData;TMap<int,float> GData;TMap<int,float> BData;bool IsCtrlDown = false;float ScalingFactor = 5.f;
};
.cpp
// Fill out your copyright notice in the Description page of Project Settings.#include "PixHistogramWidget.h"#include "Editor.h"
#include "SlateOptMacros.h"
#include "Widgets/Input/SComboBox.h"
#include "Widgets/Input/SSpinBox.h"
#include "Widgets/Input/STextComboBox.h"BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION#define LOCTEXT_NAMESPACE "PixHistogramWidget"void SPixHistogramWidget::Construct(const FArguments& InArgs)
{PixelsColor.Init(FColor::White, 65535);ResetMap();Pass.Empty();CurrentIndex = 0;Pass = {MakeShared<FString>("R"),MakeShared<FString>("G"),MakeShared<FString>("B"),MakeShared<FString>("Gray"),MakeShared<FString>("RGB"),MakeShared<FString>("RGB And Gray")};ChildSlot[SNew(SBox).WidthOverride(500).HeightOverride(200).HAlign(HAlign_Left).VAlign(VAlign_Bottom).Padding(FMargin(0, -200, 0, 0))[SNew(SHorizontalBox)+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Top)[SNew(STextComboBox).InitiallySelectedItem(Pass[CurrentIndex]).OptionsSource(&Pass).OnSelectionChanged(this, &SPixHistogramWidget::SelectionChanged)]+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center).Padding(20, 0, 0, 0)[SNew(STextBlock).Text(FText::FromString(TEXT("scale speed:")))]+ SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Center)[SNew(SSpinBox<float>).Value(ScalingFactor).MinValue(0.0f).MaxValue(100.0f).OnValueChanged(this, &SPixHistogramWidget::OnScalingFactorChanged)]]];bCanSupportFocus = true;
}void SPixHistogramWidget::SelectionChanged(TSharedPtr<FString> NewSelection, ESelectInfo::Type SelectInfo)
{CurrentIndex = Pass.Find(NewSelection);
}void SPixHistogramWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);RefreshData();
}int32 SPixHistogramWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry,const FSlateRect& MyCullingRect,FSlateWindowElementList& OutDrawElements, int32 LayerId,const FWidgetStyle& InWidgetStyle,bool bParentEnabled) const
{const int32 NewLayerId = SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect,OutDrawElements, LayerId, InWidgetStyle,bParentEnabled);const float BarWidth = 1.f * LinearScale.X;const float BarGap = 0.f * LinearScale.X;const FVector2D EdgeSize = FVector2D(0, 0);const FVector2D BackGroundSize = FVector2D((256 * LinearScale.X + EdgeSize.X * 2),(100 * LinearScale.Y + EdgeSize.Y * 2));if (CurrentIndex == 0){DrawPixelOnScreen(RData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Red);}else if (CurrentIndex == 1){DrawPixelOnScreen(GData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Green);}else if (CurrentIndex == 2){DrawPixelOnScreen(BData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Blue);}else if (CurrentIndex == 3){DrawPixelOnScreen(GData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Gray);}else if (CurrentIndex == 4){DrawPixelOnScreen(RData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Red);DrawPixelOnScreen(GData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Green);DrawPixelOnScreen(BData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Blue);}else if (CurrentIndex == 5){DrawPixelOnScreen(RData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Red);DrawPixelOnScreen(GData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Green);DrawPixelOnScreen(BData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Blue);DrawPixelOnScreen(GrayData, BarWidth, BarGap,AllottedGeometry, OutDrawElements,LayerId, EdgeSize, FLinearColor::Gray);}//Background frame drawingFSlateDrawElement::MakeBox(OutDrawElements,LayerId + 10,AllottedGeometry.ToPaintGeometry(FVector2D(BottomSize.X, AllottedGeometry.Size.Y - BackGroundSize.Y - BottomSize.Y), BackGroundSize),new FSlateBrush(),ESlateDrawEffect::None,FLinearColor(0, 0, 0, 0.3));//X Axis rulerFLinearColor XColor = FLinearColor::White;FSlateDrawElement::MakeLines(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(),TArray<FVector2D>{FVector2D(BottomSize.X, AllottedGeometry.Size.Y - BottomSize.Y),FVector2D(BottomSize.X + BackGroundSize.X + 20, AllottedGeometry.Size.Y - BottomSize.Y)},ESlateDrawEffect::None,XColor);FSlateDrawElement::MakeLines(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(),TArray<FVector2D>{FVector2D(BottomSize.X + BackGroundSize.X + 20, AllottedGeometry.Size.Y - BottomSize.Y),FVector2D(BottomSize.X + BackGroundSize.X + 10, AllottedGeometry.Size.Y - BottomSize.Y + 5)},ESlateDrawEffect::None,XColor);FSlateDrawElement::MakeLines(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(),TArray<FVector2D>{FVector2D(BottomSize.X + BackGroundSize.X + 20, AllottedGeometry.Size.Y - BottomSize.Y),FVector2D(BottomSize.X + BackGroundSize.X + 10, AllottedGeometry.Size.Y - BottomSize.Y - 5)},ESlateDrawEffect::None,XColor);//X Axis rulerint XDefaultSectionNum = FMath::Clamp<int>(8 * LinearScale.X, 2, 256);const FSlateFontInfo XCoordinateFont = FCoreStyle::GetDefaultFontStyle("Regular", 8);for (int i = 1; i <= XDefaultSectionNum; ++i){FSlateDrawElement::MakeLines(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(),TArray<FVector2D>{FVector2D(BottomSize.X + BackGroundSize.X / XDefaultSectionNum * i,AllottedGeometry.Size.Y - BottomSize.Y),FVector2D(BottomSize.X + BackGroundSize.X / XDefaultSectionNum * i,AllottedGeometry.Size.Y - BottomSize.Y - 3)},ESlateDrawEffect::None,XColor);FSlateDrawElement::MakeText(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(FVector2D(BottomSize.X + BackGroundSize.X / XDefaultSectionNum * i,AllottedGeometry.Size.Y - BottomSize.Y + 5),FVector2D(30, 30)),FString::FromInt(256 / XDefaultSectionNum * i - 1),XCoordinateFont,ESlateDrawEffect::None,FLinearColor::White);}//Y AxisFLinearColor YColor = FLinearColor::White;FSlateDrawElement::MakeLines(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(),TArray<FVector2D>{FVector2D(BottomSize.X, AllottedGeometry.Size.Y - BottomSize.Y),FVector2D(BottomSize.X, AllottedGeometry.Size.Y - BackGroundSize.Y - BottomSize.Y - 20)},ESlateDrawEffect::None,YColor);FSlateDrawElement::MakeLines(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(),TArray<FVector2D>{FVector2D(BottomSize.X, AllottedGeometry.Size.Y - BackGroundSize.Y - BottomSize.Y - 20),FVector2D(BottomSize.X - 5, AllottedGeometry.Size.Y - BackGroundSize.Y - BottomSize.Y - 10)},ESlateDrawEffect::None,YColor);FSlateDrawElement::MakeLines(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(),TArray<FVector2D>{FVector2D(BottomSize.X, AllottedGeometry.Size.Y - BackGroundSize.Y - BottomSize.Y - 20),FVector2D(BottomSize.X + 5, AllottedGeometry.Size.Y - BackGroundSize.Y - BottomSize.Y - 10)},ESlateDrawEffect::None,YColor);//Y Axis rulerint YDefaultSectionNum = FMath::Clamp<int>(5 * LinearScale.Y, 2, static_cast<int>(100 / HeightScale * HeightScalingFactor) - 1);const FSlateFontInfo YCoordinateFont = FCoreStyle::GetDefaultFontStyle("Regular", 8);for (int i = 1; i <= YDefaultSectionNum; ++i){FSlateDrawElement::MakeLines(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(),TArray<FVector2D>{FVector2D(BottomSize.X,AllottedGeometry.Size.Y - BackGroundSize.Y / YDefaultSectionNum * i - BottomSize.Y),FVector2D(BottomSize.X + 3,AllottedGeometry.Size.Y - BackGroundSize.Y / YDefaultSectionNum * i - BottomSize.Y)},ESlateDrawEffect::None,YColor);FSlateDrawElement::MakeText(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(FVector2D(BottomSize.X + 5,AllottedGeometry.Size.Y - BackGroundSize.Y / YDefaultSectionNum * i - BottomSize.Y),FVector2D(30, 30)),FString::FromInt(100 / HeightScale * HeightScalingFactor / YDefaultSectionNum * i),YCoordinateFont,ESlateDrawEffect::None,FLinearColor::White);}//Origin of coordinateconst FSlateFontInfo CoordinateFont = FCoreStyle::GetDefaultFontStyle("Regular", 10);FSlateDrawElement::MakeText(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(FVector2D(BottomSize.X - 15, AllottedGeometry.Size.Y - BottomSize.Y),FVector2D(30, 30)),FString(TEXT("O")),CoordinateFont,ESlateDrawEffect::None,FLinearColor::White);//X Axis labelingFSlateDrawElement::MakeText(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(FVector2D(BottomSize.X + BackGroundSize.X + 20, AllottedGeometry.Size.Y - BottomSize.Y - 15),FVector2D(30, 30)),FString(TEXT("Color Pass(0 ~ 255)")),CoordinateFont,ESlateDrawEffect::None,XColor);//Y Axis labelingFSlateDrawElement::MakeText(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(FVector2D(BottomSize.X + 5, AllottedGeometry.Size.Y - BottomSize.Y - BackGroundSize.Y - 20),FVector2D(30, 30)),FString(TEXT("Pixel Num")),CoordinateFont,ESlateDrawEffect::None,YColor);//Operation reminderconst FSlateFontInfo TooltipsFontInfo = FCoreStyle::GetDefaultFontStyle("Regular", 15);FSlateDrawElement::MakeText(OutDrawElements,LayerId + 12,AllottedGeometry.ToPaintGeometry(FVector2D(AllottedGeometry.Size.X / 2 - 425, 0), FVector2D(30, 30)),FString(TEXT("Adjust the zoom with the mouse wheel. Press and hold Ctrl + mouse wheel to adjust the height.")),TooltipsFontInfo,ESlateDrawEffect::None,FLinearColor::White);return NewLayerId;
}FReply SPixHistogramWidget::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{const float ScrollDelta = MouseEvent.GetWheelDelta();const float TempScalingFactor = 1.f + ScalingFactor * 0.01f;if (IsCtrlDown){if (ScrollDelta > 0){HeightScale = HeightScale * TempScalingFactor;}else if (ScrollDelta < 0){HeightScale = HeightScale / TempScalingFactor;}}else{if (ScrollDelta > 0){LinearScale = LinearScale * TempScalingFactor;}else if (ScrollDelta < 0){LinearScale = LinearScale / TempScalingFactor;}}return FReply::Handled();
}FReply SPixHistogramWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{IsCtrlDown = InKeyEvent.IsControlDown();return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
}FReply SPixHistogramWidget::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{IsCtrlDown = false;return SCompoundWidget::OnKeyUp(MyGeometry, InKeyEvent);
}void SPixHistogramWidget::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{SCompoundWidget::OnMouseEnter(MyGeometry, MouseEvent);FSlateApplication::Get().SetKeyboardFocus(SharedThis(this), EFocusCause::OtherWidgetLostFocus);
}void SPixHistogramWidget::DrawPixelOnScreen(const TMap<int, float>& InData, const float InBarWidth,const float InBarGap,const FGeometry& InAllottedGeometry,FSlateWindowElementList& InOutDrawElements,const int32 InLayerId, const FVector2D InEdgeSize,const FLinearColor InColor) const
{for (int i = 0; i < InData.Num(); ++i){const float BarHeight = InData[i] * LinearScale.Y * HeightScale / HeightScalingFactor;FVector2D BarPosition(i * (InBarWidth + InBarGap) + InEdgeSize.X,InAllottedGeometry.Size.Y - BarHeight - InEdgeSize.Y);FVector2D BarSize(InBarWidth, BarHeight);FSlateDrawElement::MakeBox(InOutDrawElements,InLayerId,InAllottedGeometry.ToPaintGeometry(BarPosition + FVector2D(BottomSize.X, -BottomSize.Y), BarSize),new FSlateBrush(),ESlateDrawEffect::None,InColor);}
}bool SPixHistogramWidget::SupportsKeyboardFocus() const
{return true;
}void SPixHistogramWidget::GetScreenPixelValue()
{PixelsColor.Empty();FViewport* Viewport = GEditor->GetActiveViewport();Viewport->ReadPixels(PixelsColor);
}void SPixHistogramWidget::RefreshData()
{GetScreenPixelValue();ResetMap();//float Gray = 0.299 * Color.R + 0.587 * Color.G + 0.114 * Color.B;for (const FColor Color : PixelsColor){RData[Color.R] += 1;GData[Color.G] += 1;BData[Color.B] += 1;const int Index = FMath::Clamp<int>((0.299 * Color.R + 0.587 * Color.G + 0.114 * Color.B), 0, 255);GrayData[Index] += 1;}
}void SPixHistogramWidget::ResetMap()
{GrayData.Empty();RData.Empty();GData.Empty();BData.Empty();for (int i = 0; i <= 255; ++i){GrayData.Add(i, 0);RData.Add(i, 0);GData.Add(i, 0);BData.Add(i, 0);}
}void SPixHistogramWidget::OnScalingFactorChanged(float InScalingFactor)
{ScalingFactor = InScalingFactor;
}END_SLATE_FUNCTION_BUILD_OPTIMIZATION
Module添加内容:
.h
#pragma once#include "CoreMinimal.h"
#include "Modules/ModuleManager.h"class FSlashEditorModule : public IModuleInterface
{
public:virtual void StartupModule() override;virtual void ShutdownModule() override;void AddProjectToolsMenu(FMenuBarBuilder& MenuBuilder);void FillProjectToolsMenu(FMenuBuilder& MenuBuilder);private:TSharedRef<SDockTab> SpawnPixHistogramTab(const FSpawnTabArgs& Args);static TSharedRef<FWorkspaceItem> ProjectMenuRoot;};
.cpp
#include "SlashEditor.h"#include "LevelEditor.h"
#include "SlashEditorStyle.h"
#include "PixHistogram/PixHistogramWidget.h"#define LOCTEXT_NAMESPACE "FSlashEditorModule"TSharedRef<FWorkspaceItem> FSlashEditorModule::ProjectMenuRoot = FWorkspaceItem::NewGroup(FText::FromString("ProjectToolsRoot"));
void FSlashEditorModule::StartupModule()
{FSlashEditorStyle::Register();FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");const TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender());MenuExtender->AddMenuBarExtension("Help",EExtensionHook::Before,nullptr,FMenuBarExtensionDelegate::CreateRaw(this, &FSlashEditorModule::AddProjectToolsMenu));LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender);FGlobalTabmanager::Get()->RegisterNomadTabSpawner(FName("PixHistogramTab"),FOnSpawnTab::CreateRaw(this,&FSlashEditorModule::SpawnPixHistogramTab)).SetGroup(ProjectMenuRoot).SetDisplayName(FText::FromString(TEXT("PixHistogram")));
}void FSlashEditorModule::ShutdownModule()
{FSlashEditorStyle::Unregister();
}void FSlashEditorModule::AddProjectToolsMenu(FMenuBarBuilder& MenuBuilder)
{MenuBuilder.AddPullDownMenu(LOCTEXT("Menu", "Project Menu"),LOCTEXT("ToolTip", "Open project menu"),FNewMenuDelegate::CreateRaw(this, &FSlashEditorModule::FillProjectToolsMenu),FName(TEXT("Project Menu")),FName(TEXT("ProjectMenu")));
}void FSlashEditorModule::FillProjectToolsMenu(FMenuBuilder& MenuBuilder)
{MenuBuilder.BeginSection("ProjectMenu", TAttribute<FText>(LOCTEXT("PixHistogramSectionName", "PixHistogram")));{FGlobalTabmanager::Get()->PopulateTabSpawnerMenu(MenuBuilder, FName("PixHistogramTab"));}MenuBuilder.EndSection();
}TSharedRef<SDockTab> FSlashEditorModule::SpawnPixHistogramTab(const FSpawnTabArgs& Args)
{TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab).TabRole(ETabRole::NomadTab)[SNew(SPixHistogramWidget)];return SpawnedTab;
}#undef LOCTEXT_NAMESPACEIMPLEMENT_MODULE(FSlashEditorModule, SlashEditor)
.Build.cs
{"CoreUObject","Engine","Slate","SlateCore", "UMG","InputCore"}
最后成功添加按钮,点击即可得到开头结果: