// Use optimization of Precomputing LambdaV
// TODO: Test if this is a win
// #define LIT_USE_BSDF_PRE_LAMBDAV
// TODO: Check if anisotropy with a dynamic if on anisotropy > 0 is performant. Because it may mean we always calculate both isotropy and anisotropy case.
// Maybe we should always calculate anisotropy in case of standard ? Don't think the compile can optimize correctly.
// Area light textures specific constant
SamplerState ltc_linear_clamp_sampler;
// TODO: This one should be set into a constant Buffer at pass frequency (with _Screensize)
TEXTURE2D(_PreIntegratedFGD);
#define LTC_LUT_OFFSET (0.5 * rcp(LTC_LUT_SIZE))
#define MIN_N_DOT_V 0.0001 // The minimum value of 'NdotV'
// Subsurface scattering specific constant
#define SSS_WRAP_ANGLE (PI/12) // Used for wrap lighting
#define SSS_WRAP_LIGHT cos(PI/2 - SSS_WRAP_ANGLE)
/* 18 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | MATERIALFEATUREFLAGS_LIT_SSS | MATERIALFEATUREFLAGS_LIT_STANDARD,
/* 19 */ LIGHT_FEATURE_MASK_FLAGS | MATERIALFEATUREFLAGS_LIT_SSS,
// Future usage
/* 20 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | MATERIALFEATUREFLAGS_LIT_UNUSED0 ,
/* 21 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_AREA | MATERIALFEATUREFLAGS_LIT_UNUSED0 ,
/* 22 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_ENV | MATERIALFEATUREFLAGS_LIT_UNUSED0 ,
/* 23 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | MATERIALFEATUREFLAGS_LIT_UNUSED0 ,
/* 24 */ LIGHT_FEATURE_MASK_FLAGS | MATERIALFEATUREFLAGS_LIT_UNUSED0 ,
// ClearCoat
/* 20 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT ,
/* 21 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_AREA | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT ,
/* 22 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_ENV | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT ,
/* 23 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT ,
/* 24 */ LIGHT_FEATURE_MASK_FLAGS | MATERIALFEATUREFLAGS_LIT_CLEAR_COAT ,
/* 25 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | MATERIALFEATUREFLAGS_LIT_UNUSED1 ,
/* 26 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_AREA | MATERIALFEATUREFLAGS_LIT_UNUSED1 ,
/* 27 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_ENV | MATERIALFEATUREFLAGS_LIT_UNUSED1 ,
/* 28 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | MATERIALFEATUREFLAGS_LIT_UNUSED1 ,
/* 29 */ LIGHT_FEATURE_MASK_FLAGS | MATERIALFEATUREFLAGS_LIT_UNUSED1 ,
/* 25 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | MATERIALFEATUREFLAGS_LIT_UNUSED,
/* 26 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_AREA | MATERIALFEATUREFLAGS_LIT_UNUSED,
/* 27 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_ENV | MATERIALFEATUREFLAGS_LIT_UNUSED,
/* 28 */ LIGHTFEATUREFLAGS_SKY | LIGHTFEATUREFLAGS_DIRECTIONAL | LIGHTFEATUREFLAGS_PUNCTUAL | LIGHTFEATUREFLAGS_ENV | MATERIALFEATUREFLAGS_LIT_UNUSED,
/* 29 */ LIGHT_FEATURE_MASK_FLAGS | MATERIALFEATUREFLAGS_LIT_UNUSED,
/* 30 */ LIGHT_FEATURE_MASK_FLAGS | MATERIAL_FEATURE_MASK_FLAGS, // Catch all case with MATERIAL_FEATURE_MASK_FLAGS is needed in case we disable material classification
};
return int(round(f * 3.0));
}
// For image based lighting, a part of the BSDF is pre-integrated.
// This is done both for specular and diffuse (in case of DisneyDiffuse)
void GetPreIntegratedFGD(float NdotV, float perceptualRoughness, float3 fresnel0, out float3 specularFGD, out float diffuseFGD)
{
// Pre-integrate GGX FGD
// _PreIntegratedFGD.x = Gv * (1 - Fc) with Fc = (1 - H.L)^5
// _PreIntegratedFGD.y = Gv * Fc
// Pre integrate DisneyDiffuse FGD:
// _PreIntegratedFGD.z = DisneyDiffuse
float3 preFGD = SAMPLE_TEXTURE2D_LOD(_PreIntegratedFGD, ltc_linear_clamp_sampler, float2(NdotV, perceptualRoughness), 0).xyz;
// f0 * Gv * (1 - Fc) + Gv * Fc
specularFGD = fresnel0 * preFGD.x + preFGD.y;
#ifdef LIT_DIFFUSE_LAMBERT_BRDF
diffuseFGD = 1.0;
#else
diffuseFGD = preFGD.z;
#endif
}
void ApplyDebugToSurfaceData(inout SurfaceData surfaceData)
{
#ifdef DEBUG_DISPLAY
if (_DebugLightingMode == DEBUGLIGHTINGMODE_SPECULAR_LIGHTING)
{
bool overrideSmoothness = _DebugLightingSmoothness.x != 0.0;
float overrideSmoothnessValue = _DebugLightingSmoothness.y;
if (overrideSmoothness)
{
surfaceData.perceptualSmoothness = overrideSmoothnessValue;
}
}
if (_DebugLightingMode == DEBUGLIGHTINGMODE_DIFFUSE_LIGHTING)
{
surfaceData.baseColor = _DebugLightingAlbedo.xyz;
}
#endif
}
static const float3 convertSpecularToValue = float3(0.02, 0.04, 0.20);
void FillMaterialIdStandardData(float3 baseColor, int specular, float metallic, inout BSDFData bsdfData)
void FillMaterialIdStandardData(float3 baseColor, float metallic, inout BSDFData bsdfData)
float val = convertSpecularToValue[specular];
float val = DEFAULT_SPECULAR_VALUE;
bsdfData.fresnel0 = lerp(val.xxx, baseColor, metallic);
}
{
bsdfData.diffuseColor = baseColor;
// TODO take from subsurfaceProfile
bsdfData.fresnel0 = 0.04; // Should be 0.028 for the skin
bsdfData.fresnel0 = SKIN_SPECULAR_VALUE; // TODO take from subsurfaceProfile instead
bsdfData.thickness = _ThicknessRemaps[subsurfaceProfile].x +
_ThicknessRemaps[subsurfaceProfile].y * thickness;
bsdfData.thickness = _ThicknessRemaps[subsurfaceProfile].x + _ThicknessRemaps[subsurfaceProfile].y * thickness;
uint transmissionMode = BitFieldExtract(_TransmissionFlags, 2u, 2u * subsurfaceProfile);
}
}
void FillMaterialIdClearCoatData(float3 coatNormalWS, float coatCoverage, float coatIOR, inout BSDFData bsdfData)
{
bsdfData.coatNormalWS = lerp(bsdfData.normalWS, coatNormalWS, coatCoverage);
bsdfData.coatIOR = lerp(1.0, 1.0 + coatIOR, coatCoverage);
bsdfData.coatCoverage = coatCoverage;
}
// For image based lighting, a part of the BSDF is pre-integrated.
// This is done both for specular and diffuse (in case of DisneyDiffuse)
void GetPreIntegratedFGD(float NdotV, float perceptualRoughness, float3 fresnel0, out float3 specularFGD, out float diffuseFGD)
{
// Pre-integrate GGX FGD
// _PreIntegratedFGD.x = Gv * (1 - Fc) with Fc = (1 - H.L)^5
// _PreIntegratedFGD.y = Gv * Fc
// Pre integrate DisneyDiffuse FGD:
// _PreIntegratedFGD.z = DisneyDiffuse
float3 preFGD = SAMPLE_TEXTURE2D_LOD(_PreIntegratedFGD, ltc_linear_clamp_sampler, float2(NdotV, perceptualRoughness), 0).xyz;
// f0 * Gv * (1 - Fc) + Gv * Fc
specularFGD = fresnel0 * preFGD.x + preFGD.y;
#ifdef LIT_DIFFUSE_LAMBERT_BRDF
diffuseFGD = 1.0;
#else
diffuseFGD = preFGD.z;
#endif
}
void ApplyDebugToSurfaceData(inout SurfaceData surfaceData)
{
#ifdef DEBUG_DISPLAY
if (_DebugLightingMode == DEBUGLIGHTINGMODE_SPECULAR_LIGHTING)
{
bool overrideSmoothness = _DebugLightingSmoothness.x != 0.0;
float overrideSmoothnessValue = _DebugLightingSmoothness.y;
if (overrideSmoothness)
{
surfaceData.perceptualSmoothness = overrideSmoothnessValue;
}
}
if (_DebugLightingMode == DEBUGLIGHTINGMODE_DIFFUSE_LIGHTING)
{
surfaceData.baseColor = _DebugLightingAlbedo.xyz;
}
#endif
}
//-----------------------------------------------------------------------------
// conversion function for forward
//-----------------------------------------------------------------------------
// IMPORTANT: In case of foward or gbuffer pass we must know what we are statically, so compiler can do compile time optimization
if (bsdfData.materialId == MATERIALID_LIT_STANDARD)
{
if (surfaceData.specular == SPECULARVALUE_SPECULAR_COLOR)
{
bsdfData.diffuseColor = surfaceData.baseColor;
bsdfData.fresnel0 = surfaceData.specularColor;
}
else
{
FillMaterialIdStandardData(surfaceData.baseColor, surfaceData.specular, surfaceData.metallic, bsdfData);
}
FillMaterialIdStandardData(surfaceData.baseColor, surfaceData.metallic, bsdfData);
}
else if (bsdfData.materialId == MATERIALID_LIT_SPECULAR)
{
// Note: Specular is not a material id but just a way to parameterize the standard materialid, thus we reset materialId to MATERIALID_LIT_STANDARD
bsdfData.materialId = MATERIALID_LIT_STANDARD;
bsdfData.diffuseColor = surfaceData.baseColor;
bsdfData.fresnel0 = surfaceData.specularColor;
FillMaterialIdStandardData(surfaceData.baseColor, surfaceData.specular, surfaceData.metallic, bsdfData);
FillMaterialIdStandardData(surfaceData.baseColor, surfaceData.metallic, bsdfData);
}
else if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
// When using clear coat we assume that bottom layer is regular
FillMaterialIdStandardData(surfaceData.baseColor, surfaceData.metallic, bsdfData);
FillMaterialIdClearCoatData(surfaceData.coatNormalWS, surfaceData.coatCoverage, surfaceData.coatIOR, bsdfData);
}
return bsdfData;
#endif
)
{
#if SHADEROPTIONS_PACK_GBUFFER_IN_U16
#if SHADEROPTIONS_PACK_GBUFFER_IN_U16
#endif
#endif
ApplyDebugToSurfaceData(surfaceData);
// RT2 - 8:8:8:8
if (surfaceData.materialId == MATERIALID_LIT_STANDARD)
{
// Encode specular on two bit for the enum
// Note: we encode two parametrization at the same time, specularColor and metal/specular
if (surfaceData.specular == SPECULARVALUE_SPECULAR_COLOR)
{
outGBuffer2 = float4(surfaceData.specularColor, PackFloatInt8bit(0.0, surfaceData.specular, 4.0)); // As all is static, Pack function should produce the result compile time
}
else
{
// Note: it is important to setup anisotropy field to 0 else materialId will be anisotropic
outGBuffer2 = float4(float3(0.0, 0.0, 0.0), PackFloatInt8bit(surfaceData.metallic, surfaceData.specular, 4.0));
}
outGBuffer2 = float4(float3(0.0, 0.0, 0.0), PackFloatInt8bit(surfaceData.metallic, GBUFFER_LIT_STANDARD_REGULAR_ID, 4.0));
}
else if (surfaceData.materialId == MATERIALID_LIT_SPECULAR)
{
outGBuffer1.a = PackMaterialId(MATERIALID_LIT_STANDARD); // Encode MATERIALID_LIT_SPECULAR as MATERIALID_LIT_STANDARD + GBUFFER_LIT_STANDARD_SPECULAR_COLOR_ID value in GBuffer2
outGBuffer2 = float4(surfaceData.specularColor, PackFloatInt8bit(0.0, GBUFFER_LIT_STANDARD_SPECULAR_COLOR_ID, 4.0));
outGBuffer1.a = PackMaterialId(MATERIALID_LIT_STANDARD); // We save 1bit in gbuffer1 and use aniso value instead to detect we are aniso
outGBuffer1.a = PackMaterialId(MATERIALID_LIT_STANDARD); // Encode MATERIALID_LIT_SPECULAR as MATERIALID_LIT_STANDARD + GBUFFER_LIT_STANDARD_ANISOTROPIC_ID value in GBuffer2
// To be recognize as anisotropic material, we need to have anisotropy > 0 (Else artits can be confuse to not have anisotropic material in material classification), thus the max
outGBuffer2 = float4(octTangentWS * 0.5 + 0.5, max(surfaceData.anisotropy, 1.5 / 255.0), PackFloatInt8bit(surfaceData.metallic, surfaceData.specular, 4.0));
outGBuffer2 = float4(octTangentWS * 0.5 + 0.5, surfaceData.anisotropy, PackFloatInt8bit(surfaceData.metallic, GBUFFER_LIT_STANDARD_ANISOTROPIC_ID, 4.0));
outGBuffer2 = float4(surfaceData.subsurfaceRadius, surfaceData.thickness, 0, PackByte(surfaceData.subsurfaceProfile));
outGBuffer2 = float4(surfaceData.subsurfaceRadius, surfaceData.thickness, 0.0, PackByte(surfaceData.subsurfaceProfile));
}
else if (surfaceData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
float2 octCoatNormalWS = PackNormalOctEncode(surfaceData.coatNormalWS);
outGBuffer2 = float4(octCoatNormalWS * 0.5 + 0.5, surfaceData.coatCoverage, PackFloatInt8bit(surfaceData.coatIOR, (int)(surfaceData.metallic * 15.5f), 16.0) );
#if SHADEROPTIONS_PACK_GBUFFER_IN_U16
#if SHADEROPTIONS_PACK_GBUFFER_IN_U16
// Now pack all buffer into 2 uint buffer
// We don't have hardware sRGB to store base color in case we pack int u16, so rather than perform full sRGB encoding just use cheap gamma20
PackFloatToUInt(outGBuffer2.z, 8, 0) | PackFloatToUInt(outGBuffer2.w, 8, 8),
(packedGBuffer3 & 0x0000FFFF),
(packedGBuffer3 & 0xFFFF0000) >> 16);
#endif
#endif
}
float4 DecodeGBuffer0(GBufferType0 encodedGBuffer0)
// The code is also call from MaterialFeatureFlagsFromGBuffer, so must work fully dynamic if featureFlags is 0xFFFFFFFF
int supportsStandard = (featureFlags & (MATERIALFEATUREFLAGS_LIT_STANDARD | MATERIALFEATUREFLAGS_LIT_ANISO)) != 0;
int supportsSSS = (featureFlags & (MATERIALFEATUREFLAGS_LIT_SSS)) != 0;
int supportClearCoat = (featureFlags & (MATERIALFEATUREFLAGS_LIT_CLEAR_COAT)) != 0;
if (supportsStandard + supportsSSS > 1)
if (supportsStandard + supportsSSS + supportClearCoat > 1)
{
// only fetch materialid if it is not statically known from feature flags
bsdfData.materialId = UnpackMaterialId(inGBuffer1.a);
// materialid is statically known. this allows the compiler to eliminate a lot of code.
if (supportsStandard)
bsdfData.materialId = MATERIALID_LIT_STANDARD;
else // if (supportsSSS)
else if (supportsSSS)
else
bsdfData.materialId = MATERIALID_LIT_CLEAR_COAT;
int specular;
UnpackFloatInt8bit(inGBuffer2.a, 4.0, metallic, specular);
int materialIdExtent;
UnpackFloatInt8bit(inGBuffer2.a, 4.0, metallic, materialIdExtent);
if (specular == SPECULARVALUE_SPECULAR_COLOR)
if (materialIdExtent == GBUFFER_LIT_STANDARD_SPECULAR_COLOR_ID)
// Note: Specular is not a material id but just a way to parameterize the standard materialid, thus we reset materialId to MATERIALID_LIT_STANDARD
// For material classification it will be consider as Standard as well, thus no need to create special case
FillMaterialIdStandardData(baseColor, specular, metallic, bsdfData);
FillMaterialIdStandardData(baseColor, metallic, bsdfData);
FillMaterialIdStandardData(baseColor, specular, metallic, bsdfData);
FillMaterialIdStandardData(baseColor, metallic, bsdfData);
if (specular == SPECULARVALUE_SPECULAR_COLOR)
if (materialIdExtent == GBUFFER_LIT_STANDARD_SPECULAR_COLOR_ID)
// Note: Specular is not a material id but just a way to parameterize the standard materialid, thus we reset materialId to MATERIALID_LIT_STANDARD
// For material classification it will be consider as Standard as well, thus no need to create special case
else if (anisotropy > 0.0)
else if (materialIdExtent == GBUFFER_LIT_STANDARD_ANISOTROPIC_ID)
FillMaterialIdStandardData(baseColor, specular, metallic, bsdfData);
FillMaterialIdStandardData(baseColor, metallic, bsdfData);
else
else // GBUFFER_LIT_STANDARD_REGULAR_ID
FillMaterialIdStandardData(baseColor, specular, metallic, bsdfData);
FillMaterialIdStandardData(baseColor, metallic, bsdfData);
else // bsdfData.materialId == MATERIALID_LIT_SSS
else if (bsdfData.materialId == MATERIALID_LIT_SSS)
{
float subsurfaceRadius = inGBuffer2.x;
float thickness = inGBuffer2.y;
}
else //if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
float3 coatNormalWS = UnpackNormalOctEncode(float2(inGBuffer2.rg * 2.0 - 1.0));
float coatCoverage = inGBuffer2.b;
float coatIOR;
int metallic;
UnpackFloatInt8bit(inGBuffer2.a, 16.0, coatIOR, metallic);
// When using clear coat we assume that bottom layer is regular
FillMaterialIdStandardData(baseColor, metallic / 15.0f, bsdfData);
FillMaterialIdClearCoatData(coatNormalWS, coatCoverage, coatIOR, bsdfData);
// Function call from the material classification compute shader
// Note that as we store materialId on two buffer (for anisotropy case), the code need to load 2 RGBA8 buffer
uint MaterialFeatureFlagsFromGBuffer(
#if SHADEROPTIONS_PACK_GBUFFER_IN_U16
GBufferType0 inGBufferU0,
void GetSurfaceDataDebug(uint paramId, SurfaceData surfaceData, inout float3 result, inout bool needLinearToSRGB)
{
GetGeneratedSurfaceDataDebug(paramId, surfaceData, result, needLinearToSRGB);
switch (paramId)
{
// TODO: Remap here!
case DEBUGVIEW_LIT_SURFACEDATA_SPECULAR:
result = surfaceData.specular.xxx;
break;
}
}
void GetBSDFDataDebug(uint paramId, BSDFData bsdfData, inout float3 result, inout bool needLinearToSRGB)
float BdotV;
float anisoGGXLambdaV;
// Clear coat
float coatNdotV;
float ieta;
float coatFresnel0;
float3 coatV;
float3 refractV; // The view vector refracted through clear coat interface
// IBL clear coat
float3 coatIblDirWS;
float3 specularFGD; // Store preconvoled BRDF for both specular and diffuse
float diffuseFGD;
float ltcGGXFresnelMagnitudeDiff; // The difference of magnitudes of GGX and Fresnel
float ltcGGXFresnelMagnitude;
float3 ltcGGXFresnelTerm;
// area light clear coat
float3x3 ltcXformClearCoat; // TODO: make sure the compiler not wasting VGPRs on constants
float ltcClearCoatFresnelTerm;
float3x3 ltcCoatT;
// This is a refract - TODO: do we call original refract or this one, original maybe slightly emore expensive, to check
float3 ClearCoatTransform(float3 X, float3 N, float ieta)
{
float XdotN = saturate(dot(N, X));
return ieta * X + (sqrt(1 + ieta * ieta * (XdotN * XdotN - 1)) - ieta * XdotN) * N;
}
float NdotV = dot(bsdfData.normalWS, V);
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
float ieta = 1.0 / bsdfData.coatIOR; // inverse eta
preLightData.ieta = ieta;
preLightData.coatFresnel0 = Sqr(bsdfData.coatIOR - 1.0) / Sqr(bsdfData.coatIOR + 1.0);
float3 iblNormalWS = GetViewShiftedNormal(bsdfData.normalWS, V, NdotV, MIN_N_DOT_V);
preLightData.coatNdotV = dot(bsdfData.coatNormalWS, V);
preLightData.NdotV = NdotV; // Store the unaltered (geometric) version
NdotV = max(NdotV, MIN_N_DOT_V); // Use the modified (clamped) version
// Clear coat IBL
// In the case of IBL we want shift a bit the normal that are not toward the viewver to reduce artifact
float3 coatIblNormalWS = GetViewShiftedNormal(bsdfData.coatNormalWS, V, preLightData.coatNdotV, MIN_N_DOT_V);
preLightData.coatIblDirWS = reflect(-V, coatIblNormalWS);
// Clear coat area light
float theta = FastACos(preLightData.coatNdotV);
float2 uv = LTC_LUT_OFFSET + LTC_LUT_SCALE * float2(0.0, theta * INV_HALF_PI); // Use Roughness of 0.0 for clearCoat roughness
// Get the inverse LTC matrix for GGX
// Note we load the matrix transpose (avoid to have to transpose it in shader)
preLightData.ltcXformClearCoat = 0.0;
preLightData.ltcXformClearCoat._m22 = 1.0;
preLightData.ltcXformClearCoat._m00_m02_m11_m20 = SAMPLE_TEXTURE2D_ARRAY_LOD(_LtcData, ltc_linear_clamp_sampler, uv, LTC_GGX_MATRIX_INDEX, 0);
float3 ltcMagnitude = SAMPLE_TEXTURE2D_ARRAY_LOD(_LtcData, ltc_linear_clamp_sampler, uv, LTC_MULTI_GGX_FRESNEL_DISNEY_DIFFUSE_INDEX, 0).rgb;
float ltcClearCoatFresnelMagnitudeDiff = ltcMagnitude.r; // The difference of magnitudes of GGX and Fresnel
float ltcClearCoatFresnelMagnitude = ltcMagnitude.g;
preLightData.ltcClearCoatFresnelTerm = preLightData.coatFresnel0 * ltcClearCoatFresnelMagnitudeDiff + ltcClearCoatFresnelMagnitude;
// TODO: Convert the area light with respect to Fresnel transmission
float3 N = bsdfData.coatNormalWS; // TODO : check with Laurentb
preLightData.ltcCoatT = float3x3( ieta + (1.0 - ieta) * N.x * N.x, 0.0 + (1.0 - ieta) * N.y * N.x, 0.0 + (1.0 - ieta) * N.z * N.x,
0.0 + (1.0 - ieta) * N.x * N.y, ieta + (1.0 - ieta) * N.y * N.y, 0.0 + (1.0 - ieta) * N.z * N.y,
0.0 + (1.0 - ieta) * N.x * N.z, 0.0 + (1.0 - ieta) * N.y * N.z, ieta + (1.0 - ieta) * N.z * N.z );
// Modify V for following calculation
preLightData.refractV = ClearCoatTransform(V, bsdfData.coatNormalWS, ieta);
V = preLightData.refractV;
}
preLightData.NdotV = dot(bsdfData.normalWS, V); // Store the unaltered (geometric) version
float NdotV = preLightData.NdotV;
// In the case of IBL we want shift a bit the normal that are not toward the viewver to reduce artifact
float3 iblNormalWS = GetViewShiftedNormal(bsdfData.normalWS, V, NdotV, MIN_N_DOT_V); // Use non clamped NdotV
NdotV = max(NdotV, MIN_N_DOT_V); // Use the modified (clamped) version
preLightData.TdotV = 0;
preLightData.BdotV = 0;
preLightData.TdotV = 0.0;
preLightData.BdotV = 0.0;
if (bsdfData.materialId == MATERIALID_LIT_ANISO)
{
preLightData.TdotV = dot(bsdfData.tangentWS, V);
// IBL
GetPreIntegratedFGD(NdotV, bsdfData.perceptualRoughness, bsdfData.fresnel0, preLightData.specularFGD, preLightData.diffuseFGD);
preLightData.iblDirWS = GetSpecularDominantDir(iblNormalWS, iblR, bsdfData.roughness, NdotV);
preLightData.iblMipLevel = PerceptualRoughnessToMipmapLevel(bsdfData.perceptualRoughness);
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
// Update the roughness and the IBL miplevel
// Bottom layer is affected by upper layer BRDF, result can't be more sharp than input (it is to mimic what a path tracer will do)
float roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
float shininess = Sqr(preLightData.ieta) * (2.0 / Sqr(roughness) - 2.0);
roughness = sqrt(2.0 / (shininess + 2.0));
preLightData.iblDirWS = GetSpecularDominantDir(iblNormalWS, iblR, roughness, NdotV);
preLightData.iblMipLevel = PerceptualRoughnessToMipmapLevel(RoughnessToPerceptualRoughness(roughness));
}
else
{
preLightData.iblDirWS = GetSpecularDominantDir(iblNormalWS, iblR, bsdfData.roughness, NdotV);
preLightData.iblMipLevel = PerceptualRoughnessToMipmapLevel(bsdfData.perceptualRoughness);
}
float theta = FastACos(NdotV);
float theta = FastACos(NdotV); // For Area light - UVs for sampling the LUTs
float2 uv = LTC_LUT_OFFSET + LTC_LUT_SCALE * float2(bsdfData.perceptualRoughness, theta * INV_HALF_PI);
// Get the inverse LTC matrix for GGX
preLightData.ltcXformDisneyDiffuse._m00_m02_m11_m20 = SAMPLE_TEXTURE2D_ARRAY_LOD(_LtcData, ltc_linear_clamp_sampler, uv, LTC_DISNEY_DIFFUSE_MATRIX_INDEX, 0);
float3 ltcMagnitude = SAMPLE_TEXTURE2D_ARRAY_LOD(_LtcData, ltc_linear_clamp_sampler, uv, LTC_MULTI_GGX_FRESNEL_DISNEY_DIFFUSE_INDEX, 0).rgb;
preLightData.ltcGGXFresnelMagnitudeDiff = ltcMagnitude.r;
preLightData.ltcGGXFresnelMagnitude = ltcMagnitude.g;
float ltcGGXFresnelMagnitudeDiff = ltcMagnitude.r; // The difference of magnitudes of GGX and Fresnel
float ltcGGXFresnelMagnitude = ltcMagnitude.g;
// TODO: the fit seems rather poor. The scaling factor of 0.5 allows us
// to match the reference for rough metals, but further darkens dielectrics.
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
// Change the Fresnel term to account for transmission through Clear Coat and reflection on the base layer
float F = F_Schlick(preLightData.coatFresnel0, preLightData.coatNdotV);
F = Sqr(-F * bsdfData.coatCoverage + 1.0);
F /= preLightData.ieta; //TODO: LaurentB why / ieta here and not for other lights ?
preLightData.ltcGGXFresnelTerm = F * bsdfData.fresnel0 * ltcGGXFresnelMagnitudeDiff + (float3)ltcGGXFresnelMagnitude;
}
else
{
preLightData.ltcGGXFresnelTerm = bsdfData.fresnel0 * ltcGGXFresnelMagnitudeDiff + (float3)ltcGGXFresnelMagnitude;
}
return preLightData;
}
out float3 diffuseLighting,
out float3 specularLighting)
{
float3 F = 1.0;
specularLighting = float3(0.0, 0.0, 0.0);
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
// Optimized math. Ref: PBR Diffuse Lighting for GGX + Smith Microsurfaces (slide 114).
float NdotL = saturate(dot(bsdfData.coatNormalWS, L));
float NdotV = preLightData.coatNdotV;
float LdotV = dot(L, V);
float invLenLV = rsqrt(abs(2 * LdotV + 2));
float NdotH = saturate((NdotL + NdotV) * invLenLV);
float LdotH = saturate(invLenLV * LdotV + invLenLV);
// Evaluate Fresnel on the Clear Coat
F = F_Schlick(preLightData.coatFresnel0, LdotH);
// TODO: No need to call D (to see with LaurentB) + question on * NdotL
//specularLighting += F * D_GGX(NdotH, max(bsdfdata.MinRoughness, 0.01)) * NdotL;
specularLighting += F * D_GGX(NdotH, 0.01) * NdotL * bsdfData.coatCoverage;
// Change the Fresnel term to account for transmission through Clear Coat and reflection on the base layer
F = Sqr(-F * bsdfData.coatCoverage + 1.0);
// Change the Light and View direction to account for IOR change.
// Update the half vector accordingly
V = preLightData.refractV;
L = ClearCoatTransform(L, bsdfData.coatNormalWS, preLightData.ieta);
}
// Optimized math. Ref: PBR Diffuse Lighting for GGX + Smith Microsurfaces (slide 114).
float NdotL = saturate(dot(bsdfData.normalWS, L)); // Must have the same value without the clamp
float NdotV = preLightData.NdotV; // Get the unaltered (geometric) version
NdotV = max(NdotV, MIN_N_DOT_V); // Use the modified (clamped) version
float3 F = F_Schlick(bsdfData.fresnel0, LdotH);
F *= F_Schlick(bsdfData.fresnel0, LdotH);
// TODO: this way of handling aniso may not be efficient, or maybe with material classification, need to check perf here
// Maybe always using aniso maybe a win ?
if (bsdfData.materialId == MATERIALID_LIT_ANISO)
{
float3 H = (L + V) * invLenLV;
#endif
D = D_GGX(NdotH, bsdfData.roughness);
}
specularLighting = F * (Vis * D);
specularLighting + = F * (Vis * D);
#ifdef LIT_DIFFUSE_LAMBERT_BRDF
float diffuseTerm = Lambert();
float ltcValue;
// Evaluate the diffuse part.
// Evaluate the diffuse part
#endif
#ifndef LIT_DIFFUSE_LAMBERT_BRDF
ltcValue *= preLightData.ltcDisneyDiffuseMagnitude;
#endif
// Evaluate the specular part.
// Evaluate the coat part
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
// TODO: the fit seems rather poor. The scaling factor of 0.5 allows us
// to match the reference for rough metals, but further darkens dielectrics.
float3 fresnelTerm = bsdfData.fresnel0 * preLightData.ltcGGXFresnelMagnitudeDiff
+ (float3)preLightData.ltcGGXFresnelMagnitude;
ltcValue = LTCEvaluate(P1, P2, B, preLightData.ltcXformClearCoat);
specularLighting += ltcValue * bsdfData.coatCoverage;
// TODO: no fresnel ?
}
ltcValue = LTCEvaluate(P1, P2, B, preLightData.ltcXformGGX);
ltcValue *= lightData.specularScale;
specularLighting = fresnelTerm * lightData.color * ltcValue;
// Evaluate the specular part
{
ltcValue = LTCEvaluate(P1, P2, B, preLightData.ltcXformGGX);
specularLighting += ltcValue;
// TODO: no fresnel ?
specularLighting *= lightData.color * lightData.specularScale;
}
#endif // LIT_DISPLAY_REFERENCE_AREA
}
float ltcValue;
// Evaluate the diffuse part.
// Evaluate the diffuse part
#endif
#ifndef LIT_DIFFUSE_LAMBERT_BRDF
ltcValue *= preLightData.ltcDisneyDiffuseMagnitude;
#endif
// Evaluate the specular part.
// Evaluate the coat part
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
// TODO: the fit seems rather poor. The scaling factor of 0.5 allows us
// to match the reference for rough metals, but further darkens dielectrics.
float3 fresnelTerm = bsdfData.fresnel0 * preLightData.ltcGGXFresnelMagnitudeDiff
+ (float3)preLightData.ltcGGXFresnelMagnitude;
ltcValue = LTCEvaluate(matL, V, bsdfData.coatNormalWS, preLightData.coatNdotV, preLightData.ltcXformClearCoat);
specularLighting += preLightData.ltcClearCoatFresnelTerm * ltcValue * bsdfData.coatCoverage;
// modify matL value based on Fresnel transmission
matL = mul(matL, preLightData.ltcCoatT);
V = preLightData.refractV;
}
// Evaluate the specular part
{
ltcValue *= lightData.specularScale;
specularLighting = fresnelTerm * lightData.color * ltcValue;
specularLighting += preLightData.ltcGGXFresnelTerm * ltcValue;
specularLighting *= lightData.color * lightData.specularScale;
}
#endif // LIT_DISPLAY_REFERENCE_AREA
}
// We use this formulation as it is the one of legacy unity that was using only AABB box.
float3 R = preLightData.iblDirWS;
float3 coatR = preLightData.coatIblDirWS;
if (lightData.envShapeType == ENVSHAPETYPE_BOX)
if (lightData.envShapeType == ENVSHAPETYPE_SPHERE)
{
float3 dirLS = mul(R, worldToLocal);
float sphereOuterDistance = lightData.innerDistance.x + lightData.blendDistance;
float dist = SphereRayIntersectSimple(positionLS, dirLS, sphereOuterDistance);
R = (positionWS + dist * R) - lightData.positionWS;
// Test again for clear code
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
dirLS = mul(coatR, worldToLocal);
dist = SphereRayIntersectSimple(positionLS, dirLS, sphereOuterDistance);
coatR = (positionWS + dist * coatR) - lightData.positionWS;
}
}
else if (lightData.envShapeType == ENVSHAPETYPE_BOX)
{
float3 dirLS = mul(R, worldToLocal);
float3 boxOuterDistance = lightData.innerDistance + float3(lightData.blendDistance, lightData.blendDistance, lightData.blendDistance);
R = (positionWS + dist * R) - lightData.positionWS;
// TODO: add distance based roughness
}
else if (lightData.envShapeType == ENVSHAPETYPE_SPHERE)
{
float3 dirLS = mul(R, worldToLocal);
float sphereOuterDistance = lightData.innerDistance.x + lightData.blendDistance ;
float dist = SphereRayIntersectSimple(positionLS, dirLS, sphere OuterDistance);
R = (positionWS + dist * R) - lightData.positionWS;
// Test again for clear code
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
dirLS = mul(coatR, worldToLocal) ;
dist = BoxRayIntersectSimple(positionLS, dirLS, -boxOuterDistance, box OuterDistance);
coatR = (positionWS + dist * coatR) - lightData.positionWS;
}
}
// 2. Apply the influence volume (Box volume is used for culling whatever the influence shape)
// Smooth weighting
weight.x = 0.0;
weight.y = smoothstep01(weight.y);
weight.y = Smoothstep01(weight.y);
float3 F = 1.0;
specularLighting = float3(0.0, 0.0, 0.0);
// Evaluate the Clear Coat component if needed and change the BSDF roughness to match Fresnel transmission
if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
F = F_Schlick(preLightData.coatFresnel0, preLightData.coatNdotV);
// Evaluate the Clear Coat color
float4 preLD = SampleEnv(lightLoopContext, lightData.envIndex, coatR, 0.0);
specularLighting += F * preLD.rgb * bsdfData.coatCoverage;
// Change the Fresnel term to account for transmission through Clear Coat and reflection on the base layer.
F = Sqr(-F * bsdfData.coatCoverage + 1.0);
}
specularLighting = preLD.rgb * preLightData.specularFGD;
specularLighting += F * preLD.rgb * preLightData.specularFGD;
// Apply specular occlusion on it
specularLighting *= bsdfData.specularOcclusion * GetSpecularOcclusion(preLightData.NdotV, lightLoopContext.ambientOcclusion, bsdfData.roughness);