ddbrown30
(ddbrown30)
September 6, 2025, 8:03pm
1
I’m attempting to generate textures in C++ and no matter what I do, I can’t get it to not generate vertical bars. I’ve even tried things like setting the first pixel to 0,0,0, the second to 1,1,1, etc., and all that does is end up making a smooth gradient from left to right.
My logic is based on a combination of https://www.orfeasel.com/generating-procedural-textures/ and How to create textures at runtime and assign them to a mesh? , both of which are from UE4 but I don’t understand what could have changed in the texture code to cause the behaviour I’m seeing.
Here is the entirety of the generation code and I’ve included an example of what the output looks like below. Does anyone know what I’m doing wrong?
int32 textureWidth = 512;
int32 textureHeight = 512;
UTexture2D* NewTexture = UTexture2D::CreateTransient(textureWidth, textureHeight);
FTexturePlatformData* PlatformData = NewTexture->GetPlatformData();
FTexture2DMipMap* MipMap = &PlatformData->Mips[0];
MipMap->BulkData.Lock(LOCK_READ_WRITE);
uint8* RawImageData = (uint8*)MipMap->BulkData.Realloc(textureWidth * textureHeight * 4);
int32 numPixels = textureWidth * textureHeight;
for (int32 x = 0; x < numPixels; x++)
{
FColor RandomColor = FColor::MakeRandomColor();
RawImageData[4 * x] = RandomColor.B;
RawImageData[4 * x + 1] = RandomColor.G;
RawImageData[4 * x + 2] = RandomColor.R;
RawImageData[4 * x + 3] = 255;
}
MipMap->BulkData.Unlock();
NewTexture->UpdateResource();
return NewTexture;
3dRaven
(3dRaven)
September 6, 2025, 8:29pm
2
Your code works. Not sure where the problem is. Maybe something is happening during the saving of the asset?
Example code I made based on it
ImageFunctionLibrary.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "ImageFunctionLibrary.h"
UTexture2D* UImageFunctionLibrary::GeneratePerlin(int32 textureWidth, int32 textureHeight)
{
UTexture2D* NewTexture = UTexture2D::CreateTransient(textureWidth, textureHeight);
FTexturePlatformData* PlatformData = NewTexture->GetPlatformData();
FTexture2DMipMap* MipMap = &PlatformData->Mips[0];
MipMap->BulkData.Lock(LOCK_READ_WRITE);
uint8* RawImageData = (uint8*)MipMap->BulkData.Realloc(textureWidth * textureHeight * 4);
int32 numPixels = textureWidth * textureHeight;
for (int32 x = 0; x < numPixels; x++)
{
FColor RandomColor = FColor::MakeRandomColor();
RawImageData[4 * x] = RandomColor.B;
RawImageData[4 * x + 1] = RandomColor.G;
RawImageData[4 * x + 2] = RandomColor.R;
RawImageData[4 * x + 3] = 255;
}
MipMap->BulkData.Unlock();
NewTexture->UpdateResource();
return NewTexture;
}
.h
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Engine/Texture2D.h"
#include "ImageFunctionLibrary.generated.h"
/**
*
*/
UCLASS()
class YOUR_API UImageFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
UFUNCTION(BlueprintCallable)
static UTexture2D* GeneratePerlin(int32 textureWidth = 512, int32 textureHeight = 512);
};
Editor widget
Generation in engine
Double check if your UV map of the target mesh is not throwing off your results.
1 Like
ddbrown30
(ddbrown30)
September 6, 2025, 9:04pm
3
Man, I have no clue what is going wrong. I created an editor widget to display it and it’s the exact same bug. I even recopied my code from your reply just to be sure that there wasn’t some subtle difference. At this point, I’m going to try a full clean and rebuild of the solution but that’s a stretch.
ddbrown30
(ddbrown30)
September 6, 2025, 9:23pm
4
Unsurprisingly, clean and rebuild didn’t help. I even tried switching from my local engine to the installed version and same thing.
I also just tried using Export to Disk to save out a png and it’s also wrong, so it’s not a display issue.
ddbrown30
(ddbrown30)
September 6, 2025, 9:56pm
5
Which exact version are you on?
3dRaven
(3dRaven)
September 6, 2025, 10:02pm
6
I’m on 5.4.4 for this specific test project.
ddbrown30
(ddbrown30)
September 6, 2025, 10:05pm
7
Hmm, interesting. I wonder if there was a legit bug added then. I’m on 5.6.1.
3dRaven
(3dRaven)
September 6, 2025, 10:36pm
8
Seems to be a bug in 5.6.1. Getting the same results with the code you provided.
ddbrown30
(ddbrown30)
September 6, 2025, 10:46pm
9
And I just finished going back to 5.4.4 and confirmed that it works. Thanks for checking on your side too. I’ll open a bug with Epic.
3dRaven
(3dRaven)
September 7, 2025, 9:43am
10
Ok narrowed it down to some sort of error with the values generated by FColor::MakeRandomColor()
Maybe they are going out of the 0,255 range for colors.
Updated code with extra texture settings
header
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "ImageFunctionLibrary.generated.h"
/**
*
*/
UCLASS()
class YOUR_API UImageFunctionLibrary : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
UFUNCTION(BlueprintCallable)
static UTexture2D* GeneratePerlin(TEnumAsByte<TextureCompressionSettings> compressionSettings, TEnumAsByte<TextureMipGenSettings> MipGenSettings, bool bSRGB, int32 textureWidth = 512, int32 textureHeight = 512);
};
cpp
#include "ImageFunctionLibrary.h"
#include "Kismet/KismetMathLibrary.h"
UTexture2D* UImageFunctionLibrary::GeneratePerlin(TEnumAsByte<TextureCompressionSettings> compressionSettings, TEnumAsByte<TextureMipGenSettings> MipGenSettings, bool bSRGB, int32 textureWidth, int32 textureHeight)
{
UTexture2D* NewTexture = UTexture2D::CreateTransient(textureWidth, textureHeight);
TextureCompressionSettings OldCompressionSettings = NewTexture->CompressionSettings;
TextureMipGenSettings OldMipGenSettings = NewTexture->MipGenSettings;
bool OldSRGB = NewTexture->SRGB;
NewTexture->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
NewTexture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
NewTexture->SRGB = false;
NewTexture->UpdateResource();
FTexture2DMipMap* MipMap = &NewTexture->GetPlatformData()->Mips[0];
FByteBulkData* ImageData = &MipMap->BulkData;
uint8* RawImageData = (uint8*)ImageData->Lock(LOCK_READ_WRITE);
for (int i = 0; i < textureWidth * textureHeight * 4; i += 4) {
FColor RandomColor = FColor::MakeRandomColor();
RawImageData[i] = UKismetMathLibrary::RandomIntegerInRange(0, 255);
RawImageData[i + 1] = UKismetMathLibrary::RandomIntegerInRange(0, 255);
RawImageData[i + 2] = UKismetMathLibrary::RandomIntegerInRange(0, 255);
RawImageData[i + 3] = 255;
}
ImageData->Unlock();
NewTexture->CompressionSettings = OldCompressionSettings;
NewTexture->MipGenSettings = OldMipGenSettings;
NewTexture->SRGB = OldSRGB;
NewTexture->UpdateResource();
return NewTexture;
}
Works in 5.6.1
The make random color node seems to return not so random numbers
Logged the values of “FColor::MakeRandomColor()” and you can clearly see a pattern that is probably causing the lines.
Alternatively you could use the seed variant of the makeRandomColor and pass in a kismet random number as the seed value (this works). I’m not sure if it’s not the same as just passing in the kismet random directly. (maybe unreal does some extra steps if it’s a linear color)
Dasay2605
(Dasay2605)
September 7, 2025, 3:03pm
11
Yea, they just broke the FLinearColor::MakeRandomColor()
function, lmao. It’s not even a bug, it’s a design flaw. Here’s how it was implemented in version 5.4:
FLinearColor FLinearColor::MakeRandomColor()
{
const uint8 Hue = (uint8)(FMath::FRand()*255.f);
return FLinearColor::MakeFromHSV8(Hue, 255, 255);
}
So it was based on the function FMath::FRand()
. And now how it is written in version 5.6:
FLinearColor FLinearColor::MakeRandomColor()
{
static std::atomic<uint32> s_hue(0);
uint32 Hue = s_hue.fetch_add(157,std::memory_order_relaxed);
return FLinearColor::MakeFromHSV8((uint8)Hue, 255, 255);
}
So what happens is that when called within a single loop, you get Hue
values in increments of 157. Starting at some value X
, you get Hue
values X + 157*n
for the n
th call of the MakeRandomColor
. Then they convert the uint32
to uint8
, which results in something like (X + 157*n) % 256
. They probably thought that this was a smart and performant move, since each subsequent call of this function would produce a visually random color. And since 157 is a prime number, you wouldn’t get a duplicate Hue
value after each call, but… Until 157 has been added 256 times. After that, the values start repeating because of the modulo operation.
I guess they wanted to avoid getting close colors in subsequent calls. Using the new function, you will get very different colors of neighbors. But it is only intended for sequences of length 256 or less. However, this limitation and the way it was implemented made it a completely different function, which, in fact, cannot be called “random” at all, because it is not.
ddbrown30
(ddbrown30)
September 7, 2025, 6:30pm
12
Wow, good catch. That does indeed seem to be the problem. Thanks for investigating.