问题遇到的现象和发生背景
需要用虚幻UE4.27 从现实的热成像图,转换到虚拟场景的热成像效果,j求方案或指导
图片效果地址:
https://ibb.co/8Xq3Bdh
操作环境、软件版本等信息
windows 10 虚幻 UE4.27
尝试过的解决方法
我的思路是,现实中的图片 根据像素坐标取颜色值,然后屏幕空间发射射线,让击中的射线对模型顶点做颜色修改。
修改颜色找到2个方案代码
1、是实现顶点颜色修改,但是好像顶点颜色修改,自己创建的模型顶点在C++里面计算好像是错误的,导致无法用这个代码实现原先应该着色的地方着色。
代码:
#include "Vertex2FunctionLibrary.h"
#include "Components/SkinnedMeshComponent.h"
#include "Rendering/SkeletalMeshRenderData.h"
#include "RenderResource.h"
//通过点影响修改骨骼模型顶点色
void UVertex2FunctionLibrary::ModfiySkeletalMeshVertexColorByPointEffect(USkeletalMeshComponent* SM, FVector Point, float LimitDistance, FColor Color)
{
for (int32 i = 0; i < SM->GetNumLODs(); ++i)
{
//获取顶点位置
TArray<FVector> Position = GetSkeletalMeshVertexPositionByLODIndex(SM, i);
TArray<FColor> VertexColor;
VertexColor.AddUninitialized(Position.Num());
for (int32 index = 0; index < Position.Num(); ++index)
{
FColor BaseColor = SM->GetVertexColor(index);
//在点范围内修改顶点色
if ((Point - Position[index]).Size() < LimitDistance)
{
BaseColor = Color;
}
VertexColor[index] = BaseColor;
}
//官方自带修改VertexColor的api
SM->SetVertexColorOverride(i, VertexColor);
}
}
//通过点影响修改静态模型顶点色
void UVertex2FunctionLibrary::ModfiyStaticMeshVertexColorByPointEffect(UStaticMeshComponent* SM, FVector Point, float LimitDistance, FColor Color)
{
for (int32 i = 0; i < SM->GetStaticMesh()->GetNumLODs(); ++i)
{
//获取顶点位置
TArray<FVector> Position = GetStaticMeshVertexPositionByLODIndex(SM, i);
TArray<FColor> VertexColor;
VertexColor.AddUninitialized(Position.Num());
//判断是否被应用顶点色
if (SM->GetStaticMesh()->RenderData->LODResources[i].bHasColorVertexData)
{
for (int32 index = 0; index < Position.Num(); ++index)
{
FColor BaseColor = SM->LODData[i].OverrideVertexColors->VertexColor(index);
if ((Point - Position[index]).Size() < LimitDistance)
{
BaseColor = Color;
}
VertexColor[index] = BaseColor;
}
}
else
{
for (int32 index = 0; index < Position.Num(); ++index)
{
FColor BaseColor = FColor(255, 255, 255, 255);
if ((Point - Position[index]).Size() < LimitDistance)
{
BaseColor = Color;
}
VertexColor[index] = BaseColor;
}
}
//StaticMesh官方没有自带api,需要自己编写
SetStaticVertexColorOverride(SM, i, VertexColor);
}
}
//获取骨骼顶点位置
TArray<FVector> UVertex2FunctionLibrary::GetSkeletalMeshVertexPositionByLODIndex(USkeletalMeshComponent* SM, int32 LODIndex)
{
TArray<FVector> OutPosition;
FSkeletalMeshLODRenderData& LODData = SM->GetSkeletalMeshRenderData()->LODRenderData[LODIndex];
TArray<FMatrix> OutRefToLocal;
SM->CacheRefToLocalMatrices(OutRefToLocal);
FSkinWeightVertexBuffer& SkinWeightBuffer = *SM->GetSkinWeightBuffer(LODIndex);
SM->ComputeSkinnedPositions(SM, OutPosition, OutRefToLocal, LODData, SkinWeightBuffer);
VPComponentSpaceToWorldSpace(SM, OutPosition);
return MoveTemp(OutPosition);
}
//获取静态顶点位置
TArray<FVector> UVertex2FunctionLibrary::GetStaticMeshVertexPositionByLODIndex(UStaticMeshComponent* SM, int32 LODIndex)
{
TArray<FVector> OutPosition;
FPositionVertexBuffer& posBuffer = SM->GetStaticMesh()->RenderData->LODResources[LODIndex].VertexBuffers.PositionVertexBuffer;
for (uint32 i = 0; i < posBuffer.GetNumVertices(); ++i)
{
OutPosition.Emplace(posBuffer.VertexPosition(i));
}
VPComponentSpaceToWorldSpace(SM, OutPosition);
return MoveTemp(OutPosition);
}
//把顶点位置从Compont Space 转化到 World Space
void UVertex2FunctionLibrary::VPComponentSpaceToWorldSpace(USceneComponent* SC, TArray<FVector>& Position)
{
FTransform RelativeTransform = SC->GetRelativeTransform();
for (FVector& pos : Position)
{
pos = TransformVector(RelativeTransform, pos);
pos += RelativeTransform.GetLocation();
if (SC->GetOwner())
{
FTransform WorldTransform = SC->GetOwner()->GetTransform();
pos = TransformVector(WorldTransform, pos);
pos += WorldTransform.GetLocation();
}
}
}
//静态顶点颜色修改
bool UVertex2FunctionLibrary::SetStaticVertexColorOverride(UStaticMeshComponent* SM, int32 LODIndex, const TArray<FColor> VertexColor)
{
FStaticMeshRenderData* RenderData = SM->GetStaticMesh()->RenderData.Get();
if (RenderData != nullptr && RenderData->LODResources.IsValidIndex(LODIndex))
{
FStaticMeshLODResources& LODResource = RenderData->LODResources[LODIndex];
FStaticMeshComponentLODInfo& InstanceMeshLODInfo = SM->LODData[LODIndex];
if (InstanceMeshLODInfo.OverrideVertexColors != nullptr)
{
InstanceMeshLODInfo.PaintedVertices.Empty();
SM->CachePaintedDataIfNecessary();
if (InstanceMeshLODInfo.OverrideVertexColors)
{
InstanceMeshLODInfo.ReleaseOverrideVertexColorsAndBlock();
SM->GetStaticMesh()->RenderData->LODResources[LODIndex].bHasColorVertexData = true;
}
InstanceMeshLODInfo.OverrideVertexColors = new FColorVertexBuffer;
InstanceMeshLODInfo.OverrideVertexColors->InitFromColorArray(VertexColor);
if (LODIndex > 0)
{
SM->bCustomOverrideVertexColorPerLOD = true;
}
BeginInitResource(InstanceMeshLODInfo.OverrideVertexColors);
SM->MarkRenderStateDirty();
return true;
}
}
return false;
}
2、有个插件ThermodynamicChart他内部好像是UV着色,只能对平面,我实际需要对3D模型进行着色。
插件内的核心代码
//核心 暴露给蓝图的 我改如何修改或者利用 到3D模型上然后可以用射线 操控3D模型点上的颜色
void AThermodynamicChartActor::AddPointToRenderTarget(FVector2D InPos, bool bIsNormalized)
{
FVector2D tempPos = FVector2D(InPos);
if (bIsNormalized)
{
tempPos = (tempPos - MinPos) / (MaxPos - MinPos);
}
TCRenderTargetMID->SetVectorParameterValue(TEXT("Pos"), FVector(tempPos, 0.0f));
TCRenderTargetMID->SetScalarParameterValue(TEXT("Strength"), Strength);
TCRenderTargetMID->SetScalarParameterValue(TEXT("Radius"), Radius);
UKismetRenderingLibrary::DrawMaterialToRenderTarget(GetWorld(), RenderTarget, TCRenderTargetMID);
}
/************************************************************************/
/* Author: YWT20 */
/* Expected release year : 2021 */
/************************************************************************/
#include "ThermodynamicChartActor.h"
#include "Kismet/KismetRenderingLibrary.h"
#include "Kismet/KismetMaterialLibrary.h"
#include "Kismet/KismetMathLibrary.h"
#include "UObject/ConstructorHelpers.h"
#include "Engine/StaticMesh.h"
#include "Components/SceneComponent.h"
// Sets default values
AThermodynamicChartActor::AThermodynamicChartActor()
{
// 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;
Resolution = 1024;
Strength = 0.1f;
Radius = 0.1f;
MinPos = FVector2D(0.0f, 0.0f);
MaxPos = FVector2D(Resolution, Resolution);
Scene = CreateDefaultSubobject<USceneComponent>(TEXT("Scene"));
RootComponent = Scene;
TCMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("TCMesh"));
TCMesh->SetupAttachment(RootComponent);
TCDecal = CreateDefaultSubobject<UDecalComponent>(TEXT("TCDecal"));
TCDecal->SetupAttachment(RootComponent);
TCDecal->DecalSize = FVector(50.0f, 50.0f, 50.0f);
TCDecal->SetRelativeRotation(FQuat(FRotator(-90.0f, 0.0f, 0.0f)));
static ConstructorHelpers::FObjectFinder<UStaticMesh> PlaneStaticMesh(TEXT("StaticMesh'/Engine/BasicShapes/Plane.Plane'"));
if (PlaneStaticMesh.Succeeded())
{
TCMesh->SetStaticMesh(PlaneStaticMesh.Object);
}
static ConstructorHelpers::FObjectFinder<UMaterialInstance> MI_ThermodynamicChart(TEXT("MaterialInstanceConstant'/ThermodynamicChart/ThermodynamicChart/Materials/M_ThermodynamicChart_Inst.M_ThermodynamicChart_Inst'"));
if (MI_ThermodynamicChart.Succeeded())
{
ThermodynamicChartMI = MI_ThermodynamicChart.Object;
}
static ConstructorHelpers::FObjectFinder<UMaterialInstance> MI_ThermodynamicChart_Decal(TEXT("MaterialInstanceConstant'/ThermodynamicChart/ThermodynamicChart/Materials/M_ThermodynamicChart_Decal_Inst.M_ThermodynamicChart_Decal_Inst'"));
if (MI_ThermodynamicChart_Decal.Succeeded())
{
ThermodynamicChart_Decal_MI = MI_ThermodynamicChart_Decal.Object;
}
static ConstructorHelpers::FObjectFinder<UMaterialInstance> MI_TCRenderTarget(TEXT("MaterialInstanceConstant'/ThermodynamicChart/ThermodynamicChart/Materials/M_TCRenderTarget_Inst.M_TCRenderTarget_Inst'"));
if (MI_TCRenderTarget.Succeeded())
{
TCRenderTargetMI = MI_TCRenderTarget.Object;
}
TCMesh->SetMaterial(0, ThermodynamicChartMI);
MatColor0 = FLinearColor(0.0f, 0.0f, 1.0f, 0.0f);
MatColor1 = FLinearColor::Green;
MatColor2 = FLinearColor::Yellow;
MatColor3 = FLinearColor::Red;
}
// Called when the game starts or when spawned
void AThermodynamicChartActor::BeginPlay()
{
Super::BeginPlay();
RenderTarget = UKismetRenderingLibrary::CreateRenderTarget2D(GetWorld(), Resolution, Resolution);
if (ThermodynamicChart_Decal_MID)
{
ThermodynamicChart_Decal_MID->SetTextureParameterValue(TEXT("RenderTarget"), RenderTarget);
TCDecal->SetDecalMaterial(ThermodynamicChart_Decal_MID);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("ThermodynamicChart_Decal_MID is null"));
}
if (ThermodynamicChartMID)
{
ThermodynamicChartMID->SetTextureParameterValue(TEXT("RenderTarget"), RenderTarget);
TCMesh->SetMaterial(0, ThermodynamicChartMID);
}
else
{
UE_LOG(LogTemp, Warning, TEXT("ThermodynamicChartMID is null"));
}
if (!TCRenderTargetMID)
{
UE_LOG(LogTemp, Warning, TEXT("TCRenderTargetMID is null"));
}
SetMatColor(MatColor0, MatColor1, MatColor2, MatColor3);
}
// Called every frame
void AThermodynamicChartActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void AThermodynamicChartActor::OnConstruction(const FTransform& Transform)
{
Super::OnConstruction(Transform);
ThermodynamicChart_Decal_MID = UKismetMaterialLibrary::CreateDynamicMaterialInstance(GetWorld(), ThermodynamicChart_Decal_MI);
ThermodynamicChartMID = UKismetMaterialLibrary::CreateDynamicMaterialInstance(GetWorld(), ThermodynamicChartMI);
TCRenderTargetMID = UKismetMaterialLibrary::CreateDynamicMaterialInstance(GetWorld(), TCRenderTargetMI);
if (bIsUseDecal)
{
TCMesh->SetVisibility(false);
TCDecal->SetVisibility(true);
}
else
{
TCMesh->SetVisibility(true);
TCDecal->SetVisibility(false);
}
}
void AThermodynamicChartActor::AddPointToRenderTarget(FVector2D InPos, bool bIsNormalized)
{
FVector2D tempPos = FVector2D(InPos);
if (bIsNormalized)
{
tempPos = (tempPos - MinPos) / (MaxPos - MinPos);
}
TCRenderTargetMID->SetVectorParameterValue(TEXT("Pos"), FVector(tempPos, 0.0f));
TCRenderTargetMID->SetScalarParameterValue(TEXT("Strength"), Strength);
TCRenderTargetMID->SetScalarParameterValue(TEXT("Radius"), Radius);
UKismetRenderingLibrary::DrawMaterialToRenderTarget(GetWorld(), RenderTarget, TCRenderTargetMID);
}
void AThermodynamicChartActor::AddPointsToRenderTarget(TArray<FVector2D> InPosArray, bool bIsNormalized)
{
for (auto pos : InPosArray)
{
AddPointToRenderTarget(pos, bIsNormalized);
}
}
void AThermodynamicChartActor::RandomMap(int32 Num /*= 1000*/)
{
float tempPosX = 0.0;
float tempPosY = 0.0;
for (int32 i = 0; i < Num; i++)
{
tempPosX = UKismetMathLibrary::RandomFloat();
tempPosY = UKismetMathLibrary::RandomFloat();
AddPointToRenderTarget(FVector2D(tempPosX, tempPosY), false);
}
}
void AThermodynamicChartActor::SetMatColor(FLinearColor InColor0, FLinearColor InColor1, FLinearColor InColor2, FLinearColor InColor3)
{
ThermodynamicChartMID->SetVectorParameterValue("Color_0", InColor0);
ThermodynamicChartMID->SetVectorParameterValue("Color_1", InColor1);
ThermodynamicChartMID->SetVectorParameterValue("Color_2", InColor2);
ThermodynamicChartMID->SetVectorParameterValue("Color_3", InColor3);
ThermodynamicChart_Decal_MID->SetVectorParameterValue("Color_0", InColor0);
ThermodynamicChart_Decal_MID->SetVectorParameterValue("Color_1", InColor1);
ThermodynamicChart_Decal_MID->SetVectorParameterValue("Color_2", InColor2);
ThermodynamicChart_Decal_MID->SetVectorParameterValue("Color_3", InColor3);
}
3、打算根据原始的热成像 贴图信息来发送 射线 ,但是这个射线好像断点进不去for循环的代码
.h代码
```c++
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "PictureActor.generated.h"
UCLASS()
class RELICHENGXIANG_API APictureActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
APictureActor();
UPROPERTY(EditAnywhere,Category="Chunk")
int Size = 16;
UPROPERTY(EditAnywhere, Category = "Chunk")
int Scale = 1;
UPROPERTY(BlueprintReadWrite,EditAnywhere ,Category = "Spawn")
UTexture2D *MyTexture2D;
void ReadImage();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
};
// Fill out your copyright notice in the Description page of Project Settings.
#include "PictureActor.h"
// Sets default values
APictureActor::APictureActor()
{
// 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;
}
// Called when the game starts or when spawned
void APictureActor::BeginPlay()
{
Super::BeginPlay();
ReadImage();
}
// Called every frame
void APictureActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
void APictureActor::ReadImage()
{
//从UTexture2D对象中读取颜色信息,并标识未只读状态
const FColor* FormatedImageData
= static_cast<const FColor*>(MyTexture2D->PlatformData->Mips[0].BulkData.LockReadOnly());
int counter = 0;
for (int32 x = 0;x<5; x++)// MyTexture2D->GetSizeX()
{
for (int32 y = 0; y <6; y++)// MyTexture2D->GetSizeY()
{
//读取指定像素点的颜色
FColor PixelColor = FormatedImageData[y* MyTexture2D->GetSizeX() + x];
//RGPB转灰度
float r = PixelColor.R;
float g = PixelColor.G;
float b = PixelColor.B;
//UE_LOG(Chunk, Log, TEXT("PixelColor %i:i%,i%,i%"), counter, PixelColor.R, PixelColor.G, PixelColor.B);
counter += 1;
}
}
MyTexture2D->PlatformData->Mips[0].BulkData.Unlock();
}
我想要达到的结果
图片效果地址:
https://ibb.co/8Xq3Bdh