You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
255 lines
12 KiB
255 lines
12 KiB
#ifndef UNIVERSAL_BRDF_INCLUDED |
|
#define UNIVERSAL_BRDF_INCLUDED |
|
|
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/BSDF.hlsl" |
|
#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/CommonMaterial.hlsl" |
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Deprecated.hlsl" |
|
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceData.hlsl" |
|
|
|
#define kDielectricSpec half4(0.04, 0.04, 0.04, 1.0 - 0.04) // standard dielectric reflectivity coef at incident angle (= 4%) |
|
|
|
struct BRDFData |
|
{ |
|
half3 albedo; |
|
half3 diffuse; |
|
half3 specular; |
|
half reflectivity; |
|
half perceptualRoughness; |
|
half roughness; |
|
half roughness2; |
|
half grazingTerm; |
|
|
|
// We save some light invariant BRDF terms so we don't have to recompute |
|
// them in the light loop. Take a look at DirectBRDF function for detailed explaination. |
|
half normalizationTerm; // roughness * 4.0 + 2.0 |
|
half roughness2MinusOne; // roughness^2 - 1.0 |
|
}; |
|
|
|
half ReflectivitySpecular(half3 specular) |
|
{ |
|
#if defined(SHADER_API_GLES) |
|
return specular.r; // Red channel - because most metals are either monochrome or with redish/yellowish tint |
|
#else |
|
return Max3(specular.r, specular.g, specular.b); |
|
#endif |
|
} |
|
|
|
half OneMinusReflectivityMetallic(half metallic) |
|
{ |
|
// We'll need oneMinusReflectivity, so |
|
// 1-reflectivity = 1-lerp(dielectricSpec, 1, metallic) = lerp(1-dielectricSpec, 0, metallic) |
|
// store (1-dielectricSpec) in kDielectricSpec.a, then |
|
// 1-reflectivity = lerp(alpha, 0, metallic) = alpha + metallic*(0 - alpha) = |
|
// = alpha - metallic * alpha |
|
half oneMinusDielectricSpec = kDielectricSpec.a; |
|
return oneMinusDielectricSpec - metallic * oneMinusDielectricSpec; |
|
} |
|
|
|
half MetallicFromReflectivity(half reflectivity) |
|
{ |
|
half oneMinusDielectricSpec = kDielectricSpec.a; |
|
return (reflectivity - kDielectricSpec.r) / oneMinusDielectricSpec; |
|
} |
|
|
|
inline void InitializeBRDFDataDirect(half3 albedo, half3 diffuse, half3 specular, half reflectivity, half oneMinusReflectivity, half smoothness, inout half alpha, out BRDFData outBRDFData) |
|
{ |
|
outBRDFData = (BRDFData)0; |
|
outBRDFData.albedo = albedo; |
|
outBRDFData.diffuse = diffuse; |
|
outBRDFData.specular = specular; |
|
outBRDFData.reflectivity = reflectivity; |
|
|
|
outBRDFData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(smoothness); |
|
outBRDFData.roughness = max(PerceptualRoughnessToRoughness(outBRDFData.perceptualRoughness), HALF_MIN_SQRT); |
|
outBRDFData.roughness2 = max(outBRDFData.roughness * outBRDFData.roughness, HALF_MIN); |
|
outBRDFData.grazingTerm = saturate(smoothness + reflectivity); |
|
outBRDFData.normalizationTerm = outBRDFData.roughness * half(4.0) + half(2.0); |
|
outBRDFData.roughness2MinusOne = outBRDFData.roughness2 - half(1.0); |
|
|
|
#ifdef _ALPHAPREMULTIPLY_ON |
|
outBRDFData.diffuse *= alpha; |
|
alpha = alpha * oneMinusReflectivity + reflectivity; // NOTE: alpha modified and propagated up. |
|
#endif |
|
} |
|
|
|
// Legacy: do not call, will not correctly initialize albedo property. |
|
inline void InitializeBRDFDataDirect(half3 diffuse, half3 specular, half reflectivity, half oneMinusReflectivity, half smoothness, inout half alpha, out BRDFData outBRDFData) |
|
{ |
|
InitializeBRDFDataDirect(half3(0.0, 0.0, 0.0), diffuse, specular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData); |
|
} |
|
|
|
// Initialize BRDFData for material, managing both specular and metallic setup using shader keyword _SPECULAR_SETUP. |
|
inline void InitializeBRDFData(half3 albedo, half metallic, half3 specular, half smoothness, inout half alpha, out BRDFData outBRDFData) |
|
{ |
|
#ifdef _SPECULAR_SETUP |
|
half reflectivity = ReflectivitySpecular(specular); |
|
half oneMinusReflectivity = half(1.0) - reflectivity; |
|
half3 brdfDiffuse = albedo * oneMinusReflectivity; |
|
half3 brdfSpecular = specular; |
|
#else |
|
half oneMinusReflectivity = OneMinusReflectivityMetallic(metallic); |
|
half reflectivity = half(1.0) - oneMinusReflectivity; |
|
half3 brdfDiffuse = albedo * oneMinusReflectivity; |
|
half3 brdfSpecular = lerp(kDieletricSpec.rgb, albedo, metallic); |
|
#endif |
|
|
|
InitializeBRDFDataDirect(albedo, brdfDiffuse, brdfSpecular, reflectivity, oneMinusReflectivity, smoothness, alpha, outBRDFData); |
|
} |
|
|
|
inline void InitializeBRDFData(inout SurfaceData surfaceData, out BRDFData brdfData) |
|
{ |
|
InitializeBRDFData(surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.alpha, brdfData); |
|
} |
|
|
|
half3 ConvertF0ForClearCoat15(half3 f0) |
|
{ |
|
#if defined(SHADER_API_MOBILE) |
|
return ConvertF0ForAirInterfaceToF0ForClearCoat15Fast(f0); |
|
#else |
|
return ConvertF0ForAirInterfaceToF0ForClearCoat15(f0); |
|
#endif |
|
} |
|
|
|
inline void InitializeBRDFDataClearCoat(half clearCoatMask, half clearCoatSmoothness, inout BRDFData baseBRDFData, out BRDFData outBRDFData) |
|
{ |
|
outBRDFData = (BRDFData)0; |
|
outBRDFData.albedo = half(1.0); |
|
|
|
// Calculate Roughness of Clear Coat layer |
|
outBRDFData.diffuse = kDielectricSpec.aaa; // 1 - kDielectricSpec |
|
outBRDFData.specular = kDielectricSpec.rgb; |
|
outBRDFData.reflectivity = kDielectricSpec.r; |
|
|
|
outBRDFData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(clearCoatSmoothness); |
|
outBRDFData.roughness = max(PerceptualRoughnessToRoughness(outBRDFData.perceptualRoughness), HALF_MIN_SQRT); |
|
outBRDFData.roughness2 = max(outBRDFData.roughness * outBRDFData.roughness, HALF_MIN); |
|
outBRDFData.normalizationTerm = outBRDFData.roughness * half(4.0) + half(2.0); |
|
outBRDFData.roughness2MinusOne = outBRDFData.roughness2 - half(1.0); |
|
outBRDFData.grazingTerm = saturate(clearCoatSmoothness + kDielectricSpec.x); |
|
|
|
// Relatively small effect, cut it for lower quality |
|
#if !defined(SHADER_API_MOBILE) |
|
// Modify Roughness of base layer using coat IOR |
|
half ieta = lerp(1.0h, CLEAR_COAT_IETA, clearCoatMask); |
|
half coatRoughnessScale = Sq(ieta); |
|
half sigma = RoughnessToVariance(PerceptualRoughnessToRoughness(baseBRDFData.perceptualRoughness)); |
|
|
|
baseBRDFData.perceptualRoughness = RoughnessToPerceptualRoughness(VarianceToRoughness(sigma * coatRoughnessScale)); |
|
|
|
// Recompute base material for new roughness, previous computation should be eliminated by the compiler (as it's unused) |
|
baseBRDFData.roughness = max(PerceptualRoughnessToRoughness(baseBRDFData.perceptualRoughness), HALF_MIN_SQRT); |
|
baseBRDFData.roughness2 = max(baseBRDFData.roughness * baseBRDFData.roughness, HALF_MIN); |
|
baseBRDFData.normalizationTerm = baseBRDFData.roughness * 4.0h + 2.0h; |
|
baseBRDFData.roughness2MinusOne = baseBRDFData.roughness2 - 1.0h; |
|
#endif |
|
|
|
// Darken/saturate base layer using coat to surface reflectance (vs. air to surface) |
|
baseBRDFData.specular = lerp(baseBRDFData.specular, ConvertF0ForClearCoat15(baseBRDFData.specular), clearCoatMask); |
|
// TODO: what about diffuse? at least in specular workflow diffuse should be recalculated as it directly depends on it. |
|
} |
|
|
|
BRDFData CreateClearCoatBRDFData(SurfaceData surfaceData, inout BRDFData brdfData) |
|
{ |
|
BRDFData brdfDataClearCoat = (BRDFData)0; |
|
|
|
#if defined(_CLEARCOAT) || defined(_CLEARCOATMAP) |
|
// base brdfData is modified here, rely on the compiler to eliminate dead computation by InitializeBRDFData() |
|
InitializeBRDFDataClearCoat(surfaceData.clearCoatMask, surfaceData.clearCoatSmoothness, brdfData, brdfDataClearCoat); |
|
#endif |
|
|
|
return brdfDataClearCoat; |
|
} |
|
|
|
// Computes the specular term for EnvironmentBRDF |
|
half3 EnvironmentBRDFSpecular(BRDFData brdfData, half fresnelTerm) |
|
{ |
|
float surfaceReduction = 1.0 / (brdfData.roughness2 + 1.0); |
|
return half3(surfaceReduction * lerp(brdfData.specular, brdfData.grazingTerm, fresnelTerm)); |
|
} |
|
|
|
half3 EnvironmentBRDF(BRDFData brdfData, half3 indirectDiffuse, half3 indirectSpecular, half fresnelTerm) |
|
{ |
|
half3 c = indirectDiffuse * brdfData.diffuse; |
|
c += indirectSpecular * EnvironmentBRDFSpecular(brdfData, fresnelTerm); |
|
return c; |
|
} |
|
|
|
// Environment BRDF without diffuse for clear coat |
|
half3 EnvironmentBRDFClearCoat(BRDFData brdfData, half clearCoatMask, half3 indirectSpecular, half fresnelTerm) |
|
{ |
|
float surfaceReduction = 1.0 / (brdfData.roughness2 + 1.0); |
|
return indirectSpecular * EnvironmentBRDFSpecular(brdfData, fresnelTerm) * clearCoatMask; |
|
} |
|
|
|
// Computes the scalar specular term for Minimalist CookTorrance BRDF |
|
// NOTE: needs to be multiplied with reflectance f0, i.e. specular color to complete |
|
half DirectBRDFSpecular(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS) |
|
{ |
|
float3 lightDirectionWSFloat3 = float3(lightDirectionWS); |
|
float3 halfDir = SafeNormalize(lightDirectionWSFloat3 + float3(viewDirectionWS)); |
|
|
|
float NoH = saturate(dot(float3(normalWS), halfDir)); |
|
half LoH = half(saturate(dot(lightDirectionWSFloat3, halfDir))); |
|
|
|
// GGX Distribution multiplied by combined approximation of Visibility and Fresnel |
|
// BRDFspec = (D * V * F) / 4.0 |
|
// D = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2 |
|
// V * F = 1.0 / ( LoH^2 * (roughness + 0.5) ) |
|
// See "Optimizing PBR for Mobile" from Siggraph 2015 moving mobile graphics course |
|
// https://community.arm.com/events/1155 |
|
|
|
// Final BRDFspec = roughness^2 / ( NoH^2 * (roughness^2 - 1) + 1 )^2 * (LoH^2 * (roughness + 0.5) * 4.0) |
|
// We further optimize a few light invariant terms |
|
// brdfData.normalizationTerm = (roughness + 0.5) * 4.0 rewritten as roughness * 4.0 + 2.0 to a fit a MAD. |
|
float d = NoH * NoH * brdfData.roughness2MinusOne + 1.00001f; |
|
|
|
half LoH2 = LoH * LoH; |
|
half specularTerm = brdfData.roughness2 / ((d * d) * max(0.1h, LoH2) * brdfData.normalizationTerm); |
|
|
|
// On platforms where half actually means something, the denominator has a risk of overflow |
|
// clamp below was added specifically to "fix" that, but dx compiler (we convert bytecode to metal/gles) |
|
// sees that specularTerm have only non-negative terms, so it skips max(0,..) in clamp (leaving only min(100,...)) |
|
#if defined (SHADER_API_MOBILE) || defined (SHADER_API_SWITCH) |
|
specularTerm = specularTerm - HALF_MIN; |
|
specularTerm = clamp(specularTerm, 0.0, 100.0); // Prevent FP16 overflow on mobiles |
|
#endif |
|
|
|
return specularTerm; |
|
} |
|
|
|
// Based on Minimalist CookTorrance BRDF |
|
// Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255 |
|
// |
|
// * NDF [Modified] GGX |
|
// * Modified Kelemen and Szirmay-Kalos for Visibility term |
|
// * Fresnel approximated with 1/LdotH |
|
half3 DirectBDRF(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS, bool specularHighlightsOff) |
|
{ |
|
// Can still do compile-time optimisation. |
|
// If no compile-time optimized, extra overhead if branch taken is around +2.5% on some untethered platforms, -10% if not taken. |
|
[branch] if (!specularHighlightsOff) |
|
{ |
|
half specularTerm = DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS); |
|
half3 color = brdfData.diffuse + specularTerm * brdfData.specular; |
|
return color; |
|
} |
|
else |
|
return brdfData.diffuse; |
|
} |
|
|
|
// Based on Minimalist CookTorrance BRDF |
|
// Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255 |
|
// |
|
// * NDF [Modified] GGX |
|
// * Modified Kelemen and Szirmay-Kalos for Visibility term |
|
// * Fresnel approximated with 1/LdotH |
|
half3 DirectBRDF(BRDFData brdfData, half3 normalWS, half3 lightDirectionWS, half3 viewDirectionWS) |
|
{ |
|
#ifndef _SPECULARHIGHLIGHTS_OFF |
|
return brdfData.diffuse + DirectBRDFSpecular(brdfData, normalWS, lightDirectionWS, viewDirectionWS) * brdfData.specular; |
|
#else |
|
return brdfData.diffuse; |
|
#endif |
|
} |
|
|
|
#endif
|
|
|