Skip to content

Instantly share code, notes, and snippets.

@pkucherov
Forked from makuto/DynamicTexture.cpp
Created March 6, 2024 15:15
Show Gist options
  • Save pkucherov/f7de018cdfcdf06938f80c28ce33ac22 to your computer and use it in GitHub Desktop.
Save pkucherov/f7de018cdfcdf06938f80c28ce33ac22 to your computer and use it in GitHub Desktop.
Procedural/Dynamic Texture System for Unreal Engine 4
//#include "GalavantUnreal.h" // Your game's header file here
#include "DynamicTexture.h"
// See https://wiki.unrealengine.com/Procedural_Materials
struct UpdateTextureRegionsParams
{
UTexture2D* Texture;
int32 MipIndex;
uint32 NumRegions;
FUpdateTextureRegion2D* Regions;
uint32 SrcPitch;
uint32 SrcBpp;
uint8* SrcData;
bool FreeData;
};
// Send the render command to update the texture
void UpdateTextureRegions(UpdateTextureRegionsParams& params)
{
if (params.Texture && params.Texture->Resource)
{
struct FUpdateTextureRegionsData
{
FTexture2DResource* Texture2DResource;
int32 MipIndex;
uint32 NumRegions;
FUpdateTextureRegion2D* Regions;
uint32 SrcPitch;
uint32 SrcBpp;
uint8* SrcData;
};
FUpdateTextureRegionsData* RegionData = new FUpdateTextureRegionsData;
RegionData->Texture2DResource = (FTexture2DResource*)params.Texture->Resource;
RegionData->MipIndex = params.MipIndex;
RegionData->NumRegions = params.NumRegions;
RegionData->Regions = params.Regions;
RegionData->SrcPitch = params.SrcPitch;
RegionData->SrcBpp = params.SrcBpp;
RegionData->SrcData = params.SrcData;
ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER(
UpdateTextureRegionsData, FUpdateTextureRegionsData*, RegionData, RegionData, bool,
FreeData, params.FreeData,
{
for (uint32 RegionIndex = 0; RegionIndex < RegionData->NumRegions; ++RegionIndex)
{
int32 CurrentFirstMip = RegionData->Texture2DResource->GetCurrentFirstMip();
if (RegionData->MipIndex >= CurrentFirstMip)
{
RHIUpdateTexture2D(
RegionData->Texture2DResource->GetTexture2DRHI(),
RegionData->MipIndex - CurrentFirstMip,
RegionData->Regions[RegionIndex], RegionData->SrcPitch,
RegionData->SrcData +
RegionData->Regions[RegionIndex].SrcY * RegionData->SrcPitch +
RegionData->Regions[RegionIndex].SrcX * RegionData->SrcBpp);
}
}
if (FreeData)
{
FMemory::Free(RegionData->Regions);
FMemory::Free(RegionData->SrcData);
}
delete RegionData;
});
}
}
void SetDynamicTexturePixelsToColor(DynamicTexturePixel* pixels, int32 numPixels,
DynamicTexturePixel color)
{
if (pixels)
{
for (int i = 0; i < numPixels; i++)
pixels[i] = color;
}
}
DynamicTexture::~DynamicTexture()
{
if (Pixels)
delete[] Pixels;
if (UpdateTextureRegion)
delete UpdateTextureRegion;
}
void DynamicTexture::Initialize(UStaticMeshComponent* staticMesh, int32 materialIndex, int32 width,
int32 height)
{
if (!staticMesh)
return;
Width = width;
Height = height;
if (!Width || !Height)
{
// Dynamic Texture needs width and height in order to function!
return;
}
// Texture2D setup
{
Texture2D = UTexture2D::CreateTransient(Width, Height);
// Ensure there's no compression (we're editing pixel-by-pixel)
Texture2D->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
// Turn off Gamma correction
Texture2D->SRGB = 0;
// Make sure it never gets garbage collected
Texture2D->AddToRoot();
// Update the texture with these new settings
Texture2D->UpdateResource();
}
// TextureRegion setup
UpdateTextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height);
// Pixels setup
{
NumPixels = Width * Height;
if (Pixels)
delete[] Pixels;
Pixels = new DynamicTexturePixel[NumPixels];
SetDynamicTexturePixelsToColor(Pixels, NumPixels, {0, 0, 0, 255});
}
// Material setup
{
UMaterialInstanceDynamic* material =
staticMesh->CreateAndSetMaterialInstanceDynamic(materialIndex);
if (!material)
return;
DynamicMaterials.Empty();
DynamicMaterials.Add(material);
for (UMaterialInstanceDynamic* dynamicMaterial : DynamicMaterials)
{
if (dynamicMaterial)
dynamicMaterial->SetTextureParameterValue("DynamicTextureParam", Texture2D);
}
}
Ready = true;
}
void DynamicTexture::Update()
{
if (Ready)
{
UpdateTextureRegionsParams params = {
/*Texture = */ Texture2D,
/*MipIndex = */ 0,
/*NumRegions = */ 1,
/*Regions = */ UpdateTextureRegion,
/*SrcPitch = */ static_cast<uint32>(Width * sizeof(DynamicTexturePixel)),
/*SrcBpp = */ sizeof(DynamicTexturePixel),
/*SrcData = */ reinterpret_cast<uint8*>(Pixels),
/*FreeData = */ false,
};
UpdateTextureRegions(params);
}
}
DynamicTexturePixel* DynamicTexture::GetPixel(int32 row, int32 column)
{
if (!Pixels || row >= Height || column >= Width)
return nullptr;
return &Pixels[(row * Width) + column];
}
#pragma once
// See https://wiki.unrealengine.com/Procedural_Materials
struct DynamicTexturePixel
{
// Do not rearrage or add members to this struct
uint8 b;
uint8 g;
uint8 r;
uint8 a;
};
void SetDynamicTexturePixelsToColor(DynamicTexturePixel* pixels, int32 numPixels,
DynamicTexturePixel color);
struct DynamicTexture
{
private:
TArray<class UMaterialInstanceDynamic*> DynamicMaterials;
UTexture2D* Texture2D;
FUpdateTextureRegion2D* UpdateTextureRegion;
bool Ready;
public:
int32 Width;
int32 Height;
int32 NumPixels;
// You can modify this as desired (it should be null when InitializeDynamicTexture() is called).
// Call Update() once you've finished modifying the Pixels to let Unreal know we want to use the
// new ones
DynamicTexturePixel* Pixels;
~DynamicTexture();
void Initialize(UStaticMeshComponent* staticMesh, int32 materialIndex, int32 width,
int32 height);
void Update();
DynamicTexturePixel* GetPixel(int32 row, int32 column);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment