// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. #if COMPILE_WITH_MATERIAL_GRAPH #include "MaterialGenerator.h" #include "Engine/Content/Assets/MaterialFunction.h" void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value) { switch (node->TypeID) { // World Position case 2: value = Value(VariantType::Vector3, TEXT("input.WorldPosition.xyz")); break; // View case 3: { switch (box->ID) { // Position case 0: value = Value(VariantType::Vector3, TEXT("ViewPos")); break; // Direction case 1: value = Value(VariantType::Vector3, TEXT("ViewDir")); break; // Far Plane case 2: value = Value(VariantType::Float, TEXT("ViewFar")); break; default: CRASH; } break; } // Normal case 4: value = getNormal; break; // Camera Vector case 5: value = getCameraVector(node); break; // Screen Position case 6: { // Position if (box->ID == 0) value = Value(VariantType::Vector2, TEXT("input.SvPosition.xy")); // Texcoord else if (box->ID == 1) value = writeLocal(VariantType::Vector2, TEXT("input.SvPosition.xy * ScreenSize.zw"), node); break; } // Screen Size case 7: { value = Value(VariantType::Vector2, box->ID == 0 ? TEXT("ScreenSize.xy") : TEXT("ScreenSize.zw")); break; } // Custom code case 8: { // Skip if has no code if (((StringView)node->Values[0]).IsEmpty()) { value = Value::Zero; break; } const int32 InputsMax = 8; const int32 OutputsMax = 4; const int32 Input0BoxID = 0; const int32 Output0BoxID = 8; // Create output variables Value values[OutputsMax]; for (int32 i = 0; i < OutputsMax; i++) { const auto outputBox = node->GetBox(Output0BoxID + i); if (outputBox && outputBox->HasConnection()) { values[i] = writeLocal(VariantType::Vector4, node); } } // Process custom code (inject inputs and outputs) String code; code = (StringView)node->Values[0]; for (int32 i = 0; i < InputsMax; i++) { auto inputName = TEXT("Input") + StringUtils::ToString(i); const auto inputBox = node->GetBox(Input0BoxID + i); if (inputBox && inputBox->HasConnection()) { auto inputValue = tryGetValue(inputBox, Value::Zero); if (inputValue.Type != VariantType::Vector4) inputValue = inputValue.Cast(VariantType::Vector4); code.Replace(*inputName, *inputValue.Value, StringSearchCase::CaseSensitive); } } for (int32 i = 0; i < OutputsMax; i++) { auto outputName = TEXT("Output") + StringUtils::ToString(i); const auto outputBox = node->GetBox(Output0BoxID + i); if (outputBox && outputBox->HasConnection()) { code.Replace(*outputName, *values[i].Value, StringSearchCase::CaseSensitive); } } // Write code _writer.Write(TEXT("{\n")); _writer.Write(*code); _writer.Write(TEXT("}\n")); // Link output values to boxes for (int32 i = 0; i < OutputsMax; i++) { const auto outputBox = node->GetBox(Output0BoxID + i); if (outputBox && outputBox->HasConnection()) { outputBox->Cache = values[i]; } } value = box->Cache; break; } // Object Position case 9: value = Value(VariantType::Vector3, TEXT("GetObjectPosition(input)")); break; // Two Sided Sign case 10: value = Value(VariantType::Float, TEXT("input.TwoSidedSign")); break; // Camera Depth Fade case 11: { auto faeLength = tryGetValue(node->GetBox(0), node->Values[0]).AsFloat(); auto fadeOffset = tryGetValue(node->GetBox(1), node->Values[1]).AsFloat(); // TODO: for pixel shader it could calc PixelDepth = mul(float4(WorldPos.xyz, 1), ViewProjMatrix).w and use it auto x1 = writeLocal(VariantType::Vector3, TEXT("ViewPos - input.WorldPosition"), node); auto x2 = writeLocal(VariantType::Vector3, TEXT("TransformViewVectorToWorld(input, float3(0, 0, -1))"), node); auto x3 = writeLocal(VariantType::Float, String::Format(TEXT("dot(normalize({0}), {1}) * length({0})"), x1.Value, x2.Value), node); auto x4 = writeLocal(VariantType::Float, String::Format(TEXT("{0} - {1}"), x3.Value, fadeOffset.Value), node); auto x5 = writeLocal(VariantType::Float, String::Format(TEXT("saturate({0} / {1})"), x4.Value, faeLength.Value), node); value = x5; break; } // Vertex Color case 12: value = getVertexColor; _treeLayer->UsageFlags |= MaterialUsageFlags::UseVertexColor; break; // Pre-skinned Local Position case 13: value = _treeType == MaterialTreeType::VertexShader ? Value(VariantType::Vector3, TEXT("input.PreSkinnedPosition")) : Value::Zero; break; // Pre-skinned Local Normal case 14: value = _treeType == MaterialTreeType::VertexShader ? Value(VariantType::Vector3, TEXT("input.PreSkinnedNormal")) : Value::Zero; break; // Depth case 15: value = writeLocal(VariantType::Float, TEXT("distance(ViewPos, input.WorldPosition)"), node); break; // Tangent case 16: value = Value(VariantType::Vector3, TEXT("input.TBN[0]")); break; // Bitangent case 17: value = Value(VariantType::Vector3, TEXT("input.TBN[1]")); break; // Camera Position case 18: value = Value(VariantType::Vector3, TEXT("ViewPos")); break; // Per Instance Random case 19: value = Value(VariantType::Float, TEXT("GetPerInstanceRandom(input)")); break; // Interpolate VS To PS case 20: { const auto input = node->GetBox(0); // If used in VS then pass the value from the input box if (_treeType == MaterialTreeType::VertexShader) { value = tryGetValue(input, Value::Zero).AsVector4(); break; } // Check if can use more interpolants if (_vsToPsInterpolants.Count() == 16) { OnError(node, box, TEXT("Too many VS to PS interpolants used.")); value = Value::Zero; break; } // Check if can use interpolants const auto layer = GetRootLayer(); if (!layer || layer->Domain == MaterialDomain::Decal || layer->Domain == MaterialDomain::PostProcess) { OnError(node, box, TEXT("VS to PS interpolants are not supported in Decal or Post Process materials.")); value = Value::Zero; break; } // Indicate the interpolator slot usage value = Value(VariantType::Vector4, String::Format(TEXT("input.CustomVSToPS[{0}]"), _vsToPsInterpolants.Count())); _vsToPsInterpolants.Add(input); break; } // Terrain Holes Mask case 21: { MaterialLayer* baseLayer = GetRootLayer(); if (baseLayer->Domain == MaterialDomain::Terrain) value = Value(VariantType::Float, TEXT("input.HolesMask")); else value = Value::One; break; } // Terrain Layer Weight case 22: { MaterialLayer* baseLayer = GetRootLayer(); if (baseLayer->Domain != MaterialDomain::Terrain) { value = Value::One; break; } const int32 layer = node->Values[0].AsInt; if (layer < 0 || layer > 7) { value = Value::One; OnError(node, box, TEXT("Invalid terrain layer index.")); break; } const int32 slotIndex = layer / 4; const int32 componentIndex = layer % 4; value = Value(VariantType::Float, String::Format(TEXT("input.Layers[{0}][{1}]"), slotIndex, componentIndex)); break; } // Depth Fade case 23: { // Calculate screen-space UVs auto screenUVs = writeLocal(VariantType::Vector2, TEXT("input.SvPosition.xy * ScreenSize.zw"), node); // Sample scene depth buffer auto sceneDepthTexture = findOrAddSceneTexture(MaterialSceneTextures::SceneDepth); auto depthSample = writeLocal(VariantType::Float, String::Format(TEXT("{0}.SampleLevel(SamplerLinearClamp, {1}, 0).x"), sceneDepthTexture.ShaderName, screenUVs.Value), node); // Linearize raw device depth Value sceneDepth; linearizeSceneDepth(node, depthSample, sceneDepth); // Calculate pixel depth auto posVS = writeLocal(VariantType::Float, TEXT("mul(float4(input.WorldPosition.xyz, 1), ViewMatrix).z"), node); // Compute depth difference auto depthDiff = writeLocal(VariantType::Float, String::Format(TEXT("{0} * ViewFar - {1}"), sceneDepth.Value, posVS.Value), node); // Apply smoothing factor and clamp the result value = writeLocal(VariantType::Float, String::Format(TEXT("saturate({0} / {1})"), depthDiff.Value, node->Values[0].AsFloat), node); break; } // Material Function case 24: { // Load function asset const auto function = Assets.LoadAsync((Guid)node->Values[0]); if (!function || function->WaitForLoaded()) { OnError(node, box, TEXT("Missing or invalid function.")); value = Value::Zero; break; } #if 0 // Prevent recursive calls for (int32 i = _callStack.Count() - 1; i >= 0; i--) { if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(1, 24)) { const auto callFunc = Assets.LoadAsync((Guid)_callStack[i]->Values[0]); if (callFunc == function) { OnError(node, box, String::Format(TEXT("Recursive call to function '{0}'!"), function->ToString())); value = Value::Zero; return; } } } #endif // Create a instanced version of the function graph Graph* graph; if (!_functions.TryGet(node, graph)) { graph = New(); function->LoadSurface((MaterialGraph&)*graph); _functions.Add(node, graph); } // Peek the function output (function->Outputs maps the functions outputs to output nodes indices) const int32 outputIndex = box->ID - 16; if (outputIndex < 0 || outputIndex >= function->Outputs.Count()) { OnError(node, box, TEXT("Invalid function output box.")); value = Value::Zero; break; } Node* functionOutputNode = &graph->Nodes[function->Outputs[outputIndex]]; Box* functionOutputBox = functionOutputNode->TryGetBox(0); // Evaluate the function output _graphStack.Push(graph); value = functionOutputBox && functionOutputBox->HasConnection() ? eatBox(node, functionOutputBox->FirstConnection()) : Value::Zero; _graphStack.Pop(); break; } // Object Size case 25: value = Value(VariantType::Vector3, TEXT("GetObjectSize(input)")); break; default: break; } } void MaterialGenerator::ProcessGroupFunction(Box* box, Node* node, Value& value) { switch (node->TypeID) { // Function Input case 1: { // Find the function call Node* functionCallNode = nullptr; ASSERT(_graphStack.Count() >= 2); Graph* graph; for (int32 i = _callStack.Count() - 1; i >= 0; i--) { if (_callStack[i]->Type == GRAPH_NODE_MAKE_TYPE(1, 24) && _functions.TryGet(_callStack[i], graph) && _graphStack[_graphStack.Count() - 1] == graph) { functionCallNode = _callStack[i]; break; } } if (!functionCallNode) { OnError(node, box, TEXT("Missing calling function node.")); value = Value::Zero; break; } const auto function = Assets.LoadAsync((Guid)functionCallNode->Values[0]); if (!_functions.TryGet(functionCallNode, graph) || !function) { OnError(node, box, TEXT("Missing calling function graph.")); value = Value::Zero; break; } // Peek the input box to use int32 inputIndex = -1; for (int32 i = 0; i < function->Inputs.Count(); i++) { if (node->ID == graph->Nodes[function->Inputs[i]].ID) { inputIndex = i; break; } } if (inputIndex < 0 || inputIndex >= function->Inputs.Count()) { OnError(node, box, TEXT("Invalid function input box.")); value = Value::Zero; break; } Box* functionCallBox = functionCallNode->TryGetBox(inputIndex); if (functionCallBox->HasConnection()) { // Use provided input value from the function call _graphStack.Pop(); value = eatBox(node, functionCallBox->FirstConnection()); _graphStack.Push(graph); } else { // Use the default value from the function graph value = tryGetValue(node->TryGetBox(1), Value::Zero); } break; } default: break; } } #endif