Rasagar/Library/PackageCache/com.unity.render-pipelines.high-definition/Runtime/Material/AxF/AxF.hlsl
2024-08-26 23:07:20 +03:00

2692 lines
123 KiB
HLSL

//-----------------------------------------------------------------------------
// SurfaceData and BSDFData
//-----------------------------------------------------------------------------
// SurfaceData is defined in AxF.cs which generates AxF.cs.hlsl
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/AxF/AxF.cs.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/NormalBuffer.hlsl"
// Declare the BSDF specific FGD property and its fetching function
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/PreIntegratedFGD/PreIntegratedFGD.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/AxF/AxFPreIntegratedFGD.hlsl"
// Add support for LTC Area Lights
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/LTCAreaLight/LTCAreaLight.hlsl"
//-----------------------------------------------------------------------------
//
// Hardcoded config
//
//-----------------------------------------------------------------------------
// DEBUG
//#define DEBUG_HIDE_COAT
#if 1 // defined(SHADER_STAGE_RAY_TRACING)
// patch: in raytracing shader context, use float props that mirror the int props
// this is a workaround until we deal properly with the transfer of int properties in raytracing shaders on the engine side.
#define AXF_MATERIAL_FLAGS (_FlagsB)
#define AXF_SVBRDF_BRDFTYPE_DIFFUSETYPE (_SVBRDF_BRDFType_DiffuseType)
#define AXF_SVBRDF_BRDFTYPE_SPECULARTYPE (_SVBRDF_BRDFType_SpecularType)
#define AXF_SVBRDF_BRDFVARIANTS_FRESNELTYPE (_SVBRDF_BRDFVariants_FresnelType)
#define AXF_SVBRDF_BRDFVARIANTS_WARDTYPE (_SVBRDF_BRDFVariants_WardType)
#define AXF_SVBRDF_BRDFVARIANTS_BLINNTYPE (_SVBRDF_BRDFVariants_BlinnType)
#define AXF_CARPAINT2_FLAKEMAXTHETAI (_CarPaint2_FlakeMaxThetaIF)
#define AXF_CARPAINT2_FLAKENUMTHETAF (_CarPaint2_FlakeNumThetaFF)
#define AXF_CARPAINT2_FLAKENUMTHETAI (_CarPaint2_FlakeNumThetaIF)
#else
#define AXF_MATERIAL_FLAGS (_Flags)
#define AXF_SVBRDF_BRDFTYPE_DIFFUSETYPE ((_SVBRDF_BRDFType >> 0) & 1)
#define AXF_SVBRDF_BRDFTYPE_SPECULARTYPE ((_SVBRDF_BRDFType >> 1) & 7)
#define AXF_SVBRDF_BRDFVARIANTS_FRESNELTYPE ((_SVBRDF_BRDFVariants >> 0 ) & 3)
#define AXF_SVBRDF_BRDFVARIANTS_WARDTYPE ((_SVBRDF_BRDFVariants >> 2) & 3)
#define AXF_SVBRDF_BRDFVARIANTS_BLINNTYPE ((_SVBRDF_BRDFVariants >> 4) & 3)
#define AXF_CARPAINT2_FLAKEMAXTHETAI (_CarPaint2_FlakeMaxThetaI)
#define AXF_CARPAINT2_FLAKENUMTHETAF (_CarPaint2_FlakeNumThetaF)
#define AXF_CARPAINT2_FLAKENUMTHETAI (_CarPaint2_FlakeNumThetaI)
#endif // defined(SHADER_STAGE_RAY_TRACING)
//-----------------------------------------------------------------------------
#define FORCE_DISABLE_LIGHT_TYPE_DIMMERS
// Comment to disable the BRDFColor table clamping (CARPAINT2 specific)
#define AUTO_PATCH_FOR_INCOMPLETE_BRDF_COLOR_TABLE // This requires importer version >= 0.1.5-preview and manually setting the diagonal clamping enable + scalings to offset the diagonal
// Uncomment to always consider carpaints as having a clearcoat:
//#define FORCE_CAR_PAINT_HAS_CLEARCOAT
#define NdotVMinCosSpread 0.0001 // ie this is the value used by ClampNdotV
#define FixedBRDFColorThetaHForIndirectLight (_CarPaint2_FixedColorThetaHForIndirectLight)
#define FixedFlakesThetaHForIndirectLight (_CarPaint2_FixedFlakesThetaHForIndirectLight)
//#define FixedThetaHForIndirectLight (0)
#ifdef CLEAR_COAT_PERCEPTUAL_ROUGHNESS
#undef CLEAR_COAT_PERCEPTUAL_ROUGHNESS
#endif
#ifdef CLEAR_COAT_PERCEPTUAL_SMOOTHNESS
#undef CLEAR_COAT_PERCEPTUAL_SMOOTHNESS
#endif
#ifdef CLEAR_COAT_ROUGHNESS
#undef CLEAR_COAT_ROUGHNESS
#endif
#define CLEAR_COAT_ROUGHNESS 0.0 // By default we set it to match the lack of dirac light response in the X-Rite Pantora viewer.
#define CLEAR_COAT_PERCEPTUAL_SMOOTHNESS RoughnessToPerceptualSmoothness(CLEAR_COAT_ROUGHNESS)
#define CLEAR_COAT_PERCEPTUAL_ROUGHNESS RoughnessToPerceptualRoughness(CLEAR_COAT_ROUGHNESS)
#define FLAKES_JUST_BTF
// To evaluate just the BTF for split-sum lights (environments and LTC), define the above.
//
// Normally, we would have to create an FGD texture for the flake BTF, but since we assume the BSDF to be very sparse
// and "sparkly", we do a bit the same as we do with the clearcoat, approximating the integral of the almost-dirac FGD with
// the Fresnel term evaluation itself, here evaluating the flakes BTF in place of having an FGD. We might want to add a
// general surface orientation effect by dimming with an additional angle-dependent pre-integrated FGD term, which we
// calculate with the GGX pre-integrated FGD with FLAKES_ROUGHNESS and FLAKES_F0.
//
// Also when FLAKES_JUST_BTF are defined, for the LTC transform, we will use the same LTC transform for the flakes as for
// the coat, as both are considered having very low roughness
#define FLAKES_ROUGHNESS 0.03
#define FLAKES_PERCEPTUAL_ROUGHNESS RoughnessToPerceptualRoughness(FLAKES_ROUGHNESS)
#define FLAKES_F0 0.95 // at 0.95 and with the angular compression of the clearcoat, variations due to this additional flakesFGD will be almost invisible though...
//#define FLAKES_IOR Fresnel0ToIor(FLAKES_F0) // f0 = 0.95, ior = ~38, makes no sense for dielectric, but is to fake metal with dielectric Fresnel equations
#ifndef FLAKES_JUST_BTF
# define IFNOT_FLAKES_JUST_BTF(a) (a)
# define IF_FLAKES_JUST_BTF(a)
#else
# define IFNOT_FLAKES_JUST_BTF(a)
# define IF_FLAKES_JUST_BTF(a) (a)
#endif
#ifdef _MAPPING_TRIPLANAR
# define NB_FLAKES_RND_SHIFTS 3
# define FLAKES_SHIFT_IDX_PLANAR_ZY (0)
# define FLAKES_SHIFT_IDX_PLANAR_XZ (1)
# define FLAKES_SHIFT_IDX_PLANAR_XY (2)
#else
# define NB_FLAKES_RND_SHIFTS 1
#endif
// Define this to sample the environment maps/LTC samples for each lobe, instead of a single sample with an average lobe
#define USE_COOK_TORRANCE_MULTI_LOBES 1
#define MAX_CT_LOBE_COUNT 3
//#define CARPAINT2_LOBE_COUNT min(_CarPaint2_LobeCount,MAX_CT_LOBE_COUNT)
#define CARPAINT2_LOBE_COUNT MAX_CT_LOBE_COUNT
//-----------------------------------------------------------------------------
// Helper functions/variable specific to this material
//-----------------------------------------------------------------------------
#define DIFFUSE_INDIRECT_FUDGE_FACTOR (1.0)
#define ENVIRONMENT_LD_FUDGE_FACTOR (1.0)
#define LTC_L_FUDGE_FACTOR (1.0)
#define SSR_L_FUDGE_FACTOR (1.0)
bool HasPhongTypeBRDF()
{
uint type = AXF_SVBRDF_BRDFTYPE_SPECULARTYPE;
return type == 1 || type == 4;
}
float2 AxFGetRoughnessFromSpecularLobeTexture(float2 specularLobe)
{
// For Blinn-Phong, AxF encodes specularLobe.xy as log2(shiniExp_xy) so
// shiniExp = exp2(abs(specularLobe.xy))
// A good fit for a corresponding Beckmann roughness is
// roughnessBeckmann^2 = 2 /(shiniExp + 2)
// See eg
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
// http://simonstechblog.blogspot.com/2011/12/microfacet-brdf.html
// We thus have
// roughnessBeckmann = sqrt(2) * rsqrt(exp2(abs(specularLobe.xy)) + 2);
// shiniExp = 2 * rcp(max(0.0001,(roughnessBeckmann*roughnessBeckmann))) - 2;
return (HasPhongTypeBRDF() ? (sqrt(2) * rsqrt(exp2(abs(specularLobe)) + 2)) : specularLobe);
}
// From Walter 2007 eq. 40
// Expects incoming pointing AWAY from the surface
// eta = IOR_above / IOR_below
// rayIntensity returns 0 in case of total internal reflection
//
// Walter et al. formula seems to have a typo in it: the b term below
// needs to have eta^2 instead of eta.
// Note also that our sign(c) term here effectively makes the refractive
// surface dual sided.
float3 Refract(float3 incoming, float3 normal, float eta, out float rayIntensity)
{
float c = dot(incoming, normal);
float b = 1.0 + Sq(eta) * (c*c - 1.0);
if (b >= 0.0)
{
float k = eta * c - sign(c) * sqrt(b);
float3 R = k * normal - eta * incoming;
rayIntensity = 1;
return normalize(R);
}
else
{
rayIntensity = 0;
return -incoming; // Total internal reflection, just return an unrefracted dir
}
}
// Same but without handling total internal reflection because eta > 1
float3 Refract(float3 incoming, float3 normal, float eta)
{
float c = dot(incoming, normal);
float b = 1.0 + Sq(eta) * (c*c - 1.0);
float k = eta * c - sign(c) * sqrt(b);
float3 R = k * normal - eta * incoming;
return normalize(R);
}
// Used directly with an angle
float Refract(float inTheta, float eta)
{
float sinout = saturate(sin(inTheta)*eta);
return FastACosPos(sqrt(1-Sq(sinout)));
}
float3 RefractSaturateToTIR(float3 incoming, float3 normal, float eta, out float rayIntensity, out float3 incomingSaturated)
{
float c = dot(incoming, normal);
float sinIncSq = 1 - c*c;
float b = 1.0 - Sq(eta) * (sinIncSq);
// The component in the "orthogonal to N direction" when
// building the refracted vector is made from
// -eta * ( incoming - N * dot(incoming, normal))
// ie - eta * incoming + eta * c * normal
// and we want it to "one" when we saturate the direction to the output-side
// horizon (just avoiding TIR)
// since the other component in the normal direction is 0.
// We will normalize R, the output, at the end, but normally, this isn't required.
bool noTIR = (b >= 0);
rayIntensity = float(noTIR);
const float exitBiasIfTIR = NdotVMinCosSpread; // so our exit direction isn't completely grazing
float k = eta * c - sign(c) * sqrt(saturate(b)) + (noTIR ? 0: exitBiasIfTIR);
float3 R = k * normal - eta * incoming;
float3 criticalDir = (float3)0;
incomingSaturated = incoming;
if (noTIR == false)
{
float sinThetaCrit = saturate(rcp(eta));
float cosThetaCrit = sqrt(1 - Sq(sinThetaCrit));
float3 incOrthoN = (incoming - c * normal) * /*normalize the ortho component:*/rcp(sqrt(sinIncSq));
// Note: sqrt(sinIncSq) shouldn't be close to 0, since b < 0 <=> (sinIncSq) > 1/Sq(eta) and eta shouldn't be close to 1/sqrt(eps)!
criticalDir = sinThetaCrit * incOrthoN + cosThetaCrit * normal;
incomingSaturated = criticalDir;
}
return normalize(R);
}
float3 SaturateDirToHorizon(float3 incoming, float3 normal)
{
// add eps if you want a bit of positive bias:
return normalize( incoming + normal * saturate(/*eps here*/NdotVMinCosSpread - dot(incoming, normal)) );
}
float GetPreIntegratedFGDCookTorranceSampleMutiplier()
{
float ret = 4.0;
return ret;
// Note: We multiply by 4 since the documentation uses a Cook-Torrance BSDF formula without the /4 from the dH/dV Jacobian,
// and since we assume the lobe coefficients are fitted, we assume the effect of this factor to be in those.
// However, our pre-integrated FGD uses the proper D() importance sampling method and weight, so that the D() is effectively
// cancelled out when integrating FGD, whichever D() you choose to do importance sampling, along with the Jacobian of the
// BSDF (FGD integrand) with the Jacobian from doing importance sampling in H while integrating over L.
// We thus restitute the * 4 here.
// The other term is mostly a tweak to enable a desired match eg VRED
}
// Safe version preventing NaNs when IOR = 1
real F_FresnelDieletricSafe(real IOR, real u)
{
u = max(1e-3, u); // Prevents NaNs
real g = sqrt(max(0.0, Sq(IOR) + Sq(u) - 1.0));
return 0.5 * Sq((g - u) / max(1e-4, g + u)) * (1.0 + Sq(((g + u) * u - 1.0) / ((g - u) * u + 1.0)));
}
float GetDiffuseIndirectDimmer()
{
float ret = 1.0;
#ifndef FORCE_DISABLE_LIGHT_TYPE_DIMMERS
ret = DIFFUSE_INDIRECT_FUDGE_FACTOR;
#endif
return ret;
}
float GetSpecularIndirectDimmer()
{
float ret = 1.0;
#ifndef FORCE_DISABLE_LIGHT_TYPE_DIMMERS
ret = ENVIRONMENT_LD_FUDGE_FACTOR;
#endif
return ret;
}
// only for carpaint specular part
float GetLTCAreaLightDimmer()
{
float ret = 1.0;
#ifndef FORCE_DISABLE_LIGHT_TYPE_DIMMERS
ret = LTC_L_FUDGE_FACTOR;
#endif
return ret;
}
float GetSSRDimmer()
{
float ret = 1.0;
#ifndef FORCE_DISABLE_LIGHT_TYPE_DIMMERS
ret = SSR_L_FUDGE_FACTOR;
#endif
return ret;
}
bool IsDebugHideCoat()
{
bool ret = false;
#ifdef DEBUG_HIDE_COAT
ret = true;
#endif
return ret;
}
bool HasFresnelTerm()
{
#if defined(_AXF_BRDF_TYPE_SVBRDF)
return (AXF_SVBRDF_BRDFVARIANTS_FRESNELTYPE) != 0;
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
return true;
#else
return false;
#endif
}
bool HasAnisotropy()
{
return (HasFlag(AXF_MATERIAL_FLAGS, FEATUREFLAGS_AXF_ANISOTROPY));
}
bool HasClearcoat()
{
bool ret = (HasFlag(AXF_MATERIAL_FLAGS, FEATUREFLAGS_AXF_CLEAR_COAT));
#if defined(_AXF_BRDF_TYPE_CAR_PAINT) && defined(FORCE_CAR_PAINT_HAS_CLEARCOAT)
ret = true;
#endif
return ret;
}
bool HasClearcoatRefraction()
{
return (HasFlag(AXF_MATERIAL_FLAGS, FEATUREFLAGS_AXF_CLEAR_COAT_REFRACTION));
}
bool HasClearcoatAndRefraction()
{
uint bits = (FEATUREFLAGS_AXF_CLEAR_COAT | FEATUREFLAGS_AXF_CLEAR_COAT_REFRACTION);
return ((AXF_MATERIAL_FLAGS & bits) == bits);
}
bool HasBRDFColorDiagonalClamp()
{
return (HasFlag(AXF_MATERIAL_FLAGS, FEATUREFLAGS_AXF_BRDFCOLOR_DIAGONAL_CLAMP));
}
bool HonorMinRoughness()
{
return (HasFlag(AXF_MATERIAL_FLAGS, FEATUREFLAGS_AXF_HONOR_MIN_ROUGHNESS));
}
bool HonorMinRoughnessCoat()
{
return (HasFlag(AXF_MATERIAL_FLAGS, FEATUREFLAGS_AXF_HONOR_MIN_ROUGHNESS_COAT));
}
// For CARPAINT2
// Samples the "BRDF Color Table" as explained in "AxF-Decoding-SDK-1.5.1/doc/html/page2.html#carpaint_ColorTable" from the SDK
float3 GetBRDFColor(float thetaH, float thetaD)
{
// [ Update1:
// Enable this path: in short, the color table seems fully defined in the sample tried like X-Rite_12-PTF_Blue-Violet_NR.axf,
// and while acos() yields values up to PI, negative input values shouldn't be used
// for cos(thetaH) (under horizon) and for cos(thetaD), it shouldn't even be possible.
// ]
float2 UV = float2(2.0 * thetaH / PI, 2.0 * thetaD / PI);
#ifdef AUTO_PATCH_FOR_INCOMPLETE_BRDF_COLOR_TABLE
// [ Update1:
// The texture should be fully defined for thetaH and thetaD.
// Although we should note here that some values of thetaD make no sense depending on phiD
// [see "A New Change of Variables for Efficient BRDF Representation by Szymon M. Rusinkiewicz
// https://www.cs.princeton.edu/~smr/papers/brdf_change_of_variables/brdf_change_of_variables.pdf
// for the definition of these angles],
// as when thetaH > 0, in the worst case when phiD = 0, thetaD must be <= (PI/2 - thetaH)
// ie when thetaH = PI/2 and phiD = 0, thetaD must be 0,
// while all values from 0 to PI/2 of thetaD are possible if phiD = PI/2.
// (This is the reason the phiD = PI/2 "slice" contains more information on the BSDF,
// see also s2012_pbs_disney_brdf_notes_v3.pdf p4-5)
//
// But with only thetaH and thetaD indexing the table, phiD is ignored, and the
// true 3D dependency of (even a non-anisotropic - anisotropic would need 4D) BSDF is lost in this parameterization.
//
// Having said that, it can happen that sometimes the color table is defined only for half of it, as if the measurements came from
// such a phiD = 0 degrees slice. In that case, the importer (as of v0.1.5-preview) will try to detect the condition and set a flag
// along with scalings to offset the diagonal clamp in case even less than half the table is defined.
// We use these values here. In case the importer misdetects this condition, the UI still allow changing these values:
// ]
bool brdfColorUseDiagonalClamp = HasBRDFColorDiagonalClamp();
if (brdfColorUseDiagonalClamp)
{
UV = float2(2.0 * thetaH / PI, INV_HALF_PI * min(HALF_PI - thetaH, thetaD));
UV *= _CarPaint2_BRDFColorMapUVScale.xy;
}
#endif
// Rescale UVs to account for 0.5 texel offset
uint2 textureSize;
_CarPaint2_BRDFColorMap.GetDimensions(textureSize.x, textureSize.y);
UV = (0.5 + UV * (textureSize - 1)) / textureSize;
return _CarPaint2_BRDFColorMapScale * SAMPLE_TEXTURE2D_LOD(_CarPaint2_BRDFColorMap, sampler_CarPaint2_BRDFColorMap, float2(UV.x, 1 - UV.y), 0).xyz;
}
// GetScalarRoughnessFromAnisoRoughness is different than GetProjectedRoughness:
// In this case, we don't have a direction to project to.
//
// All our IBL hacks or approximations where we need a single roughness are calibrated
// using an arbitrary roughness that corresponds to the average of the anisotropic roughnesses,
// as the code below show.
//
// The primary reason for this is that the (Anisotropy in [-1,1] + scalar roughness value)
// parametrization as suggested by SPI and used in HDRP has the advantage that the original
// "scalar roughness" value used always corresponds to the average of the anisotropic roughnesses,
// and in the case of isotropic roughnesses, this scalar (average) roughness will obviously match
// the axis aligned roughnesses (as they are equal).
//
// But in general (cf with GetProjectedRoughness), if we wanted eg the scalar roughness for an average
// azimuth angle of 45 degrees, (presumably chosen to be an "average direction" between T and B)
// we would need to calculate:
//
// projectedRoughness@45degree = sqrt(cos^2(pi/4) * roughnessT^2 + sin^2(pi/4) * roughnessB^2)
// = sqrt(0.5*roughnessT + 0.5*roughnessB)
// = sqrt(2)/2 * sqrt(roughnessT^2 + roughnessB^2)
//
// and we can see that this is == isotropic_roughness = roughnessT = roughnessB = 0.5 * (roughnessT + roughnessB)
// only in the isotropic case.
float GetScalarRoughnessFromAnisoRoughness(float roughnessT, float roughnessB)
{
return 0.5 * (roughnessT + roughnessB);
}
float GetScalarRoughness(float3 roughness)
{
float singleRoughness = 0.5;
#if defined(_AXF_BRDF_TYPE_SVBRDF)
singleRoughness = (HasAnisotropy()) ? GetScalarRoughnessFromAnisoRoughness(roughness.x, roughness.y) : roughness.x;
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
float sumCoeffXRoughness = 0.0;
float sumCoeff = 0.0;
UNITY_UNROLL
for (uint lobeIndex = 0; lobeIndex < CARPAINT2_LOBE_COUNT; lobeIndex++) // TODO remove all variable lobecnt code
{
float coeff = _CarPaint2_CTCoeffs[lobeIndex];
float spread = roughness[lobeIndex];
sumCoeff += coeff;
sumCoeffXRoughness += spread * coeff;
}
singleRoughness = min(1.0, SafeDiv(sumCoeffXRoughness,sumCoeff));
#endif
return singleRoughness;
}
float GetCarPaintFresnel0()
{
float ret = 0;
float curMax = 0;
uint algo = 1;
switch (algo)
{
case 0:
{
for (uint lobeIndex = 0; lobeIndex < CARPAINT2_LOBE_COUNT; lobeIndex++)
{
float f0 = _CarPaint2_CTF0s[lobeIndex];
float coeff = _CarPaint2_CTCoeffs[lobeIndex];
if (curMax < (f0*coeff))
{
ret = f0;
curMax = f0*coeff;
}
}
break;
}
case 1:
{
float4 coeffXf0 = _CarPaint2_CTF0s * _CarPaint2_CTCoeffs;
ret = Max3(coeffXf0[0], coeffXf0[1], coeffXf0[2]);
break;
}
case 2:
{
ret = dot(_CarPaint2_CTF0s.xyz,_CarPaint2_CTCoeffs.xyz);
break;
}
}
return ret;
}
float3 GetCarPaintSpecularColor()
{
//TODO: improve
return GetBRDFColor(0,0);
}
//
// Flakes BTF access
//
struct FlakesSamplingInfo
{
float2 flakesUVZY;
float2 flakesUVXZ;
float2 flakesUVXY;
float flakesMipLevelZY;
float flakesMipLevelXZ;
float flakesMipLevelXY;
float3 flakesTriplanarWeights;
float2 flakesDdxZY; // if non null, we will prefer gradients (to be used statically only!)
float2 flakesDdyZY;
float2 flakesDdxXZ;
float2 flakesDdyXZ;
float2 flakesDdxXY;
float2 flakesDdyXY;
};
FlakesSamplingInfo GetFillFlakesSamplingInfo(SurfaceData surfaceData, BSDFData bsdfData, bool useBSDFData = true)
{
FlakesSamplingInfo flakesSamplingInfo;
ZERO_INITIALIZE(FlakesSamplingInfo, flakesSamplingInfo);
if(useBSDFData)
{
flakesSamplingInfo.flakesUVZY = bsdfData.flakesUVZY;
flakesSamplingInfo.flakesUVXZ = bsdfData.flakesUVXZ;
flakesSamplingInfo.flakesUVXY = bsdfData.flakesUVXY;
flakesSamplingInfo.flakesMipLevelZY = bsdfData.flakesMipLevelZY;
flakesSamplingInfo.flakesMipLevelXZ = bsdfData.flakesMipLevelXZ;
flakesSamplingInfo.flakesMipLevelXY = bsdfData.flakesMipLevelXY;
flakesSamplingInfo.flakesTriplanarWeights = bsdfData.flakesTriplanarWeights;
flakesSamplingInfo.flakesDdxZY = bsdfData.flakesDdxZY;
flakesSamplingInfo.flakesDdyZY = bsdfData.flakesDdyZY;
flakesSamplingInfo.flakesDdxXZ = bsdfData.flakesDdxXZ;
flakesSamplingInfo.flakesDdyXZ = bsdfData.flakesDdyXZ;
flakesSamplingInfo.flakesDdxXY = bsdfData.flakesDdxXY;
flakesSamplingInfo.flakesDdyXY = bsdfData.flakesDdyXY;
}
else
{
// Fill using surfaceData: identical to FillFlakesBSDFData
#ifdef _MAPPING_TRIPLANAR
flakesSamplingInfo.flakesUVZY = surfaceData.flakesUVZY;
flakesSamplingInfo.flakesUVXZ = surfaceData.flakesUVXZ;
flakesSamplingInfo.flakesUVXY = surfaceData.flakesUVXY;
flakesSamplingInfo.flakesMipLevelZY = surfaceData.flakesMipLevelZY;
flakesSamplingInfo.flakesMipLevelXZ = surfaceData.flakesMipLevelXZ;
flakesSamplingInfo.flakesMipLevelXY = surfaceData.flakesMipLevelXY;
flakesSamplingInfo.flakesTriplanarWeights = surfaceData.flakesTriplanarWeights;
flakesSamplingInfo.flakesDdxZY = surfaceData.flakesDdxZY;
flakesSamplingInfo.flakesDdyZY = surfaceData.flakesDdyZY;
flakesSamplingInfo.flakesDdxXZ = surfaceData.flakesDdxXZ;
flakesSamplingInfo.flakesDdyXZ = surfaceData.flakesDdyXZ;
flakesSamplingInfo.flakesDdxXY = surfaceData.flakesDdxXY;
flakesSamplingInfo.flakesDdyXY = surfaceData.flakesDdyXY;
#else
// NOTE: When not triplanar UVZY has one uv set or one planar coordinate set,
// and this planar coordinate set isn't necessarily ZY, we just reuse this field
// as a common one.
flakesSamplingInfo.flakesUVZY = surfaceData.flakesUVZY;
flakesSamplingInfo.flakesMipLevelZY = surfaceData.flakesMipLevelZY;
flakesSamplingInfo.flakesDdxZY = surfaceData.flakesDdxZY;
flakesSamplingInfo.flakesDdyZY = surfaceData.flakesDdyZY;
flakesSamplingInfo.flakesUVXZ = 0;
flakesSamplingInfo.flakesUVXY = 0;
flakesSamplingInfo.flakesMipLevelXZ = 0;
flakesSamplingInfo.flakesMipLevelXY = 0;
flakesSamplingInfo.flakesTriplanarWeights = 0;
flakesSamplingInfo.flakesDdxXZ = 0;
flakesSamplingInfo.flakesDdyXZ = 0;
flakesSamplingInfo.flakesDdxXY = 0;
flakesSamplingInfo.flakesDdyXY = 0;
#endif
}
return flakesSamplingInfo;
}
void FillFlakesBSDFData(SurfaceData surfaceData, inout BSDFData bsdfData)
{
#ifdef _MAPPING_TRIPLANAR
bsdfData.flakesUVZY = surfaceData.flakesUVZY;
bsdfData.flakesUVXZ = surfaceData.flakesUVXZ;
bsdfData.flakesUVXY = surfaceData.flakesUVXY;
bsdfData.flakesMipLevelZY = surfaceData.flakesMipLevelZY;
bsdfData.flakesMipLevelXZ = surfaceData.flakesMipLevelXZ;
bsdfData.flakesMipLevelXY = surfaceData.flakesMipLevelXY;
bsdfData.flakesTriplanarWeights = surfaceData.flakesTriplanarWeights;
bsdfData.flakesDdxZY = surfaceData.flakesDdxZY;
bsdfData.flakesDdyZY = surfaceData.flakesDdyZY;
bsdfData.flakesDdxXZ = surfaceData.flakesDdxXZ;
bsdfData.flakesDdyXZ = surfaceData.flakesDdyXZ;
bsdfData.flakesDdxXY = surfaceData.flakesDdxXY;
bsdfData.flakesDdyXY = surfaceData.flakesDdyXY;
#else
// NOTE: When not triplanar UVZY has one uv set or one planar coordinate set,
// and this planar coordinate set isn't necessarily ZY, we just reuse this field
// as a common one.
bsdfData.flakesUVZY = surfaceData.flakesUVZY;
bsdfData.flakesMipLevelZY = surfaceData.flakesMipLevelZY;
bsdfData.flakesDdxZY = surfaceData.flakesDdxZY;
bsdfData.flakesDdyZY = surfaceData.flakesDdyZY;
bsdfData.flakesUVXZ = 0;
bsdfData.flakesUVXY = 0;
bsdfData.flakesMipLevelXZ = 0;
bsdfData.flakesMipLevelXY = 0;
bsdfData.flakesTriplanarWeights = 0;
bsdfData.flakesDdxXZ = 0;
bsdfData.flakesDdyXZ = 0;
bsdfData.flakesDdxXY = 0;
bsdfData.flakesDdyXY = 0;
#endif
}
// Samples the "BTF Flakes" texture as explained in "AxF-Decoding-SDK-1.5.1/doc/html/page2.html#carpaint_FlakeBTF" from the SDK
uint SampleFlakesLUT(uint index)
{
return 255.0 * _CarPaint2_FlakeThetaFISliceLUTMap[uint2(index, 0)].x;
// Hardcoded LUT
// uint pipoLUT[] = { 0, 8, 16, 24, 32, 40, 47, 53, 58, 62, 65, 67 };
// return pipoLUT[min(11, _index)];
}
float3 SampleFlakes(float2 offsets[NB_FLAKES_RND_SHIFTS], uint sliceIndex, FlakesSamplingInfo flakesSamplingInfo)
{
// We can't use SAMPLE_TEXTURE2D_ARRAY, the compiler can't unroll in that case, and the lightloop is built with unroll
// That's why we calculate gradients or LOD earlier.
// TODO: The LOD code path (useFlakesMipLevel == true) is kept for a possible performance/appearance trade-off
// (less VGPR for LOD) and also for (future) raytracing, it is easier to substitute an approximate single LOD value
// than a full 2x2 Jacobian.
float3 val = 0;
bool useFlakesMipLevel = all(flakesSamplingInfo.flakesDdxZY == (float2)0); // should be known statically!
#ifdef _MAPPING_TRIPLANAR
val += flakesSamplingInfo.flakesTriplanarWeights.x *
(useFlakesMipLevel ?
SAMPLE_TEXTURE2D_ARRAY_LOD(_CarPaint2_BTFFlakeMap, sampler_CarPaint2_BTFFlakeMap,
flakesSamplingInfo.flakesUVZY + offsets[FLAKES_SHIFT_IDX_PLANAR_ZY],
sliceIndex, flakesSamplingInfo.flakesMipLevelZY).xyz
: SAMPLE_TEXTURE2D_ARRAY_GRAD(_CarPaint2_BTFFlakeMap, sampler_CarPaint2_BTFFlakeMap,
flakesSamplingInfo.flakesUVZY + offsets[FLAKES_SHIFT_IDX_PLANAR_ZY],
sliceIndex, flakesSamplingInfo.flakesDdxZY, flakesSamplingInfo.flakesDdyZY).xyz );
val += flakesSamplingInfo.flakesTriplanarWeights.y *
(useFlakesMipLevel ?
SAMPLE_TEXTURE2D_ARRAY_LOD(_CarPaint2_BTFFlakeMap, sampler_CarPaint2_BTFFlakeMap,
flakesSamplingInfo.flakesUVXZ + offsets[FLAKES_SHIFT_IDX_PLANAR_XZ],
sliceIndex, flakesSamplingInfo.flakesMipLevelXZ).xyz
: SAMPLE_TEXTURE2D_ARRAY_GRAD(_CarPaint2_BTFFlakeMap, sampler_CarPaint2_BTFFlakeMap,
flakesSamplingInfo.flakesUVXZ + offsets[FLAKES_SHIFT_IDX_PLANAR_XZ],
sliceIndex, flakesSamplingInfo.flakesDdxXZ, flakesSamplingInfo.flakesDdyXZ).xyz );
val += flakesSamplingInfo.flakesTriplanarWeights.z *
(useFlakesMipLevel ?
SAMPLE_TEXTURE2D_ARRAY_LOD(_CarPaint2_BTFFlakeMap, sampler_CarPaint2_BTFFlakeMap,
flakesSamplingInfo.flakesUVXY + offsets[FLAKES_SHIFT_IDX_PLANAR_XY],
sliceIndex, flakesSamplingInfo.flakesMipLevelXY).xyz
: SAMPLE_TEXTURE2D_ARRAY_GRAD(_CarPaint2_BTFFlakeMap, sampler_CarPaint2_BTFFlakeMap,
flakesSamplingInfo.flakesUVXY + offsets[FLAKES_SHIFT_IDX_PLANAR_XY],
sliceIndex, flakesSamplingInfo.flakesDdxXY, flakesSamplingInfo.flakesDdyXY).xyz );
val *= _CarPaint2_BTFFlakeMapScale;
#else
val = _CarPaint2_BTFFlakeMapScale *
(useFlakesMipLevel ?
SAMPLE_TEXTURE2D_ARRAY_LOD(_CarPaint2_BTFFlakeMap, sampler_CarPaint2_BTFFlakeMap,
flakesSamplingInfo.flakesUVZY + offsets[0], sliceIndex, flakesSamplingInfo.flakesMipLevelZY).xyz
: SAMPLE_TEXTURE2D_ARRAY_GRAD(_CarPaint2_BTFFlakeMap, sampler_CarPaint2_BTFFlakeMap,
flakesSamplingInfo.flakesUVZY + offsets[0], sliceIndex, flakesSamplingInfo.flakesDdxZY, flakesSamplingInfo.flakesDdyZY).xyz );
#endif
return val;
}
//
// Working code, TODO_FLAKES: missing virtual thetaD (aka thetaI) bin generation
//
float3 CarPaint_BTF(float thetaH, float thetaD, SurfaceData surfaceData, BSDFData bsdfData, bool useBSDFData = true)
{
// debug raytracing: seems uint in constant buffer get corrupted!
uint flakeMaxThetaI = AXF_CARPAINT2_FLAKEMAXTHETAI;
uint flakeNumThetaF = AXF_CARPAINT2_FLAKENUMTHETAF;
uint flakeNumThetaI = AXF_CARPAINT2_FLAKENUMTHETAI;
// Note: this has no impact on perf, it is just to support multiple callee contexts:
FlakesSamplingInfo flakesSamplingInfo = GetFillFlakesSamplingInfo(surfaceData, bsdfData, useBSDFData);
// thetaH sampling defines the angular sampling, i.e. angular flake lifetime
float binIndexH = flakeNumThetaF * (2.0 * thetaH / PI) + 0.5; // TODO: doc says to use NumThetaF for both, check if this isn't a typo
float binIndexD = flakeNumThetaF * (2.0 * thetaD / PI) + 0.5;
// Bilinear interpolate indices and weights
uint thetaH_low = floor(binIndexH);
uint thetaD_low = floor(binIndexD);
uint thetaH_high = thetaH_low + 1;
uint thetaD_high = thetaD_low + 1;
float thetaH_weight = binIndexH - thetaH_low;
float thetaD_weight = binIndexD - thetaD_low;
// To allow lower thetaD samplings while preserving flake lifetime, "virtual" thetaD patches are generated by shifting existing ones
// NB_FLAKES_RND_SHIFTS = 1 if not triplanar; otherwise this is in case we want a randomization that takes planar coordinate index into account
float2 offset_l[NB_FLAKES_RND_SHIFTS] = (float2[NB_FLAKES_RND_SHIFTS])0;
float2 offset_h[NB_FLAKES_RND_SHIFTS] = (float2[NB_FLAKES_RND_SHIFTS])0;
// Organization of the flake BTF slice array and LUT:
//
// The two angles thetaH and thetaD (aka thetaF and thetaI in the documentation)
// index an array of slices, and an indirection is first used through an integer LUT
// (UVs are spatial to be finally used with the individual slices):
//
// Basically, the slices in the array are arranged in incrementing thetaH "steps" (or bins),
// for each constant thetaD bin number,
// ie in thetaD-major order, as for a single thetaD, the multiple thetaH slices
// are consecutive, ie incrementing the wanted thetaD bin causes big jumps in the effective
// slice index to use in the array.
//
// Another peculiarity is that the stride (number of slices to skip) to go to the next
// thetaD bin is not constant as not all slices exist, some thetaH ranges "dying off" very quickly
// depending on the thetaD: ie only a few thetaH slices can exist for a particular thetaD bin.
//
// Non-existing slices for a particular thetaD, thetaH are taken to fetch zero values (hence the
// "dying off" above).
//
// The integer LUT is indexed by thetaD and gives the index in the slice array for this thetaD
// and/at the start of the thetaH range, ie for the first thetaH bin for the thetaH range [0, 0 + deltaH)
//
// The absolute maximum index for the integer LUT is denoted _CarPaint2_FlakeMaxThetaI.
//
// Consider eg this thetaFISliceLut sized to 64 entries:
//
// 0 7 14 21 28 35 42 48 53 57 60 62 0 0 0 ... (all zeroes for the rest)
//
// FlakeMaxThetaI = 12, and indeed as we can see, after the first 12 entries, everything is 0.
// We can have valid thetaD bins with thetaD_low from 0 to 10. See comments below for details about
// this: indeed if thetaD_low = 11, LUT[11] will be 62, but LUT[11+1] will be 0, indicating no
// index space left starting at 62 for the thetaH bins for this particular thetaD bin.
//
// In short, a valid range of final indices in the slice array for a particular thetaD bin is
// indicated by a start index at LUT[i] and a limit index which is just indicated by the start
// index of the next thetaD bin, at LUT[i+1].
// ------------------------------------------------------
// TODO; what they call "virtual thetaD" bins generation:
// Check if this is needed, and port this
// (eg with a noise texture):
// ------------------------------------------------------
//
// Basically, from the documentation example, it seems that the number of bins considered
// for *both* thetaH and thetaD are NumThetaF (aka NumThetaH)
// ie for *both* angular spaces, the number of bin subdivisions is (counterintuitively) NumThetaF.
//
// However, the real sampling resolution of the thetaD space (_CarPaint_numThetaI) can be lower,
// and this is indicated by the (_CarPaint_numThetaI < _CarPaint_numThetaF) condition.
//
// If this is the case, we squash back the overflowing "binIndexD"
// (that we overextended by multiplying (2.0 * thetaD / PI) by _CarPaint2_FlakeNumThetaF)
// and thus repeat usage of some slices, but we shift them *spatially* by random amounts to hide this.
// (the offset_* below are to be used with the spatial UVs)
//
// if (_CarPaint_numThetaI < _CarPaint_numThetaF) {
// offset_l = float2(rnd_numbers[2*thetaD_low], rnd_numbers[2*thetaD_low+1]);
// offset_h = float2(rnd_numbers[2*thetaD_high], rnd_numbers[2*thetaD_high+1]);
// if (thetaD_low & 1)
// UV.xy = UV.yx;
// if (thetaD_high & 1)
// UV.xy = UV.yx;
//
// // Map to the original sampling
// thetaD_low = floor(thetaD_low * float(_CarPaint_numThetaI) / _CarPaint_numThetaF);
// thetaD_high = floor(thetaD_high * float(_CarPaint_numThetaI) / _CarPaint_numThetaF);
// //
// // WARNING: double check SDK but our original code was wrong in that case:
// //
// // Note that in that case, thetaD_low can be == to thetaD_high,
// // eg with
// // _CarPaint_numThetaI = 7,
// // _CarPaint_numThetaF = 12,
// // original thetaD_low = 2 (and thus original thetaD_high = 3).
// // we get
// // thetaD_low = floor( 2 * 7.0/12 ) = floor( 2 * 0.58333) = floor(1.1667) = 1
// // thetaD_high = floor( 3 * 7.0/12 ) = floor( 3 * 0.58333) = floor(1.75) = 1
// //
// // Again in our original code, we systematically took thetaD_high == thetaD_low + 1 when
// // verifying the indexing limit using for LUT1:
// //
// // uint LUT0 = SampleFlakesLUT(thetaD_low);
// // uint LUT1 = SampleFlakesLUT(thetaD_high);
// // uint LUT2 = SampleFlakesLUT(thetaD_high + 1);
// //
// // LUT1 is NOT the value we should use to check if we slip over in thetaH (thetaF)!
// // it could be that thetaD_low == thetaD_high (virtual thetaD bins) where we stay in the
// // same bin but shift our UVs for that same slice to fake having another "thetaD" bin
// // (taking all the same slices for another aliased thetaD but shifting the UVs of those).
// // However, we still need to make sure, when we choose a final slice taking into account
// // the int offset due to the thetaH sampling/bin, that the calculated index doesn't fall
// // off the current valid range for the current thetaD as indicated by 2 consecutive LUT
// // entries!
//
// }
float3 H0_D0 = 0.0;
float3 H1_D0 = 0.0;
float3 H0_D1 = 0.0;
float3 H1_D1 = 0.0;
// Access flake texture - make sure to stay in the correct slices (no slip over)
if (thetaD_low < flakeMaxThetaI)
{
// These are spatial UVs, we let SampleFlakes deal with them in case of triplanar,
// and just submit the random shift offsets (TODO "virtual" angular patches)
//float2 UVl = UV + offset_l;
//float2 UVh = UV + offset_h;
uint LUT0 = SampleFlakesLUT(thetaD_low);
uint LUT1 = SampleFlakesLUT(thetaD_high);
uint LUT0_limit = SampleFlakesLUT(thetaD_low+1);
// without "virtual thetaD" bins, LUT0_limit will be the same as LUT1 and optimized out.
uint LUT2 = SampleFlakesLUT(thetaD_high + 1);
if (LUT0 + thetaH_low < LUT0_limit)
{
H0_D0 = SampleFlakes(offset_l, LUT0 + thetaH_low, flakesSamplingInfo);
if (LUT0 + thetaH_high < LUT0_limit)
{
H1_D0 = SampleFlakes(offset_l, LUT0 + thetaH_high, flakesSamplingInfo);
}
}
// else it means that the calculated index for that thetaD_low and the thetaH_low
// bin doesn't even include the start of the H range we want to interpolate.
// This could happen even if thetaH_low == 0, if for example we're at the last
// non-zero value of the integer LUT due to thetaD_low itself: in that case
// LUT1 value contains 0, ie we don't even have an index for the next thetaD bin
// start which would give us a limit index to use for the maximum thetaH bin
// in the current thetaD_low bin. (ie a valid thetaD bin needs LUT[i] and LUT[i+1]
// to be valid as these indicate the limits for the final slice array index
// calculated including the offset induced by the minor dimension thetaH-bin)
if (thetaD_high < flakeMaxThetaI)
{
if (LUT1 + thetaH_low < LUT2)
{
H0_D1 = SampleFlakes(offset_h, LUT1 + thetaH_low, flakesSamplingInfo);
if (LUT1 + thetaH_high < LUT2)
{
H1_D1 = SampleFlakes(offset_h, LUT1 + thetaH_high, flakesSamplingInfo);
}
}
// else, same thing as our comment above
}
}
// Bilinear interpolation
float3 D0 = lerp(H0_D0, H1_D0, thetaH_weight);
float3 D1 = lerp(H0_D1, H1_D1, thetaH_weight);
return lerp(D0, D1, thetaD_weight);
}
// AxF splits the chromaticity and f0 from the usual "SpecularColor" convention
// to just be a chromatic f0.
// CARPAINT2 has a different way to handle colors and must be accounted for too.
// Base refers to the "base layer", ie not the coat if present.
float3 GetColorBaseFresnelF0(BSDFData bsdfData)
{
return bsdfData.fresnel0.r * bsdfData.specularColor;
}
// For raytracing fit to standard Lit:
// Giving V will use a codepath where V is used, otherwise, the ortho direction is used
void GetCarPaintSpecularColorAndFlakesComponent(BSDFData bsdfData, out float3 singleBRDFColor, out float3 singleFlakesComponent, out float coatFGD, float3 V = 0)
{
//TODO: use approximated top lobe dir (if refractive coat) to have more appropriate and consistent base dirs
// This is statically known
bool useViewDir = ((V.x * V.y * V.z) != 0.0);
if (useViewDir)
{
float3 coatNormalWS = HasClearcoat() ? bsdfData.clearcoatNormalWS : bsdfData.normalWS;
float coatNdotV = dot(coatNormalWS, V);
coatFGD = HasClearcoat() ? F_FresnelDieletricSafe(bsdfData.clearcoatIOR, coatNdotV) : 0;
float3 refractedViewWS = V;
float thetaHForBRDFColor = FixedBRDFColorThetaHForIndirectLight;
float thetaHForFlakes = FixedFlakesThetaHForIndirectLight;
if (HasClearcoatAndRefraction())
{
refractedViewWS = -Refract(V, coatNormalWS, 1.0 / bsdfData.clearcoatIOR);
thetaHForBRDFColor = Refract(thetaHForBRDFColor, 1.0 / bsdfData.clearcoatIOR);
thetaHForFlakes = Refract(thetaHForFlakes, 1.0 / bsdfData.clearcoatIOR);
}
float NdotV = dot(bsdfData.normalWS, refractedViewWS);
float thetaH = 0; //FastACosPos(clamp(NdotH, 0, 1));
float thetaD = FastACosPos(clamp(NdotV, 0, 1));
singleBRDFColor = GetBRDFColor(thetaHForBRDFColor, thetaD);
singleFlakesComponent = CarPaint_BTF(thetaHForFlakes, thetaD, (SurfaceData)0, bsdfData, /*useBSDFData:*/true);
}
else
{
//coatFGD = HasClearcoat() ? F_FresnelDieletricSafe(surfaceData.clearcoatIOR, 1) : 0;
// ...this is just F0 of coat, so we do the equivalent:
coatFGD = HasClearcoat() ? IorToFresnel0(bsdfData.clearcoatIOR) : 0;
singleBRDFColor = GetBRDFColor(0,0);
singleFlakesComponent = CarPaint_BTF(0, 0,(SurfaceData)0, bsdfData, /*useBSDFData:*/true);
}
}
// For raytracing fit to standard Lit:
// Giving V will use a codepath where V is used, this is relevant only for carpaint model
// (cf GetColorBaseDiffuse() and GetColorBaseFresnelF0())
void GetBaseSurfaceColorAndF0(BSDFData bsdfData, out float3 diffuseColor, out float3 fresnel0, out float3 specBRDFColor, out float3 singleFlakesComponent, out float coatFGD, float3 V = 0, bool mixFlakes = false)
{
coatFGD = 0;
singleFlakesComponent = (float3)0;
fresnel0 = (float3)0;
float3 specularColor = (float3)0;
specBRDFColor = float3(1,1,1); // only used for carpaint
diffuseColor = bsdfData.diffuseColor;
#ifdef _AXF_BRDF_TYPE_SVBRDF
specularColor = bsdfData.specularColor;
fresnel0 = bsdfData.fresnel0; // See AxfData.hlsl: the actual sampled texture is always 1 channel, if we ever find otherwise, we will use the others.
fresnel0 = HasFresnelTerm() ? fresnel0.r * specularColor : specularColor;
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
GetCarPaintSpecularColorAndFlakesComponent(bsdfData, /*out*/specBRDFColor, /*out*/singleFlakesComponent, /*out*/coatFGD, V);
// For carpaint, diffuseColor is not chromatic.
// A chromatic diffuse albedo is the result of a scalar diffuse coefficient multiplied by the brdf color table value.
specularColor = specBRDFColor;
diffuseColor *= specBRDFColor;
fresnel0 = saturate(3*bsdfData.fresnel0);//GetCarPaintFresnel0() TODO: presumably better fit using V, see also GetCarPaintSpecularColor that uses V
fresnel0 = fresnel0.r * specularColor;
if (mixFlakes)
{
float maxf0 = Max3(fresnel0.r, fresnel0.g, fresnel0.b);
fresnel0 = saturate(singleFlakesComponent + fresnel0);
}
#endif
float baseEnergy = (1-coatFGD); // should be Sq but at this point we eyeball anyway,
//specularColor *= baseEnergy;
//diffuseColor *= baseEnergy;
//...commented, seems better without it.
}
void GetRoughnessNormalCoatMaskForFitToStandardLit(BSDFData bsdfData, float coatFGD, out float3 normalWS, out float roughness, out float coatMask)
{
normalWS = bsdfData.normalWS; // todo: "refract back" hack
// Try to simulate apparent roughness increase when he have refraction as we can't store refracted V in the GBUFFER,
// we could try another hack and modify the normal too.
roughness = GetScalarRoughness(bsdfData.roughness);
roughness = saturate(roughness * (HasClearcoatAndRefraction() ? (max(1, bsdfData.clearcoatIOR)) : 1) );
coatMask = HasClearcoat()? Sq(coatFGD) * Max3(bsdfData.clearcoatColor.r, bsdfData.clearcoatColor.g, bsdfData.clearcoatColor.b) : 0;
// Sq(coatFGD) is a hack to better fit what AxF shows vs the usage of the coatmask with Lit
coatMask = 0;
//...disable for now coat reduces too much visibility of primary surface and in any case in performance mode where we use FitToStandardLit,
//we will not get another reflection bounce so the coat reflection will be a fallback probe
}
float3 GetColorBaseDiffuse(BSDFData bsdfData)
{
float3 diffuseColor = 0;
#if defined(_AXF_BRDF_TYPE_SVBRDF)
diffuseColor = bsdfData.diffuseColor;
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
// For carpaint, specularColor will be set from BRDFColor table and
// diffuseColor is not chromatic. ie chromatic diffuse albedo is the result of
// scalar diffuse coefficient tinted by the brdf color table
diffuseColor = bsdfData.diffuseColor * bsdfData.specularColor;
#endif
return diffuseColor;
}
float4 GetDiffuseOrDefaultColor(BSDFData bsdfData, float replace)
{
float3 fresnel0 = GetColorBaseFresnelF0(bsdfData);
float3 diffuseColor = GetColorBaseDiffuse(bsdfData);
// Use fresnel0 as mettalic weight. all value below 0.2 (ior of diamond) are dielectric
// all value above 0.45 are metal, in between we lerp.
float weight = saturate((Max3(fresnel0.r, fresnel0.g, fresnel0.b) - 0.2) / (0.45 - 0.2));
return float4(lerp(diffuseColor, fresnel0, weight * replace), weight);
}
float3 GetNormalForShadowBias(BSDFData bsdfData)
{
return bsdfData.geomNormalWS;
}
float GetAmbientOcclusionForMicroShadowing(BSDFData bsdfData)
{
return 1.0;
}
//-----------------------------------------------------------------------------
// Debug method (use to display values)
//-----------------------------------------------------------------------------
void GetSurfaceDataDebug(uint paramId, SurfaceData surfaceData, inout float3 result, inout bool needLinearToSRGB)
{
GetGeneratedSurfaceDataDebug(paramId, surfaceData, result, needLinearToSRGB);
// Overide debug value output to be more readable
switch (paramId)
{
case DEBUGVIEW_AXF_SURFACEDATA_NORMAL_VIEW_SPACE:
// Convert to view space
{
float3 vsNormal = TransformWorldToViewDir(surfaceData.normalWS);
result = IsNormalized(vsNormal) ? vsNormal * 0.5 + 0.5 : float3(1.0, 0.0, 0.0);
break;
}
case DEBUGVIEW_AXF_SURFACEDATA_GEOMETRIC_NORMAL_VIEW_SPACE:
{
float3 vsGeomNormal = TransformWorldToViewDir(surfaceData.geomNormalWS);
result = IsNormalized(vsGeomNormal) ? vsGeomNormal * 0.5 + 0.5 : float3(1.0, 0.0, 0.0);
break;
}
}
}
void GetBSDFDataDebug(uint paramId, BSDFData bsdfData, inout float3 result, inout bool needLinearToSRGB)
{
GetGeneratedBSDFDataDebug(paramId, bsdfData, result, needLinearToSRGB);
// Overide debug value output to be more readable
switch (paramId)
{
case DEBUGVIEW_AXF_BSDFDATA_NORMAL_VIEW_SPACE:
// Convert to view space
{
float3 vsNormal = TransformWorldToViewDir(bsdfData.normalWS);
result = IsNormalized(vsNormal) ? vsNormal * 0.5 + 0.5 : float3(1.0, 0.0, 0.0);
break;
}
case DEBUGVIEW_AXF_BSDFDATA_GEOMETRIC_NORMAL_VIEW_SPACE:
{
float3 vsGeomNormal = TransformWorldToViewDir(bsdfData.geomNormalWS);
result = IsNormalized(vsGeomNormal) ? vsGeomNormal * 0.5 + 0.5 : float3(1.0, 0.0, 0.0);
break;
}
}
}
void GetPBRValidatorDebug(SurfaceData surfaceData, inout float3 result)
{
result = surfaceData.diffuseColor;
}
// This function is used to help with debugging and must be implemented by any lit material
// Implementer must take into account what are the current override component and
// adjust SurfaceData properties accordingdly
void ApplyDebugToSurfaceData(float3x3 tangentToWorld, inout SurfaceData surfaceData)
{
#ifdef DEBUG_DISPLAY
// NOTE: THe _Debug* uniforms come from /HDRP/Debug/DebugDisplay.hlsl
// Override value if requested by user this can be use also in case of debug lighting mode like diffuse only
bool overrideAlbedo = _DebugLightingAlbedo.x != 0.0;
bool overrideSmoothness = _DebugLightingSmoothness.x != 0.0;
bool overrideNormal = _DebugLightingNormal.x != 0.0;
if (overrideAlbedo)
{
surfaceData.diffuseColor = _DebugLightingAlbedo.yzw;
}
if (overrideSmoothness)
{
float overrideSmoothnessValue = _DebugLightingSmoothness.y;
surfaceData.perceptualSmoothness = overrideSmoothnessValue;
surfaceData.specularLobe = PerceptualSmoothnessToRoughness(overrideSmoothnessValue);
}
if (overrideNormal)
{
surfaceData.normalWS = tangentToWorld[2];
}
if (_DebugFullScreenMode == FULLSCREENDEBUGMODE_VALIDATE_DIFFUSE_COLOR)
{
surfaceData.diffuseColor = pbrDiffuseColorValidate(surfaceData.diffuseColor, surfaceData.specularColor, false, false).xyz;
}
else if (_DebugFullScreenMode == FULLSCREENDEBUGMODE_VALIDATE_SPECULAR_COLOR)
{
surfaceData.diffuseColor = pbrSpecularColorValidate(surfaceData.diffuseColor, surfaceData.specularColor, false, false).xyz;
}
#endif
}
// This function is similar to ApplyDebugToSurfaceData but for BSDFData
//
// NOTE:
// This will be available and used in ShaderPassForward.hlsl since in AxF.shader,
// just before including the core code of the pass (ShaderPassForward.hlsl) we include
// Material.hlsl (or Lighting.hlsl which includes it) which in turn includes us,
// AxF.shader, via the #if defined(UNITY_MATERIAL_*) glue mechanism.
//
void ApplyDebugToBSDFData(inout BSDFData bsdfData)
{
#ifdef DEBUG_DISPLAY
// Override value if requested by user
// this can be use also in case of debug lighting mode like specular only
bool overrideSpecularColor = _DebugLightingSpecularColor.x != 0.0;
if (overrideSpecularColor)
{
float3 overrideSpecularColor = _DebugLightingSpecularColor.yzw;
bsdfData.specularColor = overrideSpecularColor;
}
#endif
}
NormalData ConvertSurfaceDataToNormalData(SurfaceData surfaceData)
{
NormalData normalData;
// TODO: consider coat F0 ? flakes (but would require fetching them) ?
if (HasClearcoat()) // in that case we automatically have dual normal maps
{
normalData.normalWS = surfaceData.clearcoatNormalWS;
normalData.perceptualRoughness = CLEAR_COAT_PERCEPTUAL_ROUGHNESS;
}
else
{
normalData.normalWS = surfaceData.normalWS;
// Hack: try to get a "single equivalent" roughness
normalData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surfaceData.perceptualSmoothness);
}
return normalData;
}
//----------------------------------------------------------------------
// Ref: https://seblagarde.wordpress.com/2013/04/29/memo-on-fresnel-equations/
// Fresnel dieletric / dielectric
float Fresnel0ToIorSafe(float fresnel0)
{
// We guard against f0 = 1,
// we always do conversion as if top has an IOR of 1.0, as the f0 is assumed
// measured and baked-in, ie to be evaluated as-is, with whatever was specified
// for the top in the rest of the AxF.
return Fresnel0ToIor(min(0.999, fresnel0));
}
//----------------------------------------------------------------------
// Cook-Torrance functions as provided by X-Rite in the "AxF-Decoding-SDK-1.5.1/doc/html/page2.html#carpaint_BrightnessBRDF" document from the SDK
//
// Warning: This matches the SDK but is not the Beckmann D() NDF: a /PI is missing!
float CT_D(float N_H, float m)
{
float cosb_sqr = N_H * N_H;
float m_sqr = m * m;
float e = (cosb_sqr - 1.0) / (cosb_sqr*m_sqr); // -tan(a)^2 / m^2
return exp(e) / (m_sqr*cosb_sqr*cosb_sqr); // exp(-tan(a)^2 / m^2) / (m^2 * cos(a)^4)
}
// Classical Schlick approximation for Fresnel
float CT_F(float H_V, float F0)
{
float f_1_sub_cos = 1.0 - H_V;
float f_1_sub_cos_sqr = f_1_sub_cos * f_1_sub_cos;
float f_1_sub_cos_fifth = f_1_sub_cos_sqr * f_1_sub_cos_sqr*f_1_sub_cos;
return F0 + (1.0 - F0) * f_1_sub_cos_fifth;
}
float MultiLobesCookTorrance(BSDFData bsdfData, float NdotL, float NdotV, float NdotH, float VdotH)
{
// Ensure numerical stability
if (NdotV < 0.00174532836589830883577820272085 || NdotL < 0.00174532836589830883577820272085) //sin(0.1 deg )
return 0.0;
float specularIntensity = 0.0;
for (uint lobeIndex = 0; lobeIndex < CARPAINT2_LOBE_COUNT; lobeIndex++)
{
float F0 = _CarPaint2_CTF0s[lobeIndex];
float coeff = _CarPaint2_CTCoeffs[lobeIndex];
float spread = bsdfData.roughness[lobeIndex]; // _CarPaint2_CTSpreads[lobeIndex];
specularIntensity += coeff * CT_D(NdotH, spread) * CT_F(VdotH, F0);
}
// FIXME: should be 4 instead of PI at the denominator, this was a mistake in the original paper
specularIntensity *= G_CookTorrance(NdotH, NdotV, NdotL, VdotH) // Shadowing/Masking term
/ (PI * max(1e-3, NdotV * NdotL));
return specularIntensity;
}
//----------------------------------------------------------------------
// Simple Oren-Nayar implementation (from http://patapom.com/blog/BRDF/MSBRDFEnergyCompensation/#oren-nayar-diffuse-model)
// normal, unit surface normal
// light, unit vector pointing toward the light
// view, unit vector pointing toward the view
// roughness, Oren-Nayar roughness parameter in [0,PI/2]
//
float OrenNayar(in float3 n, in float3 v, in float3 l, in float roughness)
{
float LdotN = dot(l, n);
float VdotN = dot(v, n);
float gamma = dot(v - n * VdotN, l - n * LdotN)
/ (sqrt(saturate(1.0 - VdotN * VdotN)) * sqrt(saturate(1.0 - LdotN * LdotN)));
float rough_sq = roughness * roughness;
// float A = 1.0 - 0.5 * (rough_sq / (rough_sq + 0.33)); // You can replace 0.33 by 0.57 to simulate the missing inter-reflection term, as specified in footnote of page 22 of the 1992 paper
float A = 1.0 - 0.5 * (rough_sq / (rough_sq + 0.57)); // You can replace 0.33 by 0.57 to simulate the missing inter-reflection term, as specified in footnote of page 22 of the 1992 paper
float B = 0.45 * (rough_sq / (rough_sq + 0.09));
// Original formulation
// float angle_vn = acos(VdotN);
// float angle_ln = acos(LdotN);
// float alpha = max(angle_vn, angle_ln);
// float beta = min(angle_vn, angle_ln);
// float C = sin(alpha) * tan(beta);
// Optimized formulation (without tangents, arccos or sines)
float2 cos_alpha_beta = VdotN < LdotN ? float2(VdotN, LdotN) : float2(LdotN, VdotN); // Here we reverse the min/max since cos() is a monotonically decreasing function
float2 sin_alpha_beta = sqrt(saturate(1.0 - cos_alpha_beta * cos_alpha_beta)); // Saturate to avoid NaN if ever cos_alpha > 1 (it happens with floating-point precision)
float C = sin_alpha_beta.x * sin_alpha_beta.y / (1e-6 + cos_alpha_beta.y);
return A + B * max(0.0, gamma) * C;
}
//-----------------------------------------------------------------------------
// conversion function for forward
//-----------------------------------------------------------------------------
BSDFData ConvertSurfaceDataToBSDFData(uint2 positionSS, SurfaceData surfaceData)
{
BSDFData bsdfData;
ZERO_INITIALIZE(BSDFData, bsdfData);
bsdfData.ambientOcclusion = surfaceData.ambientOcclusion;
bsdfData.specularOcclusion = surfaceData.specularOcclusion;
// V is needed for raytracing performance fit to lit:
// TODO: should just modify FitToStandardLit in ShaderPassRaytracingGBuffer.hlsl and callee
// to have "V" (from -incidentDir)
bsdfData.viewWS = surfaceData.viewWS;
bsdfData.normalWS = surfaceData.normalWS;
bsdfData.tangentWS = surfaceData.tangentWS;
bsdfData.bitangentWS = cross(bsdfData.normalWS, bsdfData.tangentWS);
bsdfData.roughness = 0;
// see AxFData.hlsl: important, this is used in PostEvaluateBSDF here and in AxFRayTracing
bsdfData.perceptualRoughness = PerceptualSmoothnessToPerceptualRoughness(surfaceData.perceptualSmoothness);
//-----------------------------------------------------------------------------
#ifdef _AXF_BRDF_TYPE_SVBRDF
bsdfData.diffuseColor = surfaceData.diffuseColor;
bsdfData.specularColor = surfaceData.specularColor;
bsdfData.fresnel0 = surfaceData.fresnel0; // See AxfData.hlsl: the actual sampled texture is always 1 channel, if we ever find otherwise, we will use the others.
bsdfData.height_mm = surfaceData.height_mm;
bsdfData.roughness.xy = HasAnisotropy() ? surfaceData.specularLobe.xy : surfaceData.specularLobe.xx;
bsdfData.clearcoatColor = surfaceData.clearcoatColor;
bsdfData.clearcoatNormalWS = HasClearcoat() ? surfaceData.clearcoatNormalWS : surfaceData.normalWS;
bsdfData.clearcoatIOR = surfaceData.clearcoatIOR;
//-----------------------------------------------------------------------------
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
bsdfData.diffuseColor = surfaceData.diffuseColor; // See GetColorBaseDiffuse() for carpaint!
FillFlakesBSDFData(surfaceData, bsdfData);
bsdfData.clearcoatColor = 1.0; // Not provided, assume white...
bsdfData.clearcoatIOR = surfaceData.clearcoatIOR;
bsdfData.clearcoatNormalWS = HasClearcoat() ? surfaceData.clearcoatNormalWS : surfaceData.normalWS;
bsdfData.specularColor = GetCarPaintSpecularColor();
bsdfData.fresnel0 = GetCarPaintFresnel0();
bsdfData.roughness.xyz = surfaceData.specularLobe.xyz; // the later stores per lobe possibly modified (for geometric specular AA) _CarPaint2_CTSpreads
bsdfData.height_mm = 0;
#endif
bsdfData.geomNormalWS = surfaceData.geomNormalWS;
ApplyDebugToBSDFData(bsdfData);
return bsdfData;
}
//-----------------------------------------------------------------------------
// PreLightData
//
// Make sure we respect naming conventions to reuse ShaderPassForward as is,
// ie struct (even if opaque to the ShaderPassForward) name is PreLightData,
// GetPreLightData prototype.
//-----------------------------------------------------------------------------
// Precomputed lighting data to send to the various lighting functions
struct PreLightData
{
float NdotV_UnderCoat; // NdotV after optional clear-coat refraction. Could be negative due to normal mapping, use ClampNdotV()
float NdotV_Clearcoat; // NdotV before optional clear-coat refraction. Could be negative due to normal mapping, use ClampNdotV()
float3 viewWS_UnderCoat; // View vector after optional clear-coat refraction.
// IBL
float3 iblDominantDirectionWS_BottomLobeOnTop; // Dominant specular direction, for bottom lobe but as it exit on top, used for IBL in EvaluateBSDF_Env()
float3 iblDominantDirectionWS_Clearcoat; // Dominant specular direction, used for IBL in EvaluateBSDF_Env() and also in area lights when clearcoat is enabled
#ifdef _AXF_BRDF_TYPE_SVBRDF
float iblPerceptualRoughness;
float3 specularFGD;
float diffuseFGD;
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
#if !defined(USE_COOK_TORRANCE_MULTI_LOBES)
float iblPerceptualRoughness; // Use this to store an average lobe roughness
float3 specularCTFGDSingleLobe;
#else
float3 iblPerceptualRoughness; // per lobe values in xyz
float3 specularCTFGDAtZeroF0; // monochromatic FGD, per lobe values in xyz
float3 specularCTFGDReflectivity; // monochromatic FGD, per lobe values in xyz
#endif
float3 singleBRDFColor;
float3 singleFlakesComponent;
float flakesFGD;
#endif
float coatReflectionWeight; // Extra light reflectionHierarchyWeight
float baseReflectionWeight; // Note: even for car paint we just track one weight for the bottom layer to simplify, see EvaluateBSDF_Env and EvaluateBSDF_ScreenSpaceReflection
float coatFGD;
float coatPartLambdaV;
// Area lights (18 VGPRs)
// TODO: 'orthoBasisViewNormal' is just a rotation around the normal and should thus be just 1x VGPR.
float3x3 orthoBasisViewNormal; // Right-handed view-dependent orthogonal basis around the normal (6x VGPRs)
#ifdef _AXF_BRDF_TYPE_SVBRDF
float3x3 ltcTransformDiffuse; // Inverse transformation (4x VGPRs)
float3x3 ltcTransformSpecular; // Inverse transformation (4x VGPRs)
#endif
float3x3 ltcTransformClearcoat;
#if defined(_AXF_BRDF_TYPE_CAR_PAINT)
float3x3 ltcTransformSpecularCT[MAX_CT_LOBE_COUNT]; // Inverse transformation (4x VGPRs)
float3x3 ltcTransformFlakes;
#endif
};
//
// ClampRoughness helper specific to this material
//
void ClampRoughness(inout PreLightData preLightData, inout BSDFData bsdfData, float minRoughness)
{
}
float3 FindAverageBaseLobeDirOnTop(BSDFData bsdfData, PreLightData preLightData, out float3 lobeDirUndercoat)
{
float3 outDir;
#if 0
// simple test: eg for carpaint or any material without any normal maps, this should give the same
// fetch alignment as just using the view reflected on top:
float3 vRefractedBottomReflected = reflect(-preLightData.viewWS_UnderCoat, bsdfData.normalWS);
outDir = Refract(-vRefractedBottomReflected, -bsdfData.clearcoatNormalWS, bsdfData.clearcoatIOR);
return outDir;
#else
float3 vRefractedBottomReflected = reflect(-preLightData.viewWS_UnderCoat, bsdfData.normalWS);
// First make sure that vRefractedBottomReflected is directed towards the coat surface we want to pass:
// ie make sure it is not under the top horizon (let alone in TIR which we ignore!)
vRefractedBottomReflected = SaturateDirToHorizon(vRefractedBottomReflected, bsdfData.clearcoatNormalWS);
//to test SaturateDirToHorizon:
//outDir = Refract(-vRefractedBottomReflected, -bsdfData.clearcoatNormalWS, bsdfData.clearcoatIOR);
//return outDir;
// Now whether the direction was past the critical angle nor not, refract while making sure that
// in case of TIR, we just output an horizon grazing direction:
//to debug when actually TIR happened:
float3 incomingSaturated;
float rayIntensity;
outDir = RefractSaturateToTIR(-vRefractedBottomReflected, -bsdfData.clearcoatNormalWS, bsdfData.clearcoatIOR, rayIntensity, incomingSaturated);
lobeDirUndercoat = -incomingSaturated; // incoming is away from the top interface from under the surface so *-1 to reverse quadrant.
#endif
return outDir;
}
PreLightData GetPreLightData(float3 viewWS_Clearcoat, PositionInputs posInput, inout BSDFData bsdfData)
{
PreLightData preLightData;
// ZERO_INITIALIZE(PreLightData, preLightData);
preLightData.NdotV_Clearcoat = dot(bsdfData.clearcoatNormalWS, viewWS_Clearcoat);
preLightData.viewWS_UnderCoat = viewWS_Clearcoat; // Save original view before optional refraction by clearcoat
//-----------------------------------------------------------------------------
// Handle clearcoat refraction of view ray
if (HasClearcoatAndRefraction())
{
preLightData.viewWS_UnderCoat = -Refract(viewWS_Clearcoat, bsdfData.clearcoatNormalWS, 1.0 / bsdfData.clearcoatIOR);
}
//todo_dir test_disable_refract for environments:
//preLightData.viewWS_UnderCoat = viewWS_Clearcoat;
// Compute under-coat view-dependent data after optional refraction
preLightData.NdotV_UnderCoat = dot(bsdfData.normalWS, preLightData.viewWS_UnderCoat);
//preLightData.NdotV_UnderCoat = min(preLightData.NdotV_UnderCoat, preLightData.NdotV_Clearcoat);
float NdotV_UnderCoat = ClampNdotV(preLightData.NdotV_UnderCoat);
float NdotV_Clearcoat = ClampNdotV(preLightData.NdotV_Clearcoat);
//test_disable_refract for environments:
//NdotV_UnderCoat = NdotV_Clearcoat;
//NdotV_UnderCoat = min(NdotV_UnderCoat, NdotV_Clearcoat);
//-----------------------------------------------------------------------------
// Handle IBL + multiscattering
// todo_dir:
// todo_dir todo_modes todo_pseudorefract: cant use undercoat like that, but better than to lose the bottom normal effect for now...
float3 reflectedLobeDirUndercoat = reflect(-preLightData.viewWS_UnderCoat, bsdfData.normalWS);
preLightData.iblDominantDirectionWS_BottomLobeOnTop = reflectedLobeDirUndercoat;
if (HasClearcoatAndRefraction())
{
preLightData.iblDominantDirectionWS_BottomLobeOnTop = FindAverageBaseLobeDirOnTop(bsdfData, preLightData, reflectedLobeDirUndercoat); // much better
// reflectedLobeDirUndercoat is now adjusted to correspond to the refracted-back on top direction returned by FindAverageBaseLobeDirOnTop()
//sanity check: If both normals are equal, then this shouldn't change the output:
//preLightData.iblDominantDirectionWS_BottomLobeOnTop = reflect(-viewWS_Clearcoat, bsdfData.clearcoatNormalWS);
//reflectedLobeDirUndercoat = reflect(-preLightData.viewWS_UnderCoat, bsdfData.normalWS);
}
preLightData.iblDominantDirectionWS_Clearcoat = reflect(-viewWS_Clearcoat, bsdfData.clearcoatNormalWS);
//preLightData.iblDominantDirectionWS_BottomLobeOnTop = preLightData.iblDominantDirectionWS_Clearcoat;
#ifdef _AXF_BRDF_TYPE_SVBRDF
// @TODO => Anisotropic IBL?
// TODO_SL
preLightData.iblPerceptualRoughness = RoughnessToPerceptualRoughness(GetScalarRoughnessFromAnisoRoughness(bsdfData.roughness.x, bsdfData.roughness.y));
// todo_fresnel: TOCHECK: Make BRDF and FGD for env. consistent with dirac lights for HasFresnelTerm() handling:
// currently, we only check it for Ward and its variants.
float3 tempF0 = HasFresnelTerm() ? bsdfData.fresnel0.rrr : 1.0;
tempF0 *= bsdfData.specularColor; // Important to use in the PreIntegratedFGD interpolated fetches!
float specularReflectivity;
switch (AXF_SVBRDF_BRDFTYPE_SPECULARTYPE)
{
//@TODO: Oren-Nayar diffuse FGD
case 0:
GetPreIntegratedFGDWardAndLambert(NdotV_UnderCoat, preLightData.iblPerceptualRoughness, tempF0, preLightData.specularFGD, preLightData.diffuseFGD, specularReflectivity);
// Although we have pre-integrated FGD for non-GGX BRDFs, all our IBL are pre-convolved with GGX, so use this rough conversion:
preLightData.iblPerceptualRoughness = PerceptualRoughnessBeckmannToGGX(preLightData.iblPerceptualRoughness);
break;
case 1: //Phong
case 4: //Blinn-Phong : just approximate with Cook-Torrance which uses a Beckmann distribution
case 2:
GetPreIntegratedFGDCookTorranceAndLambert(NdotV_UnderCoat, preLightData.iblPerceptualRoughness, tempF0, preLightData.specularFGD, preLightData.diffuseFGD, specularReflectivity);
preLightData.specularFGD *= GetPreIntegratedFGDCookTorranceSampleMutiplier();
// Although we have pre-integrated FGD for non-GGX BRDFs, all our IBL are pre-convolved with GGX, so use this rough conversion:
preLightData.iblPerceptualRoughness = PerceptualRoughnessBeckmannToGGX(preLightData.iblPerceptualRoughness);
break;
case 3:
GetPreIntegratedFGDGGXAndLambert(NdotV_UnderCoat, preLightData.iblPerceptualRoughness, tempF0, preLightData.specularFGD, preLightData.diffuseFGD, specularReflectivity);
break;
default: // Use GGX by default
GetPreIntegratedFGDGGXAndLambert(NdotV_UnderCoat, preLightData.iblPerceptualRoughness, tempF0, preLightData.specularFGD, preLightData.diffuseFGD, specularReflectivity);
break;
}
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
float sumRoughness = 0.0;
float sumCoeff = 0.0;
float sumF0 = 0.0;
float3 tempF0;
float diffuseFGD, reflectivity; //TODO
float3 specularFGD;
preLightData.iblPerceptualRoughness = 0;
preLightData.specularCTFGDAtZeroF0 = 0;
preLightData.specularCTFGDReflectivity = 0;
preLightData.ltcTransformSpecularCT = (float3x3[MAX_CT_LOBE_COUNT])0;
// TODO_diffuseFGDColor: better one, averaged maybe: ie depending on roughness also
preLightData.singleBRDFColor = 1.0;
float thetaH = 0;
float thetaD = FastACosPos(clamp(preLightData.NdotV_UnderCoat, 0, 1));
// The above is the same as
//float3 lightDir = reflect(-preLightData.viewWS_UnderCoat, bsdfData.normalWS);
//float3 H = normalize(preLightData.viewWS_UnderCoat + lightDir);
//float NdotH = dot(bsdfData.normalWS, H);
//float LdotH = dot(H, lightDir);
//thetaH = FastACosPos(clamp(NdotH, 0, 1));
//thetaD = FastACosPos(clamp(LdotH, 0, 1));
// Also, could use reflectedLobeDirUndercoat here (and see TODO_diffuseFGDColor: if we make it depends on roughness, one per lobe)
// This is relevant only if both normals aren't the same obviously.
// In the case of CARPAINT, this means a clearcoat normal map.
// (ie orange peel)
if (false)
{
float3 H = normalize(preLightData.viewWS_UnderCoat + reflectedLobeDirUndercoat);
float NdotH = dot(bsdfData.normalWS, H);
float LdotH = dot(H, reflectedLobeDirUndercoat);
thetaH = FastACosPos(clamp(NdotH, 0, 1));
thetaD = FastACosPos(clamp(LdotH, 0, 1));
}
float thetaHForBRDFColor = HasClearcoatAndRefraction() ? Refract(FixedBRDFColorThetaHForIndirectLight, 1.0 / bsdfData.clearcoatIOR) : FixedBRDFColorThetaHForIndirectLight;
float thetaHForFlakes = HasClearcoatAndRefraction() ? Refract(FixedFlakesThetaHForIndirectLight, 1.0 / bsdfData.clearcoatIOR) : FixedFlakesThetaHForIndirectLight;
preLightData.singleBRDFColor *= GetBRDFColor(thetaHForBRDFColor, thetaD);
preLightData.singleFlakesComponent = CarPaint_BTF(thetaHForFlakes, thetaD, (SurfaceData)0, bsdfData, /*useBSDFData:*/true);
UNITY_UNROLL
for (uint lobeIndex = 0; lobeIndex < CARPAINT2_LOBE_COUNT; lobeIndex++)
{
float F0 = _CarPaint2_CTF0s[lobeIndex];
float coeff = _CarPaint2_CTCoeffs[lobeIndex];
float spread = bsdfData.roughness[lobeIndex]; // _CarPaint2_CTSpreads[lobeIndex];
#if !USE_COOK_TORRANCE_MULTI_LOBES
// Computes weighted average of roughness values
sumCoeff += coeff;
sumF0 += F0;
sumRoughness += spread;
#else
// We also do the pre-integrated FGD fetches here:
// Note that PreIntegratedFGD_CookTorrance is done using (non perceptual) Beckmann roughness as it should:
float perceptualRoughnessBeckmann = RoughnessToPerceptualRoughness(spread);
GetPreIntegratedFGDCookTorranceAndLambert(NdotV_UnderCoat, perceptualRoughnessBeckmann, (float3)0.0, specularFGD, diffuseFGD, reflectivity);
preLightData.specularCTFGDAtZeroF0[lobeIndex] = specularFGD.x * GetPreIntegratedFGDCookTorranceSampleMutiplier();
preLightData.specularCTFGDReflectivity[lobeIndex] = reflectivity.x * GetPreIntegratedFGDCookTorranceSampleMutiplier();
//float3 specularFGDFromGGX;
//test_Beckmann_to_GGX on preintegratedFGD:
//GetPreIntegratedFGDGGXAndLambert(NdotV_UnderCoat, PerceptualRoughnessBeckmannToGGX(perceptualRoughnessBeckmann), F0.xxx, specularFGDFromGGX, diffuseFGD, reflectivity);
//test_Beckmann_to_GGX on preintegratedFGD:
//preLightData.specularCTFGD[lobeIndex] = lerp(specularFGD.x, specularFGDFromGGX.x, _SVBRDF_HeightMapMaxMM);
//if (_SVBRDF_HeightMapMaxMM == 3.0)
//{
// GetPreIntegratedFGDGGXAndLambert(NdotV_UnderCoat, perceptualRoughnessBeckmann, F0.xxx, specularFGDFromGGX, diffuseFGD, reflectivity);
// preLightData.specularCTFGD[lobeIndex] = specularFGDFromGGX.x;
//}
// debugtest:
//preLightData.iblPerceptualRoughness[lobeIndex] = _SVBRDF_HeightMapMaxMM * PerceptualRoughnessBeckmannToGGX(perceptualRoughnessBeckmann);
preLightData.iblPerceptualRoughness[lobeIndex] = PerceptualRoughnessBeckmannToGGX(perceptualRoughnessBeckmann);
// And the area lights LTC inverse transform:
// todo_modes todo_pseudorefract: commented, cant use undercoat like that.
preLightData.ltcTransformSpecularCT[lobeIndex] = SampleLtcMatrix(preLightData.iblPerceptualRoughness[lobeIndex], NdotV_Clearcoat, LTCLIGHTINGMODEL_COOK_TORRANCE);
#endif
}
#if !USE_COOK_TORRANCE_MULTI_LOBES
// Not used if sampling the environment for each Cook-Torrance lobe
// Simulate one lobe with averaged roughness and f0
float oneOverLobeCnt = rcp(CARPAINT2_LOBE_COUNT);
preLightData.iblPerceptualRoughness = RoughnessToPerceptualRoughness(sumRoughness * oneOverLobeCnt);
tempF0 = sumF0 * oneOverLobeCnt;
// todo_BeckmannToGGX
GetPreIntegratedFGDCookTorranceAndLambert(NdotV_UnderCoat, preLightData.iblPerceptualRoughness, tempF0 * preLightData.singleBRDFColor, specularFGD, diffuseFGD, reflectivity);
preLightData.iblPerceptualRoughness = PerceptualRoughnessBeckmannToGGX(preLightData.iblPerceptualRoughness);
specularFGD *= GetPreIntegratedFGDCookTorranceSampleMutiplier();
preLightData.specularCTFGDSingleLobe = specularFGD * sumCoeff;
#endif
// preLightData.flakesFGD =
//
// For flakes, even if they are to be taken as tiny mirrors, the orientation would need to be
// captured by a high res normal map with the problems that this implies.
// So instead we have a pseudo BTF that is the "left overs" that the CT lobes don't fit, indexed
// by two angles (which is theoretically a problem, see comments in GetBRDFColor).
// If we wanted to add more variations on top, here we could consider
// a pre-integrated FGD for flakes.
// If we assume very low roughness like the coat, we could also approximate it as being a Fresnel
// term like for coatFGD below.
// If the f0 is already very high though (metallic flakes), the variations won't be substantial.
//
// For testing for now:
preLightData.flakesFGD = 1.0;
GetPreIntegratedFGDGGXAndDisneyDiffuse(NdotV_UnderCoat, FLAKES_PERCEPTUAL_ROUGHNESS, FLAKES_F0, specularFGD, diffuseFGD, reflectivity);
IFNOT_FLAKES_JUST_BTF(preLightData.flakesFGD = specularFGD.x);
preLightData.singleFlakesComponent *= preLightData.flakesFGD;
// We will override this with the coat transform if we just want the BTF term in LTC lights
// todo_modes todo_pseudorefract: cant use undercoat like that:
IFNOT_FLAKES_JUST_BTF(preLightData.ltcTransformFlakes = SampleLtcMatrix(FLAKES_PERCEPTUAL_ROUGHNESS, NdotV_Clearcoat, LTCLIGHTINGMODEL_GGX));
#endif//#ifdef _AXF_BRDF_TYPE_SVBRDF
//-----------------------------------------------------------------------------
// Area lights
// Construct a right-handed view-dependent orthogonal basis around the normal
preLightData.orthoBasisViewNormal[2] = bsdfData.normalWS;
preLightData.orthoBasisViewNormal[0] = normalize(viewWS_Clearcoat - preLightData.NdotV_Clearcoat * bsdfData.normalWS); // Do not clamp NdotV here
preLightData.orthoBasisViewNormal[1] = cross(preLightData.orthoBasisViewNormal[2], preLightData.orthoBasisViewNormal[0]);
#ifdef _AXF_BRDF_TYPE_SVBRDF
// Load diffuse LTC & FGD
if (AXF_SVBRDF_BRDFTYPE_DIFFUSETYPE)
{
// todo_modes todo_pseudorefract: cant use undercoat like that
preLightData.ltcTransformDiffuse = SampleLtcMatrix(preLightData.iblPerceptualRoughness, NdotV_Clearcoat, LTCLIGHTINGMODEL_OREN_NAYAR);
}
else
{
preLightData.ltcTransformDiffuse = k_identity3x3; // Lambert
}
uint bsdfIndex;
switch (AXF_SVBRDF_BRDFTYPE_SPECULARTYPE)
{
case 0:
bsdfIndex = LTCLIGHTINGMODEL_WARD;
break;
case 3:
bsdfIndex = LTCLIGHTINGMODEL_GGX;
break;
default: // COOK-TORRANCE, BLINN-PHONG, PHONG, or missing
bsdfIndex = LTCLIGHTINGMODEL_COOK_TORRANCE;
break;
}
// Load specular LTC & FGD
// Warning: all these LTC_MATRIX_INDEX_ are the same for now, and fitted for GGX, hence the code
// above that selected the UVs all used a preLightData.iblPerceptualRoughness value that used a
// conversion formula for Beckmann NDF (exp) based BRDFs
// (see switch (AXF_SVBRDF_BRDFTYPE_SPECULARTYPE) above and usage of PerceptualRoughnessBeckmannToGGX)
//
// todo_modes todo_pseudorefract: cant use undercoat like that
preLightData.ltcTransformSpecular = SampleLtcMatrix(preLightData.iblPerceptualRoughness, NdotV_Clearcoat, bsdfIndex);
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
// already sampled the matrices in our loop for pre-integrated FGD above
#endif // _AXF_BRDF_TYPE_SVBRDF
// Load clear-coat LTC & FGD
preLightData.ltcTransformClearcoat = 0.0;
#if defined(_AXF_BRDF_TYPE_CAR_PAINT)
IF_FLAKES_JUST_BTF(preLightData.ltcTransformFlakes = 0.0);
#endif
preLightData.coatFGD = 0;
preLightData.coatPartLambdaV = 0;
if (HasClearcoat())
{
preLightData.ltcTransformClearcoat = SampleLtcMatrix(CLEAR_COAT_PERCEPTUAL_ROUGHNESS, NdotV_Clearcoat, LTCLIGHTINGMODEL_GGX);
#if defined(_AXF_BRDF_TYPE_CAR_PAINT)
IF_FLAKES_JUST_BTF(preLightData.ltcTransformFlakes = preLightData.ltcTransformClearcoat);
#endif
#if 0
float clearcoatF0 = IorToFresnel0(bsdfData.clearcoatIOR);
float specularReflectivity, dummyDiffuseFGD;
GetPreIntegratedFGDGGXAndDisneyDiffuse(NdotV_Clearcoat, CLEAR_COAT_PERCEPTUAL_ROUGHNESS, clearcoatF0, preLightData.coatFGD, dummyDiffuseFGD, specularReflectivity);
// Cheat a little and make the amplitude go to 0 when F0 is 0 (which the actual dieletric Fresnel should do!)
preLightData.coatFGD *= smoothstep(0, 0.01, clearcoatF0);
#else
// We can approximate the pre-integrated FGD term for a near dirac BSDF as the
// point evaluation of the Fresnel term itself when L is at the NdotV angle,
// which is the split sum environment assumption (cf Lit doing the same with preLightData.coatIblF)
// We use expensive Fresnel here so the clearcoat properly disappears when IOR -> 1
preLightData.coatFGD = F_FresnelDieletricSafe(bsdfData.clearcoatIOR, NdotV_Clearcoat);
#endif
// For the coat lobe, we need a sharp BSDF for the high smoothness,
// See axf-decoding-sdk/doc/html/page1.html#svbrdf_subsec03
// we arbitrarily use GGX
preLightData.coatPartLambdaV = GetSmithJointGGXPartLambdaV(NdotV_Clearcoat, CLEAR_COAT_ROUGHNESS);
}
// Init light hierarchy reflection weights to 0:
preLightData.coatReflectionWeight = preLightData.baseReflectionWeight = 0;
return preLightData;
}
//----------------------------------------------------------------------
// Computes Fresnel reflection/refraction of view and light vectors due to clearcoating
// Returns the ratios of the incoming reflected and refracted energy
// Also refracts the provided view and light vectors if refraction is enabled
//
//void ComputeClearcoatReflectionAndExtinction(inout float3 viewWS, inout float3 lightWS, BSDFData bsdfData, out float3 reflectedRatio, out float3 refractedRatio) {
//
// // Computes perfect mirror reflection
// float3 H = normalize(viewWS + lightWS);
// float LdotH = saturate(dot(lightWS, H));
//
// reflectedRatio = F_FresnelDieletricSafe(bsdfData.clearcoatIOR, LdotH); // Full reflection in mirror direction (we use expensive Fresnel here so the clearcoat properly disappears when IOR -> 1)
//
// // Compute input/output Fresnel reflections
// float LdotN = saturate(dot(lightWS, bsdfData.clearcoatNormalWS));
// float3 Fin = F_FresnelDieletricSafe(bsdfData.clearcoatIOR, LdotN);
//
// float VdotN = saturate(dot(viewWS, bsdfData.clearcoatNormalWS));
// float3 Fout = F_FresnelDieletricSafe(bsdfData.clearcoatIOR, VdotN);
//
// // Apply optional refraction
// if (_Flags & 4U) {
// float eta = 1.0 / bsdfData.clearcoatIOR;
// lightWS = -Refract(lightWS, bsdfData.clearcoatNormalWS, eta);
// viewWS = -Refract(viewWS, bsdfData.clearcoatNormalWS, eta);
// }
//
// refractedRatio = (1-Fin) * (1-Fout);
//}
void ComputeClearcoatReflectionAndExtinction_UsePreLightData(inout float3 viewWS, inout float3 lightWS, BSDFData bsdfData, PreLightData preLightData, out float reflectedRatio, out float refractedRatio)
{
// Computes perfect mirror reflection
float3 H = normalize(viewWS + lightWS);
float LdotH = saturate(dot(lightWS, H));
reflectedRatio = F_FresnelDieletricSafe(bsdfData.clearcoatIOR, LdotH); // we use expensive Fresnel here so the clearcoat properly disappears when IOR -> 1
// TOCHECK
// Compute input/output Fresnel reflections
float LdotN = saturate(dot(lightWS, bsdfData.clearcoatNormalWS));
float Fin = F_FresnelDieletricSafe(bsdfData.clearcoatIOR, LdotN);
float VdotN = saturate(dot(viewWS, bsdfData.clearcoatNormalWS));
float Fout = F_FresnelDieletricSafe(bsdfData.clearcoatIOR, VdotN);
// Apply optional refraction
if (HasClearcoatRefraction())
{
lightWS = -Refract(lightWS, bsdfData.clearcoatNormalWS, 1.0 / bsdfData.clearcoatIOR);
viewWS = preLightData.viewWS_UnderCoat;
}
refractedRatio = (1 - Fin) * (1 - Fout);
}
//-----------------------------------------------------------------------------
// bake lighting function
//-----------------------------------------------------------------------------
// This define allow to say that we implement a ModifyBakedDiffuseLighting function to be call in PostInitBuiltinData
#define MODIFY_BAKED_DIFFUSE_LIGHTING
void ModifyBakedDiffuseLighting(float3 V, PositionInputs posInput, PreLightData preLightData, BSDFData bsdfData, inout BuiltinData builtinData)
{
// Note: When baking reflection probes, we approximate the diffuse with the fresnel0
builtinData.bakeDiffuseLighting *= GetDiffuseIndirectDimmer();
#ifdef _AXF_BRDF_TYPE_SVBRDF
builtinData.bakeDiffuseLighting *= preLightData.diffuseFGD * GetDiffuseOrDefaultColor(bsdfData, _ReplaceDiffuseForIndirect).rgb;
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
// diffuse is Lambert, but we want the influence of the color table still...
builtinData.bakeDiffuseLighting *= preLightData.singleBRDFColor * GetDiffuseOrDefaultColor(bsdfData, _ReplaceDiffuseForIndirect).rgb;
// debugtest
//builtinData.bakeDiffuseLighting *= 0;
#endif
// todo_energy: attenuate diffuse lighting for coat ie with (1.0 - preLightData.coatFGD)
}
//-----------------------------------------------------------------------------
// light transport functions
//-----------------------------------------------------------------------------
LightTransportData GetLightTransportData(SurfaceData surfaceData, BuiltinData builtinData, BSDFData bsdfData)
{
LightTransportData lightTransportData;
lightTransportData.diffuseColor = GetColorBaseDiffuse(bsdfData);
lightTransportData.emissiveColor = float3(0.0, 0.0, 0.0);
return lightTransportData;
}
//-----------------------------------------------------------------------------
// LightLoop related function (Only include if required)
// HAS_LIGHTLOOP is define in Lighting.hlsl
//-----------------------------------------------------------------------------
#ifdef HAS_LIGHTLOOP
//-----------------------------------------------------------------------------
// BSDF shared between directional light, punctual light and area light (reference)
//-----------------------------------------------------------------------------
// Same for all shading models.
bool IsNonZeroBSDF(float3 V, float3 L, PreLightData preLightData, BSDFData bsdfData)
{
float NdotL = dot(bsdfData.normalWS, L);
return NdotL > 0.0;
}
#ifdef _AXF_BRDF_TYPE_SVBRDF
float3 ComputeWard(float3 H, float LdotH, float NdotL, float NdotV, PreLightData preLightData, BSDFData bsdfData)
{
// Evaluate Fresnel term
float F = 1.0;
switch (AXF_SVBRDF_BRDFVARIANTS_FRESNELTYPE)
{
case 1: F = F_FresnelDieletricSafe(Fresnel0ToIorSafe(bsdfData.fresnel0.r), LdotH); break;
case 2: F = F_Schlick(bsdfData.fresnel0.r, LdotH); break;
}
// Evaluate normal distribution function
float3 tsH = float3(dot(H, bsdfData.tangentWS), dot(H, bsdfData.bitangentWS), dot(H, bsdfData.normalWS));
//float2 rotH = tsH.xy / tsH.z;
float2 rotH = tsH.xy / max(0.00001, tsH.z);
//float2 roughness = bsdfData.roughness.xy;
float2 roughness = max(0.0001, bsdfData.roughness.xy);
//if (bsdfData.roughness.y == 0.0) bsdfData.specularColor = float3(1,0,0);
if (roughness.x * roughness.y <= 0.0001 && tsH.z < 1.0)
{
return 0;
}
float N = exp(-Sq(rotH.x / roughness.x) - Sq(rotH.y / roughness.y));
N /= max(0.0001, PI * roughness.x * roughness.y);
//N /= (PI * roughness.x * roughness.y);
switch (AXF_SVBRDF_BRDFVARIANTS_WARDTYPE)
{
case 0: N /= max(0.0001, 4.0 * Sq(LdotH) * Sq(Sq(tsH.z))); break; // Moroder
case 1: N /= max(0.0001, 4.0 * NdotL * NdotV); break; // Duer
case 2: N /= max(0.0001, 4.0 * sqrt(NdotL * NdotV)); break; // Ward
}
return bsdfData.specularColor * F * N;
}
float3 ComputeBlinnPhong(float3 H, float LdotH, float NdotL, float NdotV, PreLightData preLightData, BSDFData bsdfData)
{
// See AxFGetRoughnessFromSpecularLobeTexture in AxFData
float2 exponents = 2 * rcp(max(0.0001,(bsdfData.roughness.xy*bsdfData.roughness.xy))) - 2;
// Evaluate normal distribution function
float3 tsH = float3(dot(H, bsdfData.tangentWS), dot(H, bsdfData.bitangentWS), dot(H, bsdfData.normalWS));
float2 rotH = tsH.xy;
float3 N = 0;
switch (AXF_SVBRDF_BRDFVARIANTS_BLINNTYPE)
{
case 0:
{ // Ashikmin-Shirley
N = sqrt((1 + exponents.x) * (1 + exponents.y)) / (8 * PI)
* PositivePow(saturate(tsH.z), SafeDiv( (exponents.x * Sq(rotH.x) + exponents.y * Sq(rotH.y)), (1 - Sq(tsH.z)) ) )
/ (LdotH * max(NdotL, NdotV));
break;
}
case 1:
{ // Blinn
float exponent = 0.5 * (exponents.x + exponents.y); // Should be isotropic anyway...
N = (exponent + 2) / (8 * PI)
* PositivePow(saturate(tsH.z), exponent);
break;
}
case 2: // VRay
case 3: // Lewis
N = 1000 * float3(1, 0, 1); // Not documented...
break;
}
return bsdfData.specularColor * N;
}
float3 ComputeCookTorrance(float3 H, float LdotH, float NdotL, float NdotV, PreLightData preLightData, BSDFData bsdfData)
{
float NdotH = dot(H, bsdfData.normalWS);
float sqNdotH = Sq(NdotH);
// Evaluate Fresnel term
float F = F_Schlick(bsdfData.fresnel0.r, LdotH);
// Evaluate (isotropic) normal distribution function (Beckmann)
float roughness = GetScalarRoughnessFromAnisoRoughness(bsdfData.roughness.x, bsdfData.roughness.y);
float sqAlpha = roughness*roughness;
float N = exp((sqNdotH - 1) / max(0.00001, sqNdotH * sqAlpha))
/ max(0.00001, PI * Sq(sqNdotH) * sqAlpha);
// Evaluate shadowing/masking term
float G = G_CookTorrance(NdotH, NdotV, NdotL, LdotH);
return bsdfData.specularColor * F * N * G;
}
float3 ComputeGGX(float3 H, float LdotH, float NdotL, float NdotV, PreLightData preLightData, BSDFData bsdfData)
{
// Evaluate Fresnel term
float F = F_Schlick(bsdfData.fresnel0.r, LdotH);
float3 tsH = float3(dot(H, bsdfData.tangentWS), dot(H, bsdfData.bitangentWS), dot(H, bsdfData.normalWS));
// Evaluate normal distribution function (Trowbridge-Reitz)
float N = D_GGXAniso(tsH.x, tsH.y, tsH.z, bsdfData.roughness.x, bsdfData.roughness.y);
// Evaluate shadowing/masking term
float roughness = GetProjectedRoughness(tsH.x, tsH.y, tsH.z, bsdfData.roughness.x, bsdfData.roughness.y);
// G1 in the SDK matches up with
// Ref: Microfacet Models for Refraction through Rough Surfaces, Walter et al. 2007, p. 7 eq(34)
// Ref: Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs, Heitz, 2014, p. 84 (37/60)
// We have G1(NdotV, a) where a is roughness
// = 2 * NdotV / (NdotV + sqrt(a*a + (1 - a*a) * Sq(NdotV)))
// = 1 / (0.5 + 0.5 * sqrt(a*a/Sq(NdotV) + (1 - a*a)))
// = 1 / (0.5 + 0.5 * sqrt((1/Sq(NdotV) - 1)*a*a + 1))
// which we have defined as G_MaskingSmithGGX() in core/ShaderLibrary/BSDF.hlsl
float G = G_MaskingSmithGGX(NdotL, roughness) * G_MaskingSmithGGX(NdotV, roughness);
G /= max(0.00001, 4.0 * NdotL * NdotV);
return bsdfData.specularColor * F * N * G;
}
float3 ComputePhong(float3 H, float LdotH, float NdotL, float NdotV, PreLightData preLightData, BSDFData bsdfData)
{
return 1000 * float3(1, 0, 1);
}
// This function applies the BSDF. Assumes that NdotL is positive.
//_AXF_BRDF_TYPE_SVBRDF version:
CBSDF EvaluateBSDF(float3 viewWS_Clearcoat, float3 lightWS_Clearcoat, PreLightData preLightData, BSDFData bsdfData)
{
CBSDF cbsdf;
ZERO_INITIALIZE(CBSDF, cbsdf);
float NdotL;
float3 viewWS_UnderCoat = viewWS_Clearcoat; //Note: ComputeClearcoatReflectionAndExtinction_UsePreLightData possibly modifies its input directions.
float3 lightWS_UnderCoat = lightWS_Clearcoat;
// Compute half vector used by various components of the BSDF
float3 H = normalize(viewWS_Clearcoat + lightWS_Clearcoat);
// Apply clearcoat
float clearcoatExtinction = 1.0;
float3 clearcoatReflectionLobeNdotL = 0.0;
if (HasClearcoat())
{
NdotL = dot(bsdfData.clearcoatNormalWS, lightWS_Clearcoat);
float coatNdotH = dot(bsdfData.clearcoatNormalWS, H);
float coatNdotV = ClampNdotV(preLightData.NdotV_Clearcoat);
float reflectionCoeff;
ComputeClearcoatReflectionAndExtinction_UsePreLightData(viewWS_UnderCoat, lightWS_UnderCoat, bsdfData, preLightData, reflectionCoeff, clearcoatExtinction);
if (HasClearcoatRefraction())
{
// Recompute H after possible refraction:
H = normalize(viewWS_UnderCoat + lightWS_UnderCoat);
}
// See axf-decoding-sdk/doc/html/page1.html#svbrdf_subsec03
// the coat is an almost-dirac BSDF lobe like expected.
// There's nothing said about clearcoatColor, and it doesn't make sense to actually color its reflections but we
// treat clearcoatColor as other specular colors (as the AxF SVBRDF model includes both a general coloring term
// that they call "specular color" while the f0 is actually another term)
clearcoatReflectionLobeNdotL = saturate(NdotL) * bsdfData.clearcoatColor * reflectionCoeff * DV_SmithJointGGX(coatNdotH, NdotL, coatNdotV, CLEAR_COAT_ROUGHNESS, preLightData.coatPartLambdaV);
}
// undercoat values:
float NdotH = dot(bsdfData.normalWS, H);
float NdotV = ClampNdotV(preLightData.NdotV_UnderCoat);
// Compute rest of needed cosine of angles after possible refraction:
float LdotH = dot(H, lightWS_UnderCoat);
NdotL = dot(bsdfData.normalWS, lightWS_UnderCoat);
// Compute diffuse term
float3 diffuseTerm = Lambert();
if (AXF_SVBRDF_BRDFTYPE_DIFFUSETYPE)
{
float diffuseRoughness = 0.5 * HALF_PI; // Arbitrary roughness (not specified in the documentation...)
diffuseTerm = INV_PI * OrenNayar(bsdfData.normalWS, viewWS_UnderCoat, lightWS_UnderCoat, diffuseRoughness);
}
// Compute specular term
float3 specularTerm = float3(1, 0, 0);
switch (AXF_SVBRDF_BRDFTYPE_SPECULARTYPE)
{
case 0: specularTerm = ComputeWard(H, LdotH, NdotL, NdotV, preLightData, bsdfData); break;
case 1: specularTerm = ComputeBlinnPhong(H, LdotH, NdotL, NdotV, preLightData, bsdfData); break;
case 2: specularTerm = ComputeCookTorrance(H, LdotH, NdotL, NdotV, preLightData, bsdfData); break;
case 3: specularTerm = ComputeGGX(H, LdotH, NdotL, NdotV, preLightData, bsdfData); break;
case 4: specularTerm = ComputePhong(H, LdotH, NdotL, NdotV, preLightData, bsdfData); break;
default: // @TODO
specularTerm = 1000 * float3(1, 0, 1);
break;
}
// We don't multiply by 'bsdfData.diffuseColor' here. It's done only once in PostEvaluateBSDF().
cbsdf.diffR = clearcoatExtinction * diffuseTerm * saturate(NdotL);
cbsdf.specR = (clearcoatExtinction * specularTerm * saturate(NdotL) + clearcoatReflectionLobeNdotL);
// We don't multiply by 'bsdfData.diffuseColor' here. It's done only once in PostEvaluateBSDF().
return cbsdf;
}
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
float3 GetCarPaintSpecularFGDForLobe(PreLightData preLightData, uint lobeIndex)
{
return lerp(preLightData.specularCTFGDAtZeroF0[lobeIndex], preLightData.specularCTFGDReflectivity[lobeIndex], _CarPaint2_CTF0s[lobeIndex]*preLightData.singleBRDFColor);
//return lerp(preLightData.specularCTFGDAtZeroF0[lobeIndex], preLightData.specularCTFGDReflectivity[lobeIndex], _CarPaint2_CTF0s[lobeIndex])*preLightData.singleBRDFColor;
}
// This function applies the BSDF. Assumes that NdotL is positive.
// For _AXF_BRDF_TYPE_CAR_PAINT
CBSDF EvaluateBSDF(float3 viewWS_Clearcoat, float3 lightWS_Clearcoat, PreLightData preLightData, BSDFData bsdfData)
{
CBSDF cbsdf;
ZERO_INITIALIZE(CBSDF, cbsdf);
//debugtest
//return cbsdf;
#if 0
//cbsdf.diffR = Lambert() * saturate(dot(bsdfData.normalWS, lightWS_Clearcoat));
//return cbsdf;
#elif 1
float NdotL;
float3 viewWS_UnderCoat = viewWS_Clearcoat; //Note: ComputeClearcoatReflectionAndExtinction_UsePreLightData possibly modifies its input directions.
float3 lightWS_UnderCoat = lightWS_Clearcoat;
// Compute half vector used by various components of the BSDF
float3 H = normalize(viewWS_Clearcoat + lightWS_Clearcoat);
// Apply clearcoat
float clearcoatExtinction = 1.0;
float3 clearcoatReflectionLobeNdotL = 0.0;
if (HasClearcoat())
{
NdotL = dot(bsdfData.clearcoatNormalWS, lightWS_Clearcoat);
float coatNdotH = dot(bsdfData.clearcoatNormalWS, H);
float coatNdotV = ClampNdotV(preLightData.NdotV_Clearcoat);
float reflectionCoeff;
ComputeClearcoatReflectionAndExtinction_UsePreLightData(viewWS_UnderCoat, lightWS_UnderCoat, bsdfData, preLightData, reflectionCoeff, clearcoatExtinction);
if (HasClearcoatRefraction())
{
// Recompute H after possible refraction:
H = normalize(viewWS_UnderCoat + lightWS_UnderCoat);
}
// See axf-decoding-sdk/doc/html/page1.html#svbrdf_subsec03
// the coat is an almost-dirac BSDF lobe like expected.
// There's nothing said about clearcoatColor, and it doesn't make sense to actually color its reflections but we
// treat clearcoatColor as other specular colors (as the AxF SVBRDF model includes both a general coloring term
// that they call "specular color" while the f0 is actually another term)
clearcoatReflectionLobeNdotL = saturate(NdotL) * bsdfData.clearcoatColor * reflectionCoeff * DV_SmithJointGGX(coatNdotH, NdotL, coatNdotV, CLEAR_COAT_ROUGHNESS, preLightData.coatPartLambdaV);
}
// undercoat values:
float NdotH = dot(bsdfData.normalWS, H);
float NdotV = ClampNdotV(preLightData.NdotV_UnderCoat);
// Compute rest of needed cosine of angles after possible refraction:
float LdotH = dot(H, lightWS_UnderCoat);
float VdotH = LdotH;
NdotL = dot(bsdfData.normalWS, lightWS_UnderCoat);
float thetaH = FastACosPos(clamp(NdotH, 0, 1));
float thetaD = FastACosPos(clamp(LdotH, 0, 1));
// Simple lambert
float3 diffuseTerm = Lambert();
// Apply multi-lobes Cook-Torrance
float3 specularTerm = MultiLobesCookTorrance(bsdfData, NdotL, NdotV, NdotH, VdotH);
// Apply BRDF color
float3 BRDFColor = GetBRDFColor(thetaH, thetaD);
diffuseTerm *= BRDFColor; // tocheck: dont forget handling BRDFColor for the indirect diffuse lighting!
// Also note that the monochromatic bsdfData.diffuseColor (in the case of CARPAINT2)
// is still applied in PostEvaluateBSDF and not here, like in the SVBRDF case!
specularTerm *= BRDFColor;
// Apply flakes
//TODO_FLAKES
specularTerm += CarPaint_BTF(thetaH, thetaD, (SurfaceData)0, bsdfData, /*useBSDFData:*/true);
cbsdf.diffR = clearcoatExtinction * diffuseTerm * saturate(NdotL);
cbsdf.specR = (clearcoatExtinction * specularTerm * saturate(NdotL) + clearcoatReflectionLobeNdotL);
// We don't multiply by 'bsdfData.diffuseColor' here. It's done only once in PostEvaluateBSDF().
return cbsdf;
#endif // #if 0
}
#else
// No _AXF_BRDF_TYPE
// This function applies the BSDF. Assumes that NdotL is positive.
CBSDF EvaluateBSDF(float3 viewWS_UnderCoat, float3 lightWS_UnderCoat, PreLightData preLightData, BSDFData bsdfData)
{
CBSDF cbsdf;
ZERO_INITIALIZE(CBSDF, cbsdf);
float NdotL = dot(bsdfData.normalWS, lightWS_UnderCoat);
float diffuseTerm = Lambert();
cbsdf.diffR = diffuseTerm * saturate(NdotL);
// We don't multiply by 'bsdfData.diffuseColor' here. It's done only once in PostEvaluateBSDF().
return cbsdf;
}
#endif // _AXF_BRDF_TYPE_SVBRDF
//-----------------------------------------------------------------------------
// Surface shading (all light types) below
//-----------------------------------------------------------------------------
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightEvaluation.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/MaterialEvaluation.hlsl"
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/SurfaceShading.hlsl"
//-----------------------------------------------------------------------------
// EvaluateBSDF_Directional
//-----------------------------------------------------------------------------
DirectLighting EvaluateBSDF_Directional(LightLoopContext lightLoopContext,
float3 V, PositionInputs posInput,
PreLightData preLightData, DirectionalLightData lightData,
BSDFData bsdfData, BuiltinData builtinData)
{
return ShadeSurface_Directional(lightLoopContext, posInput, builtinData, preLightData, lightData, bsdfData, V);
//return (DirectLighting)0;
}
//-----------------------------------------------------------------------------
// EvaluateBSDF_Punctual (supports spot, point and projector lights)
//-----------------------------------------------------------------------------
DirectLighting EvaluateBSDF_Punctual(LightLoopContext lightLoopContext,
float3 V, PositionInputs posInput,
PreLightData preLightData, LightData lightData,
BSDFData bsdfData, BuiltinData builtinData)
{
return ShadeSurface_Punctual(lightLoopContext, posInput, builtinData,
preLightData, lightData, bsdfData, V);
}
//-----------------------------------------------------------------------------
// EvaluateBSDF_Area - Approximation with Linearly Transformed Cosines
//-----------------------------------------------------------------------------
DirectLighting EvaluateBSDF_Area(LightLoopContext lightLoopContext,
float3 V, PositionInputs posInput,
PreLightData preLightData, LightData lightData,
BSDFData bsdfData, BuiltinData builtinData)
{
DirectLighting lighting;
ZERO_INITIALIZE(DirectLighting, lighting);
const bool isRectLight = lightData.lightType == GPULIGHTTYPE_RECTANGLE; // static
#if SHADEROPTIONS_BARN_DOOR
if (isRectLight)
{
RectangularLightApplyBarnDoor(lightData, posInput.positionWS);
}
#endif
// Translate the light s.t. the shaded point is at the origin of the coordinate system.
float3 unL = lightData.positionRWS - posInput.positionWS;
// These values could be precomputed on CPU to save VGPR or ALU.
float halfLength = lightData.size.x * 0.5;
float halfHeight = lightData.size.y * 0.5; // = 0 for a line light
float intensity = PillowWindowing(unL, lightData.right, lightData.up, halfLength, halfHeight,
lightData.rangeAttenuationScale, lightData.rangeAttenuationBias);
// Make sure the light is front-facing (and has a non-zero effective area).
intensity *= (isRectLight && dot(unL, lightData.forward) >= 0) ? 0 : 1;
bool isVisible = true;
// Raytracing shadow algorithm require to evaluate lighting without shadow, so it defined SKIP_RASTERIZED_AREA_SHADOWS
// This is only present in Lit Material as it is the only one using the improved shadow algorithm.
#ifndef SKIP_RASTERIZED_AREA_SHADOWS
if (isRectLight && intensity > 0)
{
SHADOW_TYPE shadow = EvaluateShadow_RectArea(lightLoopContext, posInput, lightData, builtinData, bsdfData.normalWS, normalize(lightData.positionRWS), length(lightData.positionRWS));
lightData.color.rgb *= ComputeShadowColor(shadow, lightData.shadowTint, lightData.penumbraTint);
isVisible = Max3(lightData.color.r, lightData.color.g, lightData.color.b) > 0;
}
#endif
// Terminate if the shaded point is occluded or is too far away.
if (isVisible && intensity > 0)
{
// Rotate the light vectors into the local coordinate system.
float3 center = mul(preLightData.orthoBasisViewNormal, unL);
float3 right = mul(preLightData.orthoBasisViewNormal, lightData.right);
float3 up = mul(preLightData.orthoBasisViewNormal, lightData.up);
float4 ltcValue;
#if defined(_AXF_BRDF_TYPE_SVBRDF)
float diffusePerceptualRoughness = AXF_SVBRDF_BRDFTYPE_DIFFUSETYPE ? preLightData.iblPerceptualRoughness : 1.0;
// Evaluate the diffuse part
ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfLength, halfHeight,
// LTC light cookies appear broken unless diffuse roughness is set to 1.
transpose(preLightData.ltcTransformDiffuse), /*diffusePerceptualRoughness*/ 1.0f,
lightData.cookieMode, lightData.cookieScaleOffset);
ltcValue.a *= intensity * lightData.diffuseDimmer;
lighting.diffuse = preLightData.diffuseFGD * ltcValue.rgb * ltcValue.a;
// Evaluate the specular part
ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfLength, halfHeight,
transpose(preLightData.ltcTransformSpecular), bsdfData.perceptualRoughness,
lightData.cookieMode, lightData.cookieScaleOffset);
ltcValue.a *= intensity * lightData.specularDimmer;
lighting.specular = preLightData.specularFGD * ltcValue.rgb * ltcValue.a;
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
// Use Lambert for diffuse
ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfLength, halfHeight,
k_identity3x3, 1.0,
lightData.cookieMode, lightData.cookieScaleOffset);
ltcValue.a *= intensity * lightData.diffuseDimmer;
lighting.diffuse = preLightData.singleBRDFColor * ltcValue.rgb * ltcValue.a; // the BRDF specular flipflop color table also applies to diffuse
//
// Evaluate multi-lobes Cook-Torrance
// Each CT lobe samples the environment with the appropriate roughness
//
for (uint lobeIndex = 0; lobeIndex < CARPAINT2_LOBE_COUNT; lobeIndex++)
{
ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfLength, halfHeight,
transpose(preLightData.ltcTransformSpecularCT[lobeIndex]), RoughnessToPerceptualRoughness(bsdfData.roughness[lobeIndex]),
lightData.cookieMode, lightData.cookieScaleOffset);
ltcValue.a *= intensity * lightData.specularDimmer;
float coeff = GetLTCAreaLightDimmer() * _CarPaint2_CTCoeffs[lobeIndex];
lighting.specular += GetCarPaintSpecularFGDForLobe(preLightData, lobeIndex) * ltcValue.rgb * (ltcValue.a * coeff);
}
// Sample flakes as tiny mirrors
// (update1: this is not really doing that, more like applying a BTF on a
// lobe following the top normalmap. For them being like tiny mirrors, you would
// need the N of the flake, and then you end up with the problem of normal aliasing)
// (See also #define FLAKES_JUST_BTF, which makes us use the coat ltc transform and no FGD,
// - in that case calculated irradiance should be the same as clearcoat, should be optimized)
// todo_dir NdotV wrong
ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfLength, halfHeight,
transpose(preLightData.ltcTransformFlakes), FLAKES_PERCEPTUAL_ROUGHNESS,
lightData.cookieMode, lightData.cookieScaleOffset);
ltcValue.a *= intensity * lightData.specularDimmer;
lighting.specular += preLightData.singleFlakesComponent * ltcValue.rgb * ltcValue.a;
#endif // carpaint
// Evaluate the clear-coat
if (HasClearcoat())
{
ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfLength, halfHeight,
transpose( preLightData.ltcTransformClearcoat), CLEAR_COAT_PERCEPTUAL_ROUGHNESS,
lightData.cookieMode, lightData.cookieScaleOffset);
ltcValue.a *= intensity * lightData.specularDimmer;
// Use the complement of FGD value as an approximation of what is transmitted past the undercoat
lighting.diffuse *= 1.0 - preLightData.coatFGD;
lighting.specular = lerp(lighting.specular, bsdfData.clearcoatColor * ltcValue.rgb * ltcValue.a, preLightData.coatFGD);
}
// Save ALU by applying 'lightData.color' only once.
lighting.diffuse *= lightData.color;
lighting.specular *= lightData.color;
#ifdef DEBUG_DISPLAY
if (_DebugLightingMode == DEBUGLIGHTINGMODE_LUX_METER)
{
ltcValue = EvaluateLTC_Area(isRectLight, center, right, up, halfLength, halfHeight,
k_identity3x3, 1.0f,
lightData.cookieMode, lightData.cookieScaleOffset);
ltcValue.a *= intensity * lightData.diffuseDimmer;
// Only lighting, not BSDF
lighting.diffuse = lightData.color * ltcValue.rgb * ltcValue.a;
// Apply area light on Lambert then multiply by PI to cancel Lambert
lighting.diffuse *= PI;
}
#endif
}
return lighting;
}
//-----------------------------------------------------------------------------
// EvaluateBSDF_SSLighting for screen space lighting
// ----------------------------------------------------------------------------
IndirectLighting EvaluateBSDF_ScreenSpaceReflection(PositionInputs posInput,
inout PreLightData preLightData,
BSDFData bsdfData,
inout float reflectionHierarchyWeight)
{
IndirectLighting lighting;
ZERO_INITIALIZE(IndirectLighting, lighting);
// TODO: this texture is sparse (mostly black). Can we avoid reading every texel? How about using Hi-S?
float4 ssrLighting = LOAD_TEXTURE2D_X(_SsrLightingTexture, posInput.positionSS);
InversePreExposeSsrLighting(ssrLighting);
// Apply the weight on the ssr contribution (if required)
ApplyScreenSpaceReflectionWeight(ssrLighting);
float3 reflectanceFactor = 0.0;
if (HasClearcoat())
{
reflectanceFactor = GetSSRDimmer() * bsdfData.clearcoatColor * preLightData.coatFGD;
// TODO_flakes ?
// Init the light reflection hierarchy weight for the coat:
preLightData.coatReflectionWeight = ssrLighting.a;
// We use the coat-traced light according to how similar the base lobe roughness is to the coat roughness
// (we can assume the coat is always smoother).
// For AxF, even for CARPAINT we simplify by using a single lobe-coefficient-weighted averaged roughness
// to compare and calculate the blend factor and base hierarchyWeight.
// For that value, we use bsdfData.perceptualRoughness (set from GetScalarRoughness, see also AxFData.hlsl).
//
// - The roughness is equal to CLEAR_COAT_PERCEPTUAL_ROUGHNESS
// We use the fact the clear coat and that base layer lobe have the same roughness and use the SSR as the indirect specular signal.
// - The roughness is superior to CLEAR_COAT_PERCEPTUAL_ROUGHNESS + 0.2.
// We cannot use the SSR for that base layer lobe.
// - The roughness is within <= 0.2 away of CLEAR_COAT_PERCEPTUAL_ROUGHNESS, we lerp between the two behaviors.
float coatSSRLightOnBottomLayerBlendingFactor = lerp(1.0, 0.0, saturate( (bsdfData.perceptualRoughness - CLEAR_COAT_PERCEPTUAL_ROUGHNESS) / 0.2 ) );
// Calculate the base lobe reflectance factor (pre-integrated FGD)
float3 baseLobeReflectanceFactor = 0;
{
#if defined(_AXF_BRDF_TYPE_SVBRDF)
baseLobeReflectanceFactor = preLightData.specularFGD;
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
for (uint lobeIndex = 0; lobeIndex < CARPAINT2_LOBE_COUNT; lobeIndex++)
{
float coeff = _CarPaint2_CTCoeffs[lobeIndex];
baseLobeReflectanceFactor += coeff * GetCarPaintSpecularFGDForLobe(preLightData, lobeIndex);
}
// TODO_flakes ?
baseLobeReflectanceFactor *= GetSSRDimmer(); //now already in rebuilt specularFGD: * GetBRDFColor(thetaH, thetaD);
#else
// This is only possible if the AxF is a BTF type. However, there is a bunch of ifdefs do not support this third case
#endif
}
// Add the contribution of the coat-traced light for this base lobe, if any:
reflectanceFactor += baseLobeReflectanceFactor * coatSSRLightOnBottomLayerBlendingFactor;
// Important: EvaluateBSDF_SSLighting() assumes it is the first light loop callback that contributes lighting,
// we can thus directly set the reflectionHierarchyWeight instead of using UpdateLightingHierarchyWeights().
// We initialize and keep track of the separate light reflection hierarchy weights and (see below) since only
// reflectionHierarchyWeight is known to the light loop, normally a min() of all weights should be returned,
// but here, we know the coat "consumes" at least as much than the bottom lobe, so the coatReflectionWeight dont
// interfere with the (calculated from min of all) reflectionHierarchyWeight value returned.
preLightData.baseReflectionWeight = ssrLighting.a * coatSSRLightOnBottomLayerBlendingFactor;
reflectionHierarchyWeight = preLightData.baseReflectionWeight;
}
else
{
// No coat case:
#if defined(_AXF_BRDF_TYPE_SVBRDF)
reflectanceFactor = preLightData.specularFGD;
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
for (uint lobeIndex = 0; lobeIndex < CARPAINT2_LOBE_COUNT; lobeIndex++)
{
float coeff = _CarPaint2_CTCoeffs[lobeIndex];
reflectanceFactor += coeff * GetCarPaintSpecularFGDForLobe(preLightData, lobeIndex);
}
// TODO_flakes ?
reflectanceFactor *= GetSSRDimmer(); //now already in rebuilt specularFGD: * GetBRDFColor(thetaH, thetaD);
#else
// This is only possible if the AxF is a BTF type. However, there is a bunch of ifdefs do not support this third case
#endif
//lightHierarchyData.coatReflectionWeight = will be unused anyway
reflectionHierarchyWeight = ssrLighting.a;
preLightData.baseReflectionWeight = ssrLighting.a;
}
lighting.specularReflected = ssrLighting.rgb * reflectanceFactor;
return lighting;
}
IndirectLighting EvaluateBSDF_ScreenspaceRefraction( LightLoopContext lightLoopContext,
float3 viewWS_Clearcoat, PositionInputs posInput,
PreLightData preLightData, BSDFData bsdfData,
EnvLightData _envLightData,
inout float hierarchyWeight)
{
IndirectLighting lighting;
ZERO_INITIALIZE(IndirectLighting, lighting);
return lighting;
}
//-----------------------------------------------------------------------------
// EvaluateBSDF_Env
// ----------------------------------------------------------------------------
float3 GetModifiedEnvSamplingDir(EnvLightData lightData, float3 N, float3 iblR, float iblPerceptualRoughness, float clampedNdotV)
{
float3 ret = iblR;
if (!IsEnvIndexTexture2D(lightData.envIndex)) // ENVCACHETYPE_CUBEMAP
{
// When we are rough, we tend to see outward shifting of the reflection when at the boundary of the projection volume
// Also it appear like more sharp. To avoid these artifact and at the same time get better match to reference we lerp to original unmodified reflection.
// Formula is empirical.
ret = GetSpecularDominantDir(N, iblR, iblPerceptualRoughness, clampedNdotV);
float iblRoughness = PerceptualRoughnessToRoughness(iblPerceptualRoughness);
ret = lerp(ret, iblR, saturate(smoothstep(0, 1, iblRoughness * iblRoughness)));
}
return ret;
}
IndirectLighting EvaluateBSDF_Env_e( LightLoopContext lightLoopContext,
float3 viewWS_Clearcoat, PositionInputs posInput,
PreLightData preLightData, EnvLightData lightData, BSDFData bsdfData,
int _influenceShapeType, int _GPUImageBasedLightingType,
inout float hierarchyWeight)
{
IndirectLighting lighting;
ZERO_INITIALIZE(IndirectLighting, lighting);
return lighting;
}
// _preIntegratedFGD and _CubemapLD are unique for each BRDF
IndirectLighting EvaluateBSDF_Env( LightLoopContext lightLoopContext,
float3 viewWS_Clearcoat, PositionInputs posInput,
inout PreLightData preLightData, EnvLightData lightData, BSDFData bsdfData,
int _influenceShapeType, int _GPUImageBasedLightingType,
inout float hierarchyWeight)
{
IndirectLighting lighting;
ZERO_INITIALIZE(IndirectLighting, lighting);
if (_GPUImageBasedLightingType != GPUIMAGEBASEDLIGHTINGTYPE_REFLECTION)
return lighting; // We don't support transmission
float3 positionWS = posInput.positionWS;
float weight = 1.0;
float3 envLighting = 0.0;
float3 envSamplingDirForBottomLayer = preLightData.iblDominantDirectionWS_BottomLobeOnTop;
// If the base already grabbed all light it needed from previous lights, skip it
if (preLightData.baseReflectionWeight < 1.0)
{
#if defined(_AXF_BRDF_TYPE_SVBRDF)
float NdotV = ClampNdotV(preLightData.NdotV_UnderCoat);
// Here we use bsdfData.clearcoatNormalWS: if there's no coat, bsdfData.clearcoatNormalWS == bsdfData.normalWS anyway.
// The reason is that, normally, since GetModifiedEnvSamplingDir (off-specular effect) is roughness dependent,
// we would have to store another direction (lightData is only used to escape the modification in case of planar probe)
// and in case of carpaint, one for each lobe. However, if we would like to "correctly" take into account the effect, we would have
// to calculate the effect on the bottom layer where directions are different, and then use FindAverageBaseLobeDirOnTop().
// We decide to just apply the effect on top instead.
// (FindAverageBaseLobeDirOnTop is alreayd an approximation ignoring under-horizon or TIR. If we saturated to the critical angle undercoat
// and thus grazing when exiting on top, a tilt back for off-specular effect might in fact have no effect since the lobe could still
// be under horizon. On the other hand, if we didn't have to saturate, a little tilt-back toward normal (from GetModifiedEnvSamplingDir)
// should have translated into a bigger one on top because of angle range decompression.)
envSamplingDirForBottomLayer = GetModifiedEnvSamplingDir(lightData, bsdfData.clearcoatNormalWS, preLightData.iblDominantDirectionWS_BottomLobeOnTop, preLightData.iblPerceptualRoughness, NdotV);
// Note: using _influenceShapeType and projectionShapeType instead of (lightData|proxyData).shapeType allow to make compiler optimization in case the type is know (like for sky)
float intersectionDistance = EvaluateLight_EnvIntersection(positionWS, bsdfData.clearcoatNormalWS, lightData, _influenceShapeType, envSamplingDirForBottomLayer, weight);
// ...here the normal is only used for normal fading mode of the influence volume.
// Another problem with having even two fetch directions is the reflection hierarchy that only supports one weight.
// (TODO: We could have a vector tracking multiplied weights already applied per lobe that we update and that is
// passed back by the light loop but otherwise opaque to it, with the single hierarchyWeight tracked alongside.
// That way no "overlighting" would be done and by returning the hierarchyWeight = min(all weights) up to now,
// we could potentially avoid artifacts in having eg the clearcoat reflection not available from one influence volume
// while the base has full weight reflection. This ends up always preventing a blend for the coat reflection when the
// bottom reflection is full. Lit doesn't have this problem too much in practice since only GetModifiedEnvSamplingDir
// changes the direction vs the coat.)
// Sample the pre-integrated environment lighting
float4 preLD = SampleEnvWithDistanceBaseRoughness(lightLoopContext, posInput, lightData, envSamplingDirForBottomLayer, preLightData.iblPerceptualRoughness, intersectionDistance);
weight *= preLD.w; // Used by planar reflection to discard pixel
envLighting = GetSpecularIndirectDimmer() * preLightData.specularFGD * preLD.xyz;
//-----------------------------------------------------------------------------
#elif defined(_AXF_BRDF_TYPE_CAR_PAINT)
// A part of this BRDF depends on thetaH and thetaD and should thus have entered
// the split sum pre-integration. We do a further approximation by pulling those
// terms out and evaluating them in the specular dominant direction,
// for BRDFColor and flakes, see GetPreLightData.
// Note: we don't use GetModifiedEnvSamplingDir() per lobe here, and see comment above about reflection hierarchy.
float intersectionDistance = EvaluateLight_EnvIntersection(positionWS, bsdfData.clearcoatNormalWS, lightData, _influenceShapeType, envSamplingDirForBottomLayer, weight);
#if USE_COOK_TORRANCE_MULTI_LOBES
// Multi-lobes approach
// Each CT lobe samples the environment with the appropriate roughness
float probeSkipFactor = 1;
for (uint lobeIndex = 0; lobeIndex < CARPAINT2_LOBE_COUNT; lobeIndex++)
{
float coeff = _CarPaint2_CTCoeffs[lobeIndex];
float4 preLD = SampleEnvWithDistanceBaseRoughness(lightLoopContext, posInput, lightData, envSamplingDirForBottomLayer, preLightData.iblPerceptualRoughness[lobeIndex], intersectionDistance);
//todotodo: try removing coeff
envLighting += coeff * GetCarPaintSpecularFGDForLobe(preLightData, lobeIndex) * preLD.xyz;
// Note: preLD.w is only used by planar probes, returning 0 if outside captured direction or 1 otherwise (the influence volume weight fades, not this).
// Since this is only used for planar probes, even if we had used GetModifiedEnvSamplingDir() above, all directions would be the same in that case anyway
// since GetModifiedEnvSamplingDir() doesn't do anything for planar probes.
// For that reason, only one preLD.w needs to be used, no need to average them, they should all be the same.
// sumWeights += preLD.w;
probeSkipFactor = preLD.w;
}
// See discussion about reflection hierarchy above for SVBRDF, same thing here: When we will evaluate the coat, we will ignore its weight.
weight *= probeSkipFactor;
envLighting *= GetSpecularIndirectDimmer();
//now already in rebuilt specularFGD: envLighting *= GetBRDFColor(thetaH, thetaD);
// Sample flakes
//TODO_FLAKES
float flakesMipLevel = 0; // Flakes are supposed to be perfect mirrors
envLighting += preLightData.singleFlakesComponent * SampleEnv(lightLoopContext, lightData.envIndex, envSamplingDirForBottomLayer, flakesMipLevel, lightData.rangeCompressionFactorCompensation, posInput.positionNDC).xyz;
#else // USE_COOK_TORRANCE_MULTI_LOBES
// Single lobe approach
// We computed an average mip level stored in preLightData.iblPerceptualRoughness that we use for all CT lobes
// Sample the actual environment lighting
float4 preLD = SampleEnvWithDistanceBaseRoughness(lightLoopContext, posInput, lightData, envSamplingDirForBottomLayer, preLightData.iblPerceptualRoughness, intersectionDistance);
float3 envLighting;
envLighting = preLightData.specularCTFGDSingleLobe * GetSpecularIndirectDimmer();
//TODO_FLAKES
envLighting += preLightData.singleFlakesComponent;
envLighting *= preLD.xyz;
weight *= preLD.w; // Used by planar reflection to discard pixel
#endif // USE_COOK_TORRANCE_MULTI_LOBES
//-----------------------------------------------------------------------------
#else // ..._AXF_BRDF_TYPE_CAR_PAINT
// error / unknown BRDF type
#endif // BRDF type
UpdateLightingHierarchyWeights(preLightData.baseReflectionWeight, weight);
envLighting *= weight;
} // if (preLightData.baseReflectionWeight < 1.0)
//-----------------------------------------------------------------------------
// Evaluate the clearcoat component if needed
if (!IsDebugHideCoat() && HasClearcoat() && (preLightData.coatReflectionWeight < 1.0))
{
weight = 1.0;
// Evaluate clearcoat sampling direction
float3 lightWS_Clearcoat = preLightData.iblDominantDirectionWS_Clearcoat;
EvaluateLight_EnvIntersection(positionWS, bsdfData.clearcoatNormalWS, lightData, _influenceShapeType, lightWS_Clearcoat, weight);
// Attenuate environment lighting under the clearcoat by the complement to the Fresnel term
//todo_energy:
envLighting *= 1.0 - preLightData.coatFGD;
//envLighting *= Sq(1.0 - preLightData.coatFGD);
// Then add the environment lighting reflected by the clearcoat (with mip level 0, like mirror)
float4 preLD = SampleEnv(lightLoopContext, lightData.envIndex, lightWS_Clearcoat, 0.0, lightData.rangeCompressionFactorCompensation, posInput.positionNDC);
weight *= preLD.w;
// Update the coat weight, but make sure the weight is only then applied to the additional coat lighting:
UpdateLightingHierarchyWeights(preLightData.coatReflectionWeight, weight);
envLighting += weight * preLightData.coatFGD * preLD.xyz * bsdfData.clearcoatColor;
hierarchyWeight = min(preLightData.baseReflectionWeight, preLightData.coatReflectionWeight);
}
else
{
hierarchyWeight = preLightData.baseReflectionWeight;
}
envLighting *= lightData.multiplier;
lighting.specularReflected = envLighting;
return lighting;
}
//-----------------------------------------------------------------------------
// PostEvaluateBSDF
// ----------------------------------------------------------------------------
void PostEvaluateBSDF( LightLoopContext lightLoopContext,
float3 V, PositionInputs posInput,
PreLightData preLightData, BSDFData bsdfData, BuiltinData builtinData, AggregateLighting lighting,
out LightLoopOutput lightLoopOutput)
{
// There is no AmbientOcclusion from data with AxF, but let's apply our SSAO
AmbientOcclusionFactor aoFactor;
GetScreenSpaceAmbientOcclusionMultibounce(posInput.positionSS, preLightData.NdotV_UnderCoat,
bsdfData.perceptualRoughness,
bsdfData.ambientOcclusion, bsdfData.specularOcclusion,
GetColorBaseDiffuse(bsdfData), GetColorBaseFresnelF0(bsdfData), aoFactor);
ApplyAmbientOcclusionFactor(aoFactor, builtinData, lighting);
lightLoopOutput.diffuseLighting = bsdfData.diffuseColor * lighting.direct.diffuse + builtinData.bakeDiffuseLighting;
lightLoopOutput.specularLighting = lighting.direct.specular + lighting.indirect.specularReflected;
#if !defined(_AXF_BRDF_TYPE_SVBRDF) && !defined(_AXF_BRDF_TYPE_CAR_PAINT)
// Not supported: Display a flashy color instead
lightLoopOutput.diffuseLighting = 10 * float3(1, 0.3, 0.01);
#endif
#ifdef DEBUG_DISPLAY
PostEvaluateBSDFDebugDisplay(aoFactor, builtinData, lighting, bsdfData.diffuseColor, lightLoopOutput);
#endif
}
#endif // #ifdef HAS_LIGHTLOOP
//
// WIP
//
// todo/tocheck_envsampling, envsampling_test, todotodo,
// todo_dir todo_modes todo_pseudorefract
// todo_energy
// todo_fresnel
// debugtest (cur)
// todo_BeckmannToGGX
// TOCHECK
//
//