In order to implement an early version of pre-rendered 2D skybox I needed to compare the skyboxes with sun enabled and disabled in order to obtain the required tinting of the sun for 2D skybox (so the sun doesn’t render on top of it, shining right through dense clouds).
This system is no longer used, now a texture mask is used instead to remove stars, sun and moon and other background objects from being rendered where the clouds are.
Here is the function for sampling the cube map:
FLinearColor USynesthesiaBlueprintFunctions::SampleTextureCubeByDirection(UTextureRenderTargetCube* RenderTarget, const FVector& Direction)
{
if (!RenderTarget) return FLinearColor::Black;
// Get the correct resource
FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource();
if (!RenderTargetResource) return FLinearColor::Black;
FTextureRenderTargetCubeResource* CubeResource = RenderTargetResource->GetTextureRenderTargetCubeResource();
if (!CubeResource) return FLinearColor::Black;
// Normalize the direction vector
const FVector NormalizedDirection = Direction.GetSafeNormal();
// Get the correct cube face for the normalized direction
ECubeFace CubeFace = ECubeFace::CubeFace_NegX;
const double MaxAbsValue = FMath::Max3(FMath::Abs(NormalizedDirection.X), FMath::Abs(NormalizedDirection.Y), FMath::Abs(NormalizedDirection.Z));
if (FMath::Abs(NormalizedDirection.X) == MaxAbsValue) {
CubeFace = NormalizedDirection.X > 0 ? ECubeFace::CubeFace_PosX : ECubeFace::CubeFace_NegX;
} else if (FMath::Abs(NormalizedDirection.Y) == MaxAbsValue) {
CubeFace = NormalizedDirection.Y > 0 ? ECubeFace::CubeFace_PosY : ECubeFace::CubeFace_NegY;
} else if (FMath::Abs(NormalizedDirection.Z) == MaxAbsValue) {
CubeFace = NormalizedDirection.Z > 0 ? ECubeFace::CubeFace_PosZ : ECubeFace::CubeFace_NegZ;
}
// Calculate coordinates of the point on the selected cube maps cube face
float CubeFaceU = 0.0f;
float CubeFaceV = 0.0f;
const FVector FaceDirection = NormalizedDirection.GetAbs();
if (CubeFace == ECubeFace::CubeFace_PosX) {
CubeFaceU = -NormalizedDirection.Z / FaceDirection.X;
CubeFaceV = -NormalizedDirection.Y / FaceDirection.X;
} else if (CubeFace == ECubeFace::CubeFace_NegX) {
CubeFaceU = NormalizedDirection.Z / FaceDirection.X;
CubeFaceV = -NormalizedDirection.Y / FaceDirection.X;
} else if (CubeFace == ECubeFace::CubeFace_PosY) {
CubeFaceU = NormalizedDirection.X / FaceDirection.Y;
CubeFaceV = NormalizedDirection.Z / FaceDirection.Y;
} else if (CubeFace == ECubeFace::CubeFace_NegY) {
CubeFaceU = NormalizedDirection.X / FaceDirection.Y;
CubeFaceV = -NormalizedDirection.Z / FaceDirection.Y;
} else if (CubeFace == ECubeFace::CubeFace_PosZ) {
CubeFaceU = NormalizedDirection.X / FaceDirection.Z;
CubeFaceV = -NormalizedDirection.Y / FaceDirection.Z;
} else if (CubeFace == ECubeFace::CubeFace_NegZ) {
CubeFaceU = -NormalizedDirection.X / FaceDirection.Z;
CubeFaceV = -NormalizedDirection.Y / FaceDirection.Z;
}
// Read correct pixel from the resource
TArray<FFloat16Color> ImageData;
if (!CubeResource->ReadPixels(ImageData, FReadSurfaceDataFlags(ERangeCompressionMode::RCM_UNorm, CubeFace))) return FLinearColor::Black;
// Calculate pixel coordinates of the point on selected cubemap face
const int32 SizeX = CubeResource->GetSizeX();
const int32 SizeY = CubeResource->GetSizeY();
const int32 U = FMath::RoundToInt((CubeFaceU + 1.0f) * 0.5f * (SizeX - 1));
const int32 V = FMath::RoundToInt((CubeFaceV + 1.0f) * 0.5f * (SizeY - 1));
const int32 Offset = V * SizeX + U;
// Sample the cubemap data
if (ImageData.IsValidIndex(Offset)) {
return ImageData[Offset].GetFloats();
} else {
return FLinearColor::Black;
}
}
And here is the function for saving a cube map to disk, just in case it might be useful in a similar context:
UTextureCube* USynesthesiaBlueprintFunctions::RenderTargetCreateStaticTextureCubeEditorOnly(UTextureRenderTargetCube* RenderTarget, FString InName, TextureCompressionSettings CompressionSettings, TextureMipGenSettings MipSettings)
{
#if WITH_EDITOR
// Save the render target image as an asset
if (!RenderTarget) { // Invalid RT
FMessageLog("Blueprint").Warning(LOCTEXT("RenderTargetCreateStaticTextureCube_InvalidRenderTarget", "RenderTargetCreateStaticTextureCubeEditorOnly: RenderTarget must be non-null."));
return nullptr;
} else if (!RenderTarget->GetResource()) { // Invalid RT resource
FMessageLog("Blueprint").Warning(LOCTEXT("RenderTargetCreateStaticTextureCube_ReleasedRenderTarget", "RenderTargetCreateStaticTextureCubeEditorOnly: RenderTarget has been released."));
return nullptr;
} else { // Valid inputs, generate static texture
FString Name;
FString PackageName;
IAssetTools& AssetTools = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
// Use asset name only if directories are specified, otherwise full path
if (!InName.Contains(TEXT("/"))) {
const FString AssetName = RenderTarget->GetOutermost()->GetName();
const FString SanitizedBasePackageName = UPackageTools::SanitizePackageName(AssetName);
const FString PackagePath = FPackageName::GetLongPackagePath(SanitizedBasePackageName) + TEXT("/");
//AssetTools.CreateUniqueAssetName(PackagePath, InName, PackageName, Name);
PackageName = PackagePath + InName;
} else {
InName.RemoveFromStart(TEXT("/"));
InName.RemoveFromStart(TEXT("Content/"));
InName.StartsWith(TEXT("Game/")) == true ? InName.InsertAt(0, TEXT("/")) : InName.InsertAt(0, TEXT("/Game/"));
//AssetTools.CreateUniqueAssetName(InName, TEXT(""), PackageName, Name);
PackageName = InName;
}
const int32 LastSlashIndex = InName.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
if (LastSlashIndex != INDEX_NONE && LastSlashIndex < InName.Len() - 1) {
Name = InName.RightChop(LastSlashIndex + 1);
} else {
Name = InName;
}
// Create the package
UPackage* Package = CreatePackage(*PackageName);
if (!Package) {
UE_LOG(LogSynth, Error, TEXT("Failed to create package %s"), *PackageName);
return nullptr;
}
Package->FullyLoad();
// Create or overwrite the texture
UTextureCube* NewTexture = Cast<UTextureCube>(RenderTarget->ConstructTextureCube(Package, Name, RenderTarget->GetMaskedFlags() | RF_Public | RF_Standalone));
// If the texture is valid, set its parameters and return it
if (NewTexture != nullptr) {
// Package needs saving
//NewTexture->MarkPackageDirty();
Package->SetDirtyFlag(true);
Package->PackageMarkedDirtyEvent.Broadcast(Package, true);
// Notify the asset registry
FAssetRegistryModule::AssetCreated(NewTexture);
// Update Compression and Mip settings
NewTexture->CompressionSettings = CompressionSettings;
NewTexture->MipGenSettings = MipSettings;
NewTexture->PostEditChange();
return NewTexture;
}
FMessageLog("Blueprint").Warning(LOCTEXT("RenderTargetCreateStaticTextureCube_FailedToCreateTexture", "RenderTargetCreateStaticTextureCubeEditorOnly: Failed to create a new texture."));
}
#else
FMessageLog("Blueprint").Error(LOCTEXT("RenderTargetCreateStaticTextureCube_RuntimeFailedToCreateTexture", "RenderTargetCreateStaticTextureCubeEditorOnly: Can't create TextureCube at run time. "));
#endif
return nullptr;
}



