Merge branch 'Muzz-Triplanar-Features' into 1.10
This commit is contained in:
@@ -83,6 +83,12 @@ float3 GetObjectSize(MaterialInput input)
|
||||
return float3(1, 1, 1);
|
||||
}
|
||||
|
||||
// Gets the current object scale (supports instancing)
|
||||
float3 GetObjectScale(MaterialInput input)
|
||||
{
|
||||
return float3(1, 1, 1);
|
||||
}
|
||||
|
||||
// Get the current object random value supports instancing)
|
||||
float GetPerInstanceRandom(MaterialInput input)
|
||||
{
|
||||
|
||||
@@ -207,6 +207,20 @@ float3 GetObjectSize(MaterialInput input)
|
||||
return GeometrySize * float3(world._m00, world._m11, world._m22);
|
||||
}
|
||||
|
||||
// Gets the current object scale (supports instancing)
|
||||
float3 GetObjectScale(MaterialInput input)
|
||||
{
|
||||
float4x4 world = input.Object.WorldMatrix;
|
||||
|
||||
// Extract scale from the world matrix
|
||||
float3 scale;
|
||||
scale.x = length(float3(world._11, world._12, world._13));
|
||||
scale.y = length(float3(world._21, world._22, world._23));
|
||||
scale.z = length(float3(world._31, world._32, world._33));
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
// Get the current object random value
|
||||
float GetPerInstanceRandom(MaterialInput input)
|
||||
{
|
||||
|
||||
@@ -163,6 +163,12 @@ float3 GetObjectSize(MaterialInput input)
|
||||
return float3(1, 1, 1);
|
||||
}
|
||||
|
||||
// Gets the current object scale (supports instancing)
|
||||
float3 GetObjectScale(MaterialInput input)
|
||||
{
|
||||
return float3(1, 1, 1);
|
||||
}
|
||||
|
||||
// Get the current object random value supports instancing)
|
||||
float GetPerInstanceRandom(MaterialInput input)
|
||||
{
|
||||
|
||||
@@ -42,6 +42,12 @@ struct GeometryData
|
||||
nointerpolation uint ObjectIndex : TEXCOORD8;
|
||||
};
|
||||
|
||||
float3 DecodeNormal(float4 normalMap)
|
||||
{
|
||||
float2 xy = normalMap.rg * 2.0 - 1.0;
|
||||
return float3(xy, sqrt(1.0 - saturate(dot(xy, xy))));
|
||||
}
|
||||
|
||||
// Interpolants passed from the vertex shader
|
||||
struct VertexOutput
|
||||
{
|
||||
@@ -232,6 +238,24 @@ float3 GetObjectSize(MaterialInput input)
|
||||
return input.Object.GeometrySize * float3(world._m00, world._m11, world._m22);
|
||||
}
|
||||
|
||||
// Gets the current object scale (supports instancing)
|
||||
float3 GetObjectScale(MaterialInput input)
|
||||
{
|
||||
float4x4 world = input.Object.WorldMatrix;
|
||||
|
||||
// Get the squares of the scale factors
|
||||
float scaleXSquared = dot(world[0].xyz, world[0].xyz);
|
||||
float scaleYSquared = dot(world[1].xyz, world[1].xyz);
|
||||
float scaleZSquared = dot(world[2].xyz, world[2].xyz);
|
||||
|
||||
// Take square root to get actual scales
|
||||
return float3(
|
||||
sqrt(scaleXSquared),
|
||||
sqrt(scaleYSquared),
|
||||
sqrt(scaleZSquared)
|
||||
);
|
||||
}
|
||||
|
||||
// Get the current object random value (supports instancing)
|
||||
float GetPerInstanceRandom(MaterialInput input)
|
||||
{
|
||||
|
||||
@@ -236,6 +236,12 @@ float3 GetObjectSize(MaterialInput input)
|
||||
return float3(1, 1, 1);
|
||||
}
|
||||
|
||||
// Gets the current object scale (supports instancing)
|
||||
float3 GetObjectScale(MaterialInput input)
|
||||
{
|
||||
return float3(1, 1, 1);
|
||||
}
|
||||
|
||||
// Get the current object random value
|
||||
float GetPerInstanceRandom(MaterialInput input)
|
||||
{
|
||||
|
||||
@@ -15,6 +15,29 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
[HideInEditor]
|
||||
public static class Material
|
||||
{
|
||||
/// <summary>
|
||||
/// Blend modes (each enum item value maps to box ID).
|
||||
/// </summary>
|
||||
internal enum BlendMode
|
||||
{
|
||||
Normal,
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Screen,
|
||||
Overlay,
|
||||
LinearBurn,
|
||||
LinearLight,
|
||||
Darken,
|
||||
Lighten,
|
||||
Difference,
|
||||
Exclusion,
|
||||
Divide,
|
||||
HardLight,
|
||||
PinLight,
|
||||
HardMix
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Customized <see cref="SurfaceNode"/> for main material node.
|
||||
/// </summary>
|
||||
@@ -1073,6 +1096,49 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
NodeElementArchetype.Factory.Output(0, string.Empty, typeof(Float3), 4),
|
||||
]
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 50,
|
||||
Title = "Shift HSV",
|
||||
Description = "Modifies the HSV of a color, values are from -1:1, preserves alpha",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(175, 80),
|
||||
DefaultValues =
|
||||
[
|
||||
0.0f, // For Hue (index 0)
|
||||
0.0f, // For Sat (index 1)
|
||||
0.0f, // For Val (index 2)
|
||||
],
|
||||
Elements =
|
||||
[
|
||||
NodeElementArchetype.Factory.Input(0, "RGBA", true, typeof(Float4), 0), // No default
|
||||
NodeElementArchetype.Factory.Input(1, "Hue", true, typeof(float), 1, 0), // Uses DefaultValues[0]
|
||||
NodeElementArchetype.Factory.Input(2, "Sat", true, typeof(float), 2, 1), // Uses DefaultValues[1]
|
||||
NodeElementArchetype.Factory.Input(3, "Val", true, typeof(float), 3, 2), // Uses DefaultValues[2]
|
||||
NodeElementArchetype.Factory.Output(0, "RGBA", typeof(Float4), 4),
|
||||
]
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 51,
|
||||
Title = "Color Blend",
|
||||
Description = "Blends two colors using various blend modes. Passes base alpha through.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(180, 80),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
BlendMode.Normal, // Default blend mode
|
||||
1.0f, // Default blend amount
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(1, "Base Color", true, typeof(Float4), 0),
|
||||
NodeElementArchetype.Factory.Input(2, "Blend Color", true, typeof(Float4), 1),
|
||||
NodeElementArchetype.Factory.Input(3, "Intensity", true, typeof(float), 2, 1),
|
||||
NodeElementArchetype.Factory.Enum(0, 0, 120, 0, typeof(BlendMode)), // Blend mode selector
|
||||
NodeElementArchetype.Factory.Output(0, "Result", typeof(Float4), 3),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,21 +402,29 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 16,
|
||||
Title = "World Triplanar Texture",
|
||||
Description = "Projects a texture using world-space coordinates instead of UVs.",
|
||||
Title = "Triplanar Texture",
|
||||
Description = "Projects a texture using world-space coordinates with triplanar mapping.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(240, 60),
|
||||
Size = new Float2(280, 100),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
1.0f,
|
||||
1.0f
|
||||
1.0f, // Scale
|
||||
1.0f, // Blend
|
||||
Float2.Zero, // Offset
|
||||
2, // Sampler
|
||||
false, // Local
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(Float4), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(float), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(2, "Blend", true, typeof(float), 2, 1),
|
||||
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 3)
|
||||
NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 3, 2),
|
||||
NodeElementArchetype.Factory.Output(0, "Color", typeof(Float4), 5),
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
|
||||
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4 - 1, 100, 3, typeof(CommonSamplerType)),
|
||||
NodeElementArchetype.Factory.Text(155, Surface.Constants.LayoutOffsetY * 4, "Local"),
|
||||
NodeElementArchetype.Factory.Bool(190, Surface.Constants.LayoutOffsetY * 4, 4),
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
@@ -456,7 +464,35 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
{
|
||||
NodeElementArchetype.Factory.Output(0, "UVs", typeof(Float2), 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
new NodeArchetype
|
||||
{
|
||||
TypeID = 23,
|
||||
Title = "Triplanar Normal Map",
|
||||
Description = "Projects a normal map texture using world-space coordinates with triplanar mapping.",
|
||||
Flags = NodeFlags.MaterialGraph,
|
||||
Size = new Float2(280, 100),
|
||||
DefaultValues = new object[]
|
||||
{
|
||||
1.0f, // Scale
|
||||
1.0f, // Blend
|
||||
Float2.Zero, // Offset
|
||||
2, // Sampler
|
||||
false, // Local
|
||||
},
|
||||
Elements = new[]
|
||||
{
|
||||
NodeElementArchetype.Factory.Input(0, "Texture", true, typeof(FlaxEngine.Object), 0),
|
||||
NodeElementArchetype.Factory.Input(1, "Scale", true, typeof(float), 1, 0),
|
||||
NodeElementArchetype.Factory.Input(2, "Blend", true, typeof(float), 2, 1),
|
||||
NodeElementArchetype.Factory.Input(3, "Offset", true, typeof(Float2), 3, 2),
|
||||
NodeElementArchetype.Factory.Output(0, "Vector", typeof(Float3), 5),
|
||||
NodeElementArchetype.Factory.Text(0, Surface.Constants.LayoutOffsetY * 4, "Sampler"),
|
||||
NodeElementArchetype.Factory.ComboBox(50, Surface.Constants.LayoutOffsetY * 4 - 1, 100, 3, typeof(CommonSamplerType)),
|
||||
NodeElementArchetype.Factory.Text(155, Surface.Constants.LayoutOffsetY * 4, "Local"),
|
||||
NodeElementArchetype.Factory.Bool(190, Surface.Constants.LayoutOffsetY * 4, 4),
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1499,12 +1499,12 @@ namespace FlaxEditor.Surface.Archetypes
|
||||
2,
|
||||
|
||||
// Stop 0
|
||||
0.1f,
|
||||
Color.CornflowerBlue,
|
||||
0.05f,
|
||||
Color.Black,
|
||||
|
||||
// Stop 1
|
||||
0.9f,
|
||||
Color.GreenYellow,
|
||||
0.95f,
|
||||
Color.White,
|
||||
|
||||
// Empty stops 2-7
|
||||
0.0f, Color.Black,
|
||||
|
||||
@@ -147,6 +147,11 @@ namespace FlaxEditor.Surface.Elements
|
||||
{
|
||||
value = (double)toSet;
|
||||
}
|
||||
else if (parentNode.GroupArchetype.GroupID != 2)
|
||||
{
|
||||
// Per-component editing is used only by nodes from Constant group, otherwise use float
|
||||
value = toSet;
|
||||
}
|
||||
else if (value is Vector2 asVector2)
|
||||
{
|
||||
if (arch.BoxID == 0)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "MaterialGenerator.h"
|
||||
#include "Engine/Content/Assets/MaterialFunction.h"
|
||||
#include "Engine/Visject/ShaderStringBuilder.h"
|
||||
|
||||
void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
|
||||
{
|
||||
@@ -643,6 +644,136 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
|
||||
value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, {1}, {2})"), innerMask.Value, outerMask.Value, mask.Value), node);
|
||||
break;
|
||||
}
|
||||
// Shift HSV
|
||||
case 50:
|
||||
{
|
||||
const auto color = tryGetValue(node->GetBox(0), Value::One).AsFloat4();
|
||||
if (!color.IsValid())
|
||||
{
|
||||
value = Value::Zero;
|
||||
break;
|
||||
}
|
||||
const auto hue = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat();
|
||||
const auto saturation = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat();
|
||||
const auto val = tryGetValue(node->GetBox(3), node->Values[2]).AsFloat();
|
||||
auto result = writeLocal(Value::InitForZero(ValueType::Float4), node);
|
||||
|
||||
const String hsvAdjust = ShaderStringBuilder()
|
||||
.Code(TEXT(R"(
|
||||
{
|
||||
float3 rgb = %COLOR%.rgb;
|
||||
float minc = min(min(rgb.r, rgb.g), rgb.b);
|
||||
float maxc = max(max(rgb.r, rgb.g), rgb.b);
|
||||
float delta = maxc - minc;
|
||||
|
||||
float3 grb = float3(rgb.g - rgb.b, rgb.r - rgb.b, rgb.b - rgb.g);
|
||||
float3 cmps = float3(maxc == rgb.r, maxc == rgb.g, maxc == rgb.b);
|
||||
float h = dot(grb * rcp(delta), cmps);
|
||||
h += 6.0 * (h < 0);
|
||||
h = frac(h * (1.0/6.0) * step(0, delta) + %HUE% * 0.5);
|
||||
|
||||
float s = saturate(delta * rcp(maxc + step(maxc, 0)) * (1.0 + %SATURATION%));
|
||||
float v = maxc * (1.0 + %VALUE%);
|
||||
|
||||
float3 k = float3(1.0, 2.0 / 3.0, 1.0 / 3.0);
|
||||
%RESULT% = float4(v * lerp(1.0, saturate(abs(frac(h + k) * 6.0 - 3.0) - 1.0), s), %COLOR%.a);
|
||||
}
|
||||
)"))
|
||||
.Replace(TEXT("%COLOR%"), color.Value)
|
||||
.Replace(TEXT("%HUE%"), hue.Value)
|
||||
.Replace(TEXT("%SATURATION%"), saturation.Value)
|
||||
.Replace(TEXT("%VALUE%"), val.Value)
|
||||
.Replace(TEXT("%RESULT%"), result.Value)
|
||||
.Build();
|
||||
_writer.Write(*hsvAdjust);
|
||||
value = result;
|
||||
break;
|
||||
}
|
||||
// Color Blend
|
||||
case 51:
|
||||
{
|
||||
const auto baseColor = tryGetValue(node->GetBox(0), Value::One).AsFloat4();
|
||||
const auto blendColor = tryGetValue(node->GetBox(1), Value::One).AsFloat4();
|
||||
const auto blendAmount = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat();
|
||||
const auto blendMode = node->Values[0].AsInt;
|
||||
auto result = writeLocal(Value::InitForZero(ValueType::Float4), node);
|
||||
|
||||
String blendFormula;
|
||||
switch (blendMode)
|
||||
{
|
||||
case 0: // Normal
|
||||
blendFormula = TEXT("blend");
|
||||
break;
|
||||
case 1: // Add
|
||||
blendFormula = TEXT("base + blend");
|
||||
break;
|
||||
case 2: // Subtract
|
||||
blendFormula = TEXT("base - blend");
|
||||
break;
|
||||
case 3: // Multiply
|
||||
blendFormula = TEXT("base * blend");
|
||||
break;
|
||||
case 4: // Screen
|
||||
blendFormula = TEXT("1.0 - (1.0 - base) * (1.0 - blend)");
|
||||
break;
|
||||
case 5: // Overlay
|
||||
blendFormula = TEXT("base <= 0.5 ? 2.0 * base * blend : 1.0 - 2.0 * (1.0 - base) * (1.0 - blend)");
|
||||
break;
|
||||
case 6: // Linear Burn
|
||||
blendFormula = TEXT("base + blend - 1.0");
|
||||
break;
|
||||
case 7: // Linear Light
|
||||
blendFormula = TEXT("blend < 0.5 ? max(base + (2.0 * blend) - 1.0, 0.0) : min(base + 2.0 * (blend - 0.5), 1.0)");
|
||||
break;
|
||||
case 8: // Darken
|
||||
blendFormula = TEXT("min(base, blend)");
|
||||
break;
|
||||
case 9: // Lighten
|
||||
blendFormula = TEXT("max(base, blend)");
|
||||
break;
|
||||
case 10: // Difference
|
||||
blendFormula = TEXT("abs(base - blend)");
|
||||
break;
|
||||
case 11: // Exclusion
|
||||
blendFormula = TEXT("base + blend - (2.0 * base * blend)");
|
||||
break;
|
||||
case 12: // Divide
|
||||
blendFormula = TEXT("base / (blend + 0.000001)");
|
||||
break;
|
||||
case 13: // Hard Light
|
||||
blendFormula = TEXT("blend <= 0.5 ? 2.0 * base * blend : 1.0 - 2.0 * (1.0 - base) * (1.0 - blend)");
|
||||
break;
|
||||
case 14: // Pin Light
|
||||
blendFormula = TEXT("blend <= 0.5 ? min(base, 2.0 * blend) : max(base, 2.0 * (blend - 0.5))");
|
||||
break;
|
||||
case 15: // Hard Mix
|
||||
blendFormula = TEXT("step(1.0 - base, blend)");
|
||||
break;
|
||||
default:
|
||||
blendFormula = TEXT("blend");
|
||||
break;
|
||||
}
|
||||
|
||||
const String blendImpl = ShaderStringBuilder()
|
||||
.Code(TEXT(R"(
|
||||
{
|
||||
float3 base = %BASE%.rgb;
|
||||
float3 blend = %BLEND%.rgb;
|
||||
float alpha = %BASE%.a;
|
||||
float3 final = %BLEND_FORMULA%;
|
||||
%RESULT% = float4(lerp(base, final, %AMOUNT%), alpha);
|
||||
}
|
||||
)"))
|
||||
.Replace(TEXT("%BASE%"), baseColor.Value)
|
||||
.Replace(TEXT("%BLEND%"), blendColor.Value)
|
||||
.Replace(TEXT("%AMOUNT%"), blendAmount.Value)
|
||||
.Replace(TEXT("%BLEND_FORMULA%"), *blendFormula)
|
||||
.Replace(TEXT("%RESULT%"), result.Value)
|
||||
.Build();
|
||||
_writer.Write(*blendImpl);
|
||||
value = result;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,26 @@
|
||||
#if COMPILE_WITH_MATERIAL_GRAPH
|
||||
|
||||
#include "MaterialGenerator.h"
|
||||
#include "Engine/Visject/ShaderStringBuilder.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
enum CommonSamplerType
|
||||
{
|
||||
LinearClamp = 0,
|
||||
PointClamp = 1,
|
||||
LinearWrap = 2,
|
||||
PointWrap = 3,
|
||||
TextureGroup = 4,
|
||||
};
|
||||
const Char* SamplerNames[]
|
||||
{
|
||||
TEXT("SamplerLinearClamp"),
|
||||
TEXT("SamplerPointClamp"),
|
||||
TEXT("SamplerLinearWrap"),
|
||||
TEXT("SamplerPointWrap"),
|
||||
};
|
||||
};
|
||||
|
||||
MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture)
|
||||
{
|
||||
@@ -491,22 +511,6 @@ 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("SamplerPointClamp"),
|
||||
TEXT("SamplerLinearWrap"),
|
||||
TEXT("SamplerPointWrap"),
|
||||
};
|
||||
|
||||
// Get input boxes
|
||||
auto textureBox = node->GetBox(0);
|
||||
auto uvsBox = node->GetBox(1);
|
||||
@@ -645,9 +649,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
|
||||
// Decode normal map vector
|
||||
if (isNormalMap)
|
||||
{
|
||||
// TODO: maybe we could use helper function for UnpackNormalTexture() and unify unpacking?
|
||||
_writer.Write(TEXT("\t{0}.xy = {0}.xy * 2.0 - 1.0;\n"), textureBox->Cache.Value);
|
||||
_writer.Write(TEXT("\t{0}.z = sqrt(saturate(1.0 - dot({0}.xy, {0}.xy)));\n"), textureBox->Cache.Value);
|
||||
_writer.Write(TEXT("\t{0}.xyz = UnpackNormalMap({0}.xy);\n"), textureBox->Cache.Value);
|
||||
}
|
||||
|
||||
value = textureBox->Cache;
|
||||
@@ -690,12 +692,10 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
|
||||
value = box == gradientBox ? gradient : distance;
|
||||
break;
|
||||
}
|
||||
// World Triplanar Texture
|
||||
// Triplanar Texture
|
||||
case 16:
|
||||
{
|
||||
auto textureBox = node->GetBox(0);
|
||||
auto scaleBox = node->GetBox(1);
|
||||
auto blendBox = node->GetBox(2);
|
||||
if (!textureBox->HasConnection())
|
||||
{
|
||||
// No texture to sample
|
||||
@@ -704,28 +704,62 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
|
||||
}
|
||||
const bool canUseSample = CanUseSample(_treeType);
|
||||
const auto texture = eatBox(textureBox->GetParent<Node>(), textureBox->FirstConnection());
|
||||
const auto scale = tryGetValue(scaleBox, node->Values[0]).AsFloat4();
|
||||
const auto blend = tryGetValue(blendBox, node->Values[1]).AsFloat();
|
||||
const auto scale = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat();
|
||||
const auto blend = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat();
|
||||
const auto offset = tryGetValue(node->GetBox(3), node->Values[2]).AsFloat2();
|
||||
const bool local = node->Values.Count() >= 5 ? node->Values[4].AsBool : false;
|
||||
|
||||
const Char* samplerName;
|
||||
const int32 samplerIndex = node->Values[3].AsInt;
|
||||
if (samplerIndex == TextureGroup)
|
||||
{
|
||||
auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[3].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);
|
||||
const String triplanarTexture = String::Format(TEXT(
|
||||
" {{\n"
|
||||
" float tiling = {1} * 0.001f;\n"
|
||||
" float3 worldPos = (input.WorldPosition.xyz + GetLargeWorldsTileOffset(1.0f / tiling)) * tiling;\n"
|
||||
" float3 normal = abs(input.TBN[2]);\n"
|
||||
" normal = pow(normal, {2});\n"
|
||||
" normal = normalize(normal);\n"
|
||||
" {3} += {0}.{4}(SamplerLinearWrap, worldPos.yz{5}) * normal.x;\n"
|
||||
" {3} += {0}.{4}(SamplerLinearWrap, worldPos.xz{5}) * normal.y;\n"
|
||||
" {3} += {0}.{4}(SamplerLinearWrap, worldPos.xy{5}) * normal.z;\n"
|
||||
" }}\n"
|
||||
),
|
||||
texture.Value, // {0}
|
||||
scale.Value, // {1}
|
||||
blend.Value, // {2}
|
||||
result.Value, // {3}
|
||||
canUseSample ? TEXT("Sample") : TEXT("SampleLevel"), // {4}
|
||||
canUseSample ? TEXT("") : TEXT(", 0") // {5}
|
||||
);
|
||||
|
||||
const String triplanarTexture = ShaderStringBuilder()
|
||||
.Code(TEXT(R"(
|
||||
{
|
||||
// Get world position and normal
|
||||
float tiling = %SCALE% * 0.001f;
|
||||
float3 position = ((%POSITION%) + GetLargeWorldsTileOffset(1.0f / tiling)) * tiling;
|
||||
float3 normal = normalize(%NORMAL%);
|
||||
|
||||
// Compute triplanar blend weights using power distribution
|
||||
float3 blendWeights = pow(abs(normal), %BLEND%);
|
||||
blendWeights /= dot(blendWeights, float3(1, 1, 1));
|
||||
|
||||
// Sample projections with proper scaling and offset
|
||||
float4 xProjection = %TEXTURE%.%SAMPLE%(%SAMPLER%, position.yz + %OFFSET%%SAMPLE_ARGS%);
|
||||
float4 yProjection = %TEXTURE%.%SAMPLE%(%SAMPLER%, position.xz + %OFFSET%%SAMPLE_ARGS%);
|
||||
float4 zProjection = %TEXTURE%.%SAMPLE%(%SAMPLER%, position.xy + %OFFSET%%SAMPLE_ARGS%);
|
||||
|
||||
// Blend projections using computed weights
|
||||
%RESULT% = xProjection * blendWeights.x + yProjection * blendWeights.y + zProjection * blendWeights.z;
|
||||
}
|
||||
)"))
|
||||
.Replace(TEXT("%TEXTURE%"), texture.Value)
|
||||
.Replace(TEXT("%SCALE%"), scale.Value)
|
||||
.Replace(TEXT("%BLEND%"), blend.Value)
|
||||
.Replace(TEXT("%OFFSET%"), offset.Value)
|
||||
.Replace(TEXT("%RESULT%"), result.Value)
|
||||
.Replace(TEXT("%POSITION%"), local ? TEXT("TransformWorldVectorToLocal(input, input.WorldPosition - GetObjectPosition(input)) / GetObjectScale(input)") : TEXT("input.WorldPosition"))
|
||||
.Replace(TEXT("%NORMAL%"), local ? TEXT("TransformWorldVectorToLocal(input, input.TBN[2])") : TEXT("input.TBN[2]"))
|
||||
.Replace(TEXT("%SAMPLER%"), samplerName)
|
||||
.Replace(TEXT("%SAMPLE%"), canUseSample ? TEXT("Sample") : TEXT("SampleLevel"))
|
||||
.Replace(TEXT("%SAMPLE_ARGS%"), canUseSample ? TEXT("") : TEXT(", 0")) // Sample mip0 when cannot get auto ddx/ddy in Vertex Shader
|
||||
.Build();
|
||||
_writer.Write(*triplanarTexture);
|
||||
value = result;
|
||||
break;
|
||||
@@ -747,6 +781,96 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
|
||||
value = output;
|
||||
break;
|
||||
}
|
||||
// Triplanar Normal Map
|
||||
case 23:
|
||||
{
|
||||
auto textureBox = node->GetBox(0);
|
||||
if (!textureBox->HasConnection())
|
||||
{
|
||||
// No texture to sample
|
||||
value = Value::Zero;
|
||||
break;
|
||||
}
|
||||
const bool canUseSample = CanUseSample(_treeType);
|
||||
const auto texture = eatBox(textureBox->GetParent<Node>(), textureBox->FirstConnection());
|
||||
const auto scale = tryGetValue(node->GetBox(1), node->Values[0]).AsFloat();
|
||||
const auto blend = tryGetValue(node->GetBox(2), node->Values[1]).AsFloat();
|
||||
const auto offset = tryGetValue(node->GetBox(3), node->Values[2]).AsFloat2();
|
||||
const bool local = node->Values.Count() >= 5 ? node->Values[4].AsBool : false;
|
||||
|
||||
const Char* samplerName;
|
||||
const int32 samplerIndex = node->Values[3].AsInt;
|
||||
if (samplerIndex == TextureGroup)
|
||||
{
|
||||
auto& textureGroupSampler = findOrAddTextureGroupSampler(node->Values[3].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::Float3), node);
|
||||
|
||||
// Reference: https://bgolus.medium.com/normal-mapping-for-a-triplanar-shader-10bf39dca05a
|
||||
const String triplanarNormalMap = ShaderStringBuilder()
|
||||
.Code(TEXT(R"(
|
||||
{
|
||||
// Get world position and normal
|
||||
float tiling = %SCALE% * 0.001f;
|
||||
float3 position = ((%POSITION%) + GetLargeWorldsTileOffset(1.0f / tiling)) * tiling;
|
||||
float3 normal = normalize(%NORMAL%);
|
||||
|
||||
// Compute triplanar blend weights using power distribution
|
||||
float3 blendWeights = pow(abs(normal), %BLEND%);
|
||||
blendWeights /= dot(blendWeights, float3(1, 1, 1));
|
||||
|
||||
// Unpack normal maps
|
||||
float3 tnormalX = UnpackNormalMap(%TEXTURE%.%SAMPLE%(%SAMPLER%, position.yz + %OFFSET%%SAMPLE_ARGS%).rg);
|
||||
float3 tnormalY = UnpackNormalMap(%TEXTURE%.%SAMPLE%(%SAMPLER%, position.xz + %OFFSET%%SAMPLE_ARGS%).rg);
|
||||
float3 tnormalZ = UnpackNormalMap(%TEXTURE%.%SAMPLE%(%SAMPLER%, position.xy + %OFFSET%%SAMPLE_ARGS%).rg);
|
||||
|
||||
// Apply proper whiteout blend
|
||||
normal = normalize(input.TBN[2]);
|
||||
float3 axisSign = sign(normal);
|
||||
float2 sumX = tnormalX.xy + normal.zy;
|
||||
float2 sumY = tnormalY.xy + normal.xz;
|
||||
float2 sumZ = tnormalZ.xy + normal.xy;
|
||||
tnormalX = float3(sumX, sqrt(1.0 - saturate(dot(sumX, sumX))) * axisSign.x);
|
||||
tnormalY = float3(sumY, sqrt(1.0 - saturate(dot(sumY, sumY))) * axisSign.y);
|
||||
tnormalZ = float3(sumZ, sqrt(1.0 - saturate(dot(sumZ, sumZ))) * axisSign.z);
|
||||
|
||||
// Blend the normal maps using the blend weights
|
||||
float3 blendedNormal = normalize(
|
||||
tnormalX.zyx * blendWeights.x +
|
||||
tnormalY.xzy * blendWeights.y +
|
||||
tnormalZ.xyz * blendWeights.z
|
||||
);
|
||||
|
||||
// Transform to tangent space
|
||||
%RESULT% = normalize(TransformWorldVectorToTangent(input, blendedNormal));
|
||||
}
|
||||
)"))
|
||||
.Replace(TEXT("%TEXTURE%"), texture.Value)
|
||||
.Replace(TEXT("%SCALE%"), scale.Value)
|
||||
.Replace(TEXT("%BLEND%"), blend.Value)
|
||||
.Replace(TEXT("%OFFSET%"), offset.Value)
|
||||
.Replace(TEXT("%RESULT%"), result.Value)
|
||||
.Replace(TEXT("%POSITION%"), local ? TEXT("TransformWorldVectorToLocal(input, input.WorldPosition - GetObjectPosition(input)) / GetObjectScale(input)") : TEXT("input.WorldPosition"))
|
||||
.Replace(TEXT("%NORMAL%"), local ? TEXT("TransformWorldVectorToLocal(input, input.TBN[2])") : TEXT("input.TBN[2]"))
|
||||
.Replace(TEXT("%SAMPLER%"), samplerName)
|
||||
.Replace(TEXT("%SAMPLE%"), canUseSample ? TEXT("Sample") : TEXT("SampleLevel"))
|
||||
.Replace(TEXT("%SAMPLE_ARGS%"), canUseSample ? TEXT("") : TEXT(", 0")) // Sample mip0 when cannot get auto ddx/ddy in Vertex Shader
|
||||
.Build();
|
||||
_writer.Write(*triplanarNormalMap);
|
||||
value = result;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
34
Source/Engine/Visject/ShaderStringBuilder.cpp
Normal file
34
Source/Engine/Visject/ShaderStringBuilder.cpp
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#include "ShaderStringBuilder.h"
|
||||
|
||||
ShaderStringBuilder& ShaderStringBuilder::Code(const Char* shaderCode)
|
||||
{
|
||||
_code = shaderCode;
|
||||
return *this;
|
||||
}
|
||||
|
||||
ShaderStringBuilder& ShaderStringBuilder::Replace(const String& key, const String& value)
|
||||
{
|
||||
_replacements.Add(Pair<String, String>(key, value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
String ShaderStringBuilder::Build() const
|
||||
{
|
||||
String result = _code;
|
||||
for (const auto& replacement : _replacements)
|
||||
{
|
||||
const auto& key = replacement.First;
|
||||
const auto& value = replacement.Second;
|
||||
int32 position = 0;
|
||||
while ((position = result.Find(key)) != -1)
|
||||
{
|
||||
result = String::Format(TEXT("{0}{1}{2}"),
|
||||
StringView(result.Get(), position),
|
||||
value,
|
||||
StringView(result.Get() + position + key.Length()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
21
Source/Engine/Visject/ShaderStringBuilder.h
Normal file
21
Source/Engine/Visject/ShaderStringBuilder.h
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Types/Pair.h"
|
||||
#include "Engine/Core/Types/String.h"
|
||||
#include "Engine/Core/Types/StringView.h"
|
||||
#include "Engine/Core/Collections/Array.h"
|
||||
|
||||
// Helper utility for shader source code formatting.
|
||||
class ShaderStringBuilder
|
||||
{
|
||||
private:
|
||||
String _code;
|
||||
Array<Pair<String, String>> _replacements;
|
||||
|
||||
public:
|
||||
ShaderStringBuilder& Code(const Char* shaderCode);
|
||||
ShaderStringBuilder& Replace(const String& key, const String& value);
|
||||
String Build() const;
|
||||
};
|
||||
@@ -261,6 +261,14 @@ struct GBufferOutput
|
||||
float4 RT3 : SV_Target4;
|
||||
};
|
||||
|
||||
float3 UnpackNormalMap(float2 value)
|
||||
{
|
||||
float3 normal;
|
||||
normal.xy = value * 2.0 - 1.0;
|
||||
normal.z = sqrt(saturate(1.0 - dot(normal.xy, normal.xy)));
|
||||
return normal;
|
||||
}
|
||||
|
||||
float3x3 CalcTangentBasis(float3 normal, float3 pos, float2 uv)
|
||||
{
|
||||
// References:
|
||||
|
||||
Reference in New Issue
Block a user