2797 lines
115 KiB
C++
2797 lines
115 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
#include "MaterialUtilities.h"
|
|
#include "EngineDefines.h"
|
|
#include "ShowFlags.h"
|
|
#include "Materials/MaterialInterface.h"
|
|
#include "Materials/Material.h"
|
|
#include "Engine/Texture2D.h"
|
|
#include "Misc/App.h"
|
|
#include "Materials/MaterialInstance.h"
|
|
#include "Materials/MaterialInstanceConstant.h"
|
|
#include "Engine/TextureRenderTarget2D.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "LegacyScreenPercentageDriver.h"
|
|
|
|
#include "Materials/MaterialAttributeDefinitionMap.h"
|
|
#include "Materials/MaterialExpressionConstant.h"
|
|
#include "Materials/MaterialExpressionConstant4Vector.h"
|
|
#include "Materials/MaterialExpressionMultiply.h"
|
|
#include "Materials/MaterialRenderProxy.h"
|
|
#include "Engine/TextureCube.h"
|
|
#include "Engine/Texture2DArray.h"
|
|
#include "SceneView.h"
|
|
#include "SceneViewExtension.h"
|
|
#include "RendererInterface.h"
|
|
#include "EngineModule.h"
|
|
#include "ImageUtils.h"
|
|
#include "CanvasTypes.h"
|
|
#include "Materials/MaterialExpressionTextureSample.h"
|
|
#include "MaterialCompiler.h"
|
|
#include "MaterialDomain.h"
|
|
#include "DeviceProfiles/DeviceProfileManager.h"
|
|
#include "Materials/MaterialParameterCollection.h"
|
|
#include "LandscapeProxy.h"
|
|
#include "LandscapeComponent.h"
|
|
#include "Engine/StaticMesh.h"
|
|
#include "MeshUtilities.h"
|
|
#include "MeshRendering.h"
|
|
#include "MeshMergeData.h"
|
|
#include "PrimitiveSceneProxy.h"
|
|
#include "Templates/UniquePtr.h"
|
|
#include "TextureResource.h"
|
|
|
|
|
|
#include "IMaterialBakingModule.h"
|
|
#include "MaterialBakingStructures.h"
|
|
#include "MaterialOptions.h"
|
|
|
|
#include "StaticMeshAttributes.h"
|
|
#include "TextureCompiler.h"
|
|
|
|
#include "TriangleTypes.h"
|
|
#include "DynamicMesh/DynamicMesh3.h"
|
|
#include "DynamicMesh/DynamicMeshAttributeSet.h"
|
|
|
|
#if WITH_EDITOR
|
|
#include "DeviceProfiles/DeviceProfile.h"
|
|
#include "Tests/AutomationEditorCommon.h"
|
|
#endif // WITH_EDITOR
|
|
|
|
|
|
IMPLEMENT_MODULE(FMaterialUtilities, MaterialUtilities);
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogMaterialUtilities, Log, All);
|
|
|
|
static TAutoConsoleVariable<int32> CVarMaterialUtilitiesWarmupFrames(
|
|
TEXT("MaterialUtilities.WarmupFrames"),
|
|
10,
|
|
TEXT("Number of frames to render before each capture in order to warmup various rendering systems (VT/Nanite/etc)."));
|
|
|
|
|
|
bool FMaterialUtilities::CurrentlyRendering = false;
|
|
TArray<UTextureRenderTarget2D*> FMaterialUtilities::RenderTargetPool;
|
|
|
|
void FMaterialUtilities::StartupModule()
|
|
{
|
|
FCoreUObjectDelegates::GetPreGarbageCollectDelegate().AddRaw(this, &FMaterialUtilities::OnPreGarbageCollect);
|
|
}
|
|
|
|
void FMaterialUtilities::ShutdownModule()
|
|
{
|
|
FCoreUObjectDelegates::GetPreGarbageCollectDelegate().RemoveAll(this);
|
|
ClearRenderTargetPool();
|
|
}
|
|
|
|
void FMaterialUtilities::OnPreGarbageCollect()
|
|
{
|
|
ClearRenderTargetPool();
|
|
}
|
|
|
|
UMaterialInterface* FMaterialUtilities::CreateProxyMaterialAndTextures(UPackage* OuterPackage, const FString& AssetName, const FBakeOutput& BakeOutput, const FMeshData& MeshData, const FMaterialData& MaterialData, UMaterialOptions* Options)
|
|
{
|
|
check(MaterialData.Material);
|
|
|
|
TArray<EMaterialProperty> SRGBEnabledProperties{ MP_BaseColor, MP_EmissiveColor, MP_SubsurfaceColor };
|
|
|
|
// Certain material properties use differen compression settings
|
|
TMap<EMaterialProperty, TextureCompressionSettings> SpecialCompressionSettingProperties;
|
|
SpecialCompressionSettingProperties.Add(MP_Normal, TC_Normalmap);
|
|
SpecialCompressionSettingProperties.Add(MP_Opacity, TC_Grayscale);
|
|
SpecialCompressionSettingProperties.Add(MP_OpacityMask, TC_Grayscale);
|
|
SpecialCompressionSettingProperties.Add(MP_AmbientOcclusion, TC_Grayscale);
|
|
|
|
UMaterial* BaseMaterial = GEngine->DefaultFlattenMaterial;
|
|
check(BaseMaterial);
|
|
|
|
/** Create Proxy material and populate flags */
|
|
UMaterialInstanceConstant* Material = FMaterialUtilities::CreateInstancedMaterial(BaseMaterial, OuterPackage, AssetName, RF_Public | RF_Standalone);
|
|
check(Material);
|
|
|
|
Material->BasePropertyOverrides.TwoSided = MaterialData.Material->IsTwoSided();
|
|
Material->BasePropertyOverrides.bOverride_TwoSided = MaterialData.Material->IsTwoSided();
|
|
Material->BasePropertyOverrides.bOverride_bIsThinSurface = MaterialData.Material->IsThinSurface();
|
|
Material->BasePropertyOverrides.DitheredLODTransition = MaterialData.Material->IsDitheredLODTransition();
|
|
Material->BasePropertyOverrides.bOverride_DitheredLODTransition = MaterialData.Material->IsDitheredLODTransition();
|
|
|
|
if (!IsOpaqueBlendMode(*MaterialData.Material))
|
|
{
|
|
Material->BasePropertyOverrides.bOverride_BlendMode = true;
|
|
Material->BasePropertyOverrides.BlendMode = MaterialData.Material->GetBlendMode();
|
|
}
|
|
|
|
FStaticParameterSet NewStaticParameterSet;
|
|
// iterate over each property and its size
|
|
for (auto Iterator : BakeOutput.PropertySizes)
|
|
{
|
|
EMaterialProperty Property = Iterator.Key;
|
|
FIntPoint DataSize = Iterator.Value;
|
|
// Retrieve pixel data for the current property
|
|
const TArray<FColor>& ColorData = BakeOutput.PropertyData.FindChecked(Property);
|
|
|
|
// Look up the property name string
|
|
const UEnum* PropertyEnum = StaticEnum<EMaterialProperty>();
|
|
FName PropertyName = PropertyEnum->GetNameByValue(Property);
|
|
FString TrimmedPropertyName = PropertyName.ToString();
|
|
TrimmedPropertyName.RemoveFromStart(TEXT("MP_"));
|
|
|
|
// If the pixel data isn't constant create a texture for it
|
|
if (ColorData.Num() > 1)
|
|
{
|
|
FMaterialParameterInfo ParameterInfo(*(TrimmedPropertyName + TEXT("Texture")));
|
|
|
|
FCreateTexture2DParameters CreateParams;
|
|
CreateParams.TextureGroup = TEXTUREGROUP_HierarchicalLOD;
|
|
CreateParams.CompressionSettings = SpecialCompressionSettingProperties.Contains(Property) ? SpecialCompressionSettingProperties.FindChecked(Property) : TC_Default;
|
|
CreateParams.bSRGB = SRGBEnabledProperties.Contains(Property);
|
|
|
|
// Make sure the texture is a VT if required by the material sampler
|
|
UTexture* DefaultTexture = nullptr;
|
|
Material->GetTextureParameterValue(ParameterInfo, DefaultTexture);
|
|
if (DefaultTexture)
|
|
{
|
|
CreateParams.bVirtualTexture = DefaultTexture->VirtualTextureStreaming;
|
|
}
|
|
|
|
UTexture* Texture = FMaterialUtilities::CreateTexture(OuterPackage, TEXT("T_") + AssetName + TEXT("_") + TrimmedPropertyName, DataSize, ColorData, CreateParams, RF_Public | RF_Standalone);
|
|
|
|
// Set texture parameter value on instance material
|
|
Material->SetTextureParameterValueEditorOnly(ParameterInfo, Texture);
|
|
|
|
FStaticSwitchParameter SwitchParameter;
|
|
SwitchParameter.ParameterInfo.Name = *(TEXT("Use") + TrimmedPropertyName);
|
|
SwitchParameter.Value = true;
|
|
SwitchParameter.bOverride = true;
|
|
NewStaticParameterSet.StaticSwitchParameters.Add(SwitchParameter);
|
|
}
|
|
else
|
|
{
|
|
// Otherwise set either float4 or float constant values on instance material
|
|
FMaterialParameterInfo ParameterInfo(*(TrimmedPropertyName + TEXT("Const")));
|
|
|
|
if (Property == MP_BaseColor || Property == MP_EmissiveColor)
|
|
{
|
|
if(BakeOutput.PropertyIsLinearColor.Contains(Property) && BakeOutput.PropertyIsLinearColor.FindChecked(Property))
|
|
{
|
|
Material->SetVectorParameterValueEditorOnly(ParameterInfo, FLinearColor(float(ColorData[0].R) / 255.0f, float(ColorData[0].G) / 255.0f, float(ColorData[0].B) / 255.0f, float(ColorData[0].A) / 255.0f));
|
|
}
|
|
else
|
|
{
|
|
Material->SetVectorParameterValueEditorOnly(ParameterInfo, FLinearColor::FromSRGBColor(ColorData[0]));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (BakeOutput.PropertyIsLinearColor.Contains(Property) && BakeOutput.PropertyIsLinearColor.FindChecked(Property))
|
|
{
|
|
Material->SetScalarParameterValueEditorOnly(ParameterInfo, float(ColorData[0].R) / 255.0f);
|
|
}
|
|
else
|
|
{
|
|
Material->SetScalarParameterValueEditorOnly(ParameterInfo, FLinearColor::FromSRGBColor(ColorData[0]).R);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Apply emissive scaling
|
|
if (BakeOutput.PropertyData.Contains(MP_EmissiveColor))
|
|
{
|
|
if (BakeOutput.EmissiveScale != 1.0f)
|
|
{
|
|
FMaterialParameterInfo ParameterInfo(TEXT("EmissiveScale"));
|
|
Material->SetScalarParameterValueEditorOnly(ParameterInfo, BakeOutput.EmissiveScale);
|
|
}
|
|
}
|
|
|
|
// If the used texture coordinate was not the default UV0 set the appropriate one on the instance material
|
|
if (MeshData.TextureCoordinateIndex != 0)
|
|
{
|
|
FStaticSwitchParameter SwitchParameter;
|
|
SwitchParameter.ParameterInfo.Name = TEXT("UseCustomUV");
|
|
SwitchParameter.Value = true;
|
|
SwitchParameter.bOverride = true;
|
|
NewStaticParameterSet.StaticSwitchParameters.Add(SwitchParameter);
|
|
|
|
SwitchParameter.ParameterInfo.Name = *(TEXT("UseUV") + FString::FromInt(MeshData.TextureCoordinateIndex));
|
|
NewStaticParameterSet.StaticSwitchParameters.Add(SwitchParameter);
|
|
}
|
|
|
|
Material->UpdateStaticPermutation(NewStaticParameterSet);
|
|
Material->InitStaticPermutation();
|
|
|
|
Material->PostEditChange();
|
|
|
|
return Material;
|
|
}
|
|
|
|
UMaterialInterface* FMaterialUtilities::CreateProxyMaterialAndTextures(const FString& PackageName, const FString& AssetName, const FBakeOutput& BakeOutput, const FMeshData& MeshData, const FMaterialData& MaterialData, UMaterialOptions* Options)
|
|
{
|
|
UPackage* MaterialPackage = CreatePackage( *PackageName);
|
|
check(MaterialPackage);
|
|
MaterialPackage->FullyLoad();
|
|
MaterialPackage->Modify();
|
|
|
|
return CreateProxyMaterialAndTextures(MaterialPackage, AssetName, BakeOutput, MeshData, MaterialData, Options);
|
|
}
|
|
|
|
/*------------------------------------------------------------------------------
|
|
Helper classes for render material to texture
|
|
------------------------------------------------------------------------------*/
|
|
struct FExportMaterialCompiler : public FProxyMaterialCompiler
|
|
{
|
|
FExportMaterialCompiler(FMaterialCompiler* InCompiler) :
|
|
FProxyMaterialCompiler(InCompiler)
|
|
{}
|
|
|
|
// gets value stored by SetMaterialProperty()
|
|
virtual EShaderFrequency GetCurrentShaderFrequency() const override
|
|
{
|
|
// not used by Lightmass
|
|
return SF_Pixel;
|
|
}
|
|
|
|
virtual FMaterialShadingModelField GetMaterialShadingModels() const override
|
|
{
|
|
// not used by Lightmass
|
|
return MSM_MAX;
|
|
}
|
|
|
|
virtual FMaterialShadingModelField GetCompiledShadingModels() const override
|
|
{
|
|
// not used by Lightmass
|
|
return MSM_MAX;
|
|
}
|
|
|
|
virtual int32 WorldPosition(EWorldPositionIncludedOffsets WorldPositionIncludedOffsets) override
|
|
{
|
|
#if WITH_EDITOR
|
|
return Compiler->MaterialBakingWorldPosition();
|
|
#else
|
|
return Compiler->WorldPosition(WorldPositionIncludedOffsets);
|
|
#endif
|
|
}
|
|
|
|
virtual int32 ObjectWorldPosition(EPositionOrigin OriginType) override
|
|
{
|
|
return Compiler->ObjectWorldPosition(OriginType);
|
|
}
|
|
|
|
virtual int32 DistanceCullFade() override
|
|
{
|
|
return Compiler->Constant(1.0f);
|
|
}
|
|
|
|
virtual int32 ActorWorldPosition(EPositionOrigin OriginType) override
|
|
{
|
|
return Compiler->ActorWorldPosition(OriginType);
|
|
}
|
|
|
|
virtual int32 ParticleRelativeTime() override
|
|
{
|
|
return Compiler->Constant(0.0f);
|
|
}
|
|
|
|
virtual int32 ParticleMotionBlurFade() override
|
|
{
|
|
return Compiler->Constant(1.0f);
|
|
}
|
|
|
|
virtual int32 PixelNormalWS() override
|
|
{
|
|
// Current returning vertex normal since pixel normal will contain incorrect data (normal calculated from uv data used as vertex positions to render out the material)
|
|
return Compiler->VertexNormal();
|
|
}
|
|
|
|
virtual int32 ParticleRandom() override
|
|
{
|
|
return Compiler->Constant(0.0f);
|
|
}
|
|
|
|
virtual int32 ParticleDirection() override
|
|
{
|
|
return Compiler->Constant3(0.0f, 0.0f, 0.0f);
|
|
}
|
|
|
|
virtual int32 ParticleSpeed() override
|
|
{
|
|
return Compiler->Constant(0.0f);
|
|
}
|
|
|
|
virtual int32 ParticleSize() override
|
|
{
|
|
return Compiler->Constant2(0.0f,0.0f);
|
|
}
|
|
|
|
virtual int32 ParticleSpriteRotation() override
|
|
{
|
|
return Compiler->Constant2(0.0f, 0.0f);
|
|
}
|
|
|
|
virtual int32 ObjectRadius() override
|
|
{
|
|
return Compiler->Constant(500);
|
|
}
|
|
|
|
virtual int32 ObjectBounds() override
|
|
{
|
|
return Compiler->ObjectBounds();
|
|
}
|
|
|
|
virtual int32 PreSkinnedLocalBounds(int32 OutputIndex) override
|
|
{
|
|
return Compiler->PreSkinnedLocalBounds(OutputIndex);
|
|
}
|
|
|
|
virtual int32 CameraVector() override
|
|
{
|
|
return Compiler->Constant3(0.0f, 0.0f, 1.0f);
|
|
}
|
|
|
|
virtual int32 ReflectionAboutCustomWorldNormal(int32 CustomWorldNormal, int32 bNormalizeCustomWorldNormal) override
|
|
{
|
|
return Compiler->ReflectionAboutCustomWorldNormal(CustomWorldNormal, bNormalizeCustomWorldNormal);
|
|
}
|
|
|
|
virtual int32 VertexColor() override
|
|
{
|
|
return Compiler->VertexColor();
|
|
}
|
|
|
|
virtual int32 PreSkinnedPosition() override
|
|
{
|
|
return Compiler->PreSkinnedPosition();
|
|
}
|
|
|
|
virtual int32 PreSkinnedNormal() override
|
|
{
|
|
return Compiler->PreSkinnedNormal();
|
|
}
|
|
|
|
virtual int32 VertexInterpolator(uint32 InterpolatorIndex) override
|
|
{
|
|
return Compiler->VertexInterpolator(InterpolatorIndex);
|
|
}
|
|
|
|
virtual int32 LightVector() override
|
|
{
|
|
return Compiler->LightVector();
|
|
}
|
|
|
|
virtual int32 ReflectionVector() override
|
|
{
|
|
return Compiler->ReflectionVector();
|
|
}
|
|
|
|
virtual int32 AtmosphericFogColor(int32 WorldPosition, EPositionOrigin PositionOrigin) override
|
|
{
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
virtual int32 PrecomputedAOMask() override
|
|
{
|
|
return Compiler->PrecomputedAOMask();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
virtual int32 MaterialBakingWorldPosition() override
|
|
{
|
|
return Compiler->MaterialBakingWorldPosition();
|
|
}
|
|
#endif
|
|
|
|
virtual int32 AccessCollectionParameter(UMaterialParameterCollection* ParameterCollection, int32 ParameterIndex, int32 ComponentIndex) override
|
|
{
|
|
if (!ParameterCollection || ParameterIndex == -1)
|
|
{
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
// Collect names of all parameters
|
|
TArray<FName> ParameterNames;
|
|
ParameterCollection->GetParameterNames(ParameterNames, /*bVectorParameters=*/ false);
|
|
int32 NumScalarParameters = ParameterNames.Num();
|
|
ParameterCollection->GetParameterNames(ParameterNames, /*bVectorParameters=*/ true);
|
|
|
|
// Find a parameter corresponding to ParameterIndex/ComponentIndex pair
|
|
int32 Index;
|
|
for (Index = 0; Index < ParameterNames.Num(); Index++)
|
|
{
|
|
FGuid ParameterId = ParameterCollection->GetParameterId(ParameterNames[Index]);
|
|
int32 CheckParameterIndex, CheckComponentIndex;
|
|
ParameterCollection->GetParameterIndex(ParameterId, CheckParameterIndex, CheckComponentIndex);
|
|
if (CheckParameterIndex == ParameterIndex && CheckComponentIndex == ComponentIndex)
|
|
{
|
|
// Found
|
|
break;
|
|
}
|
|
}
|
|
if (Index >= ParameterNames.Num())
|
|
{
|
|
// Not found, should not happen
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
// Create code for parameter
|
|
if (Index < NumScalarParameters)
|
|
{
|
|
const FCollectionScalarParameter* ScalarParameter = ParameterCollection->GetScalarParameterByName(ParameterNames[Index]);
|
|
check(ScalarParameter);
|
|
return Constant(ScalarParameter->DefaultValue);
|
|
}
|
|
else
|
|
{
|
|
const FCollectionVectorParameter* VectorParameter = ParameterCollection->GetVectorParameterByName(ParameterNames[Index]);
|
|
check(VectorParameter);
|
|
const FLinearColor& Color = VectorParameter->DefaultValue;
|
|
return Constant4(Color.R, Color.G, Color.B, Color.A);
|
|
}
|
|
}
|
|
|
|
virtual EMaterialCompilerType GetCompilerType() const override { return EMaterialCompilerType::MaterialProxy; }
|
|
};
|
|
|
|
|
|
class FExportMaterialProxy : public FMaterial, public FMaterialRenderProxy
|
|
{
|
|
public:
|
|
FExportMaterialProxy()
|
|
: FMaterial()
|
|
, FMaterialRenderProxy(TEXT("FExportMaterialProxy"))
|
|
{
|
|
SetQualityLevelProperties(GMaxRHIFeatureLevel);
|
|
}
|
|
|
|
FExportMaterialProxy(UMaterialInterface* InMaterialInterface, EMaterialProperty InPropertyToCompile)
|
|
: FMaterial()
|
|
, FMaterialRenderProxy(GetPathNameSafe(InMaterialInterface->GetMaterial()))
|
|
, MaterialInterface(InMaterialInterface)
|
|
, PropertyToCompile(InPropertyToCompile)
|
|
{
|
|
SetQualityLevelProperties(GMaxRHIFeatureLevel);
|
|
Material = InMaterialInterface->GetMaterial();
|
|
ReferencedTextures = InMaterialInterface->GetReferencedTextures();
|
|
FPlatformMisc::CreateGuid(Id);
|
|
|
|
FMaterialResource* Resource = InMaterialInterface->GetMaterialResource(GMaxRHIFeatureLevel);
|
|
|
|
FMaterialShaderMapId ResourceId;
|
|
Resource->GetShaderMapId(GMaxRHIShaderPlatform, nullptr, ResourceId);
|
|
|
|
{
|
|
TArray<FShaderType*> ShaderTypes;
|
|
TArray<FVertexFactoryType*> VFTypes;
|
|
TArray<const FShaderPipelineType*> ShaderPipelineTypes;
|
|
GetDependentShaderAndVFTypes(GMaxRHIShaderPlatform, ResourceId.LayoutParams, ShaderTypes, ShaderPipelineTypes, VFTypes);
|
|
|
|
// Overwrite the shader map Id's dependencies with ones that came from the FMaterial actually being compiled (this)
|
|
// This is necessary as we change FMaterial attributes like GetShadingModels(), which factor into the ShouldCache functions that determine dependent shader types
|
|
ResourceId.SetShaderDependencies(ShaderTypes, ShaderPipelineTypes, VFTypes, GMaxRHIShaderPlatform);
|
|
}
|
|
|
|
// Override with a special usage so we won't re-use the shader map used by the material for rendering
|
|
switch (InPropertyToCompile)
|
|
{
|
|
case MP_BaseColor: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportBaseColor; break;
|
|
case MP_Specular: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportSpecular; break;
|
|
case MP_Normal: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportNormal; break;
|
|
case MP_Tangent: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportTangent; break;
|
|
case MP_Metallic: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportMetallic; break;
|
|
case MP_Roughness: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportRoughness; break;
|
|
case MP_Anisotropy: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportAnisotropy; break;
|
|
case MP_AmbientOcclusion: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportAO; break;
|
|
case MP_EmissiveColor: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportEmissive; break;
|
|
case MP_Opacity: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportOpacity; break;
|
|
case MP_OpacityMask: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportOpacity; break;
|
|
case MP_SubsurfaceColor: ResourceId.Usage = EMaterialShaderMapUsage::MaterialExportSubSurfaceColor; break;
|
|
default:
|
|
ensureMsgf(false, TEXT("ExportMaterial has no usage for property %i. Will likely reuse the normal rendering shader and crash later with a parameter mismatch"), (int32)InPropertyToCompile);
|
|
break;
|
|
};
|
|
|
|
CacheShaders(ResourceId, GMaxRHIShaderPlatform);
|
|
}
|
|
|
|
virtual bool IsUsedWithStaticLighting() const { return true; }
|
|
|
|
/** This override is required otherwise the shaders aren't ready for use when the surface is rendered resulting in a blank image */
|
|
virtual bool RequiresSynchronousCompilation() const override { return true; };
|
|
|
|
/**
|
|
* Should the shader for this material with the given platform, shader type and vertex
|
|
* factory type combination be compiled
|
|
*
|
|
* @param Platform The platform currently being compiled for
|
|
* @param ShaderType Which shader is being compiled
|
|
* @param VertexFactory Which vertex factory is being compiled (can be NULL)
|
|
*
|
|
* @return true if the shader should be compiled
|
|
*/
|
|
virtual bool ShouldCache(EShaderPlatform Platform, const FShaderType* ShaderType, const FVertexFactoryType* VertexFactoryType) const override
|
|
{
|
|
// Always cache - decreases performance but avoids missing shaders during exports.
|
|
return true;
|
|
}
|
|
|
|
virtual TArrayView<const TObjectPtr<UObject>> GetReferencedTextures() const override
|
|
{
|
|
return ReferencedTextures;
|
|
}
|
|
|
|
////////////////
|
|
// FMaterialRenderProxy interface.
|
|
virtual const FMaterial* GetMaterialNoFallback(ERHIFeatureLevel::Type InFeatureLevel) const override
|
|
{
|
|
if (GetRenderingThreadShaderMap())
|
|
{
|
|
return this;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
virtual const FMaterialRenderProxy* GetFallback(ERHIFeatureLevel::Type InFeatureLevel) const override
|
|
{
|
|
return UMaterial::GetDefaultMaterial(MD_Surface)->GetRenderProxy();
|
|
}
|
|
|
|
virtual bool GetParameterValue(EMaterialParameterType Type, const FHashedMaterialParameterInfo& ParameterInfo, FMaterialParameterValue& OutValue, const FMaterialRenderContext& Context) const override
|
|
{
|
|
return MaterialInterface->GetRenderProxy()->GetParameterValue(Type, ParameterInfo, OutValue, Context);
|
|
}
|
|
|
|
// Material properties.
|
|
/** Entry point for compiling a specific material property. This must call SetMaterialProperty. */
|
|
virtual int32 CompilePropertyAndSetMaterialProperty(EMaterialProperty Property, FMaterialCompiler* Compiler, EShaderFrequency OverrideShaderFrequency, bool bUsePreviousFrameTime) const override
|
|
{
|
|
// needs to be called in this function!!
|
|
Compiler->SetMaterialProperty(Property, OverrideShaderFrequency, bUsePreviousFrameTime);
|
|
|
|
int32 Ret = CompilePropertyAndSetMaterialPropertyWithoutCast(Property, Compiler);
|
|
|
|
return Compiler->ForceCast(Ret, FMaterialAttributeDefinitionMap::GetValueType(Property), MFCF_ExactMatch | MFCF_ReplicateValue);
|
|
}
|
|
|
|
/** helper for CompilePropertyAndSetMaterialProperty() */
|
|
int32 CompilePropertyAndSetMaterialPropertyWithoutCast(EMaterialProperty Property, FMaterialCompiler* Compiler) const
|
|
{
|
|
if (Property == MP_EmissiveColor || Property == MP_SubsurfaceColor)
|
|
{
|
|
const bool bIsOpaqueOrMasked = IsOpaqueOrMaskedBlendMode(*MaterialInterface);
|
|
UMaterial* ProxyMaterial = MaterialInterface->GetMaterial();
|
|
check(ProxyMaterial);
|
|
FExportMaterialCompiler ProxyCompiler(Compiler);
|
|
const uint32 ForceCast_Exact_Replicate = MFCF_ForceCast | MFCF_ExactMatch | MFCF_ReplicateValue;
|
|
|
|
switch (PropertyToCompile)
|
|
{
|
|
case MP_EmissiveColor:
|
|
// Emissive is ALWAYS returned...
|
|
return MaterialInterface->CompileProperty(&ProxyCompiler, MP_EmissiveColor, ForceCast_Exact_Replicate);
|
|
case MP_BaseColor:
|
|
// Only return for Opaque and Masked...
|
|
if (bIsOpaqueOrMasked)
|
|
{
|
|
return MaterialInterface->CompileProperty(&ProxyCompiler, MP_BaseColor, ForceCast_Exact_Replicate);
|
|
}
|
|
break;
|
|
case MP_Specular:
|
|
case MP_Roughness:
|
|
case MP_Anisotropy:
|
|
case MP_Metallic:
|
|
case MP_AmbientOcclusion:
|
|
case MP_SubsurfaceColor:
|
|
// Only return for Opaque and Masked...
|
|
if (bIsOpaqueOrMasked)
|
|
{
|
|
return MaterialInterface->CompileProperty(&ProxyCompiler, PropertyToCompile, ForceCast_Exact_Replicate);
|
|
}
|
|
break;
|
|
case MP_Normal:
|
|
case MP_Tangent:
|
|
// Only return for Opaque and Masked...
|
|
if (bIsOpaqueOrMasked)
|
|
{
|
|
return Compiler->Add(
|
|
Compiler->Mul(MaterialInterface->CompileProperty(&ProxyCompiler, PropertyToCompile, ForceCast_Exact_Replicate), Compiler->Constant(0.5f)), // [-1,1] * 0.5
|
|
Compiler->Constant(0.5f)); // [-0.5,0.5] + 0.5
|
|
}
|
|
break;
|
|
case MP_ShadingModel:
|
|
return MaterialInterface->CompileProperty(&ProxyCompiler, MP_ShadingModel);
|
|
case MP_SurfaceThickness:
|
|
return MaterialInterface->CompileProperty(&ProxyCompiler, MP_SurfaceThickness);
|
|
case MP_FrontMaterial:
|
|
return MaterialInterface->CompileProperty(&ProxyCompiler, MP_FrontMaterial);
|
|
default:
|
|
return Compiler->Constant(1.0f);
|
|
}
|
|
|
|
return Compiler->Constant(0.0f);
|
|
}
|
|
else if (Property == MP_WorldPositionOffset || Property == MP_Displacement)
|
|
{
|
|
//This property MUST return 0 as a default or during the process of rendering textures out for lightmass to use, pixels will be off by 1.
|
|
return Compiler->Constant(0.0f);
|
|
}
|
|
else if (Property >= MP_CustomizedUVs0 && Property <= MP_CustomizedUVs7)
|
|
{
|
|
// Pass through customized UVs
|
|
return MaterialInterface->CompileProperty(Compiler, Property);
|
|
}
|
|
else if (Property == MP_ShadingModel)
|
|
{
|
|
return MaterialInterface->CompileProperty(Compiler, MP_ShadingModel);
|
|
}
|
|
else if (Property == MP_SurfaceThickness)
|
|
{
|
|
return MaterialInterface->CompileProperty(Compiler, MP_SurfaceThickness);
|
|
}
|
|
else if (Property == MP_FrontMaterial)
|
|
{
|
|
return MaterialInterface->CompileProperty(Compiler, MP_FrontMaterial);
|
|
}
|
|
else
|
|
{
|
|
return Compiler->Constant(1.0f);
|
|
}
|
|
}
|
|
|
|
virtual FString GetMaterialUsageDescription() const override
|
|
{
|
|
return FString::Printf(TEXT("ExportMaterialRenderer %s"), MaterialInterface ? *MaterialInterface->GetName() : TEXT("NULL"));
|
|
}
|
|
virtual EMaterialDomain GetMaterialDomain() const override
|
|
{
|
|
if (Material)
|
|
{
|
|
return Material->MaterialDomain;
|
|
}
|
|
return MD_Surface;
|
|
}
|
|
virtual bool IsTwoSided() const override
|
|
{
|
|
if (MaterialInterface)
|
|
{
|
|
return MaterialInterface->IsTwoSided();
|
|
}
|
|
return false;
|
|
}
|
|
virtual bool IsThinSurface() const override
|
|
{
|
|
if (MaterialInterface)
|
|
{
|
|
return MaterialInterface->IsThinSurface();
|
|
}
|
|
return false;
|
|
}
|
|
virtual bool IsDitheredLODTransition() const override
|
|
{
|
|
if (MaterialInterface)
|
|
{
|
|
return MaterialInterface->IsDitheredLODTransition();
|
|
}
|
|
return false;
|
|
}
|
|
virtual bool IsLightFunction() const override
|
|
{
|
|
if (Material)
|
|
{
|
|
return (Material->MaterialDomain == MD_LightFunction);
|
|
}
|
|
return false;
|
|
}
|
|
virtual bool IsDeferredDecal() const override
|
|
{
|
|
return Material && Material->MaterialDomain == MD_DeferredDecal;
|
|
}
|
|
virtual bool IsUIMaterial() const override
|
|
{
|
|
return Material && Material->MaterialDomain == MD_UI;
|
|
}
|
|
virtual bool IsSpecialEngineMaterial() const override
|
|
{
|
|
if (Material)
|
|
{
|
|
return (Material->bUsedAsSpecialEngineMaterial == 1);
|
|
}
|
|
return true;
|
|
}
|
|
virtual bool IsWireframe() const override
|
|
{
|
|
if (Material)
|
|
{
|
|
return (Material->Wireframe == 1);
|
|
}
|
|
return false;
|
|
}
|
|
virtual bool IsMasked() const override { return false; }
|
|
virtual enum EBlendMode GetBlendMode() const override { return BLEND_Opaque; }
|
|
virtual enum ERefractionMode GetRefractionMode() const override { return Material ? (ERefractionMode)Material->RefractionMethod : RM_None; }
|
|
virtual bool GetRootNodeOverridesDefaultRefraction()const override { return Material ? Material->bRootNodeOverridesDefaultDistortion : false; }
|
|
virtual FMaterialShadingModelField GetShadingModels() const override { return MSM_Unlit; }
|
|
virtual bool IsShadingModelFromMaterialExpression() const override { return false; }
|
|
virtual float GetOpacityMaskClipValue() const override { return 0.5f; }
|
|
virtual bool GetCastDynamicShadowAsMasked() const override { return false; }
|
|
virtual FString GetFriendlyName() const override { return FString::Printf(TEXT("FExportMaterialRenderer %s"), MaterialInterface ? *MaterialInterface->GetName() : TEXT("NULL")); }
|
|
/**
|
|
* Should shaders compiled for this material be saved to disk?
|
|
*/
|
|
virtual bool IsPersistent() const override { return false; }
|
|
virtual FGuid GetMaterialId() const override { return Id; }
|
|
|
|
virtual UMaterialInterface* GetMaterialInterface() const override
|
|
{
|
|
return MaterialInterface;
|
|
}
|
|
|
|
friend FArchive& operator<< ( FArchive& Ar, FExportMaterialProxy& V )
|
|
{
|
|
return Ar << V.MaterialInterface;
|
|
}
|
|
|
|
static bool WillFillData(bool bIsOpaque, EMaterialProperty InMaterialProperty)
|
|
{
|
|
if (InMaterialProperty == MP_EmissiveColor)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bIsOpaque)
|
|
{
|
|
switch (InMaterialProperty)
|
|
{
|
|
case MP_BaseColor: return true;
|
|
case MP_Specular: return true;
|
|
case MP_Normal: return true;
|
|
case MP_Tangent: return true;
|
|
case MP_Metallic: return true;
|
|
case MP_Roughness: return true;
|
|
case MP_Anisotropy: return true;
|
|
case MP_AmbientOcclusion: return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
virtual bool IsVolumetricPrimitive() const override
|
|
{
|
|
return Material && Material->MaterialDomain == MD_Volume;
|
|
}
|
|
|
|
virtual void GatherExpressionsForCustomInterpolators(TArray<UMaterialExpression*>& OutExpressions) const override
|
|
{
|
|
if(Material)
|
|
{
|
|
Material->GetAllExpressionsForCustomInterpolators(OutExpressions);
|
|
}
|
|
}
|
|
|
|
virtual bool CheckInValidStateForCompilation(class FMaterialCompiler* Compiler) const override
|
|
{
|
|
return Material && Material->CheckInValidStateForCompilation(Compiler);
|
|
}
|
|
|
|
private:
|
|
/** The material interface for this proxy */
|
|
UMaterialInterface* MaterialInterface;
|
|
UMaterial* Material;
|
|
TArray<TObjectPtr<UObject>> ReferencedTextures;
|
|
/** The property to compile for rendering the sample */
|
|
EMaterialProperty PropertyToCompile;
|
|
FGuid Id;
|
|
};
|
|
|
|
/**
|
|
* Render the scene to the provided canvas. Will potentially perform the render multiple times, depending on the value of
|
|
* the CVarMaterialUtilitiesWarmupFrames CVar. This is needed to ensure various rendering systems are primed properly before capturing
|
|
* the scene.
|
|
*/
|
|
static void PerformSceneRender(FCanvas& Canvas, FSceneViewFamily& ViewFamily, FSceneView& View, bool bWithWarmup)
|
|
{
|
|
if (bWithWarmup)
|
|
{
|
|
for (int32 i = 0; i < CVarMaterialUtilitiesWarmupFrames.GetValueOnGameThread(); i++)
|
|
{
|
|
// Increment frame index when warming up virtual textures.
|
|
// This is necessary to jitter the virtual texture feedback location.
|
|
View.OverrideFrameIndexValue = i;
|
|
|
|
GetRendererModule().BeginRenderingViewFamily(&Canvas, &ViewFamily);
|
|
}
|
|
}
|
|
|
|
// Do final capture with deterministic frame index 0.
|
|
View.OverrideFrameIndexValue = 0;
|
|
|
|
GetRendererModule().BeginRenderingViewFamily(&Canvas, &ViewFamily);
|
|
}
|
|
|
|
static void RenderSceneToTexture(
|
|
FSceneInterface* Scene,
|
|
const FName& VisualizationMode,
|
|
const FVector& ViewOrigin,
|
|
const FMatrix& ViewRotationMatrix,
|
|
const FMatrix& ProjectionMatrix,
|
|
const TSet<FPrimitiveComponentId>& ShowOnlyPrimitives,
|
|
const TSet<FPrimitiveComponentId>& HiddenPrimitives,
|
|
FIntPoint TargetSize,
|
|
float TargetGamma,
|
|
bool bPerformWarmpup,
|
|
TArray<FColor>& OutSamples)
|
|
{
|
|
auto RenderTargetTexture = NewObject<UTextureRenderTarget2D>();
|
|
check(RenderTargetTexture);
|
|
RenderTargetTexture->AddToRoot();
|
|
RenderTargetTexture->ClearColor = FLinearColor::Transparent;
|
|
RenderTargetTexture->TargetGamma = TargetGamma;
|
|
RenderTargetTexture->InitCustomFormat(TargetSize.X, TargetSize.Y, PF_FloatRGBA, false);
|
|
FTextureRenderTargetResource* RenderTargetResource = RenderTargetTexture->GameThread_GetRenderTargetResource();
|
|
|
|
FSceneViewFamilyContext ViewFamily(
|
|
FSceneViewFamily::ConstructionValues(RenderTargetResource, Scene, FEngineShowFlags(ESFIM_Game))
|
|
.SetTime(FGameTime::GetTimeSinceAppStart())
|
|
);
|
|
|
|
// To enable visualization mode
|
|
ViewFamily.EngineShowFlags.SetPostProcessing(true);
|
|
ViewFamily.EngineShowFlags.SetVisualizeBuffer(true);
|
|
ViewFamily.EngineShowFlags.SetTonemapper(false);
|
|
ViewFamily.EngineShowFlags.SetScreenPercentage(false);
|
|
|
|
FSceneViewStateReference ViewState;
|
|
ViewState.Allocate(Scene->GetFeatureLevel());
|
|
|
|
FSceneViewInitOptions ViewInitOptions;
|
|
ViewInitOptions.SetViewRectangle(FIntRect(0, 0, TargetSize.X, TargetSize.Y));
|
|
ViewInitOptions.ViewFamily = &ViewFamily;
|
|
ViewInitOptions.ViewOrigin = ViewOrigin;
|
|
ViewInitOptions.ViewRotationMatrix = ViewRotationMatrix;
|
|
ViewInitOptions.ProjectionMatrix = ProjectionMatrix;
|
|
ViewInitOptions.SceneViewStateInterface = ViewState.GetReference();
|
|
|
|
// If no "show only" primitives are provided, we must pass an unset TOptional - otherwise an empty set will mean no primitive should be visible.
|
|
ViewInitOptions.ShowOnlyPrimitives = !ShowOnlyPrimitives.IsEmpty() ? TOptional<TSet<FPrimitiveComponentId>>(ShowOnlyPrimitives) : TOptional<TSet<FPrimitiveComponentId>>();
|
|
ViewInitOptions.HiddenPrimitives = HiddenPrimitives;
|
|
|
|
FSceneView* NewView = new FSceneView(ViewInitOptions);
|
|
NewView->CurrentBufferVisualizationMode = VisualizationMode;
|
|
ViewFamily.Views.Add(NewView);
|
|
|
|
ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(
|
|
ViewFamily, /* GlobalResolutionFraction = */ 1.0f));
|
|
|
|
FCanvas Canvas(RenderTargetResource, NULL, FGameTime::GetTimeSinceAppStart(), Scene->GetFeatureLevel());
|
|
Canvas.Clear(FLinearColor::Transparent);
|
|
|
|
ViewFamily.ViewExtensions = GEngine->ViewExtensions->GatherActiveExtensions(FSceneViewExtensionContext(Scene));
|
|
for (const FSceneViewExtensionRef& Extension : ViewFamily.ViewExtensions)
|
|
{
|
|
Extension->SetupViewFamily(ViewFamily);
|
|
Extension->SetupView(ViewFamily, *NewView);
|
|
}
|
|
|
|
PerformSceneRender(Canvas, ViewFamily, *NewView, bPerformWarmpup);
|
|
|
|
// Copy the contents of the remote texture to system memory
|
|
OutSamples.SetNumUninitialized(TargetSize.X*TargetSize.Y);
|
|
FReadSurfaceDataFlags ReadSurfaceDataFlags;
|
|
ReadSurfaceDataFlags.SetLinearToGamma(false);
|
|
RenderTargetResource->ReadPixelsPtr(OutSamples.GetData(), ReadSurfaceDataFlags, FIntRect(0, 0, TargetSize.X, TargetSize.Y));
|
|
|
|
RenderTargetTexture->RemoveFromRoot();
|
|
RenderTargetTexture = nullptr;
|
|
}
|
|
|
|
|
|
static void RenderSceneToTextures(
|
|
FSceneInterface* Scene,
|
|
const FVector& ViewOrigin,
|
|
const FMatrix& ViewRotationMatrix,
|
|
const FMatrix& ProjectionMatrix,
|
|
const TSet<FPrimitiveComponentId>& ShowOnlyPrimitives,
|
|
const TSet<FPrimitiveComponentId>& HiddenPrimitives,
|
|
FFlattenMaterial& OutFlattenMaterial)
|
|
{
|
|
TMap<EFlattenMaterialProperties, TPair<FName, float>> SupportedProperties; // Property -> VisualisationMode|Gamma
|
|
SupportedProperties.Add(EFlattenMaterialProperties::Diffuse) = TPair<FName, float>(FName("BaseColor"), true); // BaseColor to gamma space
|
|
SupportedProperties.Add(EFlattenMaterialProperties::Normal) = TPair<FName, float>(FName("WorldNormal"), false); // Dump normal texture in linear space
|
|
SupportedProperties.Add(EFlattenMaterialProperties::Metallic) = TPair<FName, float>(FName("Metallic"), false); // Dump metallic texture in linear space
|
|
SupportedProperties.Add(EFlattenMaterialProperties::Roughness) = TPair<FName, float>(FName("Roughness"), true); // Roughness material powers color by 2.2, transform it back to linear
|
|
SupportedProperties.Add(EFlattenMaterialProperties::Specular) = TPair<FName, float>(FName("Specular"), false); // Dump specular texture in linear space
|
|
|
|
bool bPerformWarmpup = true;
|
|
for (int32 PropertyIndex = 0; PropertyIndex < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++PropertyIndex)
|
|
{
|
|
const EFlattenMaterialProperties Property = (EFlattenMaterialProperties)PropertyIndex;
|
|
if (OutFlattenMaterial.ShouldGenerateDataForProperty(Property))
|
|
{
|
|
TPair<FName, float>* PropertyInfo = SupportedProperties.Find(Property);
|
|
if (PropertyInfo)
|
|
{
|
|
TArray<FColor>& Samples = OutFlattenMaterial.GetPropertySamples(Property);
|
|
const FIntPoint& Size = OutFlattenMaterial.GetPropertySize(Property);
|
|
const FName VisModeName = PropertyInfo->Key;
|
|
const float Gamma = PropertyInfo->Value ? 2.2f : 1.0f;
|
|
|
|
RenderSceneToTexture(Scene, VisModeName, ViewOrigin, ViewRotationMatrix, ProjectionMatrix, ShowOnlyPrimitives, HiddenPrimitives, Size, Gamma, bPerformWarmpup, Samples);
|
|
bPerformWarmpup = false; // Perform warmup only on first render
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMaterialUtilities, Error, TEXT("RenderSceneToTextures - Ignoring unsupported property"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FIntPoint FMaterialUtilities::FindMaxTextureSize(UMaterialInterface* InMaterialInterface, FIntPoint MinimumSize)
|
|
{
|
|
// static lod settings so that we only initialize them once
|
|
UTextureLODSettings* GameTextureLODSettings = UDeviceProfileManager::Get().GetActiveProfile()->GetTextureLODSettings();
|
|
|
|
TArray<UTexture*> MaterialTextures;
|
|
|
|
InMaterialInterface->GetUsedTextures(MaterialTextures, EMaterialQualityLevel::Num, false, GMaxRHIFeatureLevel, false);
|
|
FTextureCompilingManager::Get().FinishCompilation(MaterialTextures);
|
|
|
|
// find the largest texture in the list (applying it's LOD bias)
|
|
FIntPoint MaxSize = MinimumSize;
|
|
for (int32 TexIndex = 0; TexIndex < MaterialTextures.Num(); TexIndex++)
|
|
{
|
|
UTexture* Texture = MaterialTextures[TexIndex];
|
|
|
|
if (Texture == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// get the max size of the texture
|
|
FIntPoint LocalSize(0, 0);
|
|
if (Texture->IsA(UTexture2D::StaticClass()))
|
|
{
|
|
UTexture2D* Tex2D = (UTexture2D*)Texture;
|
|
LocalSize = FIntPoint(Tex2D->GetSizeX(), Tex2D->GetSizeY());
|
|
}
|
|
else if (Texture->IsA(UTextureCube::StaticClass()))
|
|
{
|
|
UTextureCube* TexCube = (UTextureCube*)Texture;
|
|
LocalSize = FIntPoint(TexCube->GetSizeX(), TexCube->GetSizeY());
|
|
}
|
|
else if (Texture->IsA(UTexture2DArray::StaticClass()))
|
|
{
|
|
UTexture2DArray* TexArray = (UTexture2DArray*)Texture;
|
|
LocalSize = FIntPoint(TexArray->GetSizeX(), TexArray->GetSizeY());
|
|
}
|
|
|
|
int32 LocalBias = GameTextureLODSettings->CalculateLODBias(Texture);
|
|
|
|
// bias the texture size based on LOD group
|
|
FIntPoint BiasedLocalSize(LocalSize.X >> LocalBias, LocalSize.Y >> LocalBias);
|
|
|
|
MaxSize.X = FMath::Max(BiasedLocalSize.X, MaxSize.X);
|
|
MaxSize.Y = FMath::Max(BiasedLocalSize.Y, MaxSize.Y);
|
|
}
|
|
|
|
return MaxSize;
|
|
}
|
|
|
|
bool FMaterialUtilities::SupportsExport(bool bIsOpaque, EMaterialProperty InMaterialProperty)
|
|
{
|
|
return FExportMaterialProxy::WillFillData(bIsOpaque, InMaterialProperty);
|
|
}
|
|
|
|
bool FMaterialUtilities::SupportsExport(EBlendMode InBlendMode, EMaterialProperty InMaterialProperty)
|
|
{
|
|
return FExportMaterialProxy::WillFillData(IsOpaqueBlendMode(InBlendMode), InMaterialProperty);
|
|
}
|
|
|
|
static bool ExportLandscapeMaterial(const ALandscapeProxy* InLandscape, const TSet<FPrimitiveComponentId>& ShowOnlyPrimitives, const TSet<FPrimitiveComponentId>& HiddenPrimitives, FFlattenMaterial& OutFlattenMaterial)
|
|
{
|
|
check(InLandscape);
|
|
|
|
FIntRect LandscapeRect = InLandscape->GetBoundingRect();
|
|
FVector MidPoint = FVector(LandscapeRect.Min, 0.f) + FVector(LandscapeRect.Size(), 0.f)*0.5f;
|
|
|
|
FVector LandscapeCenter = InLandscape->GetTransform().TransformPosition(MidPoint);
|
|
FVector LandscapeExtent = FVector(LandscapeRect.Size(), 0.f)*InLandscape->GetActorScale()*0.5f;
|
|
|
|
FVector ViewOrigin = LandscapeCenter;
|
|
FMatrix ViewRotationMatrix = FInverseRotationMatrix(InLandscape->GetActorRotation());
|
|
ViewRotationMatrix*= FMatrix(FPlane(1, 0, 0, 0),
|
|
FPlane(0, -1, 0, 0),
|
|
FPlane(0, 0, -1, 0),
|
|
FPlane(0, 0, 0, 1));
|
|
|
|
const FMatrix::FReal ZOffset = UE_OLD_WORLD_MAX;
|
|
FMatrix ProjectionMatrix = FReversedZOrthoMatrix(
|
|
LandscapeExtent.X,
|
|
LandscapeExtent.Y,
|
|
0.5f / ZOffset,
|
|
ZOffset);
|
|
|
|
FSceneInterface* Scene = InLandscape->GetWorld()->Scene;
|
|
|
|
RenderSceneToTextures(Scene, ViewOrigin, ViewRotationMatrix, ProjectionMatrix, ShowOnlyPrimitives, HiddenPrimitives, OutFlattenMaterial);
|
|
|
|
OutFlattenMaterial.MaterialId = InLandscape->GetLandscapeGuid();
|
|
return true;
|
|
}
|
|
|
|
bool FMaterialUtilities::ExportLandscapeMaterial(const ALandscapeProxy* InLandscape, FFlattenMaterial& OutFlattenMaterial)
|
|
{
|
|
bool bExportSuccess = false;
|
|
|
|
if (InLandscape)
|
|
{
|
|
TSet<FPrimitiveComponentId> ShowOnlyPrimitives;
|
|
|
|
// Include all landscape components scene proxies
|
|
for (ULandscapeComponent* LandscapeComponent : InLandscape->LandscapeComponents)
|
|
{
|
|
if (LandscapeComponent && LandscapeComponent->SceneProxy)
|
|
{
|
|
ShowOnlyPrimitives.Add(LandscapeComponent->SceneProxy->GetPrimitiveComponentId());
|
|
}
|
|
}
|
|
|
|
// Include Nanite landscape scene proxy - these are the ones that are actually visible when rendering LS with Nanite support
|
|
if (InLandscape->HasNaniteComponents())
|
|
{
|
|
ShowOnlyPrimitives.Append(InLandscape->GetNanitePrimitiveComponentIds());
|
|
}
|
|
|
|
bExportSuccess = ::ExportLandscapeMaterial(InLandscape, ShowOnlyPrimitives, {}, OutFlattenMaterial);
|
|
}
|
|
|
|
if (!bExportSuccess)
|
|
{
|
|
UE_LOG(LogMaterialUtilities, Warning, TEXT("ExportLandscapeMaterial: Failed to export material for the provided ALandcapeProxy (%s)"), InLandscape ? *InLandscape->GetName() : TEXT("<null>"));
|
|
}
|
|
|
|
return bExportSuccess;
|
|
}
|
|
|
|
bool FMaterialUtilities::ExportLandscapeMaterial(const ALandscapeProxy* InLandscape, const TSet<FPrimitiveComponentId>& HiddenPrimitives, FFlattenMaterial& OutFlattenMaterial)
|
|
{
|
|
return ::ExportLandscapeMaterial(InLandscape, {}, HiddenPrimitives, OutFlattenMaterial);
|
|
}
|
|
|
|
UMaterial* FMaterialUtilities::CreateMaterial(const FFlattenMaterial& InFlattenMaterial, UPackage* InOuter, const FString& BaseName, EObjectFlags Flags, const struct FMaterialProxySettings& MaterialProxySettings, TArray<UObject*>& OutGeneratedAssets, const TextureGroup& InTextureGroup /*= TEXTUREGROUP_World*/)
|
|
{
|
|
// Base name for a new assets
|
|
// In case outer is null BaseName has to be long package name
|
|
if (InOuter == nullptr && FPackageName::IsShortPackageName(BaseName))
|
|
{
|
|
UE_LOG(LogMaterialUtilities, Warning, TEXT("Invalid long package name: '%s'."), *BaseName);
|
|
return nullptr;
|
|
}
|
|
|
|
const FString AssetBaseName = FPackageName::GetShortName(BaseName);
|
|
const FString AssetBasePath = InOuter ? TEXT("") : FPackageName::GetLongPackagePath(BaseName);
|
|
|
|
// Create material
|
|
const FString MaterialAssetName = TEXT("M_") + AssetBaseName;
|
|
UPackage* MaterialOuter = InOuter;
|
|
if (MaterialOuter == NULL)
|
|
{
|
|
MaterialOuter = CreatePackage( *(AssetBasePath / MaterialAssetName));
|
|
MaterialOuter->FullyLoad();
|
|
MaterialOuter->Modify();
|
|
}
|
|
|
|
UMaterial* Material = NewObject<UMaterial>(MaterialOuter, FName(*MaterialAssetName), Flags);
|
|
Material->TwoSided = false;
|
|
Material->DitheredLODTransition = InFlattenMaterial.bDitheredLODTransition;
|
|
Material->SetShadingModel(MSM_DefaultLit);
|
|
OutGeneratedAssets.Add(Material);
|
|
|
|
UMaterialEditorOnlyData* MaterialEditorOnly = Material->GetEditorOnlyData();
|
|
|
|
int32 MaterialNodeY = -150;
|
|
int32 MaterialNodeStepY = 180;
|
|
|
|
// BaseColor
|
|
const TArray<FColor>& DiffuseSamples = InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse);
|
|
if (DiffuseSamples.Num() > 1)
|
|
{
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_D");
|
|
const FString AssetLongName = AssetBasePath / AssetName;
|
|
const bool bSRGB = true;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetLongName, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse), DiffuseSamples, TC_Default, InTextureGroup, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
auto BasecolorExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
BasecolorExpression->Texture = Texture;
|
|
BasecolorExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Color;
|
|
BasecolorExpression->MaterialExpressionEditorX = -400;
|
|
BasecolorExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(BasecolorExpression);
|
|
MaterialEditorOnly->BaseColor.Expression = BasecolorExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
else if (DiffuseSamples.Num() == 1)
|
|
{
|
|
// Set Roughness to constant
|
|
FLinearColor BaseColor = FLinearColor(DiffuseSamples[0]);
|
|
auto BaseColorExpression = NewObject<UMaterialExpressionConstant4Vector>(Material);
|
|
BaseColorExpression->Constant = BaseColor;
|
|
BaseColorExpression->MaterialExpressionEditorX = -400;
|
|
BaseColorExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(BaseColorExpression);
|
|
MaterialEditorOnly->BaseColor.Expression = BaseColorExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
// Whether or not a material property is baked down
|
|
const bool bHasMetallic = InFlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Metallic) && !InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Metallic);
|
|
const bool bHasSpecular = InFlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Specular) && !InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Specular);
|
|
const bool bHasRoughness = InFlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Roughness) && !InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Roughness);
|
|
|
|
// Number of material properties baked down to textures
|
|
const int BakedMaterialPropertyCount = bHasMetallic + bHasRoughness + bHasSpecular;
|
|
|
|
// Check for same texture sizes
|
|
bool bSameTextureSize = true;
|
|
|
|
int32 SampleCount = 0;
|
|
FIntPoint MergedSize(0,0);
|
|
for (int32 PropertyIndex = 0; PropertyIndex < 3; ++PropertyIndex)
|
|
{
|
|
EFlattenMaterialProperties Property = (EFlattenMaterialProperties)(PropertyIndex + (int32)EFlattenMaterialProperties::Metallic);
|
|
const bool HasProperty = InFlattenMaterial.DoesPropertyContainData(Property) && !InFlattenMaterial.IsPropertyConstant(Property);
|
|
FIntPoint PropertySize = InFlattenMaterial.GetPropertySize(Property);
|
|
SampleCount = (bHasMetallic && SampleCount == 0) ? InFlattenMaterial.GetPropertySamples(Property).Num() : SampleCount;
|
|
MergedSize = (bHasMetallic && MergedSize.X == 0) ? PropertySize : MergedSize;
|
|
}
|
|
bSameTextureSize &= bHasMetallic ? (SampleCount == InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic).Num()) : true;
|
|
bSameTextureSize &= bHasSpecular ? (SampleCount == InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular).Num()) : true;
|
|
bSameTextureSize &= bHasRoughness ? (SampleCount == InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness).Num()) : true;
|
|
|
|
// Merge values into one texture if more than one material property exists
|
|
if (BakedMaterialPropertyCount > 1 && bSameTextureSize)
|
|
{
|
|
// Metallic = R, Specular = G, Roughness = B
|
|
TArray<FColor> MergedSamples;
|
|
MergedSamples.AddZeroed(SampleCount);
|
|
|
|
// R G B masks
|
|
const uint32 ColorMask[3] = { 0x00FF0000, 0x0000FF00, 0x000000FF };
|
|
|
|
for (int32 PropertyIndex = 0; PropertyIndex < 3; ++PropertyIndex)
|
|
{
|
|
EFlattenMaterialProperties Property = (EFlattenMaterialProperties)(PropertyIndex + (int32)EFlattenMaterialProperties::Metallic);
|
|
const bool HasProperty = InFlattenMaterial.DoesPropertyContainData(Property) && !InFlattenMaterial.IsPropertyConstant(Property);
|
|
|
|
if (HasProperty)
|
|
{
|
|
const TArray<FColor>& PropertySamples = InFlattenMaterial.GetPropertySamples(Property);
|
|
// OR masked values (samples initialized to zero, so no random data)
|
|
for (int32 SampleIndex = 0; SampleIndex < SampleCount; ++SampleIndex)
|
|
{
|
|
MergedSamples[SampleIndex].DWColor() |= (PropertySamples[SampleIndex].DWColor() & ColorMask[PropertyIndex]);
|
|
}
|
|
}
|
|
}
|
|
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_MSR");
|
|
const bool bSRGB = true;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetBasePath / AssetName, MergedSize, MergedSamples, TC_Default, InTextureGroup, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
auto MergedExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
MergedExpression->Texture = Texture;
|
|
MergedExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Color;
|
|
MergedExpression->MaterialExpressionEditorX = -400;
|
|
MergedExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(MergedExpression);
|
|
|
|
// Metallic
|
|
if (bHasMetallic)
|
|
{
|
|
MaterialEditorOnly->Metallic.Expression = MergedExpression;
|
|
MaterialEditorOnly->Metallic.Mask = MaterialEditorOnly->Metallic.Expression->GetOutputs()[0].Mask;
|
|
MaterialEditorOnly->Metallic.MaskR = 1;
|
|
MaterialEditorOnly->Metallic.MaskG = 0;
|
|
MaterialEditorOnly->Metallic.MaskB = 0;
|
|
MaterialEditorOnly->Metallic.MaskA = 0;
|
|
}
|
|
|
|
// Specular
|
|
if (bHasSpecular)
|
|
{
|
|
MaterialEditorOnly->Specular.Expression = MergedExpression;
|
|
MaterialEditorOnly->Specular.Mask = MaterialEditorOnly->Specular.Expression->GetOutputs()[0].Mask;
|
|
MaterialEditorOnly->Specular.MaskR = 0;
|
|
MaterialEditorOnly->Specular.MaskG = 1;
|
|
MaterialEditorOnly->Specular.MaskB = 0;
|
|
MaterialEditorOnly->Specular.MaskA = 0;
|
|
}
|
|
|
|
// Roughness
|
|
if (bHasRoughness)
|
|
{
|
|
MaterialEditorOnly->Roughness.Expression = MergedExpression;
|
|
MaterialEditorOnly->Roughness.Mask = MaterialEditorOnly->Roughness.Expression->GetOutputs()[0].Mask;
|
|
MaterialEditorOnly->Roughness.MaskR = 0;
|
|
MaterialEditorOnly->Roughness.MaskG = 0;
|
|
MaterialEditorOnly->Roughness.MaskB = 1;
|
|
MaterialEditorOnly->Roughness.MaskA = 0;
|
|
}
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
else
|
|
{
|
|
// Metallic
|
|
if (bHasMetallic && MaterialProxySettings.bMetallicMap)
|
|
{
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_M");
|
|
const bool bSRGB = true;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetBasePath / AssetName, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic), InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), TC_Default, InTextureGroup, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
auto MetallicExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
MetallicExpression->Texture = Texture;
|
|
MetallicExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Color;
|
|
MetallicExpression->MaterialExpressionEditorX = -400;
|
|
MetallicExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(MetallicExpression);
|
|
MaterialEditorOnly->Metallic.Expression = MetallicExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
// Specular
|
|
if (bHasSpecular && MaterialProxySettings.bSpecularMap)
|
|
{
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_S");
|
|
const bool bSRGB = true;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetBasePath / AssetName, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular), InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), TC_Default, InTextureGroup, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
auto SpecularExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
SpecularExpression->Texture = Texture;
|
|
SpecularExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Color;
|
|
SpecularExpression->MaterialExpressionEditorX = -400;
|
|
SpecularExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(SpecularExpression);
|
|
MaterialEditorOnly->Specular.Expression = SpecularExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
// Roughness
|
|
if (bHasRoughness && MaterialProxySettings.bRoughnessMap)
|
|
{
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_R");
|
|
const bool bSRGB = true;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetBasePath / AssetName, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness), InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), TC_Default, InTextureGroup, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
auto RoughnessExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
RoughnessExpression->Texture = Texture;
|
|
RoughnessExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Color;
|
|
RoughnessExpression->MaterialExpressionEditorX = -400;
|
|
RoughnessExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(RoughnessExpression);
|
|
MaterialEditorOnly->Roughness.Expression = RoughnessExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
}
|
|
|
|
if (InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Metallic) || !MaterialProxySettings.bMetallicMap)
|
|
{
|
|
auto MetallicExpression = NewObject<UMaterialExpressionConstant>(Material);
|
|
MetallicExpression->R = MaterialProxySettings.bMetallicMap ? FLinearColor(InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic)[0]).R : MaterialProxySettings.MetallicConstant;
|
|
MetallicExpression->MaterialExpressionEditorX = -400;
|
|
MetallicExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(MetallicExpression);
|
|
MaterialEditorOnly->Metallic.Expression = MetallicExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
if (InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Specular ) || !MaterialProxySettings.bSpecularMap)
|
|
{
|
|
// Set Specular to constant
|
|
auto SpecularExpression = NewObject<UMaterialExpressionConstant>(Material);
|
|
SpecularExpression->R = MaterialProxySettings.bSpecularMap ? FLinearColor(InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular)[0]).R : MaterialProxySettings.SpecularConstant;
|
|
SpecularExpression->MaterialExpressionEditorX = -400;
|
|
SpecularExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(SpecularExpression);
|
|
MaterialEditorOnly->Specular.Expression = SpecularExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
if (InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Roughness) || !MaterialProxySettings.bRoughnessMap)
|
|
{
|
|
// Set Roughness to constant
|
|
auto RoughnessExpression = NewObject<UMaterialExpressionConstant>(Material);
|
|
RoughnessExpression->R = MaterialProxySettings.bRoughnessMap ? FLinearColor(InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness)[0]).R : MaterialProxySettings.RoughnessConstant;
|
|
RoughnessExpression->MaterialExpressionEditorX = -400;
|
|
RoughnessExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(RoughnessExpression);
|
|
MaterialEditorOnly->Roughness.Expression = RoughnessExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
// Normal
|
|
if (InFlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Normal) && !InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Normal))
|
|
{
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_N");
|
|
const bool bSRGB = false;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetBasePath / AssetName, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal), InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), TC_Normalmap, (InTextureGroup != TEXTUREGROUP_World) ? InTextureGroup : TEXTUREGROUP_WorldNormalMap, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
auto NormalExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
NormalExpression->Texture = Texture;
|
|
NormalExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Normal;
|
|
NormalExpression->MaterialExpressionEditorX = -400;
|
|
NormalExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(NormalExpression);
|
|
MaterialEditorOnly->Normal.Expression = NormalExpression;
|
|
|
|
MaterialNodeY+= MaterialNodeStepY;
|
|
}
|
|
|
|
if (InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive))
|
|
{
|
|
// Set Emissive to constant
|
|
FColor EmissiveColor = InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive)[0];
|
|
// Don't have to deal with black emissive color
|
|
if (EmissiveColor != FColor(0, 0, 0, 255))
|
|
{
|
|
auto EmissiveColorExpression = NewObject<UMaterialExpressionConstant4Vector>(Material);
|
|
EmissiveColorExpression->Constant = EmissiveColor.ReinterpretAsLinear() * InFlattenMaterial.EmissiveScale;
|
|
EmissiveColorExpression->MaterialExpressionEditorX = -400;
|
|
EmissiveColorExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(EmissiveColorExpression);
|
|
MaterialEditorOnly->EmissiveColor.Expression = EmissiveColorExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
}
|
|
else if (InFlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Emissive) && !InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Emissive))
|
|
{
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_E");
|
|
const bool bSRGB = true;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetBasePath / AssetName, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive), InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), TC_Default, InTextureGroup, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
//Assign emissive color to the material
|
|
UMaterialExpressionTextureSample* EmissiveColorExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
EmissiveColorExpression->Texture = Texture;
|
|
EmissiveColorExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Color;
|
|
EmissiveColorExpression->MaterialExpressionEditorX = -400;
|
|
EmissiveColorExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(EmissiveColorExpression);
|
|
|
|
UMaterialExpressionMultiply* EmissiveColorScale = NewObject<UMaterialExpressionMultiply>(Material);
|
|
EmissiveColorScale->A.Expression = EmissiveColorExpression;
|
|
EmissiveColorScale->ConstB = InFlattenMaterial.EmissiveScale;
|
|
EmissiveColorScale->MaterialExpressionEditorX = -200;
|
|
EmissiveColorScale->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(EmissiveColorScale);
|
|
|
|
MaterialEditorOnly->EmissiveColor.Expression = EmissiveColorScale;
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
if (InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::Opacity))
|
|
{
|
|
// Set Opacity to constant
|
|
FLinearColor Opacity = FLinearColor(InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity)[0]);
|
|
auto OpacityExpression = NewObject<UMaterialExpressionConstant>(Material);
|
|
OpacityExpression->R = Opacity.R;
|
|
OpacityExpression->MaterialExpressionEditorX = -400;
|
|
OpacityExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(OpacityExpression);
|
|
MaterialEditorOnly->Opacity.Expression = OpacityExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
else if (InFlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::Opacity))
|
|
{
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_O");
|
|
const bool bSRGB = true;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetBasePath / AssetName, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity), InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), TC_Default, InTextureGroup, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
//Assign opacity to the material
|
|
UMaterialExpressionTextureSample* OpacityExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
OpacityExpression->Texture = Texture;
|
|
OpacityExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Color;
|
|
OpacityExpression->MaterialExpressionEditorX = -400;
|
|
OpacityExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(OpacityExpression);
|
|
MaterialEditorOnly->Opacity.Expression = OpacityExpression;
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
if (InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::OpacityMask))
|
|
{
|
|
// Set OpacityMask to constant
|
|
FLinearColor OpacityMask = FLinearColor(InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask)[0]);
|
|
auto OpacityMaskExpression = NewObject<UMaterialExpressionConstant>(Material);
|
|
OpacityMaskExpression->R = OpacityMask.R;
|
|
OpacityMaskExpression->MaterialExpressionEditorX = -400;
|
|
OpacityMaskExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(OpacityMaskExpression);
|
|
MaterialEditorOnly->OpacityMask.Expression = OpacityMaskExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
else if (InFlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::OpacityMask))
|
|
{
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_OM");
|
|
const bool bSRGB = true;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetBasePath / AssetName, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask), InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), TC_Default, InTextureGroup, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
//Assign opacity to the material
|
|
UMaterialExpressionTextureSample* OpacityMaskExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
OpacityMaskExpression->Texture = Texture;
|
|
OpacityMaskExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Color;
|
|
OpacityMaskExpression->MaterialExpressionEditorX = -400;
|
|
OpacityMaskExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(OpacityMaskExpression);
|
|
MaterialEditorOnly->OpacityMask.Expression = OpacityMaskExpression;
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
if (InFlattenMaterial.IsPropertyConstant(EFlattenMaterialProperties::SubSurface))
|
|
{
|
|
// Set Emissive to constant
|
|
FColor SubSurfaceColor = InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::SubSurface)[0];
|
|
|
|
// Don't have to deal with black sub surface color
|
|
if (SubSurfaceColor != FColor(0, 0, 0, 255))
|
|
{
|
|
auto SubSurfaceColorExpression = NewObject<UMaterialExpressionConstant4Vector>(Material);
|
|
SubSurfaceColorExpression->Constant = (SubSurfaceColor.ReinterpretAsLinear());
|
|
SubSurfaceColorExpression->MaterialExpressionEditorX = -400;
|
|
SubSurfaceColorExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(SubSurfaceColorExpression);
|
|
MaterialEditorOnly->SubsurfaceColor.Expression = SubSurfaceColorExpression;
|
|
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
}
|
|
|
|
Material->SetShadingModel(MSM_Subsurface);
|
|
|
|
}
|
|
else if (InFlattenMaterial.DoesPropertyContainData(EFlattenMaterialProperties::SubSurface))
|
|
{
|
|
const FString AssetName = TEXT("T_") + AssetBaseName + TEXT("_SSC");
|
|
const bool bSRGB = true;
|
|
UTexture2D* Texture = CreateTexture(InOuter, AssetBasePath / AssetName, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::SubSurface), InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::SubSurface), TC_Default, InTextureGroup, Flags, bSRGB);
|
|
OutGeneratedAssets.Add(Texture);
|
|
|
|
//Assign emissive color to the material
|
|
UMaterialExpressionTextureSample* SubSurfaceColorExpression = NewObject<UMaterialExpressionTextureSample>(Material);
|
|
SubSurfaceColorExpression->Texture = Texture;
|
|
SubSurfaceColorExpression->SamplerType = EMaterialSamplerType::SAMPLERTYPE_Color;
|
|
SubSurfaceColorExpression->MaterialExpressionEditorX = -400;
|
|
SubSurfaceColorExpression->MaterialExpressionEditorY = MaterialNodeY;
|
|
Material->GetExpressionCollection().AddExpression(SubSurfaceColorExpression);
|
|
|
|
MaterialEditorOnly->SubsurfaceColor.Expression = SubSurfaceColorExpression;
|
|
MaterialNodeY += MaterialNodeStepY;
|
|
|
|
Material->SetShadingModel(MSM_Subsurface);
|
|
}
|
|
|
|
|
|
Material->PostEditChange();
|
|
return Material;
|
|
}
|
|
|
|
UMaterialInstanceConstant* FMaterialUtilities::CreateInstancedMaterial(UMaterialInterface* BaseMaterial, UPackage* InOuter, const FString& BaseName, EObjectFlags Flags)
|
|
{
|
|
// Base name for a new assets
|
|
// In case outer is null BaseName has to be long package name
|
|
if (InOuter == nullptr && FPackageName::IsShortPackageName(BaseName))
|
|
{
|
|
UE_LOG(LogMaterialUtilities, Warning, TEXT("Invalid long package name: '%s'."), *BaseName);
|
|
return nullptr;
|
|
}
|
|
|
|
const FString AssetBaseName = FPackageName::GetShortName(BaseName);
|
|
const FString AssetBasePath = InOuter ? TEXT("") : FPackageName::GetLongPackagePath(BaseName);
|
|
|
|
// Create material
|
|
const FString MaterialAssetName = TEXT("MI_") + AssetBaseName;
|
|
UPackage* MaterialOuter = InOuter;
|
|
if (MaterialOuter == NULL)
|
|
{
|
|
MaterialOuter = CreatePackage( *(AssetBasePath / MaterialAssetName));
|
|
MaterialOuter->FullyLoad();
|
|
MaterialOuter->Modify();
|
|
}
|
|
|
|
// We need to check for this due to the change in material object type, this causes a clash of path/type with old assets that were generated, so we delete the old (resident) UMaterial objects
|
|
UObject* ExistingPackage = FindObject<UMaterial>(MaterialOuter, *MaterialAssetName);
|
|
if (ExistingPackage && !ExistingPackage->IsA<UMaterialInstanceConstant>())
|
|
{
|
|
#if WITH_AUTOMATION_TESTS
|
|
FAutomationEditorCommonUtils::NullReferencesToObject(ExistingPackage);
|
|
#endif // WITH_AUTOMATION_TESTS
|
|
ExistingPackage->MarkAsGarbage();
|
|
CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS, true);
|
|
}
|
|
|
|
UMaterialInstanceConstant* MaterialInstance = NewObject<UMaterialInstanceConstant>(MaterialOuter, FName(*MaterialAssetName), Flags);
|
|
checkf(MaterialInstance, TEXT("Failed to create instanced material"));
|
|
MaterialInstance->Parent = BaseMaterial;
|
|
|
|
return MaterialInstance;
|
|
}
|
|
|
|
UTexture2D* FMaterialUtilities::CreateTexture(UPackage* Outer, const FString& AssetLongName, FIntPoint Size, const TArray<FColor>& Samples, TextureCompressionSettings CompressionSettings, TextureGroup LODGroup, EObjectFlags Flags, bool bSRGB, const FGuid& SourceGuidHash)
|
|
{
|
|
FCreateTexture2DParameters TexParams;
|
|
TexParams.bUseAlpha = false;
|
|
TexParams.CompressionSettings = CompressionSettings;
|
|
TexParams.bDeferCompression = true;
|
|
TexParams.bSRGB = bSRGB;
|
|
TexParams.SourceGuidHash = SourceGuidHash;
|
|
TexParams.TextureGroup = LODGroup;
|
|
|
|
return CreateTexture(Outer, AssetLongName, Size, Samples, TexParams, Flags);
|
|
}
|
|
|
|
UTexture2D* FMaterialUtilities::CreateTexture(UPackage* Outer, const FString& AssetLongName, FIntPoint Size, const TArray<FColor>& Samples, const FCreateTexture2DParameters& CreateParams, EObjectFlags Flags)
|
|
{
|
|
if (Outer == nullptr)
|
|
{
|
|
Outer = CreatePackage(*AssetLongName);
|
|
Outer->FullyLoad();
|
|
Outer->Modify();
|
|
}
|
|
|
|
return FImageUtils::CreateTexture2D(Size.X, Size.Y, Samples, Outer, FPackageName::GetShortName(AssetLongName), Flags, CreateParams);
|
|
}
|
|
|
|
bool FMaterialUtilities::ExportBaseColor(ULandscapeComponent* LandscapeComponent, int32 TextureSize, TArray<FColor>& OutSamples)
|
|
{
|
|
ALandscapeProxy* LandscapeProxy = LandscapeComponent->GetLandscapeProxy();
|
|
|
|
FIntPoint ComponentOrigin = LandscapeComponent->GetSectionBase() - LandscapeProxy->LandscapeSectionOffset;
|
|
FIntPoint ComponentSize(LandscapeComponent->ComponentSizeQuads, LandscapeComponent->ComponentSizeQuads);
|
|
FVector MidPoint = FVector(ComponentOrigin, 0.f) + FVector(ComponentSize, 0.f)*0.5f;
|
|
|
|
FVector LandscapeCenter = LandscapeProxy->GetTransform().TransformPosition(MidPoint);
|
|
FVector LandscapeExtent = FVector(ComponentSize, 0.f)*LandscapeProxy->GetActorScale()*0.5f;
|
|
|
|
FVector ViewOrigin = LandscapeCenter;
|
|
FMatrix ViewRotationMatrix = FInverseRotationMatrix(LandscapeProxy->GetActorRotation());
|
|
ViewRotationMatrix *= FMatrix(FPlane(1, 0, 0, 0),
|
|
FPlane(0, -1, 0, 0),
|
|
FPlane(0, 0, -1, 0),
|
|
FPlane(0, 0, 0, 1));
|
|
|
|
const FMatrix::FReal ZOffset = UE_OLD_WORLD_MAX;
|
|
FMatrix ProjectionMatrix = FReversedZOrthoMatrix(
|
|
LandscapeExtent.X,
|
|
LandscapeExtent.Y,
|
|
0.5f / ZOffset,
|
|
ZOffset);
|
|
|
|
FSceneInterface* Scene = LandscapeProxy->GetWorld()->Scene;
|
|
|
|
// Hide all but the component
|
|
TSet<FPrimitiveComponentId> ShowOnlyPrimitives = { LandscapeComponent->SceneProxy->GetPrimitiveComponentId() };
|
|
TSet<FPrimitiveComponentId> HiddenPrimitives;
|
|
|
|
FIntPoint TargetSize(TextureSize, TextureSize);
|
|
|
|
// Render diffuse texture using BufferVisualizationMode=BaseColor
|
|
static const FName BaseColorName("BaseColor");
|
|
const float BaseColorGamma = 2.2f;
|
|
RenderSceneToTexture(Scene, BaseColorName, ViewOrigin, ViewRotationMatrix, ProjectionMatrix, ShowOnlyPrimitives, HiddenPrimitives, TargetSize, BaseColorGamma, true, OutSamples);
|
|
return true;
|
|
}
|
|
|
|
FFlattenMaterial FMaterialUtilities::CreateFlattenMaterialWithSettings(const FMaterialProxySettings& InMaterialLODSettings)
|
|
{
|
|
// Create new material.
|
|
FFlattenMaterial Material;
|
|
|
|
FIntPoint MaximumSize = FIntPoint::ZeroValue;
|
|
|
|
if (InMaterialLODSettings.TextureSizingType == TextureSizingType_UseManualOverrideTextureSize)
|
|
{
|
|
// If the user is manually overriding the texture size, make sure we have the max texture size to render with
|
|
MaximumSize = (MaximumSize.X < InMaterialLODSettings.DiffuseTextureSize.X) ? InMaterialLODSettings.DiffuseTextureSize : MaximumSize ;
|
|
MaximumSize = (InMaterialLODSettings.bSpecularMap && (MaximumSize.X < InMaterialLODSettings.SpecularTextureSize.X)) ? InMaterialLODSettings.SpecularTextureSize : MaximumSize;
|
|
MaximumSize = (InMaterialLODSettings.bMetallicMap && (MaximumSize.X < InMaterialLODSettings.MetallicTextureSize.X)) ? InMaterialLODSettings.MetallicTextureSize : MaximumSize;
|
|
MaximumSize = (InMaterialLODSettings.bRoughnessMap && (MaximumSize.X < InMaterialLODSettings.RoughnessTextureSize.X)) ? InMaterialLODSettings.RoughnessTextureSize : MaximumSize;
|
|
MaximumSize = (InMaterialLODSettings.bNormalMap && (MaximumSize.X < InMaterialLODSettings.NormalTextureSize.X)) ? InMaterialLODSettings.NormalTextureSize : MaximumSize;
|
|
MaximumSize = (InMaterialLODSettings.bEmissiveMap && (MaximumSize.X < InMaterialLODSettings.EmissiveTextureSize.X)) ? InMaterialLODSettings.EmissiveTextureSize : MaximumSize;
|
|
MaximumSize = (InMaterialLODSettings.bOpacityMap && (MaximumSize.X < InMaterialLODSettings.OpacityTextureSize.X)) ? InMaterialLODSettings.OpacityTextureSize : MaximumSize;
|
|
MaximumSize = (InMaterialLODSettings.bOpacityMaskMap && (MaximumSize.X < InMaterialLODSettings.OpacityMaskTextureSize.X)) ? InMaterialLODSettings.OpacityMaskTextureSize : MaximumSize;
|
|
|
|
Material.RenderSize = MaximumSize;
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Diffuse, InMaterialLODSettings.DiffuseTextureSize);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Specular, InMaterialLODSettings.bSpecularMap ? InMaterialLODSettings.SpecularTextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Metallic, InMaterialLODSettings.bMetallicMap ? InMaterialLODSettings.MetallicTextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Roughness, InMaterialLODSettings.bRoughnessMap ? InMaterialLODSettings.RoughnessTextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Normal, InMaterialLODSettings.bNormalMap ? InMaterialLODSettings.NormalTextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Emissive, InMaterialLODSettings.bEmissiveMap ? InMaterialLODSettings.EmissiveTextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Opacity, InMaterialLODSettings.bOpacityMap ? InMaterialLODSettings.OpacityTextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::OpacityMask, InMaterialLODSettings.bOpacityMaskMap ? InMaterialLODSettings.OpacityMaskTextureSize : FIntPoint::ZeroValue);
|
|
}
|
|
else if (InMaterialLODSettings.TextureSizingType == TextureSizingType_UseAutomaticBiasedSizes)
|
|
{
|
|
Material.RenderSize = InMaterialLODSettings.TextureSize;
|
|
|
|
int NormalSize = InMaterialLODSettings.TextureSize.X;
|
|
int DiffuseSize = FMath::Max(InMaterialLODSettings.TextureSize.X >> 1, 32);
|
|
int OtherSize = FMath::Max(InMaterialLODSettings.TextureSize.X >> 2, 16);
|
|
FIntPoint PropertiesSize = FIntPoint(OtherSize, OtherSize);
|
|
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Diffuse, FIntPoint(DiffuseSize, DiffuseSize));
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Normal, (InMaterialLODSettings.bNormalMap) ? FIntPoint(NormalSize, NormalSize) : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Specular, (InMaterialLODSettings.bSpecularMap) ? PropertiesSize : FIntPoint::ZeroValue );
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Metallic, (InMaterialLODSettings.bMetallicMap) ? PropertiesSize : FIntPoint::ZeroValue );
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Roughness, (InMaterialLODSettings.bRoughnessMap) ? PropertiesSize : FIntPoint::ZeroValue );
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Emissive, (InMaterialLODSettings.bEmissiveMap) ? PropertiesSize : FIntPoint::ZeroValue );
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Opacity, (InMaterialLODSettings.bOpacityMap) ? PropertiesSize : FIntPoint::ZeroValue );
|
|
Material.SetPropertySize(EFlattenMaterialProperties::OpacityMask, (InMaterialLODSettings.bOpacityMaskMap) ? PropertiesSize : FIntPoint::ZeroValue);
|
|
}
|
|
else if (InMaterialLODSettings.TextureSizingType == TextureSizingType_UseSingleTextureSize)
|
|
{
|
|
Material.RenderSize = InMaterialLODSettings.TextureSize;
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Diffuse, InMaterialLODSettings.TextureSize);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Specular, (InMaterialLODSettings.bSpecularMap) ? InMaterialLODSettings.TextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Metallic, (InMaterialLODSettings.bMetallicMap) ? InMaterialLODSettings.TextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Roughness, (InMaterialLODSettings.bRoughnessMap) ? InMaterialLODSettings.TextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Normal, (InMaterialLODSettings.bNormalMap) ? InMaterialLODSettings.TextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Emissive, (InMaterialLODSettings.bEmissiveMap) ? InMaterialLODSettings.TextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::Opacity, (InMaterialLODSettings.bOpacityMap) ? InMaterialLODSettings.TextureSize : FIntPoint::ZeroValue);
|
|
Material.SetPropertySize(EFlattenMaterialProperties::OpacityMask, (InMaterialLODSettings.bOpacityMaskMap) ? InMaterialLODSettings.TextureSize : FIntPoint::ZeroValue);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogMaterialUtilities, Error, TEXT("Unsupported TextureSizingType value. You should resolve the material texture size first with ResolveTextureSize()"));
|
|
}
|
|
|
|
return Material;
|
|
}
|
|
|
|
void FMaterialUtilities::AnalyzeMaterial(UMaterialInterface* InMaterial, const struct FMaterialProxySettings& InMaterialSettings, int32& OutNumTexCoords, bool& OutRequiresVertexData)
|
|
{
|
|
OutRequiresVertexData = false;
|
|
OutNumTexCoords = 0;
|
|
|
|
bool PropertyBeingBaked[MP_Tangent + 1];
|
|
PropertyBeingBaked[MP_BaseColor] = true;
|
|
PropertyBeingBaked[MP_Specular] = InMaterialSettings.bSpecularMap;
|
|
PropertyBeingBaked[MP_Roughness] = InMaterialSettings.bRoughnessMap;
|
|
PropertyBeingBaked[MP_Anisotropy] = InMaterialSettings.bAnisotropyMap;
|
|
PropertyBeingBaked[MP_Metallic] = InMaterialSettings.bMetallicMap;
|
|
PropertyBeingBaked[MP_Normal] = InMaterialSettings.bNormalMap;
|
|
PropertyBeingBaked[MP_Tangent] = InMaterialSettings.bTangentMap;
|
|
PropertyBeingBaked[MP_Metallic] = InMaterialSettings.bOpacityMap;
|
|
PropertyBeingBaked[MP_EmissiveColor] = InMaterialSettings.bEmissiveMap;
|
|
|
|
for (int32 PropertyIndex = 0; PropertyIndex < UE_ARRAY_COUNT(PropertyBeingBaked); ++PropertyIndex)
|
|
{
|
|
if (PropertyBeingBaked[PropertyIndex])
|
|
{
|
|
EMaterialProperty Property = (EMaterialProperty)PropertyIndex;
|
|
|
|
if (PropertyIndex == MP_Opacity)
|
|
{
|
|
if (IsMaskedBlendMode(*InMaterial))
|
|
{
|
|
Property = MP_OpacityMask;
|
|
}
|
|
else if (IsTranslucentBlendMode(*InMaterial))
|
|
{
|
|
Property = MP_Opacity;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Analyze this material channel.
|
|
int32 NumTextureCoordinates = 0;
|
|
bool bUseVertexData = false;
|
|
InMaterial->AnalyzeMaterialProperty(Property, NumTextureCoordinates, bUseVertexData);
|
|
// Accumulate data.
|
|
OutNumTexCoords = FMath::Max(NumTextureCoordinates, OutNumTexCoords);
|
|
OutRequiresVertexData |= bUseVertexData;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FMaterialUtilities::AnalyzeMaterial(class UMaterialInterface* InMaterial, const TArray<EMaterialProperty>& Properties, int32& OutNumTexCoords, bool& OutRequiresVertexData)
|
|
{
|
|
OutRequiresVertexData = false;
|
|
OutNumTexCoords = 0;
|
|
|
|
for (EMaterialProperty Property : Properties)
|
|
{
|
|
if (Property == MP_Opacity)
|
|
{
|
|
if (IsMaskedBlendMode(*InMaterial))
|
|
{
|
|
Property = MP_OpacityMask;
|
|
}
|
|
else if (IsTranslucentBlendMode(*InMaterial))
|
|
{
|
|
Property = MP_Opacity;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Analyze this material channel.
|
|
int32 NumTextureCoordinates = 0;
|
|
bool bUseVertexData = false;
|
|
InMaterial->AnalyzeMaterialProperty(Property, NumTextureCoordinates, bUseVertexData);
|
|
// Accumulate data.
|
|
OutNumTexCoords = FMath::Max(NumTextureCoordinates, OutNumTexCoords);
|
|
OutRequiresVertexData |= bUseVertexData;
|
|
}
|
|
}
|
|
|
|
void FMaterialUtilities::RemapUniqueMaterialIndices(const TArray<FSectionInfo>& InSections, const TArray<FRawMeshExt>& InMeshData, const TMap<FMeshIdAndLOD, TArray<int32> >& InMaterialMap, const FMaterialProxySettings& InMaterialProxySettings, const bool bBakeVertexData, const bool bMergeMaterials, TArray<bool>& OutMeshShouldBakeVertexData, TMap<FMeshIdAndLOD, TArray<int32> >& OutMaterialMap, TArray<FSectionInfo>& OutSections)
|
|
{
|
|
// Gather material properties
|
|
TMap<UMaterialInterface*, int32> MaterialNumTexCoords;
|
|
TMap<UMaterialInterface*, bool> MaterialUseVertexData;
|
|
|
|
for (int32 SectionIndex = 0; SectionIndex < InSections.Num(); SectionIndex++)
|
|
{
|
|
const FSectionInfo& Section = InSections[SectionIndex];
|
|
if (MaterialNumTexCoords.Find(Section.Material) != nullptr)
|
|
{
|
|
// This material was already processed.
|
|
continue;
|
|
}
|
|
|
|
if (!bBakeVertexData || !bMergeMaterials)
|
|
{
|
|
// We are not baking vertex data at all, don't analyze materials.
|
|
MaterialNumTexCoords.Add(Section.Material, 0);
|
|
MaterialUseVertexData.Add(Section.Material, false);
|
|
continue;
|
|
}
|
|
int32 NumTexCoords = 0;
|
|
bool bUseVertexData = false;
|
|
FMaterialUtilities::AnalyzeMaterial(Section.Material, InMaterialProxySettings, NumTexCoords, bUseVertexData);
|
|
MaterialNumTexCoords.Add(Section.Material, NumTexCoords);
|
|
MaterialUseVertexData.Add(Section.Material, bUseVertexData);
|
|
}
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < InMeshData.Num(); MeshIndex++)
|
|
{
|
|
for (int32 LODIndex = 0; LODIndex < MAX_STATIC_MESH_LODS; ++LODIndex)
|
|
{
|
|
TVertexAttributesRef<FVector3f> VertexPositions = InMeshData[MeshIndex].MeshLODData[LODIndex].RawMesh->GetVertexPositions();
|
|
|
|
if (InMeshData[MeshIndex].bShouldExportLOD[LODIndex])
|
|
{
|
|
checkf(VertexPositions.GetNumElements(), TEXT("No vertex data found in mesh LOD"));
|
|
|
|
const TArray<int32>& MeshMaterialMap = InMaterialMap[FMeshIdAndLOD(MeshIndex, LODIndex)];
|
|
int32 NumTexCoords = 0;
|
|
bool bUseVertexData = false;
|
|
// Accumulate data of all materials.
|
|
for (int32 LocalMaterialIndex = 0; LocalMaterialIndex < MeshMaterialMap.Num(); LocalMaterialIndex++)
|
|
{
|
|
UMaterialInterface* Material = InSections[MeshMaterialMap[LocalMaterialIndex]].Material;
|
|
NumTexCoords = FMath::Max(NumTexCoords, MaterialNumTexCoords[Material]);
|
|
bUseVertexData |= MaterialUseVertexData[Material];
|
|
}
|
|
|
|
// Store data.
|
|
OutMeshShouldBakeVertexData[MeshIndex] |= bUseVertexData || (NumTexCoords >= 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build new material map.
|
|
// Structure used to simplify material merging.
|
|
struct FMeshMaterialData
|
|
{
|
|
FSectionInfo SectionInfo;
|
|
UStaticMesh* Mesh;
|
|
bool bHasVertexColors;
|
|
|
|
FMeshMaterialData(const FSectionInfo& InSection, UStaticMesh* InMesh, bool bInHasVertexColors)
|
|
: SectionInfo(InSection)
|
|
, Mesh(InMesh)
|
|
, bHasVertexColors(bInHasVertexColors)
|
|
{
|
|
}
|
|
|
|
bool operator==(const FMeshMaterialData& Other) const
|
|
{
|
|
return SectionInfo == Other.SectionInfo && Mesh == Other.Mesh && bHasVertexColors == Other.bHasVertexColors;
|
|
}
|
|
};
|
|
|
|
TArray<FMeshMaterialData> MeshMaterialData;
|
|
OutMaterialMap.Empty();
|
|
for (int32 MeshIndex = 0; MeshIndex < InMeshData.Num(); MeshIndex++)
|
|
{
|
|
for (int32 LODIndex = 0; LODIndex < MAX_STATIC_MESH_LODS; ++LODIndex)
|
|
{
|
|
TVertexAttributesRef<FVector3f> VertexPositions = InMeshData[MeshIndex].MeshLODData[LODIndex].RawMesh->GetVertexPositions();
|
|
|
|
if (InMeshData[MeshIndex].bShouldExportLOD[LODIndex])
|
|
{
|
|
checkf(VertexPositions.GetNumElements(), TEXT("No vertex data found in mesh LOD"));
|
|
|
|
const TArray<int32>& MeshMaterialMap = InMaterialMap[FMeshIdAndLOD(MeshIndex, LODIndex)];
|
|
TArray<int32>& NewMeshMaterialMap = OutMaterialMap.Add(FMeshIdAndLOD(MeshIndex, LODIndex));
|
|
UStaticMesh* StaticMesh = InMeshData[MeshIndex].SourceStaticMesh;
|
|
|
|
if (!OutMeshShouldBakeVertexData[MeshIndex])
|
|
{
|
|
// No vertex data needed - could merge materials with other meshes.
|
|
// Set to 'nullptr' if don't need to bake vertex data to be able to merge materials with any meshes
|
|
// which don't require vertex data baking too.
|
|
StaticMesh = nullptr;
|
|
|
|
for (int32 LocalMaterialIndex = 0; LocalMaterialIndex < MeshMaterialMap.Num(); LocalMaterialIndex++)
|
|
{
|
|
FMeshMaterialData Data(InSections[MeshMaterialMap[LocalMaterialIndex]], StaticMesh, false);
|
|
int32 Index = MeshMaterialData.Find(Data);
|
|
if (Index == INDEX_NONE)
|
|
{
|
|
// Not found, add new entry.
|
|
Index = MeshMaterialData.Add(Data);
|
|
}
|
|
NewMeshMaterialMap.Add(Index);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Mesh with vertex data baking, and with vertex colors - don't share materials at all.
|
|
for (int32 LocalMaterialIndex = 0; LocalMaterialIndex < MeshMaterialMap.Num(); LocalMaterialIndex++)
|
|
{
|
|
FMeshMaterialData Data(InSections[MeshMaterialMap[LocalMaterialIndex]], StaticMesh, true);
|
|
int32 Index = MeshMaterialData.Add(Data);
|
|
NewMeshMaterialMap.Add(Index);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build new material list - simply extract MeshMaterialData[i].Material.
|
|
OutSections.Empty();
|
|
OutSections.AddDefaulted(MeshMaterialData.Num());
|
|
for (int32 MaterialIndex = 0; MaterialIndex < MeshMaterialData.Num(); MaterialIndex++)
|
|
{
|
|
OutSections[MaterialIndex] = MeshMaterialData[MaterialIndex].SectionInfo;
|
|
}
|
|
}
|
|
|
|
void FMaterialUtilities::OptimizeFlattenMaterial(FFlattenMaterial& InFlattenMaterial)
|
|
{
|
|
// Try to optimize each individual property sample
|
|
for (int32 PropertyIndex = 0; PropertyIndex < (int32)EFlattenMaterialProperties::NumFlattenMaterialProperties; ++PropertyIndex)
|
|
{
|
|
EFlattenMaterialProperties Property = (EFlattenMaterialProperties)PropertyIndex;
|
|
FIntPoint Size = InFlattenMaterial.GetPropertySize(Property);
|
|
OptimizeSampleArray(InFlattenMaterial.GetPropertySamples(Property), Size);
|
|
InFlattenMaterial.SetPropertySize(Property, Size);
|
|
}
|
|
}
|
|
|
|
void FMaterialUtilities::ResizeFlattenMaterial(FFlattenMaterial& InFlattenMaterial, const struct FMeshProxySettings& InMeshProxySettings)
|
|
{
|
|
const FMaterialProxySettings& MaterialSettings = InMeshProxySettings.MaterialSettings;
|
|
if (MaterialSettings.TextureSizingType == TextureSizingType_UseAutomaticBiasedSizes)
|
|
{
|
|
int NormalSizeX, DiffuseSizeX, PropertiesSizeX;
|
|
NormalSizeX = MaterialSettings.TextureSize.X;
|
|
DiffuseSizeX = FMath::Max(MaterialSettings.TextureSize.X >> 1, 32);
|
|
PropertiesSizeX = FMath::Max(MaterialSettings.TextureSize.X >> 2, 16);
|
|
|
|
if (InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse).X != DiffuseSizeX)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), DiffuseSizeX, DiffuseSizeX, NewSamples, true);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, FIntPoint(DiffuseSizeX, DiffuseSizeX));
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular).Num() && MaterialSettings.bSpecularMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular).X != PropertiesSizeX)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), PropertiesSizeX, PropertiesSizeX, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Specular, FIntPoint(PropertiesSizeX, PropertiesSizeX));
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic).Num() && MaterialSettings.bMetallicMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic).X != PropertiesSizeX)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), PropertiesSizeX, PropertiesSizeX, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Metallic, FIntPoint(PropertiesSizeX, PropertiesSizeX));
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness).Num() && MaterialSettings.bRoughnessMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness).X != PropertiesSizeX)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), PropertiesSizeX, PropertiesSizeX, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Roughness, FIntPoint(PropertiesSizeX, PropertiesSizeX));
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal).Num() && MaterialSettings.bNormalMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal).X != NormalSizeX)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), NormalSizeX, NormalSizeX, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Normal, FIntPoint(NormalSizeX, NormalSizeX));
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Num() && MaterialSettings.bEmissiveMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive).X != PropertiesSizeX)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), PropertiesSizeX, PropertiesSizeX, NewSamples, true);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Emissive, FIntPoint(PropertiesSizeX, PropertiesSizeX));
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity).Num() && MaterialSettings.bOpacityMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity).X != PropertiesSizeX)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), PropertiesSizeX, PropertiesSizeX, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Opacity, FIntPoint(PropertiesSizeX, PropertiesSizeX));
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask).Num() && MaterialSettings.bOpacityMaskMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask).X != PropertiesSizeX)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), PropertiesSizeX, PropertiesSizeX, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::OpacityMask, FIntPoint(PropertiesSizeX, PropertiesSizeX));
|
|
}
|
|
}
|
|
else if (MaterialSettings.TextureSizingType == TextureSizingType_UseManualOverrideTextureSize)
|
|
{
|
|
if (InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse) != MaterialSettings.DiffuseTextureSize)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Diffuse).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse), MaterialSettings.DiffuseTextureSize.X, MaterialSettings.DiffuseTextureSize.Y, NewSamples, true);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Diffuse).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Diffuse, MaterialSettings.DiffuseTextureSize);
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular).Num() && MaterialSettings.bSpecularMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular) != MaterialSettings.SpecularTextureSize)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Specular).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular), MaterialSettings.SpecularTextureSize.X, MaterialSettings.SpecularTextureSize.Y, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Specular).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Specular, MaterialSettings.SpecularTextureSize);
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic).Num() && MaterialSettings.bMetallicMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic) != MaterialSettings.MetallicTextureSize)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Metallic).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic), MaterialSettings.MetallicTextureSize.X, MaterialSettings.MetallicTextureSize.Y, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Metallic).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Metallic, MaterialSettings.MetallicTextureSize);
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness).Num() && MaterialSettings.bRoughnessMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness) != MaterialSettings.RoughnessTextureSize)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Roughness).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness), MaterialSettings.RoughnessTextureSize.X, MaterialSettings.RoughnessTextureSize.Y, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Roughness).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Roughness, MaterialSettings.RoughnessTextureSize);
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal).Num() && MaterialSettings.bNormalMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal) != MaterialSettings.NormalTextureSize)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Normal).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal), MaterialSettings.NormalTextureSize.X, MaterialSettings.NormalTextureSize.Y, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Normal).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Normal, MaterialSettings.NormalTextureSize);
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Num() && MaterialSettings.bEmissiveMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive) != MaterialSettings.EmissiveTextureSize)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Emissive).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive), MaterialSettings.EmissiveTextureSize.X, MaterialSettings.EmissiveTextureSize.Y, NewSamples, true);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Emissive).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Emissive, MaterialSettings.EmissiveTextureSize);
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity).Num() && MaterialSettings.bOpacityMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity) != MaterialSettings.OpacityTextureSize)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::Opacity).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity), MaterialSettings.OpacityTextureSize.X, MaterialSettings.OpacityTextureSize.Y, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::Opacity).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::Opacity, MaterialSettings.OpacityTextureSize);
|
|
}
|
|
|
|
if (InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask).Num() && MaterialSettings.bOpacityMaskMap && InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask) != MaterialSettings.OpacityMaskTextureSize)
|
|
{
|
|
TArray<FColor> NewSamples;
|
|
FImageUtils::ImageResize(InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask).X, InFlattenMaterial.GetPropertySize(EFlattenMaterialProperties::OpacityMask).Y, InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask), MaterialSettings.OpacityMaskTextureSize.X, MaterialSettings.OpacityMaskTextureSize.Y, NewSamples, false);
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask).Reset(NewSamples.Num());
|
|
InFlattenMaterial.GetPropertySamples(EFlattenMaterialProperties::OpacityMask).Append(NewSamples);
|
|
InFlattenMaterial.SetPropertySize(EFlattenMaterialProperties::OpacityMask, MaterialSettings.OpacityMaskTextureSize);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Computes the uniform scale from the input scales, if one exists. */
|
|
static float GetUniformScale(const TArray<float> Scales)
|
|
{
|
|
if (Scales.Num())
|
|
{
|
|
float Average = 0;
|
|
float Mean = 0;
|
|
|
|
for (float V : Scales)
|
|
{
|
|
Average += V;
|
|
}
|
|
Average /= (float)Scales.Num();
|
|
|
|
for (float V : Scales)
|
|
{
|
|
Mean += FMath::Abs(V - Average);
|
|
}
|
|
Mean /= (float)Scales.Num();
|
|
|
|
if (Mean * 15.f < Average) // If they are almost all the same
|
|
{
|
|
return Average;
|
|
}
|
|
else // Otherwise do a much more expensive test by counting the number of similar values
|
|
{
|
|
// Try to find a small range where 80% of values fit within.
|
|
const int32 TryThreshold = FMath::CeilToInt(.80f * (float)Scales.Num());
|
|
|
|
int32 NextTryDomain = Scales.Num();
|
|
|
|
float NextTryMinV = 1024;
|
|
for (float V : Scales)
|
|
{
|
|
NextTryMinV = FMath::Min<float>(V, NextTryMinV);
|
|
}
|
|
|
|
while (NextTryDomain >= TryThreshold) // Stop the search it is garantied to fail.
|
|
{
|
|
float TryMinV = NextTryMinV;
|
|
float TryMaxV = TryMinV * 1.25f;
|
|
int32 TryMatches = 0;
|
|
NextTryMinV = 1024;
|
|
NextTryDomain = 0;
|
|
for (float V : Scales)
|
|
{
|
|
if (TryMinV <= V && V <= TryMaxV)
|
|
{
|
|
++TryMatches;
|
|
}
|
|
|
|
if (V > TryMinV)
|
|
{
|
|
NextTryMinV = FMath::Min<float>(V, NextTryMinV);
|
|
++NextTryDomain;
|
|
}
|
|
}
|
|
|
|
if (TryMatches >= TryThreshold)
|
|
{
|
|
return TryMinV;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint32 GetTypeHash(const FMaterialUtilities::FExportErrorManager::FError& Error)
|
|
{
|
|
return GetTypeHash(Error.Material);
|
|
}
|
|
|
|
bool FMaterialUtilities::FExportErrorManager::FError::operator==(const FError& Rhs) const
|
|
{
|
|
return Material == Rhs.Material && RegisterIndex == Rhs.RegisterIndex && ErrorType == Rhs.ErrorType;
|
|
}
|
|
|
|
|
|
void FMaterialUtilities::FExportErrorManager::Register(const UMaterialInterface* Material, FName TextureName, int32 RegisterIndex, EErrorType ErrorType)
|
|
{
|
|
if (!Material || TextureName == NAME_None) return;
|
|
|
|
FError Error;
|
|
Error.Material = Material->GetMaterialResource(FeatureLevel);
|
|
if (!Error.Material) return;
|
|
Error.RegisterIndex = RegisterIndex;
|
|
Error.ErrorType = ErrorType;
|
|
|
|
FInstance Instance;
|
|
Instance.Material = Material;
|
|
Instance.TextureName = TextureName;
|
|
|
|
ErrorInstances.FindOrAdd(Error).Push(Instance);
|
|
}
|
|
|
|
void FMaterialUtilities::FExportErrorManager::OutputToLog()
|
|
{
|
|
const UMaterialInterface* CurrentMaterial = nullptr;
|
|
int32 MaxInstanceCount = 0;
|
|
FString TextureErrors;
|
|
|
|
for (TMap<FError, TArray<FInstance> >::TIterator It(ErrorInstances);; ++It)
|
|
{
|
|
if (It && !It->Value.Num()) continue;
|
|
|
|
// Here we pack texture list per material.
|
|
if (!It || CurrentMaterial != It->Value[0].Material)
|
|
{
|
|
// Flush
|
|
if (CurrentMaterial)
|
|
{
|
|
FString SimilarCount(TEXT(""));
|
|
if (MaxInstanceCount > 1)
|
|
{
|
|
SimilarCount = FString::Printf(TEXT(", %d similar"), MaxInstanceCount - 1);
|
|
}
|
|
|
|
if (CurrentMaterial == CurrentMaterial->GetMaterial())
|
|
{
|
|
UE_LOG(TextureStreamingBuild, Verbose, TEXT("Incomplete texcoord scale analysis for %s%s: %s"), *CurrentMaterial->GetName(), *SimilarCount, *TextureErrors);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(TextureStreamingBuild, Verbose, TEXT("Incomplete texcoord scale analysis for %s, UMaterial=%s%s: %s"), *CurrentMaterial->GetName(), *CurrentMaterial->GetMaterial()->GetName(), *SimilarCount, *TextureErrors);
|
|
}
|
|
}
|
|
|
|
// Exit
|
|
if (!It)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Start new
|
|
CurrentMaterial = It->Value[0].Material;
|
|
MaxInstanceCount = It->Value.Num();
|
|
TextureErrors.Empty();
|
|
}
|
|
else
|
|
{
|
|
// Append
|
|
MaxInstanceCount = FMath::Max<int32>(MaxInstanceCount, It->Value.Num());
|
|
}
|
|
|
|
const TCHAR* ErrorMsg = TEXT("Unkown Error");
|
|
if (It->Key.ErrorType == EET_IncohorentValues)
|
|
{
|
|
ErrorMsg = TEXT("Incoherent");
|
|
}
|
|
else if (It->Key.ErrorType == EET_NoValues)
|
|
{
|
|
ErrorMsg = TEXT("NoValues");
|
|
}
|
|
|
|
TextureErrors.Append(FString::Printf(TEXT("(%s:%d,%s) "), ErrorMsg, It->Key.RegisterIndex, *It->Value[0].TextureName.ToString()));
|
|
}
|
|
}
|
|
|
|
bool FMaterialUtilities::ExportMaterialUVDensities(UMaterialInterface* InMaterial, EMaterialQualityLevel::Type QualityLevel, ERHIFeatureLevel::Type FeatureLevel, FExportErrorManager& OutErrors)
|
|
{
|
|
check(InMaterial);
|
|
|
|
// Clear the build data.
|
|
TArray<FMaterialTextureInfo> TextureStreamingData;
|
|
InMaterial->SetTextureStreamingData(TextureStreamingData);
|
|
|
|
TArray<FFloat16Color> RenderedVectors;
|
|
|
|
TArray<UTexture*> Textures;
|
|
TArray< TArray<int32> > Indices;
|
|
InMaterial->GetUsedTexturesAndIndices(Textures, Indices, QualityLevel, FeatureLevel);
|
|
FTextureCompilingManager::Get().FinishCompilation(Textures);
|
|
|
|
check(Textures.Num() >= Indices.Num()); // Can't have indices if no texture.
|
|
|
|
const int32 SCALE_PRECISION = 64.f;
|
|
|
|
int32 MaxRegisterIndex = INDEX_NONE;
|
|
for (const TArray<int32>& TextureIndices : Indices)
|
|
{
|
|
for (int32 RegisterIndex : TextureIndices)
|
|
{
|
|
MaxRegisterIndex = FMath::Max<int32>(RegisterIndex, MaxRegisterIndex);
|
|
}
|
|
}
|
|
|
|
if (MaxRegisterIndex == INDEX_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Find the streaming texture for each material texture register index.
|
|
TArray<UTexture2D*> RegisterIndexToTextures;
|
|
RegisterIndexToTextures.AddZeroed(MaxRegisterIndex + 1);
|
|
for (int32 TextureIndex = 0; TextureIndex < Textures.Num(); ++TextureIndex)
|
|
{
|
|
UTexture2D* Texture2D = Cast<UTexture2D>(Textures[TextureIndex]);
|
|
if (Texture2D) // Don't check IsStreamingTexture() yet as this could change before cooking.
|
|
{
|
|
for (int32 RegisterIndex : Indices[TextureIndex])
|
|
{
|
|
RegisterIndexToTextures[RegisterIndex] = Texture2D;
|
|
}
|
|
}
|
|
}
|
|
|
|
const int32 NumTileX = (MaxRegisterIndex / 4 + 1);
|
|
const int32 NumTileY = TEXSTREAM_MAX_NUM_UVCHANNELS;
|
|
FIntPoint RenderTargetSize(TEXSTREAM_TILE_RESOLUTION * NumTileX, TEXSTREAM_TILE_RESOLUTION * NumTileY);
|
|
|
|
// Render the vectors
|
|
{
|
|
// The rendertarget contain factors stored in XYZW. Every X tile maps to another group : (0, 1, 2, 3), (4, 5, 6, 7), ...
|
|
UTextureRenderTarget2D* RenderTarget = CreateRenderTarget(true, false, PF_FloatRGBA, RenderTargetSize);
|
|
|
|
// Allocate the render output.
|
|
RenderedVectors.Empty(RenderTargetSize.X * RenderTargetSize.Y);
|
|
|
|
FMaterialRenderProxy* MaterialProxy = InMaterial->GetRenderProxy();
|
|
if (!MaterialProxy)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FBox2D DummyBounds(FVector2D(0, 0), FVector2D(1, 1));
|
|
TArray<FVector2D> EmptyTexCoords;
|
|
FMaterialMergeData MaterialData(InMaterial, nullptr, nullptr, 0, DummyBounds, EmptyTexCoords);
|
|
|
|
CurrentlyRendering = true;
|
|
bool bResult = FMeshRenderer::RenderMaterialTexCoordScales(MaterialData, MaterialProxy, RenderTarget, RenderedVectors);
|
|
CurrentlyRendering = false;
|
|
|
|
if (!bResult)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Now compute the scale for each texture index (several indices could map to the same texture)
|
|
for (int32 RegisterIndex = 0; RegisterIndex <= MaxRegisterIndex; ++RegisterIndex)
|
|
{
|
|
UTexture2D* Texture2D = RegisterIndexToTextures[RegisterIndex];
|
|
if (!Texture2D) continue; // Only handle streaming textures
|
|
|
|
int32 TextureTile = RegisterIndex / 4;
|
|
int32 ComponentIndex = RegisterIndex % 4;
|
|
|
|
bool bSuccess = false;
|
|
bool bHadAnyValues = false;
|
|
|
|
for (int32 CoordIndex = 0; CoordIndex < TEXSTREAM_MAX_NUM_UVCHANNELS && !bSuccess; ++CoordIndex)
|
|
{
|
|
TArray<float> TextureScales;
|
|
TextureScales.Empty(TEXSTREAM_TILE_RESOLUTION * TEXSTREAM_TILE_RESOLUTION);
|
|
for (int32 TexelX = 0; TexelX < TEXSTREAM_TILE_RESOLUTION; ++TexelX)
|
|
{
|
|
for (int32 TexelY = 0; TexelY < TEXSTREAM_TILE_RESOLUTION; ++TexelY)
|
|
{
|
|
int32 TexelIndex = TextureTile * TEXSTREAM_TILE_RESOLUTION + TexelX + (TexelY + CoordIndex * TEXSTREAM_TILE_RESOLUTION) * RenderTargetSize.X;
|
|
FFloat16Color& Scale16 = RenderedVectors[TexelIndex];
|
|
|
|
float TexelScale = 0;
|
|
if (ComponentIndex == 0) TexelScale = Scale16.R.GetFloat();
|
|
if (ComponentIndex == 1) TexelScale = Scale16.G.GetFloat();
|
|
if (ComponentIndex == 2) TexelScale = Scale16.B.GetFloat();
|
|
if (ComponentIndex == 3) TexelScale = Scale16.A.GetFloat();
|
|
|
|
// Quantize scale to converge faster in the TryLogic
|
|
TexelScale = FMath::RoundToFloat(TexelScale * SCALE_PRECISION) / SCALE_PRECISION;
|
|
|
|
if (TexelScale > 0 && TexelScale < TEXSTREAM_INITIAL_GPU_SCALE)
|
|
{
|
|
TextureScales.Push(TexelScale);
|
|
}
|
|
}
|
|
}
|
|
|
|
const float SamplingScale = GetUniformScale(TextureScales);
|
|
if (SamplingScale > 0)
|
|
{
|
|
FMaterialTextureInfo TextureInfo;
|
|
TextureInfo.SamplingScale = SamplingScale;
|
|
TextureInfo.UVChannelIndex = CoordIndex;
|
|
TextureInfo.TextureReference = FSoftObjectPath(Texture2D);
|
|
TextureInfo.TextureIndex = RegisterIndex;
|
|
TextureStreamingData.Add(TextureInfo);
|
|
bSuccess = true;
|
|
}
|
|
else if (TextureScales.Num())
|
|
{
|
|
bHadAnyValues = true;
|
|
}
|
|
}
|
|
|
|
// If we couldn't find the scale, then output a warning detailing which index, texture, material is having an issue.
|
|
if (!bSuccess)
|
|
{
|
|
OutErrors.Register(InMaterial, Texture2D->GetFName(), RegisterIndex, bHadAnyValues ? FExportErrorManager::EErrorType::EET_IncohorentValues : FExportErrorManager::EErrorType::EET_NoValues);
|
|
}
|
|
}
|
|
|
|
// Update to the final data.
|
|
InMaterial->SetTextureStreamingData(TextureStreamingData);
|
|
|
|
return true;
|
|
}
|
|
|
|
UTextureRenderTarget2D* FMaterialUtilities::CreateRenderTarget(bool bInForceLinearGamma, bool bNormalMap, EPixelFormat InPixelFormat, FIntPoint& InTargetSize)
|
|
{
|
|
const FLinearColor ClearColour = bNormalMap ? FLinearColor(0.0f, 0.0f, 0.0f, 0.0f) : FLinearColor(1.0f, 0.0f, 1.0f, 0.0f);
|
|
|
|
// Find any pooled render target with suitable properties.
|
|
for (int32 RTIndex = 0; RTIndex < RenderTargetPool.Num(); RTIndex++)
|
|
{
|
|
UTextureRenderTarget2D* RenderTarget = RenderTargetPool[RTIndex];
|
|
if (RenderTarget->SizeX == InTargetSize.X &&
|
|
RenderTarget->SizeY == InTargetSize.Y &&
|
|
RenderTarget->OverrideFormat == InPixelFormat &&
|
|
RenderTarget->bForceLinearGamma == bInForceLinearGamma &&
|
|
RenderTarget->ClearColor == ClearColour )
|
|
{
|
|
return RenderTarget;
|
|
}
|
|
}
|
|
|
|
// Not found - create a new one.
|
|
UTextureRenderTarget2D* NewRenderTarget = NewObject<UTextureRenderTarget2D>();
|
|
check(NewRenderTarget);
|
|
NewRenderTarget->AddToRoot();
|
|
NewRenderTarget->ClearColor = ClearColour;
|
|
NewRenderTarget->TargetGamma = 0.0f;
|
|
NewRenderTarget->InitCustomFormat(InTargetSize.X, InTargetSize.Y, InPixelFormat, bInForceLinearGamma);
|
|
|
|
RenderTargetPool.Add(NewRenderTarget);
|
|
return NewRenderTarget;
|
|
}
|
|
|
|
void FMaterialUtilities::ClearRenderTargetPool()
|
|
{
|
|
if (CurrentlyRendering)
|
|
{
|
|
// Just in case - if garbage collection will happen during rendering, don't allow to GC used render target.
|
|
return;
|
|
}
|
|
|
|
// Allow garbage collecting of all render targets.
|
|
for (int32 RTIndex = 0; RTIndex < RenderTargetPool.Num(); RTIndex++)
|
|
{
|
|
RenderTargetPool[RTIndex]->RemoveFromRoot();
|
|
}
|
|
RenderTargetPool.Empty();
|
|
}
|
|
|
|
void FMaterialUtilities::OptimizeSampleArray(TArray<FColor>& InSamples, FIntPoint& InSampleSize)
|
|
{
|
|
if (InSamples.Num() > 1)
|
|
{
|
|
TArray<FColor> Colors;
|
|
|
|
for (const FColor& Sample : InSamples)
|
|
{
|
|
if (Colors.AddUnique(Sample) != 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Colors.Num() == 1)
|
|
{
|
|
InSamples.Empty(1);
|
|
InSamples.Add(Colors[0]);
|
|
InSampleSize = FIntPoint(1, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
FExportMaterialProxyCache::FExportMaterialProxyCache()
|
|
{
|
|
FMemory::Memzero(Proxies);
|
|
}
|
|
|
|
FExportMaterialProxyCache::~FExportMaterialProxyCache()
|
|
{
|
|
Release();
|
|
}
|
|
|
|
void FExportMaterialProxyCache::Release()
|
|
{
|
|
for (int32 PropertyIndex = 0; PropertyIndex < UE_ARRAY_COUNT(Proxies); PropertyIndex++)
|
|
{
|
|
FMaterialRenderProxy* Proxy = Proxies[PropertyIndex];
|
|
if (Proxy)
|
|
{
|
|
delete Proxy;
|
|
Proxies[PropertyIndex] = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FMaterialUtilities::DetermineMaterialImportance(const TArray<UMaterialInterface*>& InMaterials, TArray<float>& OutImportance)
|
|
{
|
|
TArray<int32> MaterialImportance;
|
|
int32 SummedSize = 0;
|
|
for (UMaterialInterface* Material : InMaterials)
|
|
{
|
|
TArray<UObject*> UsedTextures(Material->GetReferencedTextures());
|
|
if (UMaterialInstance* MaterialInstance = Cast<UMaterialInstance>(Material))
|
|
{
|
|
for (const FTextureParameterValue& TextureParameter : MaterialInstance->TextureParameterValues)
|
|
{
|
|
if (TextureParameter.ParameterValue != nullptr)
|
|
{
|
|
UsedTextures.Add(TextureParameter.ParameterValue);
|
|
}
|
|
}
|
|
}
|
|
int32 MaxSize = 64 * 64;
|
|
for (UObject* Texture : UsedTextures)
|
|
{
|
|
if (UTexture2D* Texture2D = Cast<UTexture2D>(Texture))
|
|
{
|
|
const int32 MaxResMipBias = Texture2D->GetNumMips() - Texture2D->GetNumMipsAllowed(true);
|
|
const int32 MaxResSize = FMath::Max<int32>(Texture2D->GetSizeX() >> MaxResMipBias, 1) * FMath::Max<int32>(Texture2D->GetSizeY() >> MaxResMipBias, 1);
|
|
MaxSize = FMath::Max<int32>(MaxSize, MaxResSize);
|
|
}
|
|
}
|
|
|
|
MaterialImportance.Add(MaxSize);
|
|
SummedSize += MaxSize;
|
|
}
|
|
float WeightPerPixel = 1.0f / SummedSize;
|
|
for (int32 MaterialIndex = 0; MaterialIndex < InMaterials.Num(); ++MaterialIndex)
|
|
{
|
|
OutImportance.Add((float)MaterialImportance[MaterialIndex] * WeightPerPixel);
|
|
}
|
|
}
|
|
|
|
void FMaterialUtilities::GeneratedBinnedTextureSquares(const FVector2D DestinationSize, TArray<float>& InTexureWeights, TArray<FBox2D>& OutGeneratedBoxes)
|
|
{
|
|
typedef FBox2D FTextureArea;
|
|
struct FWeightedTexture
|
|
{
|
|
FTextureArea Area;
|
|
int32 TextureIndex;
|
|
float Weight;
|
|
};
|
|
|
|
TArray<FWeightedTexture> WeightedTextures;
|
|
const float TotalArea = DestinationSize.X * DestinationSize.Y;
|
|
// Generate textures with their size calculated according to their weight
|
|
for (int32 WeightIndex = 0; WeightIndex < InTexureWeights.Num(); ++WeightIndex)
|
|
{
|
|
const float Weight = InTexureWeights[WeightIndex];
|
|
FWeightedTexture Texture;
|
|
float TextureSize = FMath::Sqrt(TotalArea*Weight);
|
|
Texture.Area = FTextureArea( FVector2D(0.0f,0.0f), FVector2D(TextureSize, TextureSize));
|
|
Texture.TextureIndex = WeightIndex;
|
|
Texture.Weight = Weight;
|
|
WeightedTextures.Add(Texture);
|
|
}
|
|
|
|
// Sort textures by their weight (high to low) which influences the insert order
|
|
WeightedTextures.Sort([](const FWeightedTexture& One, const FWeightedTexture& Two) { return One.Weight > Two.Weight; });
|
|
|
|
TArray<FWeightedTexture> InsertedTextures;
|
|
typedef FBox2D FUnusedArea;
|
|
TArray<FUnusedArea> UnusedAreas;
|
|
|
|
bool bSuccess = true;
|
|
do
|
|
{
|
|
// Reset state
|
|
bSuccess = true;
|
|
UnusedAreas.Empty();
|
|
InsertedTextures.Empty();
|
|
FUnusedArea StartArea(FVector2D(0, 0), DestinationSize);
|
|
UnusedAreas.Add(StartArea);
|
|
|
|
for (const FWeightedTexture& Texture : WeightedTextures)
|
|
{
|
|
int32 BestAreaIndex = -1;
|
|
float RemainingArea = FLT_MAX;
|
|
FVector2D TextureSize = Texture.Area.GetSize();
|
|
float TextureSurface = TextureSize.X * TextureSize.Y;
|
|
|
|
// Find best area to insert this texture in (determined by tightest fit)
|
|
for (int32 AreaIndex = 0; AreaIndex < UnusedAreas.Num(); ++AreaIndex)
|
|
{
|
|
const FUnusedArea& UnusedArea = UnusedAreas[AreaIndex];
|
|
if (UnusedArea.GetSize().ComponentwiseAllGreaterOrEqual(TextureSize))
|
|
{
|
|
const float Remainder = UnusedArea.GetArea() - TextureSurface;
|
|
if (Remainder < RemainingArea && Remainder >= 0)
|
|
{
|
|
BestAreaIndex = AreaIndex;
|
|
RemainingArea = Remainder;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Insert the texture in case we found an appropriate area
|
|
if (BestAreaIndex != -1)
|
|
{
|
|
FUnusedArea& UnusedArea = UnusedAreas[BestAreaIndex];
|
|
FVector2D UnusedSize = UnusedArea.GetSize();
|
|
|
|
// Push back texture
|
|
FWeightedTexture WeightedTexture;
|
|
WeightedTexture.Area = FTextureArea(UnusedArea.Min, UnusedArea.Min + TextureSize);
|
|
WeightedTexture.TextureIndex = Texture.TextureIndex;
|
|
InsertedTextures.Add(WeightedTexture);
|
|
|
|
// Generate two new resulting unused areas from splitting up the result
|
|
/*
|
|
___________
|
|
| | |
|
|
| | V |
|
|
|_____| |
|
|
| H | |
|
|
|_____|___|
|
|
*/
|
|
FUnusedArea HorizontalArea, VerticalArea;
|
|
HorizontalArea.Min.X = UnusedArea.Min.X;
|
|
HorizontalArea.Min.Y = UnusedArea.Min.Y + TextureSize.Y;
|
|
HorizontalArea.Max.X = HorizontalArea.Min.X + TextureSize.X;
|
|
HorizontalArea.Max.Y = HorizontalArea.Min.Y + (UnusedSize.Y - TextureSize.Y);
|
|
|
|
VerticalArea.Min.X = UnusedArea.Min.X + TextureSize.X;
|
|
VerticalArea.Min.Y = UnusedArea.Min.Y;
|
|
VerticalArea.Max.X = VerticalArea.Min.X + (UnusedSize.X - TextureSize.X);
|
|
VerticalArea.Max.Y = UnusedSize.Y;
|
|
|
|
// Append valid new areas to list (replace original one with either one of the new ones)
|
|
const bool bValidHorizontal = HorizontalArea.GetArea() > 0.0f;
|
|
const bool bValidVertical = VerticalArea.GetArea() > 0.0f;
|
|
if (bValidVertical && bValidHorizontal)
|
|
{
|
|
UnusedAreas[BestAreaIndex] = HorizontalArea;
|
|
UnusedAreas.Add(VerticalArea);
|
|
}
|
|
else if (bValidVertical)
|
|
{
|
|
UnusedAreas[BestAreaIndex] = VerticalArea;
|
|
}
|
|
else if (bValidHorizontal)
|
|
{
|
|
UnusedAreas[BestAreaIndex] = HorizontalArea;
|
|
}
|
|
else
|
|
{
|
|
// Make sure we remove the area entry
|
|
UnusedAreas.RemoveAtSwap(BestAreaIndex);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bSuccess = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This means we failed to find a fit, in this case we resize the textures and try again until we find one
|
|
if (bSuccess == false)
|
|
{
|
|
for (FWeightedTexture& Texture : WeightedTextures)
|
|
{
|
|
Texture.Area.Max *= .99f;
|
|
}
|
|
}
|
|
} while (!bSuccess);
|
|
|
|
// Now generate boxes
|
|
OutGeneratedBoxes.Empty(InTexureWeights.Num());
|
|
OutGeneratedBoxes.AddZeroed(InTexureWeights.Num());
|
|
|
|
// Generate boxes according to the inserted textures
|
|
for (const FWeightedTexture& Texture : InsertedTextures)
|
|
{
|
|
FBox2D& Box = OutGeneratedBoxes[Texture.TextureIndex];
|
|
Box = Texture.Area;
|
|
}
|
|
}
|
|
|
|
float FMaterialUtilities::ComputeRequiredTexelDensityFromScreenSize(const float InScreenSize, float InWorldSpaceRadius)
|
|
{
|
|
static const float ScreenX = 1920;
|
|
|
|
float WorldSizeCM = InWorldSpaceRadius * 2;
|
|
float WorldSizeMeter = WorldSizeCM / 100;
|
|
|
|
float ScreenSizePercent = InScreenSize;
|
|
float ScreenSizePixel = ScreenSizePercent * ScreenX;
|
|
|
|
float TexelDensityPerMeter = ScreenSizePixel / WorldSizeMeter;
|
|
|
|
return TexelDensityPerMeter;
|
|
}
|
|
|
|
float FMaterialUtilities::ComputeRequiredTexelDensityFromDrawDistance(const float InDrawDistance, float InWorldSpaceRadius)
|
|
{
|
|
// Generate a projection matrix.
|
|
static const float ScreenX = 1920;
|
|
static const float ScreenY = 1080;
|
|
static const float HalfFOVRad = FMath::DegreesToRadians(45.0f);
|
|
static const FMatrix ProjectionMatrix = FPerspectiveMatrix(HalfFOVRad, ScreenX, ScreenY, 0.01f);
|
|
|
|
float WorldSizeCM = InWorldSpaceRadius * 2;
|
|
float WorldSizeMeter = WorldSizeCM / 100;
|
|
|
|
float ScreenSizePercent = ComputeBoundsScreenSize(FVector::ZeroVector, InWorldSpaceRadius, FVector(0.0f, 0.0f, InDrawDistance), ProjectionMatrix);
|
|
float ScreenSizePixel = ScreenSizePercent * ScreenX;
|
|
|
|
float TexelDensityPerMeter = ScreenSizePixel / WorldSizeMeter;
|
|
|
|
return TexelDensityPerMeter;
|
|
}
|
|
|
|
static int32 ComputeTextureSizeFromTexelRatio(const double TexelRatio, const double TargetTexelDensity)
|
|
{
|
|
// Compute the perfect texture size that would get us to our texture density
|
|
// Also compute the nearest power of two sizes (below and above our target)
|
|
const int32 SizePerfect = FMath::CeilToInt(TargetTexelDensity / TexelRatio);
|
|
const int32 SizeHi = FMath::RoundUpToPowerOfTwo(SizePerfect);
|
|
const int32 SizeLo = SizeHi >> 1;
|
|
|
|
// Compute the texel density we achieve with these two texture sizes
|
|
const double TexelDensityLo = SizeLo * TexelRatio;
|
|
const double TexelDensityHi = SizeHi * TexelRatio;
|
|
|
|
// Select best match between low & high res textures.
|
|
const double TexelDensityLoDiff = TargetTexelDensity - TexelDensityLo;
|
|
const double TexelDensityHiDiff = TexelDensityHi - TargetTexelDensity;
|
|
const int32 BestTextureSize = TexelDensityLoDiff < TexelDensityHiDiff ? SizeLo : SizeHi;
|
|
|
|
return BestTextureSize;
|
|
}
|
|
|
|
int32 FMaterialUtilities::GetTextureSizeFromTargetTexelDensity(const UE::Geometry::FDynamicMesh3& Mesh, float InTargetTexelDensity)
|
|
{
|
|
const UE::Geometry::FDynamicMeshUVOverlay* UVOverlay = Mesh.Attributes()->PrimaryUV();
|
|
|
|
double Mesh3DArea = 0;
|
|
double MeshUVArea = 0;
|
|
|
|
// If no UVs, assume perfect UV space usage.
|
|
const bool bHasUVs = UVOverlay != nullptr;
|
|
if (!bHasUVs)
|
|
{
|
|
MeshUVArea = 1.0;
|
|
}
|
|
|
|
for (int TriangleID : Mesh.TriangleIndicesItr())
|
|
{
|
|
// World space area
|
|
Mesh3DArea += Mesh.GetTriArea(TriangleID);
|
|
|
|
// UV space area
|
|
if (bHasUVs)
|
|
{
|
|
UE::Geometry::FIndex3i UVVertices = UVOverlay->GetTriangle(TriangleID);
|
|
UE::Geometry::FTriangle2d TriangleUV = UE::Geometry::FTriangle2d(
|
|
(FVector2d)UVOverlay->GetElement(UVVertices.A),
|
|
(FVector2d)UVOverlay->GetElement(UVVertices.B),
|
|
(FVector2d)UVOverlay->GetElement(UVVertices.C));
|
|
|
|
MeshUVArea += TriangleUV.Area();
|
|
}
|
|
}
|
|
|
|
return GetTextureSizeFromTargetTexelDensity(Mesh3DArea, MeshUVArea, InTargetTexelDensity);
|
|
}
|
|
|
|
int32 FMaterialUtilities::GetTextureSizeFromTargetTexelDensity(const FMeshDescription& InMesh, float InTargetTexelDensity)
|
|
{
|
|
FStaticMeshConstAttributes Attributes(InMesh);
|
|
|
|
TVertexAttributesConstRef<FVector3f> Positions = Attributes.GetVertexPositions();
|
|
TUVAttributesConstRef<FVector2f> UVs = Attributes.GetUVCoordinates(0);
|
|
|
|
double Mesh3DArea = 0;
|
|
double MeshUVArea = 0;
|
|
|
|
// If no UVs, assume perfect UV space usage.
|
|
const bool bHasUVs = UVs.IsValid() && UVs.GetNumElements() != 0;
|
|
if (!bHasUVs)
|
|
{
|
|
MeshUVArea = 1.0;
|
|
}
|
|
|
|
for (const FTriangleID TriangleID : InMesh.Triangles().GetElementIDs())
|
|
{
|
|
// World space area
|
|
TArrayView<const FVertexID> TriVertices = InMesh.GetTriangleVertices(TriangleID);
|
|
Mesh3DArea += UE::Geometry::VectorUtil::Area(Positions[TriVertices[0]], Positions[TriVertices[1]], Positions[TriVertices[2]]);
|
|
|
|
// UV space area
|
|
if (bHasUVs)
|
|
{
|
|
TArrayView<const FUVID> TriUVs = InMesh.GetTriangleUVIndices(TriangleID);
|
|
MeshUVArea += UE::Geometry::VectorUtil::Area(UVs[0], UVs[1], UVs[2]);
|
|
}
|
|
}
|
|
|
|
return GetTextureSizeFromTargetTexelDensity(Mesh3DArea, MeshUVArea, InTargetTexelDensity);
|
|
}
|
|
|
|
int32 FMaterialUtilities::GetTextureSizeFromTargetTexelDensity(double InMesh3DArea, double InMeshUVArea, double InTargetTexelDensity)
|
|
{
|
|
double TexelRatio = FMath::Sqrt(InMeshUVArea / InMesh3DArea) * 100;
|
|
|
|
static const int32 MinTextureSize = 16;
|
|
static const int32 MaxTextureSize = 8192;
|
|
|
|
int32 TextureSize = ComputeTextureSizeFromTexelRatio(TexelRatio, InTargetTexelDensity);
|
|
if (TextureSize > MaxTextureSize)
|
|
{
|
|
UE_LOG(LogMaterialUtilities, Warning, TEXT("Mesh would require %d x %d textures, clamping down to maximum (%d x %d)"), TextureSize, TextureSize, MaxTextureSize, MaxTextureSize);
|
|
}
|
|
|
|
return FMath::Clamp(TextureSize, MinTextureSize, MaxTextureSize);
|
|
}
|