Refactor specular lighting to properly map specular as reflectance in BRDF

Reference: https://google.github.io/filament/Filament.md.html

#1492
This commit is contained in:
Wojtek Figat
2025-09-30 15:43:55 +02:00
parent 02c5ad3fa4
commit 0848f1fa83
8 changed files with 65 additions and 21 deletions

View File

@@ -190,16 +190,55 @@ Asset::LoadResult Material::load()
// Load layer
layer = MaterialLayer::Load(GetID(), &stream, _shaderHeader.Material.Info, name);
if (ContentDeprecated::Clear())
const bool upgradeOldSpecular = _shaderHeader.Material.GraphVersion < 177;
if (ContentDeprecated::Clear() || upgradeOldSpecular)
{
// If encountered any deprecated data when loading graph then serialize it
MaterialGraph graph;
MemoryWriteStream writeStream(1024);
stream.SetPosition(0);
if (!graph.Load(&stream, true) && !graph.Save(&writeStream, true))
if (!graph.Load(&stream, true))
{
surfaceChunk->Data.Copy(ToSpan(writeStream));
ContentDeprecated::Clear();
if (upgradeOldSpecular)
{
// [Deprecated in 1.11]
// Specular calculations were changed to support up to 16% of reflectance via ^2 curve instead of linear up to 8%
// Insert Custom Code node that converts old materials into a new system to ensure they look the same
MaterialGraph::Node* rootNode = nullptr;
for (auto& e : graph.Nodes)
{
if (e.Type == ROOT_NODE_TYPE)
{
rootNode = &e;
break;
}
}
const auto& specularBoxInfo = MaterialGenerator::GetMaterialRootNodeBox(MaterialGraphBoxes::Specular);
auto specularBox = rootNode ? rootNode->GetBox(specularBoxInfo.ID) : nullptr;
if (specularBox && specularBox->HasConnection())
{
auto& customCodeNode = graph.Nodes.AddOne();
customCodeNode.ID = graph.Nodes.Count() + 1000;
customCodeNode.Type = GRAPH_NODE_MAKE_TYPE(1, 8);
customCodeNode.Boxes.Resize(2);
customCodeNode.Boxes[0] = MaterialGraphBox(&customCodeNode, 0, VariantType::Float4); // Input0
customCodeNode.Boxes[1] = MaterialGraphBox(&customCodeNode, 8, VariantType::Float4); // Output0
customCodeNode.Values.Resize(1);
customCodeNode.Values[0] = TEXT("// Convert old Specular value to a new range\nOutput0.x = min(Input0.x * 0.5f, 0.6f);");
auto specularSourceBox = specularBox->Connections[0];
specularBox->Connections.Clear();
specularSourceBox->Connections.Clear();
#define CONNECT(boxA, boxB) boxA->Connections.Add(boxB); boxB->Connections.Add(boxA)
CONNECT(specularSourceBox, (&customCodeNode.Boxes[0])); // Specular -> Input0
CONNECT((&customCodeNode.Boxes[1]), specularBox); // Output0 -> Specular
#undef CONNECT
}
}
if (!graph.Save(&writeStream, true))
{
surfaceChunk->Data.Copy(ToSpan(writeStream));
ContentDeprecated::Clear();
}
}
}
}

View File

@@ -10,7 +10,7 @@
/// <summary>
/// Current materials shader version.
/// </summary>
#define MATERIAL_GRAPH_VERSION 176
#define MATERIAL_GRAPH_VERSION 177
class Material;
class GPUShader;

View File

@@ -106,7 +106,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
Value values[OutputsMax];
for (int32 i = 0; i < OutputsMax; i++)
{
const auto outputBox = node->GetBox(Output0BoxID + i);
const auto outputBox = node->TryGetBox(Output0BoxID + i);
if (outputBox && outputBox->HasConnection())
{
values[i] = writeLocal(VariantType::Float4, node);
@@ -119,7 +119,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
for (int32 i = 0; i < InputsMax; i++)
{
auto inputName = TEXT("Input") + StringUtils::ToString(i);
const auto inputBox = node->GetBox(Input0BoxID + i);
const auto inputBox = node->TryGetBox(Input0BoxID + i);
if (inputBox && inputBox->HasConnection())
{
auto inputValue = tryGetValue(inputBox, Value::Zero);
@@ -131,7 +131,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
for (int32 i = 0; i < OutputsMax; i++)
{
auto outputName = TEXT("Output") + StringUtils::ToString(i);
const auto outputBox = node->GetBox(Output0BoxID + i);
const auto outputBox = node->TryGetBox(Output0BoxID + i);
if (outputBox && outputBox->HasConnection())
{
code.Replace(*outputName, *values[i].Value, StringSearchCase::CaseSensitive);
@@ -146,7 +146,7 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
// Link output values to boxes
for (int32 i = 0; i < OutputsMax; i++)
{
const auto outputBox = node->GetBox(Output0BoxID + i);
const auto outputBox = node->TryGetBox(Output0BoxID + i);
if (outputBox && outputBox->HasConnection())
{
outputBox->Cache = values[i];

View File

@@ -58,6 +58,12 @@ float3 F_Schlick(float3 specularColor, float VoH)
return saturate(50.0 * specularColor.g) * fc + (1 - fc) * specularColor;
}
float3 F_Schlick(float3 f0, float3 f90, float VoH)
{
float fc = Pow5(1 - VoH);
return f90 * fc + (1 - fc) * f0;
}
#define REFLECTION_CAPTURE_NUM_MIPS 7
#define REFLECTION_CAPTURE_ROUGHEST_MIP 1
#define REFLECTION_CAPTURE_ROUGHNESS_MIP_SCALE 1.2

View File

@@ -27,26 +27,28 @@ bool IsSubsurfaceMode(int shadingModel)
return shadingModel == SHADING_MODEL_SUBSURFACE || shadingModel == SHADING_MODEL_FOLIAGE;
}
float3 GetDiffuseColor(in float3 color, in float metalness)
float3 GetDiffuseColor(float3 color, float metalness)
{
return color - color * metalness;
return color * (1.0 - metalness);
}
float3 GetSpecularColor(in float3 color, in float specular, in float metalness)
// [https://google.github.io/filament/Filament.md.html]
float3 GetSpecularColor(float3 color, float specular, float metalness)
{
return lerp(0.08 * specular.xxx, color.rgb, metalness.xxx);
float dielectricF0 = 0.16 * specular * specular;
return lerp(dielectricF0.xxx, color, metalness.xxx);
}
// Calculate material diffuse color
float3 GetDiffuseColor(in GBufferSample gBuffer)
float3 GetDiffuseColor(GBufferSample gBuffer)
{
return gBuffer.Color - gBuffer.Color * gBuffer.Metalness;
return GetDiffuseColor(gBuffer.Color, gBuffer.Metalness);
}
// Calculate material specular color
float3 GetSpecularColor(in GBufferSample gBuffer)
float3 GetSpecularColor(GBufferSample gBuffer)
{
return lerp(0.08 * gBuffer.Specular.xxx, gBuffer.Color.rgb, gBuffer.Metalness.xxx);
return GetSpecularColor(gBuffer.Color, gBuffer.Specular, gBuffer.Metalness);
}
// Compact Normal Storage for Small G-Buffers

View File

@@ -31,6 +31,7 @@ LightSample StandardShading(GBufferSample gBuffer, float energy, float3 L, float
float3 F = F_Schlick(specularColor, VoH);
float D = D_GGX(gBuffer.Roughness, NoH) * energy;
float Vis = Vis_SmithJointApprox(gBuffer.Roughness, NoV, NoL);
// TODO: apply energy compensation to specular (1.0 + specularColor * (1.0 / PreIntegratedGF.y - 1.0))
lighting.Specular = (D * Vis) * F;
#endif
lighting.Transmission = 0;

View File

@@ -78,8 +78,6 @@ float4 PS_CombinePass(Quad_VS2PS input) : SV_Target0
// Calculate specular color
float3 specularColor = GetSpecularColor(gBuffer);
if (gBuffer.Metalness < 0.001)
specularColor = 0.04f * gBuffer.Specular;
// Calculate reflecion color
float3 V = normalize(gBufferData.ViewPos - gBuffer.WorldPos);

View File

@@ -83,8 +83,6 @@ float4 PS_CombinePass(Quad_VS2PS input) : SV_Target0
// Calculate specular color
float3 specularColor = GetSpecularColor(gBuffer);
if (gBuffer.Metalness < 0.001)
specularColor = 0.04f * gBuffer.Specular;
// Calculate reflection color
float3 V = normalize(gBufferData.ViewPos - gBuffer.WorldPos);