Last active
April 12, 2024 11:18
-
-
Save makuto/bf87e5ccd0b15b0859608c9b745ac5f1 to your computer and use it in GitHub Desktop.
Procedural/Dynamic Texture System for Unreal Engine 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//#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]; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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
This gist is in the Public Domain.