Files
FlaxEngine/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layer.cpp

328 lines
13 KiB
C++

// Copyright (c) Wojciech Figat. All rights reserved.
#if COMPILE_WITH_MATERIAL_GRAPH
#include "MaterialGenerator.h"
#include "Engine/Core/Math/Vector4.h"
#include "Engine/Core/Math/Matrix.h"
#include "Engine/Content/Assets/MaterialInstance.h"
#include "Engine/Content/Assets/Material.h"
#include "Engine/Serialization/MemoryReadStream.h"
#include "Engine/Engine/Engine.h"
MaterialGenerator::MaterialGraphBoxesMapping MaterialGenerator::MaterialGraphBoxesMappings[] =
{
{ 0, nullptr, MaterialTreeType::PixelShader, MaterialValue::Zero },
{ 1, TEXT("Color"), MaterialTreeType::PixelShader, MaterialValue::InitForZero(VariantType::Float3) },
{ 2, TEXT("Mask"), MaterialTreeType::PixelShader, MaterialValue::InitForOne(VariantType::Float) },
{ 3, TEXT("Emissive"), MaterialTreeType::PixelShader, MaterialValue::InitForZero(VariantType::Float3) },
{ 4, TEXT("Metalness"), MaterialTreeType::PixelShader, MaterialValue::InitForZero(VariantType::Float) },
{ 5, TEXT("Specular"), MaterialTreeType::PixelShader, MaterialValue::InitForHalf(VariantType::Float) },
{ 6, TEXT("Roughness"), MaterialTreeType::PixelShader, MaterialValue::InitForHalf(VariantType::Float) },
{ 7, TEXT("AO"), MaterialTreeType::PixelShader, MaterialValue::InitForOne(VariantType::Float) },
{ 8, TEXT("TangentNormal"), MaterialTreeType::PixelShader, MaterialValue(VariantType::Float3, TEXT("float3(0, 0, 1.0)")) },
{ 9, TEXT("Opacity"), MaterialTreeType::PixelShader, MaterialValue::InitForOne(VariantType::Float) },
{ 10, TEXT("Refraction"), MaterialTreeType::PixelShader, MaterialValue::InitForOne(VariantType::Float) },
{ 11, TEXT("PositionOffset"), MaterialTreeType::VertexShader, MaterialValue::InitForZero(VariantType::Float3) },
{ 12, TEXT("TessellationMultiplier"), MaterialTreeType::VertexShader, MaterialValue(VariantType::Float, TEXT("4.0f")) },
{ 13, TEXT("WorldDisplacement"), MaterialTreeType::DomainShader, MaterialValue::InitForZero(VariantType::Float3) },
{ 14, TEXT("SubsurfaceColor"), MaterialTreeType::PixelShader, MaterialValue::InitForZero(VariantType::Float3) },
};
const MaterialGenerator::MaterialGraphBoxesMapping& MaterialGenerator::GetMaterialRootNodeBox(MaterialGraphBoxes box)
{
static_assert(ARRAY_COUNT(MaterialGenerator::MaterialGraphBoxesMappings) == 15, "Invalid amount of boxes added for root node. Please update the code above");
return MaterialGraphBoxesMappings[static_cast<int32>(box)];
}
void MaterialGenerator::AddLayer(MaterialLayer* layer)
{
_layers.Add(layer);
}
MaterialLayer* MaterialGenerator::GetLayer(const Guid& id, Node* caller)
{
// Find layer first
for (int32 i = 0; i < _layers.Count(); i++)
{
if (_layers[i]->ID == id)
{
// Found
return _layers[i];
}
}
// Load asset
Asset* asset = Assets.LoadAsync<MaterialBase>(id);
if (asset == nullptr || asset->WaitForLoaded(30000))
{
OnError(caller, nullptr, TEXT("Failed to load material asset."));
return nullptr;
}
// Special case for engine exit event
if (Engine::ShouldExit())
{
// End
return nullptr;
}
// Check if load failed
if (!asset->IsLoaded())
{
OnError(caller, nullptr, TEXT("Failed to load material asset."));
return nullptr;
}
// Check if it's a material instance
Material* material = nullptr;
Asset* iterator = asset;
while (material == nullptr)
{
// Wait for material to be loaded
if (iterator->WaitForLoaded())
{
OnError(caller, nullptr, TEXT("Material asset load failed."));
return nullptr;
}
if (iterator->GetTypeName() == MaterialInstance::TypeName)
{
auto instance = ((MaterialInstance*)iterator);
// Check if base material has been assigned
if (instance->GetBaseMaterial() == nullptr)
{
OnError(caller, nullptr, TEXT("Material instance has missing base material."));
return nullptr;
}
iterator = instance->GetBaseMaterial();
}
else
{
material = ((Material*)iterator);
// Check if instanced material is not instancing current material
ASSERT(GetRootLayer());
if (material->GetID() == GetRootLayer()->ID)
{
OnError(caller, nullptr, TEXT("Cannot use instance of the current material as layer."));
return nullptr;
}
}
}
ASSERT(material);
// Get surface data
BytesContainer surfaceData = material->LoadSurface(true);
if (surfaceData.IsInvalid())
{
OnError(caller, nullptr, TEXT("Cannot load surface data."));
return nullptr;
}
MemoryReadStream surfaceDataStream(surfaceData.Get(), surfaceData.Length());
// Load layer
auto layer = MaterialLayer::Load(id, &surfaceDataStream, material->GetInfo(), material->ToString());
// Validate layer info
if (layer == nullptr)
{
OnError(caller, nullptr, TEXT("Cannot load layer."));
return nullptr;
}
#if 0 // allow mixing material domains because material generator uses the base layer for features usage checks and sub layers produce only Material structure for blending or extracting
if (layer->Domain != GetRootLayer()->Domain) // TODO: maybe allow solid on transparent?
{
OnError(caller, nullptr, TEXT("Cannot mix materials with different Domains."));
return nullptr;
}
#endif
// Override parameters values if using material instance
if (asset->GetTypeName() == MaterialInstance::TypeName)
{
auto instance = ((MaterialInstance*)asset);
auto instanceParams = &instance->Params;
// Clone overriden parameters values
for (auto& param : layer->Graph.Parameters)
{
const auto instanceParam = instanceParams->Get(param.Identifier);
if (instanceParam && instanceParam->IsOverride())
{
param.Value = instanceParam->GetValue();
// Fold object references to their ids
if (param.Value.Type == VariantType::Object)
param.Value = param.Value.AsObject ? param.Value.AsObject->GetID() : Guid::Empty;
else if (param.Value.Type == VariantType::Asset)
param.Value = param.Value.AsObject ? param.Value.AsObject->GetID() : Guid::Empty;
}
}
}
// Prepare layer
layer->Prepare();
const bool allowPublicParameters = true;
prepareLayer(layer, allowPublicParameters);
AddLayer(layer);
return layer;
}
MaterialLayer* MaterialGenerator::GetRootLayer() const
{
return _layers.Count() > 0 ? _layers[0] : nullptr;
}
void MaterialGenerator::prepareLayer(MaterialLayer* layer, bool allowVisibleParams)
{
LayerParamMapping m;
// Ensure that layer hasn't been used
ASSERT(layer->HasAnyVariableName() == false);
// Add all parameters to be saved in the result material parameters collection (perform merge)
bool isRooLayer = GetRootLayer() == layer;
for (int32 j = 0; j < layer->Graph.Parameters.Count(); j++)
{
const auto param = &layer->Graph.Parameters[j];
// For all not root layers (sub-layers) we won't to change theirs ID in order to prevent duplicated ID)
m.SrcId = param->Identifier;
m.DstId = param->Identifier;
if (!isRooLayer)
{
// Generate new ID (stable permutation based on the original ID)
m.DstId.A += _parameters.Count() * 17 + 13;
}
layer->ParamIdsMappings.Add(m);
SerializedMaterialParam& mp = _parameters.AddOne();
mp.ID = m.DstId;
mp.IsPublic = param->IsPublic && allowVisibleParams;
mp.Override = true;
mp.Name = param->Name;
mp.ShaderName = getParamName(_parameters.Count());
mp.Type = MaterialParameterType::Bool;
mp.AsBool = false;
switch (param->Type.Type)
{
case VariantType::Bool:
mp.Type = MaterialParameterType::Bool;
mp.AsBool = param->Value.AsBool;
break;
case VariantType::Int:
mp.Type = MaterialParameterType::Integer;
mp.AsInteger = param->Value.AsInt;
break;
case VariantType::Int64:
mp.Type = MaterialParameterType::Integer;
mp.AsInteger = (int32)param->Value.AsInt64;
break;
case VariantType::Uint:
mp.Type = MaterialParameterType::Integer;
mp.AsInteger = (int32)param->Value.AsUint;
break;
case VariantType::Uint64:
mp.Type = MaterialParameterType::Integer;
mp.AsInteger = (int32)param->Value.AsUint64;
break;
case VariantType::Float:
mp.Type = MaterialParameterType::Float;
mp.AsFloat = param->Value.AsFloat;
break;
case VariantType::Double:
mp.Type = MaterialParameterType::Float;
mp.AsFloat = (float)param->Value.AsDouble;
break;
case VariantType::Float2:
mp.Type = MaterialParameterType::Vector2;
mp.AsFloat2 = param->Value.AsFloat2();
break;
case VariantType::Float3:
mp.Type = MaterialParameterType::Vector3;
mp.AsFloat3 = param->Value.AsFloat3();
break;
case VariantType::Float4:
mp.Type = MaterialParameterType::Vector4;
*(Float4*)&mp.AsData = param->Value.AsFloat4();
break;
case VariantType::Double2:
mp.Type = MaterialParameterType::Vector2;
mp.AsFloat2 = (Float2)param->Value.AsDouble2();
break;
case VariantType::Double3:
mp.Type = MaterialParameterType::Vector3;
mp.AsFloat3 = (Float3)param->Value.AsDouble3();
break;
case VariantType::Double4:
mp.Type = MaterialParameterType::Vector4;
*(Float4*)&mp.AsData = (Float4)param->Value.AsDouble4();
break;
case VariantType::Color:
mp.Type = MaterialParameterType::Color;
mp.AsColor = param->Value.AsColor();
break;
case VariantType::Matrix:
mp.Type = MaterialParameterType::Matrix;
*(Matrix*)&mp.AsData = *(Matrix*)param->Value.AsBlob.Data;
break;
case VariantType::Asset:
if (!param->Type.TypeName)
{
OnError(nullptr, nullptr, String::Format(TEXT("Invalid or unsupported material parameter type {0}."), param->Type));
break;
}
if (StringUtils::Compare(param->Type.TypeName, "FlaxEngine.Texture") == 0)
{
mp.Type = MaterialParameterType::Texture;
// Special case for Normal Maps
auto asset = (Texture*)::LoadAsset((Guid)param->Value, Texture::TypeInitializer);
if (asset && !asset->WaitForLoaded() && asset->IsNormalMap())
mp.Type = MaterialParameterType::NormalMap;
}
else if (StringUtils::Compare(param->Type.TypeName, "FlaxEngine.CubeTexture") == 0)
mp.Type = MaterialParameterType::CubeTexture;
else
OnError(nullptr, nullptr, String::Format(TEXT("Invalid or unsupported material parameter type {0}."), param->Type));
mp.AsGuid = (Guid)param->Value;
break;
case VariantType::Object:
if (!param->Type.TypeName)
{
OnError(nullptr, nullptr, String::Format(TEXT("Invalid or unsupported material parameter type {0}."), param->Type));
break;
}
if (StringUtils::Compare(param->Type.TypeName, "FlaxEngine.GPUTexture") == 0)
mp.Type = MaterialParameterType::GPUTexture;
else
OnError(nullptr, nullptr, String::Format(TEXT("Invalid or unsupported material parameter type {0}."), param->Type));
mp.AsGuid = (Guid)param->Value;
break;
case VariantType::Enum:
if (!param->Type.TypeName)
{
OnError(nullptr, nullptr, String::Format(TEXT("Invalid or unsupported material parameter type {0}."), param->Type));
break;
}
if (StringUtils::Compare(param->Type.TypeName, "FlaxEngine.MaterialSceneTextures") == 0)
mp.Type = MaterialParameterType::SceneTexture;
else if (StringUtils::Compare(param->Type.TypeName, "FlaxEngine.ChannelMask") == 0)
mp.Type = MaterialParameterType::ChannelMask;
else
OnError(nullptr, nullptr, String::Format(TEXT("Invalid or unsupported material parameter type {0}."), param->Type));
mp.AsInteger = (int32)param->Value.AsUint64;
break;
default:
OnError(nullptr, nullptr, String::Format(TEXT("Invalid or unsupported material parameter type {0}."), param->Type));
break;
}
}
}
#endif