Add auto-generated selection outline post-process and gizmo polish
Selection Outline: - Post-process material auto-generated in C++ (no manual UE editor work) - 8-neighbor edge detection on CustomStencil buffer - Dynamic pixel-accurate offsets via SceneTexture InvSize (any resolution/ratio) - Lerp blending (no overexposure), color AF1500, 1px thickness - BL_SceneColorAfterDOF placement (before TAA, matches stencil resolution) - Deferred creation via OnPostEngineInit (no startup crash) - Material saved as persistent .uasset, loaded at runtime Gizmo Polish: - Arrow shaft thickness halved (0.03), cone tips halved (0.075x0.1) - Gizmo stays at initial position during rotation/scale (no jitter) - Gizmo follows selection during translation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -343,11 +343,9 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
|||||||
}
|
}
|
||||||
else if (Mode == EPS_Editor_TransformMode::Rotate)
|
else if (Mode == EPS_Editor_TransformMode::Rotate)
|
||||||
{
|
{
|
||||||
// Use combined mouse X+Y screen movement to drive rotation angle
|
|
||||||
const float RotationSpeed = 1.0f;
|
const float RotationSpeed = 1.0f;
|
||||||
const float AngleDelta = (LookInput.X + LookInput.Y) * RotationSpeed;
|
const float AngleDelta = (LookInput.X + LookInput.Y) * RotationSpeed;
|
||||||
|
|
||||||
// Accumulate total angle since drag start
|
|
||||||
GizmoDragAccumulator += AngleDelta;
|
GizmoDragAccumulator += AngleDelta;
|
||||||
|
|
||||||
const FQuat RotationQuat(AxisDir, FMath::DegreesToRadians(GizmoDragAccumulator));
|
const FQuat RotationQuat(AxisDir, FMath::DegreesToRadians(GizmoDragAccumulator));
|
||||||
@@ -386,8 +384,12 @@ void APS_Editor_Pawn::HandleLook(const FInputActionValue& Value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In translate mode, gizmo follows the selection. In rotate/scale, stay at initial position to avoid jitter.
|
||||||
|
if (Mode == EPS_Editor_TransformMode::Translate)
|
||||||
|
{
|
||||||
Gizmo->SetActorLocation(SM->GetSelectionPivot());
|
Gizmo->SetActorLocation(SM->GetSelectionPivot());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void APS_Editor_Pawn::HandleScroll(const FInputActionValue& Value)
|
void APS_Editor_Pawn::HandleScroll(const FInputActionValue& Value)
|
||||||
|
|||||||
@@ -114,8 +114,8 @@ UStaticMeshComponent* APS_Editor_Gizmo::CreateArrow(const FName& Name, USceneCom
|
|||||||
Comp->SetStaticMesh(CylinderMesh.Object);
|
Comp->SetStaticMesh(CylinderMesh.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shaft: radius ~3 units, length ~60 units
|
// Shaft: thin cylinder, length ~60 units
|
||||||
Comp->SetRelativeScale3D(FVector(0.06f, 0.06f, 0.6f));
|
Comp->SetRelativeScale3D(FVector(0.03f, 0.03f, 0.6f));
|
||||||
Comp->SetRelativeRotation(Rotation);
|
Comp->SetRelativeRotation(Rotation);
|
||||||
|
|
||||||
const FVector AxisDir = Rotation.RotateVector(FVector::UpVector);
|
const FVector AxisDir = Rotation.RotateVector(FVector::UpVector);
|
||||||
@@ -136,7 +136,7 @@ UStaticMeshComponent* APS_Editor_Gizmo::CreateCone(const FName& Name, USceneComp
|
|||||||
Comp->SetStaticMesh(ConeMesh.Object);
|
Comp->SetStaticMesh(ConeMesh.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
Comp->SetRelativeScale3D(FVector(0.15f, 0.15f, 0.2f));
|
Comp->SetRelativeScale3D(FVector(0.075f, 0.075f, 0.1f));
|
||||||
Comp->SetRelativeRotation(Rotation);
|
Comp->SetRelativeRotation(Rotation);
|
||||||
|
|
||||||
const FVector AxisDir = Rotation.RotateVector(FVector::UpVector);
|
const FVector AxisDir = Rotation.RotateVector(FVector::UpVector);
|
||||||
|
|||||||
@@ -1,10 +1,232 @@
|
|||||||
#include "PS_Editor.h"
|
#include "PS_Editor.h"
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
#include "Materials/Material.h"
|
||||||
|
#include "MaterialDomain.h"
|
||||||
|
#include "Materials/MaterialExpressionSceneTexture.h"
|
||||||
|
#include "Materials/MaterialExpressionAdd.h"
|
||||||
|
#include "Materials/MaterialExpressionSubtract.h"
|
||||||
|
#include "Materials/MaterialExpressionMultiply.h"
|
||||||
|
#include "Materials/MaterialExpressionAbs.h"
|
||||||
|
#include "Materials/MaterialExpressionSaturate.h"
|
||||||
|
#include "Materials/MaterialExpressionConstant.h"
|
||||||
|
#include "Materials/MaterialExpressionConstant4Vector.h"
|
||||||
|
#include "Materials/MaterialExpressionTextureCoordinate.h"
|
||||||
|
#include "Materials/MaterialExpressionLinearInterpolate.h"
|
||||||
|
#include "Materials/MaterialExpressionComponentMask.h"
|
||||||
|
#include "Materials/MaterialExpressionAppendVector.h"
|
||||||
|
#include "AssetRegistry/AssetRegistryModule.h"
|
||||||
|
#include "UObject/SavePackage.h"
|
||||||
|
#include "Misc/PackageName.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#define LOCTEXT_NAMESPACE "FPS_EditorModule"
|
#define LOCTEXT_NAMESPACE "FPS_EditorModule"
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
static void CreateOutlineMaterialAsset()
|
||||||
|
{
|
||||||
|
const FString MaterialPath = TEXT("/PS_Editor/M_PS_Editor_SelectionOutline");
|
||||||
|
const FString AssetName = TEXT("M_PS_Editor_SelectionOutline");
|
||||||
|
|
||||||
|
if (LoadObject<UMaterial>(nullptr, *(MaterialPath + TEXT(".") + AssetName)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UPackage* Package = CreatePackage(*MaterialPath);
|
||||||
|
UMaterial* Mat = NewObject<UMaterial>(Package, *AssetName, RF_Public | RF_Standalone);
|
||||||
|
Mat->MaterialDomain = EMaterialDomain::MD_PostProcess;
|
||||||
|
Mat->BlendableLocation = BL_SceneColorAfterDOF; // Before TAA, matches stencil buffer resolution
|
||||||
|
|
||||||
|
auto AddExpr = [&](UMaterialExpression* Expr) { Mat->GetExpressionCollection().AddExpression(Expr); };
|
||||||
|
|
||||||
|
const float ThicknessPixels = 1.0f;
|
||||||
|
|
||||||
|
// ---- TexCoord ----
|
||||||
|
UMaterialExpressionTextureCoordinate* TexCoord = NewObject<UMaterialExpressionTextureCoordinate>(Mat);
|
||||||
|
AddExpr(TexCoord);
|
||||||
|
TexCoord->MaterialExpressionEditorX = -1600;
|
||||||
|
TexCoord->MaterialExpressionEditorY = 400;
|
||||||
|
|
||||||
|
// ---- Center stencil sample ----
|
||||||
|
UMaterialExpressionSceneTexture* StencilCenter = NewObject<UMaterialExpressionSceneTexture>(Mat);
|
||||||
|
StencilCenter->SceneTextureId = PPI_CustomStencil;
|
||||||
|
AddExpr(StencilCenter);
|
||||||
|
StencilCenter->Coordinates.Connect(0, TexCoord);
|
||||||
|
StencilCenter->MaterialExpressionEditorX = -1200;
|
||||||
|
StencilCenter->MaterialExpressionEditorY = 0;
|
||||||
|
|
||||||
|
// ---- Get InvSize from SceneTexture (output index 2) = 1/resolution ----
|
||||||
|
// This gives dynamic pixel size regardless of resolution/aspect ratio
|
||||||
|
UMaterialExpressionMultiply* PixelOffset = NewObject<UMaterialExpressionMultiply>(Mat);
|
||||||
|
AddExpr(PixelOffset);
|
||||||
|
PixelOffset->A.Connect(2, StencilCenter); // Output 2 = InvSize (float2)
|
||||||
|
PixelOffset->ConstB = ThicknessPixels;
|
||||||
|
PixelOffset->MaterialExpressionEditorX = -1000;
|
||||||
|
PixelOffset->MaterialExpressionEditorY = 200;
|
||||||
|
|
||||||
|
// Split into X and Y components
|
||||||
|
UMaterialExpressionComponentMask* OffsetX = NewObject<UMaterialExpressionComponentMask>(Mat);
|
||||||
|
AddExpr(OffsetX);
|
||||||
|
OffsetX->Input.Connect(0, PixelOffset);
|
||||||
|
OffsetX->R = true; OffsetX->G = false; OffsetX->B = false; OffsetX->A = false;
|
||||||
|
OffsetX->MaterialExpressionEditorX = -800;
|
||||||
|
OffsetX->MaterialExpressionEditorY = 100;
|
||||||
|
|
||||||
|
UMaterialExpressionComponentMask* OffsetY = NewObject<UMaterialExpressionComponentMask>(Mat);
|
||||||
|
AddExpr(OffsetY);
|
||||||
|
OffsetY->Input.Connect(0, PixelOffset);
|
||||||
|
OffsetY->R = false; OffsetY->G = true; OffsetY->B = false; OffsetY->A = false;
|
||||||
|
OffsetY->MaterialExpressionEditorX = -800;
|
||||||
|
OffsetY->MaterialExpressionEditorY = 300;
|
||||||
|
|
||||||
|
UMaterialExpressionConstant* ZeroConst = NewObject<UMaterialExpressionConstant>(Mat);
|
||||||
|
ZeroConst->R = 0.0f;
|
||||||
|
AddExpr(ZeroConst);
|
||||||
|
ZeroConst->MaterialExpressionEditorX = -800;
|
||||||
|
ZeroConst->MaterialExpressionEditorY = 500;
|
||||||
|
|
||||||
|
// Negative offsets
|
||||||
|
UMaterialExpressionMultiply* NegX = NewObject<UMaterialExpressionMultiply>(Mat);
|
||||||
|
AddExpr(NegX);
|
||||||
|
NegX->A.Connect(0, OffsetX);
|
||||||
|
NegX->ConstB = -1.0f;
|
||||||
|
NegX->MaterialExpressionEditorX = -800;
|
||||||
|
NegX->MaterialExpressionEditorY = 0;
|
||||||
|
|
||||||
|
UMaterialExpressionMultiply* NegY = NewObject<UMaterialExpressionMultiply>(Mat);
|
||||||
|
AddExpr(NegY);
|
||||||
|
NegY->A.Connect(0, OffsetY);
|
||||||
|
NegY->ConstB = -1.0f;
|
||||||
|
NegY->MaterialExpressionEditorX = -800;
|
||||||
|
NegY->MaterialExpressionEditorY = 400;
|
||||||
|
|
||||||
|
// ---- 8 direction samples ----
|
||||||
|
struct FDirDef { UMaterialExpression* XE; UMaterialExpression* YE; };
|
||||||
|
FDirDef Dirs[8] = {
|
||||||
|
{ OffsetX, ZeroConst }, // right
|
||||||
|
{ NegX, ZeroConst }, // left
|
||||||
|
{ ZeroConst, OffsetY }, // down
|
||||||
|
{ ZeroConst, NegY }, // up
|
||||||
|
{ OffsetX, OffsetY }, // bottom-right
|
||||||
|
{ NegX, OffsetY }, // bottom-left
|
||||||
|
{ OffsetX, NegY }, // top-right
|
||||||
|
{ NegX, NegY }, // top-left
|
||||||
|
};
|
||||||
|
|
||||||
|
UMaterialExpression* EdgeAccum = nullptr;
|
||||||
|
|
||||||
|
for (int32 i = 0; i < 8; ++i)
|
||||||
|
{
|
||||||
|
int32 PosY = 100 + i * 160;
|
||||||
|
|
||||||
|
// Append(X, Y) -> float2 offset
|
||||||
|
UMaterialExpressionAppendVector* OffsetVec = NewObject<UMaterialExpressionAppendVector>(Mat);
|
||||||
|
AddExpr(OffsetVec);
|
||||||
|
OffsetVec->A.Connect(0, Dirs[i].XE);
|
||||||
|
OffsetVec->B.Connect(0, Dirs[i].YE);
|
||||||
|
OffsetVec->MaterialExpressionEditorX = -600;
|
||||||
|
OffsetVec->MaterialExpressionEditorY = PosY;
|
||||||
|
|
||||||
|
// TexCoord + offset
|
||||||
|
UMaterialExpressionAdd* UVAdd = NewObject<UMaterialExpressionAdd>(Mat);
|
||||||
|
AddExpr(UVAdd);
|
||||||
|
UVAdd->A.Connect(0, TexCoord);
|
||||||
|
UVAdd->B.Connect(0, OffsetVec);
|
||||||
|
UVAdd->MaterialExpressionEditorX = -400;
|
||||||
|
UVAdd->MaterialExpressionEditorY = PosY;
|
||||||
|
|
||||||
|
// Sample stencil at offset
|
||||||
|
UMaterialExpressionSceneTexture* StencilN = NewObject<UMaterialExpressionSceneTexture>(Mat);
|
||||||
|
StencilN->SceneTextureId = PPI_CustomStencil;
|
||||||
|
AddExpr(StencilN);
|
||||||
|
StencilN->Coordinates.Connect(0, UVAdd);
|
||||||
|
StencilN->MaterialExpressionEditorX = -200;
|
||||||
|
StencilN->MaterialExpressionEditorY = PosY;
|
||||||
|
|
||||||
|
// abs(center - neighbor)
|
||||||
|
UMaterialExpressionSubtract* Sub = NewObject<UMaterialExpressionSubtract>(Mat);
|
||||||
|
AddExpr(Sub);
|
||||||
|
Sub->A.Connect(0, StencilCenter);
|
||||||
|
Sub->B.Connect(0, StencilN);
|
||||||
|
Sub->MaterialExpressionEditorX = 0;
|
||||||
|
Sub->MaterialExpressionEditorY = PosY;
|
||||||
|
|
||||||
|
UMaterialExpressionAbs* AbsN = NewObject<UMaterialExpressionAbs>(Mat);
|
||||||
|
AddExpr(AbsN);
|
||||||
|
AbsN->Input.Connect(0, Sub);
|
||||||
|
AbsN->MaterialExpressionEditorX = 200;
|
||||||
|
AbsN->MaterialExpressionEditorY = PosY;
|
||||||
|
|
||||||
|
if (!EdgeAccum)
|
||||||
|
{
|
||||||
|
EdgeAccum = AbsN;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UMaterialExpressionAdd* AccumAdd = NewObject<UMaterialExpressionAdd>(Mat);
|
||||||
|
AddExpr(AccumAdd);
|
||||||
|
AccumAdd->A.Connect(0, EdgeAccum);
|
||||||
|
AccumAdd->B.Connect(0, AbsN);
|
||||||
|
AccumAdd->MaterialExpressionEditorX = 400;
|
||||||
|
AccumAdd->MaterialExpressionEditorY = PosY;
|
||||||
|
EdgeAccum = AccumAdd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Saturate edge mask ----
|
||||||
|
UMaterialExpressionSaturate* Saturate = NewObject<UMaterialExpressionSaturate>(Mat);
|
||||||
|
AddExpr(Saturate);
|
||||||
|
Saturate->Input.Connect(0, EdgeAccum);
|
||||||
|
Saturate->MaterialExpressionEditorX = 600;
|
||||||
|
Saturate->MaterialExpressionEditorY = 500;
|
||||||
|
|
||||||
|
// ---- Outline color (orange, float4) ----
|
||||||
|
UMaterialExpressionConstant4Vector* OutlineColor = NewObject<UMaterialExpressionConstant4Vector>(Mat);
|
||||||
|
OutlineColor->Constant = FColor(0xAF, 0x15, 0x00, 0xFF).ReinterpretAsLinear();
|
||||||
|
AddExpr(OutlineColor);
|
||||||
|
OutlineColor->MaterialExpressionEditorX = 600;
|
||||||
|
OutlineColor->MaterialExpressionEditorY = 700;
|
||||||
|
|
||||||
|
// ---- Scene input ----
|
||||||
|
UMaterialExpressionSceneTexture* SceneInput = NewObject<UMaterialExpressionSceneTexture>(Mat);
|
||||||
|
SceneInput->SceneTextureId = PPI_PostProcessInput0;
|
||||||
|
AddExpr(SceneInput);
|
||||||
|
SceneInput->MaterialExpressionEditorX = 600;
|
||||||
|
SceneInput->MaterialExpressionEditorY = 300;
|
||||||
|
|
||||||
|
// ---- Lerp(Scene, OutlineColor, EdgeMask) ----
|
||||||
|
UMaterialExpressionLinearInterpolate* Lerp = NewObject<UMaterialExpressionLinearInterpolate>(Mat);
|
||||||
|
AddExpr(Lerp);
|
||||||
|
Lerp->A.Connect(0, SceneInput);
|
||||||
|
Lerp->B.Connect(0, OutlineColor);
|
||||||
|
Lerp->Alpha.Connect(0, Saturate);
|
||||||
|
Lerp->MaterialExpressionEditorX = 800;
|
||||||
|
Lerp->MaterialExpressionEditorY = 500;
|
||||||
|
|
||||||
|
// ---- Output ----
|
||||||
|
Mat->GetEditorOnlyData()->EmissiveColor.Connect(0, Lerp);
|
||||||
|
|
||||||
|
Mat->PreEditChange(nullptr);
|
||||||
|
Mat->PostEditChange();
|
||||||
|
|
||||||
|
FString PackageFilename = FPackageName::LongPackageNameToFilename(MaterialPath, FPackageName::GetAssetPackageExtension());
|
||||||
|
FSavePackageArgs SaveArgs;
|
||||||
|
SaveArgs.TopLevelFlags = RF_Public | RF_Standalone;
|
||||||
|
UPackage::SavePackage(Package, Mat, *PackageFilename, SaveArgs);
|
||||||
|
FAssetRegistryModule::AssetCreated(Mat);
|
||||||
|
|
||||||
|
UE_LOG(LogTemp, Log, TEXT("PS_Editor: Selection outline material auto-generated (8-sample, dynamic resolution)"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void FPS_EditorModule::StartupModule()
|
void FPS_EditorModule::StartupModule()
|
||||||
{
|
{
|
||||||
UE_LOG(LogTemp, Log, TEXT("PS_Editor module started."));
|
UE_LOG(LogTemp, Log, TEXT("PS_Editor module started."));
|
||||||
|
|
||||||
|
#if WITH_EDITOR
|
||||||
|
FCoreDelegates::OnPostEngineInit.AddStatic(&CreateOutlineMaterialAsset);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void FPS_EditorModule::ShutdownModule()
|
void FPS_EditorModule::ShutdownModule()
|
||||||
|
|||||||
Reference in New Issue
Block a user