Files
BladeSmith/Source/BladeSmith/IronBlock.cpp
T

525 lines
19 KiB
C++

// Fill out your copyright notice in the Description page of Project Settings.
#include "IronBlock.h"
// 确保包含了 RDG 所需的头文件
#include "RenderGraphBuilder.h"
#include "RenderGraphUtils.h" // FComputeShaderUtils::AddPass
#include "FDeformComputeShader.h" // 确保这个头文件存在且包含了 FDeformationCSDispatchParams
#include "StaticMeshResources.h"
#include "TextureResource.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/StaticMesh.h"
#include "Engine/Texture2D.h"
#include "Materials/MaterialInstanceDynamic.h"
#include "PhysicsEngine/BodySetup.h"
#include "RHICommandList.h"
#include "Engine/Texture.h"
#include "GenericPlatform/GenericPlatformProcess.h"
// Sets default values
AIronBlock::AIronBlock() : StaticMeshComp(nullptr),
PositionRenderTarget0(nullptr), PositionRenderTarget1(nullptr),
DynMaterial(nullptr),
CurrentImpactPoint(),
CurrentImpactNormal()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
StaticMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMeshComponent"));
RootComponent = StaticMeshComp;
}
void AIronBlock::PostInitProperties()
{
// 必须调用父类实现
Super::PostInitProperties();
// 检查:如果 StaticMeshComp 没有被赋予任何网格(即在编辑器中没有设置)
// 并且我们正在处理 Class Default Object (CDO),通常用来设置类级的默认值
if (StaticMeshComp && !StaticMeshComp->GetStaticMesh())
{
// 只有当网格为空时,才加载并设置默认的 Cube
UStaticMesh* DefaultMesh = LoadObject<UStaticMesh>(nullptr, TEXT("/Engine/BasicShapes/Cube"));
if (DefaultMesh)
{
StaticMeshComp->SetStaticMesh(DefaultMesh);
}
}
}
// Called when the game starts or when spawned
void AIronBlock::BeginPlay()
{
Super::BeginPlay();
InitializePositionTexture();
}
void AIronBlock::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
// RenderTargets 会被 UPROPERTY 自动 GC,无需手动释放 RHI 资源
Super::EndPlay(EndPlayReason);
}
// 安全的渲染目标获取
FTextureRHIRef AIronBlock::GetRenderTargetRHI(UTextureRenderTarget2D* RT)
{
if (!RT || !RT->GetResource()) return nullptr;
return RT->GetResource()->TextureRHI;
}
void AIronBlock::GetStaticMeshVertexPositions(TArray<FVector3f>& OutPositions)
{
OutPositions.Empty();
if (!StaticMeshComp || !StaticMeshComp->GetStaticMesh())
return;
// 获取静态网格资源
UStaticMesh* StaticMesh = StaticMeshComp->GetStaticMesh();
// 确保有有效的 RenderData 和 LOD
if (StaticMesh->GetRenderData() && StaticMesh->GetRenderData()->LODResources.Num() > 0)
{
// 获取LOD0的顶点数据
FStaticMeshLODResources& LOD = StaticMesh->GetRenderData()->LODResources[0];
const FPositionVertexBuffer& VertexBuffer = LOD.VertexBuffers.PositionVertexBuffer;
const int32 VertexCount = VertexBuffer.GetNumVertices();
OutPositions.SetNum(VertexCount);
for (int32 i = 0; i < VertexCount; i++)
{
OutPositions[i] = VertexBuffer.VertexPosition(i);
}
}
}
/**
* 核心修复:创建一个配置好用于变形计算的 RenderTarget
*/
UTextureRenderTarget2D* AIronBlock::CreateDeformRT(const FString& RTName, int32 Size)
{
UTextureRenderTarget2D* RenderTarget = NewObject<UTextureRenderTarget2D>(this, FName(*RTName));
if (RenderTarget)
{
// **关键修复 1:** 设置为32位浮点数格式,以匹配 FLinearColor 和 CS 的 PF_A32B32G32R32F
//RenderTarget->RenderTargetFormat = RTF_RGBA32f;
// **关键修复 2:** 明确告诉引擎我们需要为这个纹理创建UAV
RenderTarget->bCanCreateUAV = true;
RenderTarget->InitCustomFormat(Size, Size, PF_A32B32G32R32F, false);
RenderTarget->bNeedsTwoCopies = true; // 告知引擎这个纹理会被用于Ping-Pong,优化内存
//RenderTarget->SizeX = Size;
//RenderTarget->SizeY = Size;
RenderTarget->bAutoGenerateMips = false;
RenderTarget->AddressX = TA_Clamp;
RenderTarget->AddressY = TA_Clamp;
// 立即创建RHI资源
//RenderTarget->UpdateResource();
}
return RenderTarget;
}
void AIronBlock::InitializePositionTexture()
{
CalculateLocalBounds();
// 获取顶点位置 (用于计算初始UV->WorldPos)
TArray<FVector3f> VertexPositions;
GetStaticMeshVertexPositions(VertexPositions);
// 创建位置纹理
const int32 TextureSize = 256; // 纹理尺寸
TArray<FLinearColor> InitialData;
InitialData.SetNum(TextureSize * TextureSize);
// 初始化纹理数据 (将UV映射到本地包围盒的XY平面)
for (int32 Y = 0; Y < TextureSize; ++Y)
{
for (int32 X = 0; X < TextureSize; ++X)
{
int32 Index = Y * TextureSize + X;
FVector2D UV(FVector2D(X, Y) / (TextureSize - 1.0f)); // 使用 255.0 或 (Size-1)
// 将UV坐标转换为本地空间位置 (XY平面)
FVector LocalPos(
FMath::Lerp(LocalBounds.Min.X, LocalBounds.Max.X, UV.X),
FMath::Lerp(LocalBounds.Min.Y, LocalBounds.Max.Y, UV.Y),
LocalBounds.Min.Z // 假设初始是一个平面
);
InitialData[Index] = FLinearColor(LocalPos.X * 1000, LocalPos.Y * 1000, LocalPos.Z * 1000, 1.0f);
}
}
// --- 关键修复: 创建两个 RenderTarget ---
PositionRenderTarget0 = CreateDeformRT(TEXT("PositionRT0"), TextureSize);
PositionRenderTarget1 = CreateDeformRT(TEXT("PositionRT1"), TextureSize);
// --- 将初始数据写入GPU ---
// 我们需要将CPU端的 InitialData 写入到GPU纹理中
// 最简单的方法是创建一个临时的UTexture2D,用BulkData上传,然后拷贝到RT
UTexture2D* TempTexture = UTexture2D::CreateTransient(TextureSize, TextureSize, PF_A32B32G32R32F); // 必须匹配格式
if (TempTexture && PositionRenderTarget0 && PositionRenderTarget1)
{
// 锁定 Mip 并拷贝数据
FTexture2DMipMap& Mip = TempTexture->GetPlatformData()->Mips[0];
void* MipData = Mip.BulkData.Lock(LOCK_READ_WRITE);
FMemory::Memcpy(MipData, InitialData.GetData(), InitialData.Num() * sizeof(FLinearColor));
Mip.BulkData.Unlock();
TempTexture->UpdateResource(); // 提交到GPU
// 获取RHI资源
FTextureRHIRef DestRT0_RHI = GetRenderTargetRHI(PositionRenderTarget0);
FTextureRHIRef DestRT1_RHI = GetRenderTargetRHI(PositionRenderTarget1);
FTextureRHIRef SrcTex_RHI = TempTexture->GetResource() ? TempTexture->GetResource()->TextureRHI : nullptr;
if (DestRT0_RHI && DestRT1_RHI && SrcTex_RHI)
{
// 在渲染线程上执行拷贝
ENQUEUE_RENDER_COMMAND(InitializeDeformRTs)(
[DestRT0_RHI, DestRT1_RHI, SrcTex_RHI](FRHICommandListImmediate& RHICmdList)
{
// 将初始数据拷贝到两个RT
RHICmdList.CopyTexture(SrcTex_RHI, DestRT0_RHI, {});
RHICmdList.CopyTexture(SrcTex_RHI, DestRT1_RHI, {});
});
// 确保拷贝完成
FlushRenderingCommands();
}
}
// 设置变形材质
SetupDeformationMaterial(TempTexture);
UE_LOG(LogTemp, Log, TEXT("Position textures initialized"));
}
void AIronBlock::CalculateLocalBounds()
{
if (!StaticMeshComp)
{
StaticMeshComp = GetComponentByClass<UStaticMeshComponent>();
}
if (StaticMeshComp && StaticMeshComp->GetStaticMesh())
{
// 获取本地空间包围盒
LocalBounds = StaticMeshComp->GetStaticMesh()->GetBoundingBox();
}
else
{
// 智能默认值
float Extent = 50.0f;
LocalBounds = FBox(
FVector(-Extent, -Extent, 0),
FVector(Extent, Extent, Extent * 0.2f)
);
}
// 确保包围盒有效
if (LocalBounds.GetVolume() <= 0)
{
LocalBounds = FBox(FVector(-50, -50, 0), FVector(50, 50, 10));
}
}
void AIronBlock::SetupDeformationMaterial(UTexture2D* OrigTex)
{
// 创建动态材质实例
if (StaticMeshComp)
{
// 加载基础材质 (请确保你的材质支持读取 PositionTexture)
UMaterial* BaseMaterial = LoadObject<UMaterial>(nullptr, TEXT("/Game/Materials/M_Iron")); // 示例路径
if (!BaseMaterial)
{
BaseMaterial = LoadObject<UMaterial>(nullptr, TEXT("/Engine/BasicShapes/BasicShapeMaterial"));
}
if (BaseMaterial)
{
DynMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, this);
StaticMeshComp->SetMaterial(0, DynMaterial);
// 设置初始参数
if (DynMaterial)
{
// 初始时读取 RT0
DynMaterial->SetTextureParameterValue(TEXT("PositionTexture"), OrigTex);
//DynMaterial->SetTextureParameterValue(TEXT("PositionTexture"), PositionRenderTarget0);
DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMin"), FLinearColor(LocalBounds.Min));
DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMax"), FLinearColor(LocalBounds.Max));
DynMaterial->SetScalarParameterValue(TEXT("DeformationStrength"), 1.0f);
}
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to load base material for iron block"));
}
}
}
void AIronBlock::OnHammerHit(const FHitResult& HitResult)
{
// 获取锤子冲击力
float ImpactForce = 0.0f;
// 检查 HitResult.GetComponent() 是否有效
if (HitResult.GetComponent() && !HitResult.GetComponent()->GetPhysicsLinearVelocity().IsNearlyZero())
{
ImpactForce = HitResult.GetComponent()->GetPhysicsLinearVelocity().Size();
}
// 默认冲击力
if (ImpactForce < 10.0f) ImpactForce = 500.0f;
// 转换到本地空间
FVector ImpactPoint = HitResult.ImpactPoint;
FVector ImpactNormal = HitResult.ImpactNormal;
FVector LocalImpactPoint = GetTransform().InverseTransformPosition(ImpactPoint);
FVector LocalImpactNormal = GetTransform().InverseTransformVector(ImpactNormal);
UE_LOG(LogTemp,Display,TEXT("[AIronBlock::OnHammerHit] ImpactForce:%f,ImpactPoint:%s,ImpactNormal:%s,LocalImpactPoint:%s,LocalImpactNormal:%s")
,ImpactForce,*ImpactPoint.ToString(),*ImpactNormal.ToString(),*LocalImpactPoint.ToString(),*LocalImpactNormal.ToString());
UpdateComputeShaderParams(LocalImpactPoint, LocalImpactNormal, ImpactForce);
ExecuteDeformationCS();
}
void AIronBlock::UpdateComputeShaderParams(FVector LocalImpactPoint, FVector LocalImpactNormal, float ImpactForce)
{
// 更新碰撞参数
CurrentImpactPoint = LocalImpactPoint;
CurrentImpactNormal = LocalImpactNormal.GetSafeNormal();
CurrentImpactForce = FMath::Clamp(ImpactForce, 0.0f, 5000.0f);
// 根据冲击力计算动态变形半径
CurrentDeformationRadius = FMath::Lerp(5.0f, 30.0f, CurrentImpactForce / 2000.0f);
// 根据材质类型调整塑性系数 (示例)
UMaterialInterface* Material = StaticMeshComp->GetMaterial(0);
if (Material && Material->GetName().Contains("Steel"))
{
CurrentPlasticity = 0.4f; // 钢材塑性较低
}
else
{
CurrentPlasticity = 0.8f; // 默认高塑性
}
// 切换纹理缓冲索引
CurrentTextureIndex = 1 - CurrentTextureIndex;
UE_LOG(LogTemp,Display,TEXT("[AIronBlock::OnHammerHitUpdateComputeShaderParams]CurrentImpactForce:%f, CurrentImpactPoint:%s,CurrentImpactNormal:%s,CurrentDeformationRadius:%f,CurrentPlasticity:%f")
,CurrentImpactForce,*CurrentImpactPoint.ToString(),*CurrentImpactNormal.ToString(),CurrentDeformationRadius,CurrentPlasticity);
}
void AIronBlock::ExecuteDeformationCS()
{
// 确保纹理资源有效
if (!PositionRenderTarget0 || !PositionRenderTarget0->GetResource() ||
!PositionRenderTarget1 || !PositionRenderTarget1->GetResource())
{
UE_LOG(LogTemp, Error, TEXT("Position render targets not initialized!"));
return;
}
// --- 关键修复: 决定哪个是读取RT,哪个是写入RT ---
UTextureRenderTarget2D* ReadTarget = (CurrentTextureIndex == 0) ? PositionRenderTarget0 : PositionRenderTarget1;
UTextureRenderTarget2D* WriteTarget = (CurrentTextureIndex == 1) ? PositionRenderTarget0 : PositionRenderTarget1; // Flipped
// --- 关键修复: 在游戏线程只获取 Input RHI Texture 句柄 ---
FTextureRHIRef InputRHI = GetRenderTargetRHI(ReadTarget);
FTextureRHIRef WriteRHI = GetRenderTargetRHI(WriteTarget); // 必须获取写入目标的RHI
if (!InputRHI.IsValid() || !WriteRHI.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("Invalid texture RHI resources!"));
return;
}
// 计算纹理UV坐标
const int32 TexSize = ReadTarget->SizeX;
FVector2D ImpactUV(
(CurrentImpactPoint.X - LocalBounds.Min.X) / LocalBounds.GetSize().X,
(CurrentImpactPoint.Y - LocalBounds.Min.Y) / LocalBounds.GetSize().Y
);
UE_LOG(LogTemp,Display,TEXT("[AIronBlock::ExecuteDeformationCS]LocalBounds:%s")
,*LocalBounds.ToString());
// 假设 LocalBounds 仍然是未缩放的原始包围盒
// 获得 Actor 的缩放
FVector ComponentScale = StaticMeshComp->GetComponentScale();
// 1. 计算缩放后包围盒的最大延伸 (在 X/Y 平面上)
// 假设您的形变发生在 XY 平面
float MaxLocalExtentX = LocalBounds.GetExtent().X * ComponentScale.X;
float MaxLocalExtentY = LocalBounds.GetExtent().Y * ComponentScale.Y;
float MaxScaledExtent = FMath::Max(MaxLocalExtentX, MaxLocalExtentY);
// 2. 使用这个缩放后的最大延伸进行比例计算
// 这里的 CurrentDeformationRadius 应该是一个世界空间的值。
const float RadiusRatio = CurrentDeformationRadius / MaxScaledExtent;
// 计算更新区域 (只更新受影响部分)
//const float RadiusRatio = CurrentDeformationRadius / LocalBounds.GetExtent().GetMax();
FIntRect UpdateRegion;
UpdateRegion.Min.X = FMath::Clamp(FMath::FloorToInt((ImpactUV.X - RadiusRatio) * TexSize), 0, TexSize - 1);
UpdateRegion.Min.Y = FMath::Clamp(FMath::FloorToInt((ImpactUV.Y - RadiusRatio) * TexSize), 0, TexSize - 1);
UpdateRegion.Max.X = FMath::Clamp(FMath::CeilToInt((ImpactUV.X + RadiusRatio) * TexSize), 1, TexSize);
UpdateRegion.Max.Y = FMath::Clamp(FMath::CeilToInt((ImpactUV.Y + RadiusRatio) * TexSize), 1, TexSize);
// 确保 Min < Max
if (UpdateRegion.Min.X >= UpdateRegion.Max.X) UpdateRegion.Max.X = UpdateRegion.Min.X + 1;
if (UpdateRegion.Min.Y >= UpdateRegion.Max.Y) UpdateRegion.Max.Y = UpdateRegion.Min.Y + 1;
// 临时代码,先更新全部区域
UpdateRegion.Min.X = 0;
UpdateRegion.Min.Y = 0;
UpdateRegion.Max.X = TexSize;
UpdateRegion.Max.Y = TexSize;
// --- 关键修改:使用 RDG ---
ENQUEUE_RENDER_COMMAND(ExecuteDeformationCS_RDG)(
[
// 捕获所有需要的参数
InputRHI,
WriteRHI,
CurrentImpactPoint = this->CurrentImpactPoint, // 拷贝一份
CurrentImpactNormal = this->CurrentImpactNormal,
CurrentImpactForce = this->CurrentImpactForce,
CurrentDeformationRadius = this->CurrentDeformationRadius,
CurrentPlasticity = this->CurrentPlasticity,
UpdateRegion, //
TexSize //
](FRHICommandListImmediate& RHICmdList)
{
// 1. 创建 RDG Builder
FRDGBuilder builder(RHICmdList);
// 2. 将我们的外部 RT 注册到 RDG 中
// 注意:我们使用 WriteRHI->GetDesc() 来获取纹理描述
FRDGTextureRef RDGReadTexture = builder.RegisterExternalTexture(
CreateRenderTarget(InputRHI, TEXT("DeformInputTex"))
);
FRDGTextureRef RDGWriteTexture = builder.RegisterExternalTexture(
CreateRenderTarget(WriteRHI, TEXT("DeformOutputTex"))
);
// 3. 分配着色器参数
FDeformComputeShader::FParameters* Params = builder.AllocParameters<FDeformComputeShader::FParameters>();
// 4. 填充 CPU 参数
Params->ImpactPoint = FVector3f(CurrentImpactPoint); // 注意:从 FVector 转为 FVector3f
Params->ImpactNormal = FVector3f(CurrentImpactNormal);
Params->ImpactForce = CurrentImpactForce;
Params->DeformationRadius = CurrentDeformationRadius;
Params->Plasticity = CurrentPlasticity;
Params->UpdateRegionParam = FIntVector4(UpdateRegion.Min.X, UpdateRegion.Min.Y, UpdateRegion.Max.X, UpdateRegion.Max.Y);
Params->TextureSize = FIntVector(TexSize, TexSize, 1);
UE_LOG(LogTemp,Display,TEXT("[AIronBlock::ExecuteDeformationCS]ImpactForce:%f, ImpactPoint:%s,ImpactNormal:%s,DeformationRadius:%f,Plasticity:%f,UpdateRegion:%s")
,Params->ImpactForce,*Params->ImpactPoint.ToString(),*Params->ImpactNormal.ToString(),Params->DeformationRadius,Params->Plasticity,*Params->UpdateRegionParam.ToString());
// 5. ✨ 魔法发生的地方:RDG 为我们创建 SRV 和 UAV
// 这就是解决你 nullptr 问题的核心
Params->InputTexture = builder.CreateSRV(RDGReadTexture);
Params->OutputTexture = builder.CreateUAV(RDGWriteTexture);
// 6. 获取着色器实例
TShaderMapRef<FDeformComputeShader> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
if (!ComputeShader.IsValid())
{
UE_LOG(LogTemp, Error, TEXT("FDeformComputeShader is not valid!"));
return;
}
// 7. 计算线程组数量
// (注意:你的USF需要处理 UpdateRegion,这里我们调度整个区域)
FIntVector NumGroups = FIntVector::DivideAndRoundUp(
FIntVector(UpdateRegion.Width(), UpdateRegion.Height(), 1),
FDeformComputeShader::GetGroupSize()
);
// 如果你的shader总是处理整个纹理,请使用:
// FIntVector NumGroups = FIntVector::DivideAndRoundUp(
// Params->TextureSize, FDeformComputeShader::GetGroupSize()
// );
// 8. 添加 Compute Pass
FComputeShaderUtils::AddPass(
builder,
RDG_EVENT_NAME("ExecuteDeformationCS"),
ComputeShader,
Params,
NumGroups
);
// 9. 执行 RDG (这会处理所有屏障和调度)
builder.Execute();
});
// --- 结束 RDG 修改 ---
// 确保渲染命令完成 (如果你需要立即更新碰撞体)
// FlushRenderingCommands();
// UpdateCollisionAsync(UpdateRegion, WriteTarget);
// --- 关键修复: 更新材质参数,使其下一帧读取我们刚刚写入的纹理 ---
if (DynMaterial)
{
DynMaterial->SetTextureParameterValue(TEXT("PositionTexture"), WriteTarget);
DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMin"), FLinearColor(LocalBounds.Min));
DynMaterial->SetVectorParameterValue(TEXT("LocalBoundsMax"), FLinearColor(LocalBounds.Max));
}
}
// 异步更新物理碰撞体 (示例,未优化)
void AIronBlock::UpdateCollisionAsync(const FIntRect& UpdateRegion, UTextureRenderTarget2D* FromTarget)
{
// 注意:ReadPixels 是一项非常慢的操作 (GPU->CPU 同步)
// 更好的方法是使用另一个Compute Shader将顶点数据简化并读回到一个小的StructuredBuffer中
if (!FromTarget || !FromTarget->GetResource())
{
return;
}
// 从GPU读取关键区域数据
TArray<FLinearColor> SampledData; // 使用 FLinearColor
FReadSurfaceDataFlags Flags;
Flags.SetLinearToGamma(false);
FRenderTarget* RTResource = FromTarget->GameThread_GetRenderTargetResource();
if (!RTResource) return;
RTResource->ReadLinearColorPixels(
SampledData,
Flags,
UpdateRegion // FIntRect
);
// 创建临时碰撞体
TArray<FVector> CollisionVerts;
for (const FLinearColor& Pixel : SampledData)
{
// 像素数据已经是 FLinearColor (即 FVector)
CollisionVerts.Add(FVector(Pixel.R, Pixel.G, Pixel.B));
}
if (CollisionVerts.Num() == 0) return;
// 更新简单碰撞体
UBodySetup* BodySetup = StaticMeshComp->GetBodySetup();
if (BodySetup && BodySetup->AggGeom.ConvexElems.Num() > 0)
{
BodySetup->AggGeom.ConvexElems[0].VertexData = CollisionVerts;
BodySetup->CreatePhysicsMeshes(); // 重新创建物理网格体
}
}
// Called every frame
void AIronBlock::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}