Skip to content

Instantly share code, notes, and snippets.

@makuto
Last active April 12, 2024 11:18
Show Gist options
  • Save makuto/bf87e5ccd0b15b0859608c9b745ac5f1 to your computer and use it in GitHub Desktop.
Save makuto/bf87e5ccd0b15b0859608c9b745ac5f1 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);
};
@makuto
Copy link
Author

makuto commented May 8, 2017

This gist is in the Public Domain.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment