diff --git a/Content/Editor/MaterialTemplates/GUI.shader b/Content/Editor/MaterialTemplates/GUI.shader
index c15b7f20f..0fd68d4c9 100644
--- a/Content/Editor/MaterialTemplates/GUI.shader
+++ b/Content/Editor/MaterialTemplates/GUI.shader
@@ -2,6 +2,7 @@
// Version: @0
#define MATERIAL 1
+#define MaterialTextureMipBias 0.0f
@3
#include "./Flax/Common.hlsl"
#include "./Flax/MaterialCommon.hlsl"
diff --git a/Content/Editor/MaterialTemplates/PostProcess.shader b/Content/Editor/MaterialTemplates/PostProcess.shader
index 1ef747ecb..6b63ef2e9 100644
--- a/Content/Editor/MaterialTemplates/PostProcess.shader
+++ b/Content/Editor/MaterialTemplates/PostProcess.shader
@@ -2,6 +2,7 @@
// Version: @0
#define MATERIAL 1
+#define MaterialTextureMipBias 0.0f
@3
#include "./Flax/Common.hlsl"
diff --git a/Source/Editor/Surface/Archetypes/Textures.cs b/Source/Editor/Surface/Archetypes/Textures.cs
index 929cb153b..7ce269fb7 100644
--- a/Source/Editor/Surface/Archetypes/Textures.cs
+++ b/Source/Editor/Surface/Archetypes/Textures.cs
@@ -316,7 +316,7 @@ namespace FlaxEditor.Surface.Archetypes
DefaultValues = new object[]
{
(int)CommonSamplerType.LinearClamp, // Sampler
- -1.0f, // Level
+ -1.0f, // Level (-1 when unused)
0, // Texture Group
},
Elements = new[]
@@ -476,7 +476,7 @@ namespace FlaxEditor.Surface.Archetypes
DefaultValues = new object[]
{
(int)CommonSamplerType.LinearWrap, // Sampler
- -1.0f, // Level
+ -1.0f, // Level (-1 when unused)
0, // Texture Group
},
Elements = new[]
diff --git a/Source/Engine/Graphics/Materials/MaterialShader.cpp b/Source/Engine/Graphics/Materials/MaterialShader.cpp
index 51c6d49fd..c1faa53da 100644
--- a/Source/Engine/Graphics/Materials/MaterialShader.cpp
+++ b/Source/Engine/Graphics/Materials/MaterialShader.cpp
@@ -37,7 +37,8 @@ GPU_CB_STRUCT(MaterialShaderDataPerView {
Float4 TemporalAAJitter;
Float3 LargeWorldsChunkIndex;
float LargeWorldsChunkSize;
- Float3 ViewPadding0;
+ float MaterialTextureMipBias;
+ float ViewPadding0;
float TestValue;
float ScaledTimeParam;
});
@@ -87,6 +88,7 @@ void IMaterial::BindParameters::BindViewData()
cb.TemporalAAJitter = view.TemporalAAJitter;
cb.LargeWorldsChunkIndex = LargeWorlds::Enable ? (Float3)Int3(view.Origin / LargeWorlds::ChunkSize) : Float3::Zero;
cb.LargeWorldsChunkSize = LargeWorlds::ChunkSize;
+ cb.MaterialTextureMipBias = RenderContext.List->Setup.MaterialTextureMipBias;
cb.TestValue = Graphics::TestValue;
// Update constants
diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h
index c14c54851..fa3d47629 100644
--- a/Source/Engine/Graphics/Materials/MaterialShader.h
+++ b/Source/Engine/Graphics/Materials/MaterialShader.h
@@ -10,7 +10,7 @@
///
/// Current materials shader version.
///
-#define MATERIAL_GRAPH_VERSION 185
+#define MATERIAL_GRAPH_VERSION 186
class Material;
class GPUShader;
diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h
index 73daf937e..b350419e7 100644
--- a/Source/Engine/Graphics/RenderTask.h
+++ b/Source/Engine/Graphics/RenderTask.h
@@ -286,6 +286,11 @@ public:
///
API_FIELD() float RenderScale = 1.0f;
+ ///
+ /// The texture mip level bias for materials. Positive values make textures appear blurrier; negative values sharpen.
+ ///
+ API_FIELD() float MaterialTextureMipBias = 0.0f;
+
///
/// The image resolution upscale location within rendering pipeline. Unused if RenderingPercentage is 1.
///
diff --git a/Source/Engine/Renderer/RenderSetup.h b/Source/Engine/Renderer/RenderSetup.h
index bba7738b5..b22b00cc8 100644
--- a/Source/Engine/Renderer/RenderSetup.h
+++ b/Source/Engine/Renderer/RenderSetup.h
@@ -11,6 +11,7 @@ struct FLAXENGINE_API RenderSetup
{
RenderingUpscaleLocation UpscaleLocation = RenderingUpscaleLocation::DuringAntiAliasing;
float RenderScale = 1.0f;
+ float MaterialTextureMipBias = 0.0f;
bool UseShadows = false;
bool UseMotionVectors = false;
bool UseTemporalAAJitter = false;
diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp
index 7b3f64727..e8f3bcb6a 100644
--- a/Source/Engine/Renderer/Renderer.cpp
+++ b/Source/Engine/Renderer/Renderer.cpp
@@ -426,6 +426,11 @@ PRAGMA_ENABLE_DEPRECATION_WARNINGS
setup.UseGlobalSurfaceAtlas;
setup.UseVolumetricFog = (renderContext.View.Flags & ViewFlags::Fog) != ViewFlags::None;
+ // Calculate material texture mip bias
+ setup.MaterialTextureMipBias = renderContext.Task->MaterialTextureMipBias;
+ if (setup.RenderScale < 1.0f) // Increase textures sharpness when upscaling (eg. -1.0 bias when using 2x upscaling)
+ setup.MaterialTextureMipBias += Math::Log2(setup.RenderScale);
+
// Disable TAA jitter in debug modes
switch (renderContext.View.Mode)
{
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp
index 8def3ce62..d24cf5117 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Parameters.cpp
@@ -71,11 +71,13 @@ void MaterialGenerator::ProcessGroupParameters(Box* box, Node* node, Value& valu
case MaterialParameterType::CubeTexture:
case MaterialParameterType::NormalMap:
case MaterialParameterType::Texture:
+ sampleTexture(node, value, box, param, true);
+ break;
case MaterialParameterType::GPUTextureArray:
case MaterialParameterType::GPUTextureCube:
case MaterialParameterType::GPUTextureVolume:
case MaterialParameterType::GPUTexture:
- sampleTexture(node, value, box, param);
+ sampleTexture(node, value, box, param, false);
break;
default: CRASH;
break;
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
index e09b1e9ad..ee47db85e 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Textures.cpp
@@ -24,7 +24,7 @@ namespace
};
};
-MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture)
+MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture, bool useGlobalMipBias)
{
ASSERT(texture && box);
const auto parent = box->GetParent>();
@@ -60,15 +60,27 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
// Check if hasn't been sampled during that tree eating
if (valueBox->Cache.IsInvalid())
{
- bool canUseSample = CanUseSample(_treeType);
+ bool useLevel = !CanUseSample(_treeType), useBias = false;
String mipLevel = TEXT("0");
const auto layer = GetRootLayer();
if (layer && layer->Domain == MaterialDomain::Decal && _treeType == MaterialTreeType::PixelShader)
{
// Decals use computed mip level due to ddx/ddy being unreliable
- canUseSample = false;
+ useLevel = true;
mipLevel = String::Format(TEXT("CalculateTextureMipmap(input, {})"), texture->ShaderName);
}
+ if (useGlobalMipBias)
+ {
+ if (useLevel)
+ {
+ mipLevel += TEXT(" + MaterialTextureMipBias");
+ }
+ else
+ {
+ useBias = true;
+ mipLevel = TEXT("MaterialTextureMipBias");
+ }
+ }
// Check if use custom UVs
String uv;
@@ -93,12 +105,8 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
uv = use3dUVs ? TEXT("float3(input.TexCoord.xy, 0)") : TEXT("input.TexCoord.xy");
}
- // Select sampler
- // TODO: add option for texture groups and per texture options like wrap mode etc.
- // TODO: changing texture sampler option
- const Char* sampler = TEXT("SamplerLinearWrap");
-
// Sample texture
+ const Char* sampler = TEXT("SamplerLinearWrap");
if (texture->AsInteger == (int32)MaterialSceneTextures::SceneDepth)
{
// Sample depth buffer
@@ -107,9 +115,8 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
}
else if (isNormalMap)
{
- const Char* format = canUseSample ? TEXT("{0}.Sample({1}, {2}).xyz") : TEXT("{0}.SampleLevel({1}, {2}, {3}).xyz");
-
// Sample encoded normal map
+ const Char* format = useLevel ? TEXT("{0}.SampleLevel({1}, {2}, {3}).xyz") : (useBias ? TEXT("{0}.SampleBias({1}, {2}, {3}).xyz") : TEXT("{0}.Sample({1}, {2}).xyz"));
const String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, mipLevel);
const auto normalVector = writeLocal(VariantType::Float3, sampledValue, parent);
@@ -120,27 +127,8 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
}
else
{
- // Select format string based on texture type
- const Char* format;
- /*if (isCubemap)
- {
- MISSING_CODE("sampling cube maps and 3d texture in material generator");
- //format = TEXT("SAMPLE_CUBEMAP({0}, {1})");
- }
- else*/
- {
- /*if (useCustomUVs)
- {
- createGradients(writer, parent);
- format = TEXT("SAMPLE_TEXTURE_GRAD({0}, {1}, {2}, {3})");
- }
- else*/
- {
- format = canUseSample ? TEXT("{0}.Sample({1}, {2})") : TEXT("{0}.SampleLevel({1}, {2}, {3})");
- }
- }
-
// Sample texture
+ const Char* format = useLevel ? TEXT("{0}.SampleLevel({1}, {2}, {3})") : (useBias ? TEXT("{0}.SampleBias({1}, {2}, {3})") : TEXT("{0}.Sample({1}, {2})"));
String sampledValue = String::Format(format, texture->ShaderName, sampler, uv, mipLevel);
valueBox->Cache = writeLocal(VariantType::Float4, sampledValue, parent);
}
@@ -149,9 +137,9 @@ MaterialValue* MaterialGenerator::sampleTextureRaw(Node* caller, Value& value, B
return &valueBox->Cache;
}
-void MaterialGenerator::sampleTexture(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture)
+void MaterialGenerator::sampleTexture(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture, bool useGlobalMipBias)
{
- const auto sample = sampleTextureRaw(caller, value, box, texture);
+ const auto sample = sampleTextureRaw(caller, value, box, texture, useGlobalMipBias);
if (sample == nullptr)
return;
@@ -202,16 +190,26 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
{
// Texture
case 1:
+ // Cube Texture
+ case 3:
+ // Normal Map
+ case 4:
{
// Check if texture has been selected
Guid textureId = (Guid)node->Values[0];
if (textureId.IsValid())
{
// Get or create parameter for that texture
- auto param = findOrAddTexture(textureId);
+ SerializedMaterialParam param;
+ if (node->TypeID == 1) // Texture
+ param = findOrAddTexture(textureId);
+ else if (node->TypeID == 3) // Cube Texture
+ param = findOrAddCubeTexture(textureId);
+ else if (node->TypeID == 4) // Normal Map
+ param = findOrAddNormalMap(textureId);
// Sample texture
- sampleTexture(node, value, box, ¶m);
+ sampleTexture(node, value, box, ¶m, true);
}
else
{
@@ -236,46 +234,6 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
}
break;
}
- // Cube Texture
- case 3:
- {
- // Check if texture has been selected
- Guid textureId = (Guid)node->Values[0];
- if (textureId.IsValid())
- {
- // Get or create parameter for that cube texture
- auto param = findOrAddCubeTexture(textureId);
-
- // Sample texture
- sampleTexture(node, value, box, ¶m);
- }
- else
- {
- // Use default value
- value = Value::Zero;
- }
- break;
- }
- // Normal Map
- case 4:
- {
- // Check if texture has been selected
- Guid textureId = (Guid)node->Values[0];
- if (textureId.IsValid())
- {
- // Get or create parameter for that texture
- auto param = findOrAddNormalMap(textureId);
-
- // Sample texture
- sampleTexture(node, value, box, ¶m);
- }
- else
- {
- // Use default value
- value = Value::Zero;
- }
- break;
- }
// Parallax Occlusion Mapping
case 5:
{
@@ -327,10 +285,11 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
" float currRayHeight = 1.0;\n"
" float lastSampledHeight = 1;\n"
" int currSample = 0;\n"
+ " float mipBiasDerivScale = pow(2, MaterialTextureMipBias);\n"
" while (currSample < (int)numSamples)\n"
" {{\n"
- " float currSampledHeight = {1}.SampleGrad(SamplerLinearWrap, {10} + currOffset, {5}, {6}){7};\n"
+ " float currSampledHeight = {1}.SampleGrad(SamplerLinearWrap, {10} + currOffset, {5} * mipBiasDerivScale, {6} * mipBiasDerivScale){7};\n"
" if (currSampledHeight > currRayHeight)\n"
" {{\n"
@@ -584,6 +543,7 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
const bool isVolume = textureParam->Type == MaterialParameterType::GPUTextureVolume;
const bool isNormalMap = textureParam->Type == MaterialParameterType::NormalMap;
const bool use3dUVs = isCubemap || isArray || isVolume;
+ const bool useGlobalMipBias = true; // Could be exposed as node option (separate check or within new set of CommonSamplerType values)
uvs = Value::Cast(uvs, use3dUVs ? VariantType::Float3 : VariantType::Float2);
// Get other inputs
@@ -613,7 +573,16 @@ void MaterialGenerator::ProcessGroupTextures(Box* box, Node* node, Value& value)
{
// Sample Texture
const Char* format;
- if (useLevel || !canUseSample)
+ if (useGlobalMipBias)
+ {
+ if (useLevel && useOffset)
+ format = TEXT("{0}.SampleLevel({1}, {2}, {3} + MaterialTextureMipBias, {4})");
+ else if (useOffset)
+ format = TEXT("{0}.SampleBias({1}, {2}, MaterialTextureMipBias, {4})");
+ else
+ format = TEXT("{0}.SampleBias({1}, {2}, MaterialTextureMipBias)");
+ }
+ else if (useLevel || !canUseSample)
{
if (useOffset)
format = TEXT("{0}.SampleLevel({1}, {2}, {3}, {4})");
@@ -662,8 +631,9 @@ 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"
- " float2 fdx = ddx({0});\n"
- " float2 fdy = ddy({0});\n"
+ " float mipBiasDerivScale = pow(2, MaterialTextureMipBias);\n"
+ " float2 fdx = ddx({0}) * mipBiasDerivScale;\n"
+ " float2 fdy = ddy({0}) * mipBiasDerivScale;\n"
" float4 tex1 = {1}.SampleGrad({2}, uv1, fdx, fdy, {4}) * weights.x;\n"
" float4 tex2 = {1}.SampleGrad({2}, uv2, fdx, fdy, {4}) * weights.y;\n"
" float4 tex3 = {1}.SampleGrad({2}, uv3, fdx, fdy, {4}) * weights.z;\n"
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h
index d80e1e359..9446924aa 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.h
@@ -179,8 +179,8 @@ private:
SerializedMaterialParam* findParam(const Guid& id, MaterialLayer* layer);
MaterialGraphParameter* findGraphParam(const Guid& id);
- MaterialValue* sampleTextureRaw(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture);
- void sampleTexture(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture);
+ MaterialValue* sampleTextureRaw(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture, bool useGlobalMipBias = false);
+ void sampleTexture(Node* caller, Value& value, Box* box, SerializedMaterialParam* texture, bool useGlobalMipBias = false);
void sampleSceneDepth(Node* caller, Value& value, Box* box);
void linearizeSceneDepth(Node* caller, const Value& depth, Value& value);
diff --git a/Source/Shaders/MaterialCommon.hlsl b/Source/Shaders/MaterialCommon.hlsl
index 09530eb16..ab44e5081 100644
--- a/Source/Shaders/MaterialCommon.hlsl
+++ b/Source/Shaders/MaterialCommon.hlsl
@@ -181,7 +181,8 @@ cbuffer ViewData : register(b1)
float4 TemporalAAJitter;
float3 LargeWorldsChunkIndex;
float LargeWorldsChunkSize;
- float2 ViewPadding0;
+ float MaterialTextureMipBias;
+ float ViewPadding0;
float TestValue;
float ScaledTimeParam;
};