diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index c484c12a1..842667173 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -86,6 +86,71 @@ namespace FlaxEditor.Surface.Archetypes } } + // TODO merge the above and below function into one? + internal class ProceduralSampleNode : SurfaceNode + { + private ComboBox _textureGroupPicker; + + public ProceduralSampleNode(uint id, VisjectSurfaceContext context, NodeArchetype nodeArch, GroupArchetype groupArch) + : base(id, context, nodeArch, groupArch) + { + } + + public override void OnValuesChanged() + { + base.OnValuesChanged(); + + UpdateUI(); + } + + public override void OnLoaded() + { + base.OnLoaded(); + + UpdateUI(); + } + + private void UpdateUI() + { + if ((int)Values[1] == (int)CommonSamplerType.TextureGroup) + { + if (_textureGroupPicker == null) + { + _textureGroupPicker = new ComboBox + { + Location = new Float2(FlaxEditor.Surface.Constants.NodeMarginX + 50, FlaxEditor.Surface.Constants.NodeMarginY + FlaxEditor.Surface.Constants.NodeHeaderSize + FlaxEditor.Surface.Constants.LayoutOffsetY * 3), + Width = 100, + Parent = this, + }; + _textureGroupPicker.SelectedIndexChanged += OnSelectedTextureGroupChanged; + var groups = GameSettings.Load(); + if (groups?.TextureGroups != null) + { + for (int i = 0; i < groups.TextureGroups.Length; i++) + _textureGroupPicker.AddItem(groups.TextureGroups[i].Name); + } + } + else + { + _textureGroupPicker.Visible = true; + } + _textureGroupPicker.SelectedIndexChanged -= OnSelectedTextureGroupChanged; + _textureGroupPicker.SelectedIndex = (int)Values[2]; + _textureGroupPicker.SelectedIndexChanged += OnSelectedTextureGroupChanged; + } + else if (_textureGroupPicker != null) + { + _textureGroupPicker.Visible = false; + } + ResizeAuto(); + } + + private void OnSelectedTextureGroupChanged(ComboBox comboBox) + { + SetValue(2, _textureGroupPicker.SelectedIndex); + } + } + /// /// The nodes for that group. /// @@ -405,6 +470,29 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, "Color", typeof(Float3), 3) } }, + new NodeArchetype + { + TypeID = 17, + Create = (id, context, arch, groupArch) => new ProceduralSampleNode(id, context, arch, groupArch), + Title = "Procedural Sample Texture", + Description = "Samples a texture to create a more natural look with less obvious tiling.", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(240, 100), + DefaultValues = new object[] + { + new Float2(1.0f, 1.0f), + 2, + 0, + }, + Elements = new[] + { + NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0), + NodeElementArchetype.Factory.Input(1, "UV", true, typeof(Float2), 1, 0), + NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 2), + NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 2, "Sampler"), + NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 2, 100, 1, typeof(CommonSamplerType)) + } + }, }; } } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index 98a9e7c87..2011fb2c5 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -669,6 +669,148 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) _writer.Write(*triplanarTexture); value = result; } + // Procedural Texture Sample + case 17: + { + enum CommonSamplerType + { + LinearClamp = 0, + PointClamp = 1, + LinearWrap = 2, + PointWrap = 3, + TextureGroup = 4, + }; + const Char* SamplerNames[] + { + TEXT("SamplerLinearClamp"), + TEXT("SamplerLinearWrap"), + }; + + auto textureBox = node->GetBox(0); + auto uvsBox = node->GetBox(1); + + Value uvs; + const bool useCustomUVs = uvsBox->HasConnection(); + if (useCustomUVs) + { + // Get custom UVs + uvs = eatBox(uvsBox->GetParent(), uvsBox->FirstConnection()); + } + else + { + // Use default UVs + uvs = getUVs; + } + + if (!textureBox->HasConnection()) + { + // No texture to sample + value = Value::Zero; + break; + } + + if (!CanUseSample(_treeType)) + { + // Must sample texture in pixel shader + value = Value::Zero; + break; + } + + const auto texture = eatBox(textureBox->GetParent(), textureBox->FirstConnection()); + const auto textureParam = findParam(texture.Value); + const bool isNormalMap = textureParam->Type == MaterialParameterType::NormalMap; + + const Char* samplerName; + const int32 samplerIndex = node->Values[1].AsInt; + if (samplerIndex == TextureGroup) + { + auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[2].AsInt); + samplerName = *textureGroupSampler.ShaderName; + } + else if (samplerIndex >= 0 && samplerIndex < ARRAY_COUNT(SamplerNames)) + { + samplerName = SamplerNames[samplerIndex]; + } + else + { + OnError(node, box, TEXT("Invalid texture sampler.")); + return; + } + + auto result = writeLocal(Value::InitForZero(ValueType::Float4), node); + createGradients(node); + + auto proceduralSample = String::Format(TEXT( + " {{\n" + " float W1, W2, W3;\n" + " float2 vertex1, vertex2, vertex3;\n" + " float2 uv = {0} * 3.464; // 2 * sqrt (3);\n" + " float2 UV1, UV2, UV3;\n" + " const float2x2 gridToSkewedGrid = float2x2( 1.0, 0.0, -0.57735027, 1.15470054 );\n" + " float2 skewedCoord = mul( gridToSkewedGrid, uv );\n" + " int2 baseId = int2( floor( skewedCoord ) );\n" + " float3 temp = float3( frac( skewedCoord ), 0 );\n" + " temp.z = 1.0 - temp.x - temp.y;\n" + " if ( temp.z > 0.0 )\n" + " {{\n" + " W1 = temp.z;\n" + " W2 = temp.y;\n" + " W3 = temp.x;\n" + " vertex1 = baseId;\n" + " vertex2 = baseId + int2( 0, 1 );\n" + " vertex3 = baseId + int2( 1, 0 );\n" + " }}\n" + " else\n" + " {{\n" + " W1 = -temp.z;\n" + " W2 = 1.0 - temp.y;\n" + " W3 = 1.0 - temp.x;\n" + " vertex1 = baseId + int2( 1, 1 );\n" + " vertex2 = baseId + int2( 1, 0 );\n" + " vertex3 = baseId + int2( 0, 1 );\n" + " }}\n" + " UV1 = {0} + frac( sin( mul( float2x2( 127.1, 311.7, 269.5, 183.3 ), vertex1 ) ) * 43758.5453 );\n" + " UV2 = {0} + frac( sin( mul( float2x2( 127.1, 311.7, 269.5, 183.3 ), vertex2 ) ) * 43758.5453 );\n" + " UV3 = {0} + frac( sin( mul( float2x2( 127.1, 311.7, 269.5, 183.3 ), vertex3 ) ) * 43758.5453 );\n" + " float4 tex1 = {1}.SampleGrad({4}, UV1, {2}, {3});\n" + " float4 tex2 = {1}.SampleGrad({4}, UV2, {2}, {3});\n" + " float4 tex3 = {1}.SampleGrad({4}, UV3, {2}, {3});\n" + ), + uvs.Value, // {0} + texture.Value, // {1} + _ddx.Value, // {2} + _ddy.Value, // {3} + samplerName // {4} + ); + + // Decode normal map vector + if (isNormalMap) + { + proceduralSample += String::Format(TEXT( + " tex1.xy = tex1.xy * 2.0 - 1.0;\n" + " tex1.z = sqrt(saturate(1.0 - dot(tex1.xy, tex1.xy))); \n" + " tex2.xy = tex2.xy * 2.0 - 1.0;\n" + " tex2.z = sqrt(saturate(1.0 - dot(tex2.xy, tex2.xy))); \n" + " tex3.xy = tex3.xy * 2.0 - 1.0;\n" + " tex3.z = sqrt(saturate(1.0 - dot(tex3.xy, tex3.xy))); \n" + )); + } + + proceduralSample += String::Format(TEXT( + " tex1 *= W1;\n" + " tex2 *= W2;\n" + " tex3 *= W3;\n" + " {0} = tex1 + tex2 + tex3;\n" + " }}\n" + ), + result.Value // {0} + ); + + _writer.Write(*proceduralSample); + value = result; + + break; + } default: break; }