While our game may be running without any issues in the editor or even in…
Generating Procedural Textures
In this post I’m going to show you how to create procedural textures inside the editor. While in the following example we will generate a 1024 x 1024 texture using random RGB values, the same approach can be used with different generation algorithms to achieve your goals. This post was written using 4.22 version of the engine so you may have to modify the following code in case you’re using a newer version.
The whole process takes place in a single c++ method (named GenerateTexture) that:
- Generates the asset file inside the editor
- Creates the pixel data
- Copies the pixel data to the file
- Saves the asset when the above steps are completed
In order to test the following code, I created a C++ class that inherits the actor class and then inside the BeginPlay function I called the GenerateTexture method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
void AProceduralTextureTest::GenerateTexture() { //Create a string containing the texture's path FString PackageName = TEXT("/Game/ProceduralTextures/"); FString BaseTextureName = FString("ProcTexture"); PackageName += BaseTextureName; //Create the package that will store our texture UPackage* Package = CreatePackage(NULL, *PackageName); GLog->Log("project dir:" + FPaths::ProjectDir()); //Create a unique name for our asset. For example, if a texture named ProcTexture already exists the editor //will name the new texture as "ProcTexture_1" FName TextureName = MakeUniqueObjectName(Package, UTexture2D::StaticClass(), FName(*BaseTextureName)); Package->FullyLoad(); UTexture2D* NewTexture = NewObject<UTexture2D>(Package, TextureName, RF_Public | RF_Standalone | RF_MarkAsRootSet); if (NewTexture) { int32 TextureWidth = 1024; int32 TextureHeight = 1024; //Prevent the object and all its descedants from being deleted during garbage collecion NewTexture->AddToRoot(); //Initialize the platform data to store necessary information regarding our texture asset NewTexture->PlatformData = new FTexturePlatformData(); NewTexture->PlatformData->SizeX = TextureWidth; NewTexture->PlatformData->SizeY = TextureHeight; NewTexture->PlatformData->NumSlices = 1; NewTexture->PlatformData->PixelFormat = EPixelFormat::PF_B8G8R8A8; //uint8* Pixels = GeneratePixels(TextureHeight, TextureWidth); //Each element of the array contains a single color so in order to represent information in //RGBA we need to have an array of length TextureWidth * TextureHeight * 4 uint8* Pixels = new uint8[TextureWidth * TextureHeight * 4]; for (int32 y = 0; y < TextureHeight; y++) { for (int32 x = 0; x < TextureWidth; x++) { //Get the current pixel int32 CurrentPixelIndex = ((y * TextureWidth) + x); //Get a random vector that will represent the RGB values for the current pixel FColor RandomColor = FColor::MakeRandomColor(); Pixels[4 * CurrentPixelIndex] = RandomColor.B; //b Pixels[4 * CurrentPixelIndex + 1] = RandomColor.G; //g Pixels[4 * CurrentPixelIndex + 2] = RandomColor.R; //r Pixels[4 * CurrentPixelIndex + 3] = 255; //set A channel always to maximum } } //Allocate first mipmap. FTexture2DMipMap* Mip = new FTexture2DMipMap(); NewTexture->PlatformData->Mips.Add(Mip); Mip->SizeX = TextureWidth; Mip->SizeY = TextureHeight; //Lock the mipmap data so it can be modified Mip->BulkData.Lock(LOCK_READ_WRITE); uint8* TextureData = (uint8*)Mip->BulkData.Realloc(TextureWidth * TextureHeight * 4); //Copy the pixel data into the Texture data FMemory::Memcpy(TextureData, Pixels, sizeof(uint8) * TextureHeight * TextureWidth * 4); Mip->BulkData.Unlock(); //Initialize a new texture NewTexture->Source.Init(TextureWidth, TextureHeight, 1, 1, ETextureSourceFormat::TSF_BGRA8, Pixels); NewTexture->UpdateResource(); //Mark the package as dirty so the editor will prompt you to save the file if you haven't Package->MarkPackageDirty(); //Notify the editor that we created a new asset FAssetRegistryModule::AssetCreated(NewTexture); //Auto-save the new asset FString PackageFileName = FPackageName::LongPackageNameToFilename(PackageName, FPackageName::GetAssetPackageExtension()); bool bSaved = UPackage::SavePackage(Package, NewTexture, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *PackageFileName, GError, nullptr, true, true, SAVE_NoError); //Since we don't need access to the pixel data anymore free the memory delete[] Pixels; } } |
Here are a couple of textures that were generated by the previous algorithm:
Thank you for taking the time to write these tutorials it is much appreciated.
Line 31 the NewTexture->PlatformData->NumSlices = 1; statement throws an error on VS2019. Using Unreal engine 4.25, has NumSlices been deprecated? All I could find is PlatformData->SetNumSlices( 1 ); There is no property in Texture.h for NumSlices. Also did you create two different files for the Actor descendent and the ProcTexture classes? Could you clarify for me please.