简化代码,修正固定模式之后切换不回来bug,调整计算偏移阈值为0.5

This commit is contained in:
2025-11-15 22:43:43 +08:00
parent 9d3552bac2
commit 5afc656fba
10 changed files with 4 additions and 403 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -374
View File
@@ -23,9 +23,7 @@
// Sets default values
AIronBlock::AIronBlock() : StaticMeshComp(nullptr),
PositionRenderTarget0(nullptr), PositionRenderTarget1(nullptr),
DynMaterial(nullptr),
CurrentImpactPoint(),
CurrentImpactNormal()
DynMaterial(nullptr)
{
// 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;
@@ -57,47 +55,6 @@ void AIronBlock::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
*/
@@ -119,7 +76,7 @@ UTextureRenderTarget2D* AIronBlock::CreateDeformRT(const FString& RTName, int32
RenderTarget->bAutoGenerateMips = false;
RenderTarget->AddressX = TA_Clamp;
RenderTarget->AddressY = TA_Clamp;
FLinearColor ClearColor = FLinearColor(0.25f, 0.25f, 0.25f, 1.0f);
FLinearColor ClearColor = FLinearColor(0.5f, 0.5f, 0.5f, 1.0f);
RenderTarget->ClearColor = ClearColor;
// 立即创建RHI资源
RenderTarget->UpdateResource();
@@ -141,108 +98,18 @@ UTextureRenderTarget2D* AIronBlock::CreateDeformRT(const FString& RTName, int32
void AIronBlock::InitializePositionTexture()
{
CalculateLocalBounds();
// // 获取顶点位置 (用于计算初始UV->WorldPos)
// TArray<FVector3f> VertexPositions;
// GetStaticMeshVertexPositions(VertexPositions);
// 创建位置纹理
const int32 TextureSize = TexSize; // 纹理尺寸
// 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(nullptr);
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)
{
// 创建动态材质实例
@@ -285,7 +152,7 @@ void AIronBlock::OnHammerHit(const FHitResult& HitResult)
FVector2D HitUV;
UGameplayStatics::FindCollisionUV(HitResult,0,HitUV);
FVector ImpactNormal = -HitResult.ImpactNormal;
CurrentDeformationRadius = ImpactAffectRange/TexSize;
float DeformationRadius = ImpactAffectRange/TexSize;
UTextureRenderTarget2D* LastTex = nullptr;
UTextureRenderTarget2D* CurTex = nullptr;
if(CurrentTextureIndex == 0)
@@ -302,7 +169,7 @@ void AIronBlock::OnHammerHit(const FHitResult& HitResult)
{
DynCalMaterial->SetVectorParameterValue(TEXT("ImpactUV"), FLinearColor(HitUV.X,HitUV.Y,0,0));
DynCalMaterial->SetScalarParameterValue(TEXT("ImpactForce"),ImpactForce);
DynCalMaterial->SetScalarParameterValue(TEXT("ImpactRadius"),CurrentDeformationRadius);
DynCalMaterial->SetScalarParameterValue(TEXT("ImpactRadius"), DeformationRadius);
DynCalMaterial->SetVectorParameterValue(TEXT("ImpactNormal"),ImpactNormal);
DynCalMaterial->SetTextureParameterValue(TEXT("LastPosition"),LastTex);
UKismetRenderingLibrary::DrawMaterialToRenderTarget(this->GetWorld(),CurTex,DynCalMaterial);
@@ -312,243 +179,6 @@ void AIronBlock::OnHammerHit(const FHitResult& HitResult)
DynMaterial->SetTextureParameterValue(TEXT("PositionTexture"),CurTex);
CurrentTextureIndex = 1 - CurrentTextureIndex;
}
// // 转换到本地空间
// 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;
}
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
-29
View File
@@ -53,7 +53,6 @@ public:
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
virtual void PostInitProperties();
/** 初始化两个渲染目标和它们的初始数据 */
void InitializePositionTexture();
@@ -61,38 +60,10 @@ protected:
/** 创建一个配置好用于变形计算的 RenderTarget */
UTextureRenderTarget2D* CreateDeformRT(const FString& RTName, int32 Size);
/** 获取静态网格体的顶点位置 (用于计算初始纹理) */
void GetStaticMeshVertexPositions(TArray<FVector3f>& OutPositions);
/** 设置动态材质实例和参数 */
void SetupDeformationMaterial(UTexture2D* OrigTex);
/** 安全地获取 RenderTarget 的 RHI 资源 */
FTextureRHIRef GetRenderTargetRHI(UTextureRenderTarget2D* RT);
private:
/** 更新 Compute Shader 所需的参数 */
void UpdateComputeShaderParams(FVector LocalImpactPoint, FVector LocalImpactNormal, float ImpactForce);
/** 在渲染线程上执行 Compute Shader */
void ExecuteDeformationCS();
/** (可选) 异步更新物理碰撞体 */
void UpdateCollisionAsync(const FIntRect& UpdateRegion, UTextureRenderTarget2D* FromTarget);
/** 计算网格体的本地包围盒 */
void CalculateLocalBounds();
// 网格体的本地包围盒
FBox LocalBounds;
// --- 碰撞参数 ---
FVector CurrentImpactPoint;
FVector CurrentImpactNormal;
float CurrentImpactForce = 0.0f;
float CurrentDeformationRadius = 10.0f;
float CurrentPlasticity = 0.7f;
/** * 双缓冲纹理索引
* 0: 读 RT0, 写 RT1
* 1: 读 RT1, 写 RT0