Add MaterialTextureMipBias to control texture mip bias in materials for better frame upscaling

This commit is contained in:
2026-08-16 14:30:53 +02:00
parent 1c25519d99
commit 708bdc7bcb
12 changed files with 74 additions and 86 deletions
@@ -2,6 +2,7 @@
// Version: @0
#define MATERIAL 1
#define MaterialTextureMipBias 0.0f
@3
#include "./Flax/Common.hlsl"
#include "./Flax/MaterialCommon.hlsl"
@@ -2,6 +2,7 @@
// Version: @0
#define MATERIAL 1
#define MaterialTextureMipBias 0.0f
@3
#include "./Flax/Common.hlsl"
+2 -2
View File
@@ -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[]
@@ -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
@@ -10,7 +10,7 @@
/// <summary>
/// Current materials shader version.
/// </summary>
#define MATERIAL_GRAPH_VERSION 185
#define MATERIAL_GRAPH_VERSION 186
class Material;
class GPUShader;
+5
View File
@@ -286,6 +286,11 @@ public:
/// </summary>
API_FIELD() float RenderScale = 1.0f;
/// <summary>
/// The texture mip level bias for materials. Positive values make textures appear blurrier; negative values sharpen.
/// </summary>
API_FIELD() float MaterialTextureMipBias = 0.0f;
/// <summary>
/// The image resolution upscale location within rendering pipeline. Unused if RenderingPercentage is 1.
/// </summary>
+1
View File
@@ -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;
+5
View File
@@ -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)
{
@@ -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;
@@ -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<ShaderGraphNode<>>();
@@ -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, &param);
sampleTexture(node, value, box, &param, 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, &param);
}
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, &param);
}
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"
@@ -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);
+2 -1
View File
@@ -181,7 +181,8 @@ cbuffer ViewData : register(b1)
float4 TemporalAAJitter;
float3 LargeWorldsChunkIndex;
float LargeWorldsChunkSize;
float2 ViewPadding0;
float MaterialTextureMipBias;
float ViewPadding0;
float TestValue;
float ScaledTimeParam;
};