From 5022e05c25e18ac49d5586e44b824eeda27994f1 Mon Sep 17 00:00:00 2001 From: Olly Rybak Date: Fri, 23 Dec 2022 21:04:13 +1100 Subject: [PATCH 1/4] Added Stochastic Sampling material node --- Source/Editor/Surface/Archetypes/Textures.cs | 18 +++ .../MaterialGenerator.Textures.cpp | 109 ++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index 6e4e34f43..4d9f64085 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -405,6 +405,24 @@ namespace FlaxEditor.Surface.Archetypes NodeElementArchetype.Factory.Output(0, "Color", typeof(Float3), 3) } }, + new NodeArchetype + { + TypeID = 17, + Title = "Stochastic Sample Texture", + Description = "Projects a texture using world-space coordinates instead of UVs.", + Flags = NodeFlags.MaterialGraph, + Size = new Float2(240, 60), + DefaultValues = new object[] + { + new Float2(1.0f, 1.0f), + }, + 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), + } + }, }; } } diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index d6ae2719f..d339bb72e 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -669,6 +669,115 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) _writer.Write(*triplanarTexture); value = result; } + case 17: + { + 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; + + 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(SamplerLinearWrap, UV1, {2}, {3});\n" + " float4 tex2 = {1}.SampleGrad(SamplerLinearWrap, UV2, {2}, {3});\n" + " float4 tex3 = {1}.SampleGrad(SamplerLinearWrap, UV3, {2}, {3});\n" + ), + uvs.Value, // {0} + texture.Value, // {1} + _ddx.Value, // {2} + _ddy.Value // {3} + ); + + // 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; } From bba136c9fed1ed0c234faf4f4ce5be013286db62 Mon Sep 17 00:00:00 2001 From: Olly Rybak Date: Mon, 26 Dec 2022 20:35:22 +1100 Subject: [PATCH 2/4] Added comment to denote which node it is --- .../Tools/MaterialGenerator/MaterialGenerator.Textures.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index d339bb72e..aae3fac4f 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -669,6 +669,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) _writer.Write(*triplanarTexture); value = result; } + // Stochastic Texture Sample case 17: { auto textureBox = node->GetBox(0); From a2f52edd4780ae91d50397b07940c250ae7e53bd Mon Sep 17 00:00:00 2001 From: Olly Rybak Date: Mon, 26 Dec 2022 20:44:53 +1100 Subject: [PATCH 3/4] Updated description & comments --- Source/Editor/Surface/Archetypes/Textures.cs | 4 ++-- .../Tools/MaterialGenerator/MaterialGenerator.Textures.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index 4d9f64085..af8ddf608 100644 --- a/Source/Editor/Surface/Archetypes/Textures.cs +++ b/Source/Editor/Surface/Archetypes/Textures.cs @@ -408,8 +408,8 @@ namespace FlaxEditor.Surface.Archetypes new NodeArchetype { TypeID = 17, - Title = "Stochastic Sample Texture", - Description = "Projects a texture using world-space coordinates instead of UVs.", + 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, 60), DefaultValues = new object[] diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp index aae3fac4f..66024540c 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -669,7 +669,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) _writer.Write(*triplanarTexture); value = result; } - // Stochastic Texture Sample + // Procedural Texture Sample case 17: { auto textureBox = node->GetBox(0); From 9099197a885c9918ff31b649e62eefa7d1aa317c Mon Sep 17 00:00:00 2001 From: Olly Rybak Date: Tue, 27 Dec 2022 22:26:01 +1100 Subject: [PATCH 4/4] Added ability to change sample method --- Source/Editor/Surface/Archetypes/Textures.cs | 72 ++++++++++++++++++- .../MaterialGenerator.Textures.cpp | 40 +++++++++-- 2 files changed, 107 insertions(+), 5 deletions(-) diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs index af8ddf608..60a43a6cb 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. /// @@ -408,19 +473,24 @@ namespace FlaxEditor.Surface.Archetypes 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, 60), + 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 66024540c..9c786a827 100644 --- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp +++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp @@ -672,6 +672,20 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) // 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); @@ -706,6 +720,23 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) 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); @@ -741,14 +772,15 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value) " 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(SamplerLinearWrap, UV1, {2}, {3});\n" - " float4 tex2 = {1}.SampleGrad(SamplerLinearWrap, UV2, {2}, {3});\n" - " float4 tex3 = {1}.SampleGrad(SamplerLinearWrap, UV3, {2}, {3});\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} + _ddy.Value, // {3} + samplerName // {4} ); // Decode normal map vector