Files
FlaxEngine/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Layers.cpp
2023-01-29 22:09:51 +01:00

354 lines
12 KiB
C++

// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
#if COMPILE_WITH_MATERIAL_GRAPH
#include "MaterialGenerator.h"
void MaterialGenerator::ProcessGroupLayers(Box* box, Node* node, Value& value)
{
switch (node->TypeID)
{
// Sample Layer
case 1:
{
Guid id = (Guid)node->Values[0];
if (!id.IsValid())
{
OnError(node, box, TEXT("Missing material."));
break;
}
ASSERT(GetRootLayer() != nullptr && GetRootLayer()->ID.IsValid());
if (id == GetRootLayer()->ID)
{
OnError(node, box, TEXT("Cannot use current material as layer."));
break;
}
// Load material layer
auto layer = GetLayer(id, node);
if (layer == nullptr)
{
OnError(node, box, TEXT("Cannot load material."));
break;
}
ASSERT(_layers.Contains(layer));
// Peek material variable name (may be empty if not used before)
auto uvsBox = node->GetBox(0);
String& varName = layer->GetVariableName(uvsBox->HasConnection() ? uvsBox->Connections[0] : nullptr);
// Check if layer has no generated data
if (varName.IsEmpty())
{
// Create material variable
MaterialValue defaultValue = MaterialValue::InitForZero(VariantType::Void);
varName = writeLocal(defaultValue, node).Value;
// Check if use custom UVs
String customUVs;
String orginalUVs;
if (uvsBox->HasConnection())
{
// Sample custom UVs
MaterialValue v = eatBox(uvsBox->GetParent<Node>(), uvsBox->FirstConnection());
customUVs = v.Value;
// TODO: better idea would to be to use variable for current UVs, by default=input.TexCoord.xy could be modified when sampling layers
// Cache original pixel UVs
orginalUVs = writeLocal(VariantType::Float2, TEXT("input.TexCoord.xy"), node).Value;
// Modify current pixel UVs
_writer.Write(*String::Format(TEXT("\tinput.TexCoord.xy = {0};\n"), customUVs));
}
// Cache current layer and tree type
auto callingLayerVarName = _treeLayerVarName;
auto callingLayer = _treeLayer;
auto treeType = _treeType;
_treeLayer = layer;
_graphStack.Push(&_treeLayer->Graph);
_treeLayerVarName = varName;
// Sample layer
const MaterialGraphBox* layerInputBox = layer->Root->GetBox(0);
if (layerInputBox->HasConnection())
{
MaterialValue subLayer = eatBox(layer->Root, layerInputBox->FirstConnection());
_writer.Write(TEXT("\t{0} = {1};\n"), varName, subLayer.Value);
}
else
{
#define EAT_BOX(type) eatMaterialGraphBox(layer, MaterialGraphBoxes::type)
switch (_treeType)
{
case MaterialTreeType::VertexShader:
{
EAT_BOX(PositionOffset);
EAT_BOX(TessellationMultiplier);
break;
}
case MaterialTreeType::DomainShader:
{
EAT_BOX(WorldDisplacement);
break;
}
case MaterialTreeType::PixelShader:
{
EAT_BOX(Normal);
EAT_BOX(Color);
EAT_BOX(Metalness);
EAT_BOX(Specular);
EAT_BOX(Roughness);
EAT_BOX(AmbientOcclusion);
EAT_BOX(Opacity);
EAT_BOX(Refraction);
EAT_BOX(DepthOffset);
EAT_BOX(Mask);
EAT_BOX(Emissive);
EAT_BOX(SubsurfaceColor);
if ((GetRootLayer()->FeaturesFlags & MaterialFeaturesFlags::InputWorldSpaceNormal) != (layer->FeaturesFlags & MaterialFeaturesFlags::InputWorldSpaceNormal))
{
// TODO convert normal vector to match the output layer properties
LOG(Warning, "TODO: convert normal vector to match the output layer properties");
}
break;
}
default:
break;
}
#undef EAT_BOX
}
// Mix usage flags
callingLayer->UsageFlags |= _treeLayer->UsageFlags;
// Restore calling tree and layer
_treeLayerVarName = callingLayerVarName;
_treeLayer = callingLayer;
_graphStack.Pop();
_treeType = treeType;
// Check was using custom UVs for sampling
if (uvsBox->HasConnection())
{
// Restore current pixel UVs
_writer.Write(*String::Format(TEXT("\tinput.TexCoord.xy = {0};\n"), orginalUVs));
}
}
ASSERT(varName.HasChars());
// Use value
value = MaterialValue(VariantType::Void, varName);
break;
}
// Blend Linear
case 2:
case 5:
case 8:
{
const Value defaultValue = MaterialValue::InitForZero(VariantType::Void);
const Value alpha = tryGetValue(node->GetBox(2), 0, Value::Zero);
if (alpha.IsZero())
{
// Bottom-only
value = tryGetValue(node->GetBox(0), defaultValue);
return;
}
if (alpha.IsOne())
{
// Top-only
value = tryGetValue(node->GetBox(1), defaultValue);
return;
}
// Sample layers
const MaterialValue bottom = tryGetValue(node->GetBox(0), defaultValue);
const MaterialValue top = tryGetValue(node->GetBox(1), defaultValue);
// Create new layer
value = writeLocal(defaultValue, node);
// Blend layers
if (node->TypeID == 8)
{
// Height Layer Blend
auto bottomHeight = tryGetValue(node->GetBox(4), Value::Zero);
auto topHeight = tryGetValue(node->GetBox(5), Value::Zero);
auto bottomHeightScaled = writeLocal(VariantType::Float, String::Format(TEXT("{0} * (1.0 - {1})"), bottomHeight.Value, alpha.Value), node);
auto topHeightScaled = writeLocal(VariantType::Float, String::Format(TEXT("{0} * {1}"), topHeight.Value, alpha.Value), node);
auto heightStart = writeLocal(VariantType::Float, String::Format(TEXT("max({0}, {1}) - 0.05"), bottomHeightScaled.Value, topHeightScaled.Value), node);
auto bottomLevel = writeLocal(VariantType::Float, String::Format(TEXT("max({0} - {1}, 0.0001)"), topHeightScaled.Value, heightStart.Value), node);
_writer.Write(TEXT("\t{0} = {1} / (max({2} - {3}, 0) + {4});\n"), alpha.Value, bottomLevel.Value, bottomHeightScaled.Value, heightStart.Value, bottomLevel.Value);
}
#define EAT_BOX(type) writeBlending(MaterialGraphBoxes::type, value, bottom, top, alpha)
switch (_treeType)
{
case MaterialTreeType::VertexShader:
{
EAT_BOX(PositionOffset);
EAT_BOX(TessellationMultiplier);
break;
}
case MaterialTreeType::DomainShader:
{
EAT_BOX(WorldDisplacement);
break;
}
case MaterialTreeType::PixelShader:
{
EAT_BOX(Normal);
EAT_BOX(Color);
EAT_BOX(Metalness);
EAT_BOX(Specular);
EAT_BOX(Roughness);
EAT_BOX(AmbientOcclusion);
EAT_BOX(Opacity);
EAT_BOX(Refraction);
EAT_BOX(DepthOffset);
EAT_BOX(Mask);
EAT_BOX(Emissive);
EAT_BOX(SubsurfaceColor);
break;
}
default:
break;
}
#undef EAT_BOX
break;
}
// Pack Material Layer (old: without TessellationMultiplier, SubsurfaceColor and WorldDisplacement support)
// [Deprecated on 2018.10.01, expires on 2019.10.01]
case 3:
{
// Create new layer
const MaterialValue defaultValue = MaterialValue::InitForZero(VariantType::Void);
value = writeLocal(defaultValue, node);
// Sample layer
#define CHECK_MATERIAL_FEATURE(type, feature) if (node->GetBox(static_cast<int32>(MaterialGraphBoxes::type))->HasConnection()) _treeLayer->UsageFlags |= MaterialUsageFlags::feature
#define EAT_BOX(type) eatMaterialGraphBox(value.Value, node->GetBox((int32)MaterialGraphBoxes::type), MaterialGraphBoxes::type)
switch (_treeType)
{
case MaterialTreeType::VertexShader:
{
EAT_BOX(PositionOffset);
CHECK_MATERIAL_FEATURE(PositionOffset, UsePositionOffset);
break;
}
case MaterialTreeType::PixelShader:
{
EAT_BOX(Normal);
EAT_BOX(Color);
EAT_BOX(Metalness);
EAT_BOX(Specular);
EAT_BOX(Roughness);
EAT_BOX(AmbientOcclusion);
EAT_BOX(Opacity);
EAT_BOX(Refraction);
EAT_BOX(DepthOffset);
EAT_BOX(Mask);
EAT_BOX(Emissive);
CHECK_MATERIAL_FEATURE(Emissive, UseEmissive);
CHECK_MATERIAL_FEATURE(Normal, UseNormal);
CHECK_MATERIAL_FEATURE(Mask, UseMask);
CHECK_MATERIAL_FEATURE(Refraction, UseRefraction);
CHECK_MATERIAL_FEATURE(DepthOffset, UseDepthOffset);
break;
}
default:
break;
}
#undef CHECK_MATERIAL_FEATURE
#undef EAT_BOX
break;
}
// Unpack Material Layer
// Node type 4 -> [Deprecated on 2018.10.01, expires on 2019.10.01]
case 4:
case 7:
{
// Get layer
MaterialValue defaultValue = MaterialValue::InitForZero(VariantType::Void);
MaterialValue layer = tryGetValue(node->GetBox(0), defaultValue);
// Extract component or use default value if cannot use that box in the current tree
auto& nodeMapping = MaterialGraphBoxesMappings[box->ID];
if (nodeMapping.TreeType == _treeType)
{
value = MaterialValue(nodeMapping.DefaultValue.Type, layer.Value + TEXT(".") + nodeMapping.SubName);
}
else
{
value = nodeMapping.DefaultValue;
}
break;
}
// Pack Material Layer
case 6:
{
// Create new layer
const MaterialValue defaultValue = MaterialValue::InitForZero(VariantType::Void);
value = writeLocal(defaultValue, node);
// Sample layer
#define CHECK_MATERIAL_FEATURE(type, feature) if (node->GetBox(static_cast<int32>(MaterialGraphBoxes::type))->HasConnection()) _treeLayer->UsageFlags |= MaterialUsageFlags::feature
#define EAT_BOX(type) eatMaterialGraphBox(value.Value, node->GetBox((int32)MaterialGraphBoxes::type), MaterialGraphBoxes::type)
switch (_treeType)
{
case MaterialTreeType::VertexShader:
{
EAT_BOX(PositionOffset);
EAT_BOX(TessellationMultiplier);
CHECK_MATERIAL_FEATURE(PositionOffset, UsePositionOffset);
break;
}
case MaterialTreeType::DomainShader:
{
EAT_BOX(WorldDisplacement);
CHECK_MATERIAL_FEATURE(WorldDisplacement, UseDisplacement);
break;
}
case MaterialTreeType::PixelShader:
{
EAT_BOX(Normal);
EAT_BOX(Color);
EAT_BOX(Metalness);
EAT_BOX(Specular);
EAT_BOX(Roughness);
EAT_BOX(AmbientOcclusion);
EAT_BOX(Opacity);
EAT_BOX(Refraction);
EAT_BOX(Mask);
EAT_BOX(Emissive);
EAT_BOX(SubsurfaceColor);
CHECK_MATERIAL_FEATURE(Emissive, UseEmissive);
CHECK_MATERIAL_FEATURE(Normal, UseNormal);
CHECK_MATERIAL_FEATURE(Mask, UseMask);
CHECK_MATERIAL_FEATURE(Refraction, UseRefraction);
CHECK_MATERIAL_FEATURE(DepthOffset, UseDepthOffset);
break;
}
default:
break;
}
#undef CHECK_MATERIAL_FEATURE
#undef EAT_BOX
break;
}
}
}
#endif