Here’s some example code to get you started. You’ll need to adapt/change this for your own purposes. I’ve copied this out from my own more specialized code which dealt with fixed-size buffers (which is why it’s hard-coded to 64x64), but it shouldn’t be too hard to generalize it to arbitrary sizes or color depths. This is mostly just to get you started, it’s not a complete solution.
.h:
class MYPROJ_API FMyProjDynamicTextureUtilities
{
public:
// Create a dynamic texture intended to be used for passing non-texture data
// into materials. Defaults to 32-bit RGBA. The texture is not added to the
// root set, so something else will need to hold a reference to it.
static UTexture2D* CreateTransientDynamicTexture(int32 Width, int32 Height, EPixelFormat PixelFormat = PF_A32B32G32R32F);
// Updates a region of a texture with the supplied input data. Does nothing
// if the pixel formats do not match.
static void UpdateTextureRegion(UTexture2D* Texture, int32 MipIndex, FUpdateTextureRegion2D Region, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData);
// Convenience wrapper for updating a dynamic texture with an array of
// FLinearColors.
static void UpdateDynamicVectorTexture(const TArray<FLinearColor>& Source, UTexture2D* Texture);
// Sets up a component's material instance parameters (on all materials) for
// use with the supplied UTexture. The proper parameters (specified by
// IndexParameterName and TextureParameterName) should exist on the
// material, otherwise this will not have the proper effect.
static void SetDynamicTextureAndIndex(class UStaticMeshComponent* Component, class UTexture2D* Texture, int32 Index, FName IndexParameterName, FName TextureParameterName);
};
.cpp:
// NOTE hard-coded to 64 * 64 texture sizes! I just copied this from some of my own code and modified it for you a little. You'll want to change a bunch of stuff in here for your own purposes.
void FMyProjDynamicTextureUtilities::UpdateTextureRegion(UTexture2D* Texture, int32 MipIndex, FUpdateTextureRegion2D Region, uint32 SrcPitch, uint32 SrcBpp, uint8* SrcData, bool bFreeData)
{
if (Texture->Resource)
{
struct FUpdateTextureRegionsData
{
FTexture2DResource* Texture2DResource;
int32 MipIndex;
FUpdateTextureRegion2D Region;
uint32 SrcPitch;
uint32 SrcBpp;
uint8* SrcData;
};
FUpdateTextureRegionsData* RegionData = new FUpdateTextureRegionsData;
RegionData->Texture2DResource = (FTexture2DResource*)Texture->Resource;
RegionData->MipIndex = MipIndex;
RegionData->Region = Region;
RegionData->SrcPitch = SrcPitch;
RegionData->SrcBpp = SrcBpp;
RegionData->SrcData = SrcData;
{
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
UpdateTextureRegionsData,
FUpdateTextureRegionsData*, RegionData, RegionData,
bool, bFreeData, bFreeData,
{
int32 CurrentFirstMip = RegionData->Texture2DResource->GetCurrentFirstMip();
if (RegionData->MipIndex >= CurrentFirstMip)
{
RHIUpdateTexture2D(
RegionData->Texture2DResource->GetTexture2DRHI(),
RegionData->MipIndex - CurrentFirstMip,
RegionData->Region,
RegionData->SrcPitch,
RegionData->SrcData
+ RegionData->Region.SrcY * RegionData->SrcPitch
+ RegionData->Region.SrcX * RegionData->SrcBpp
);
}
// TODO is this leaking if we never set this to true??
if (bFreeData)
{
FMemory::Free(RegionData->SrcData);
}
delete RegionData;
});
}
}
}
void FMyProjDynamicTextureUtilities::UpdateDynamicVectorTexture(const TArray<FLinearColor>& Source, UTexture2D* Texture)
{
// Only handles 32-bit float textures
if (!Texture || Texture->GetPixelFormat() != PF_A32B32G32R32F) return;
// Shouldn't do anything if there's no data
if (Source.Num() < 1) return;
UpdateTextureRegion(Texture, 0, FUpdateTextureRegion2D(0, 0, 0, 0, Texture->GetSizeX(), Texture->GetSizeY()), Texture->GetSizeX() * sizeof(FLinearColor), sizeof(FLinearColor), (uint8*)Source.GetData(), false);
}
UTexture2D* FMyProjDynamicTextureUtilities::CreateTransientDynamicTexture(int32 Width, int32 Height, EPixelFormat PixelFormat /*= PF_A32B32G32R32F*/)
{
auto* Texture = UTexture2D::CreateTransient(Width, Height, PixelFormat);
if (Texture)
{
Texture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
Texture->SRGB = 0;
Texture->UpdateResource();
}
return Texture;
}
void FMyProjDynamicTextureUtilities::SetDynamicTextureAndIndex(class UStaticMeshComponent* Component, class UTexture2D* Texture, int32 Index, FName IndexParameterName, FName TextureParameterName)
{
if (!Component || !Texture) return;
for (int32 i = 0; i < Component->GetNumMaterials(); i++)
{
auto* DynamicMaterial = FMyProjInstanceProcedures::TryGetDynamicMaterial(Component, i);
if (!DynamicMaterial) continue;
FLinearColor CalculatedIndex(FMath::Fmod((float)Index, 64.0f) + 0.5f, FMath::FloorToFloat((float)Index / 64.0f) + 0.5f, 0.0f, 0.0f);
CalculatedIndex /= 64.0f;
DynamicMaterial->SetVectorParameterValue(IndexParameterName, CalculatedIndex);
DynamicMaterial->SetTextureParameterValue(TextureParameterName, Texture);
}
}