Unexpected values when reading from UTexture2D

Hey there. I have an issue that bothers me since days now. I’m trying to read pixel data from a UTexture2D, but am not able to get the correct result.

My goal:
For my survival game I want the whole map to have environment parameters like oxygen density, radiation level, soil quailty, etc. I want to have actors (let’s say a plant that generates oxygen) that influence said environment. I also want to have a global “delta” map that applies a predefined amount of values to the environment.

My solution:

  • I divided the world into a 2048x2048 grid. For that I am using a 2048x2048 PF_FloatRGBA UTexture2D.
  • Each tick (each 1s for now) I read from a second UTexture2D and add its values to the first one. I substruct -0.5f for each value, so my “delta map” can have areas in which for example oxygen is produced and areas in which oxygen is reduced.
  • Each tick I iterate through all my UEnvironmentComponents and apply their influences in a given value directly to the environment texture data.

My Problem:
When reading pixel values from my delta texture I get invalid RGBA values. I created a texture in GIMP that has 0.5 in all channels. I saved it as PNG RGBA 16 bit. When running the game, instead of all values being (0.5, 0.5, 0.5, 0.5) they are (0.106994629, 0.106994629, 0.106994629, 0.5).

Let’s check a bit of code.

Initialization:

void UEnvironmentalMapSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);

	EnvironmentTexture = UTexture2D::CreateTransient(2048, 2048, PF_FloatRGBA, TEXT("EnvironmentTexture"));
	EnvironmentMipMap = &EnvironmentTexture->GetPlatformData()->Mips[0];

	if (AMyWorldSettings* WorldSettings = Cast<AMyWorldSettings>(GetWorld()->GetWorldSettings()))
	{
		if (WorldSettings->EnvironmentBase && WorldSettings->EnvironmentBase->GetPixelFormat() == PF_FloatRGBA)
		{
			InputTexture = WorldSettings->EnvironmentBase;
			DeltaMipMap = &InputTexture->GetPlatformData()->Mips[0];
		}
	}
}

Each tick:

void UEnvironmentalMapSubsystem::ApplyTextureEffects(float DeltaSeconds)
{
	if (!DeltaMipMap || !EnvironmentMipMap || DeltaMipMap->SizeX != EnvironmentMipMap->SizeX || DeltaMipMap->SizeY != EnvironmentMipMap->SizeY)
	{
		return;
	}

	const FFloat16Color* DeltaTextureData = static_cast<FFloat16Color*>(DeltaMipMap->BulkData.Lock(LOCK_READ_ONLY));
	FFloat16Color* EnvironmentTextureData = static_cast<FFloat16Color*>(EnvironmentMipMap->BulkData.Lock(LOCK_READ_WRITE));

	const int32 TextureWidth = EnvironmentTexture->GetSizeX();
	const int32 TextureHeight = EnvironmentTexture->GetSizeY();

	for (int32 y = 0; y < TextureHeight; ++y)
	{
		for (int32 x = 0; x < TextureWidth; ++x)
		{
			if (bValid)
			{
				const int32 Index = y * TextureWidth + x;

				// Extract input values
				float InputOxygen = DeltaTextureData[Index].R.GetFloat(); // 0.106994629 instead of 0.5
				float InputRadiation = DeltaTextureData[Index].G.GetFloat(); // 0.106994629 instead of 0.5
				float InputTemperature = DeltaTextureData[Index].B.GetFloat(); // 0.106994629 instead of 0.5
				float InputHumidity = DeltaTextureData[Index].A.GetFloat(); // 0.5, correct

				// Convert from range [0, 1] to range [-0.5, 0.5]
				InputOxygen -= 0.5f;
				InputRadiation -= 0.5f;
				InputTemperature -= 0.5f;
				InputHumidity -= 0.5f;

				// Extract existing values
				float ExistingOxygen = EnvironmentTextureData[Index].R.GetFloat();
				float ExistingRadiation = EnvironmentTextureData[Index].G.GetFloat();
				float ExistingTemperature = EnvironmentTextureData[Index].B.GetFloat();
				float ExistingHumidity = EnvironmentTextureData[Index].A.GetFloat();

				// Apply changes
				ExistingOxygen += InputOxygen * DeltaSeconds;
				ExistingRadiation += InputRadiation * DeltaSeconds;
				ExistingTemperature += InputTemperature * DeltaSeconds;
				ExistingHumidity += InputHumidity * DeltaSeconds;

				// Clamp values between 0 and 1
				ExistingOxygen = FMath::Clamp(ExistingOxygen, 0.0f, 1.0f);
				ExistingRadiation = FMath::Clamp(ExistingRadiation, 0.0f, 1.0f);
				ExistingTemperature = FMath::Clamp(ExistingTemperature, 0.0f, 1.0f);
				ExistingHumidity = FMath::Clamp(ExistingHumidity, 0.0f, 1.0f);

				// Update texture data
				EnvironmentTextureData[Index].R = FFloat16(ExistingOxygen);
				EnvironmentTextureData[Index].G = FFloat16(ExistingRadiation);
				EnvironmentTextureData[Index].B = FFloat16(ExistingTemperature);
				EnvironmentTextureData[Index].A = FFloat16(ExistingHumidity);
			}
		}
	}

	EnvironmentMipMap->BulkData.Unlock();
	DeltaMipMap->BulkData.Unlock();
}

Texture settings:

I tried so many things like different texture formats, different encoding override and color space settings. I exported and reimported in various formats (png, tga, tiff, exr) with all possible combinations of export settings :smiley: I also tried creating the texture in blender instead of GIMP as it has some more export settings, especially for EXR. All of them gives different results, sometimes its 0.106994629, sometimes its 0.2… but none of them gives the correct result of 0.5 in all channels. What am I doing wrong?

Does your image have color management on in GIMP? If so, try turning it off.

I checked the floating point number you provided and it indeed only uses 10 bits for the fraction and that matches the number of bits in IEEE 754 half precision number (16 bit float). So all the evidence points toward these being the original values from the file which would indicate that something in GIMP changing the values. Color management would be my first guess.