#endif
// Use Lambert diffuse instead of Disney diffuse
// #define LIT_DIFFUSE_LAMBERT_BRDF
// Use optimization of Precomputing LambdaV
// TODO: Test if this is a win
// #define LIT_USE_BSDF_PRE_LAMBDAV
#define LIT_USE_GGX_ENERGY_COMPENSATION
// Sampler use by area light, gaussian pyramid, ambient occlusion etc...
// If a user do a lighting architecture without material classification, this can be remove
#include "../../Lighting/TilePass/TilePass.cs.hlsl"
static uint g_FeatureFlags = UINT_MAX;
static uint g_FeatureFlags = UINT_MAX & ~MATERIALFEATUREFLAGS_LIT_CLEAR_COAT ;
// This method allows us to know at compile time what shader features should be removed from the code when the materialID cannot be known on the whole tile (any combination of 2 or more differnet materials in the same tile)
// This is only useful for classification during lighting, so it's not needed in EncodeIntoGBuffer and ConvertSurfaceDataToBSDFData (where we always know exactly what the MaterialID is)
bsdfData.fresnel0 = lerp(val.xxx, baseColor, metallic);
}
void FillMaterialIdAnisoData(float roughness, float3 normalWS, float3 tangentWS, float anisotropy, inout BSDFData bsdfData)
{
bsdfData.tangentWS = tangentWS;
bsdfData.bitangentWS = cross(normalWS, tangentWS);
ConvertAnisotropyToRoughness(roughness, anisotropy, bsdfData.roughnessT, bsdfData.roughnessB);
bsdfData.anisotropy = anisotropy;
}
void FillMaterialIdSSSData(float3 baseColor, int subsurfaceProfile, float subsurfaceRadius, float thickness, inout BSDFData bsdfData)
{
bsdfData.diffuseColor = baseColor;
BSDFData bsdfData;
ZERO_INITIALIZE(BSDFData, bsdfData);
bsdfData.specularOcclusion = surfaceData.specularOcclusion;
bsdfData.normalWS = surfaceData.normalWS;
bsdfData.materialId = surfaceData.materialId;
bsdfData.specularOcclusion = surfaceData.specularOcclusion;
bsdfData.normalWS = surfaceData.normalWS;
bsdfData.anisotropy = surfaceData.anisotropy;
bsdfData.roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
bsdfData.materialId = surfaceData.materialId;
// Warning: 'bsdfData.roughness' is not meant to be used. Otherwise, we increase the register pressure.
// 'bsdfData.roughnessT' and 'bsdfData.roughnessB' are clamped, and are meant to be used with analytical lights.
// 'bsdfData.perceptualRoughness' is not clamped, and is meant to be used for IBL.
// If IBL needs the linear roughness value for some reason, it can be computed as follows:
// float roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
bsdfData.roughness = FLT_NAN;
ConvertAnisotropyToRoughness(bsdfData.perceptualRoughness, bsdfData.anisotropy, bsdfData.roughnessT, bsdfData.roughnessB);
if (surfaceData.materialId != MATERIALID_LIT_ANISO)
{
else if (bsdfData.materialId == MATERIALID_LIT_ANISO)
{
FillMaterialIdStandardData(surfaceData.baseColor, surfaceData.metallic, bsdfData);
FillMaterialIdAnisoData(bsdfData.roughness, surfaceData.normalWS, surfaceData.tangentWS, surfaceData.anisotropy, bsdfData);
bsdfData.tangentWS = surfaceData.tangentWS;
bsdfData.bitangentWS = cross(bsdfData.normalWS, bsdfData.tangentWS);
}
else if (bsdfData.materialId == MATERIALID_LIT_CLEAR_COAT)
{
ZERO_INITIALIZE(BSDFData, bsdfData);
g_FeatureFlags = featureFlags;
g_FeatureFlags = featureFlags & ~MATERIALFEATUREFLAGS_LIT_CLEAR_COAT ;
#if SHADEROPTIONS_PACK_GBUFFER_IN_U16
float4 inGBuffer0, inGBuffer1, inGBuffer2, inGBuffer3;
bsdfData.normalWS = UnpackNormalOctEncode(float2(inGBuffer1.r, inGBuffer1.g));
bsdfData.roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
// The material features system for material classification must allow compile time optimization (i.e everything should be static)
// Note that as we store materialId for Aniso based on content of RT2 we need to add few extra condition.
// The code is also call from MaterialFeatureFlagsFromGBuffer, so must work fully dynamic if featureFlags is UINT_MAX
// If the tile has anisotropy, all the pixels within the tile are evaluated as anisotropic.
if (HasMaterialFeatureFlag(MATERIALFEATUREFLAGS_LIT_ANISO))
{
float anisotropy ;
float3 tangentWS ;
bsdfData.anisotropy = 0 ;
bsdfData.tangentWS = GetLocalFrame(bsdfData.normalWS)[0] ;
if (bsdfData.materialId == MATERIALID_LIT_ANISO)
{
tangentWS = UnpackNormalOctEncode(inGBuffer2.rg);
anisotropy = inGBuffer2.b * 2 - 1;
bsdfData.anisotropy = inGBuffer2.b * 2 - 1;
bsdfData.tangentWS = UnpackNormalOctEncode(inGBuffer2.rg);
else
{
anisotropy = 0;
tangentWS = GetLocalFrame(bsdfData.normalWS)[0];
}
FillMaterialIdAnisoData(bsdfData.roughness, bsdfData.normalWS, tangentWS, anisotropy, bsdfData);
bsdfData.bitangentWS = cross(bsdfData.normalWS, bsdfData.tangentWS);
// Warning: 'bsdfData.roughness' is not meant to be used. Otherwise, we increase the register pressure.
// 'bsdfData.roughnessT' and 'bsdfData.roughnessB' are clamped, and are meant to be used with analytical lights.
// 'bsdfData.perceptualRoughness' is not clamped, and is meant to be used for IBL.
// If IBL needs the linear roughness value for some reason, it can be computed as follows:
// float roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
// TODO: remove 'bsdfData.roughness' completely.
bsdfData.roughness = FLT_NAN;
ConvertAnisotropyToRoughness(bsdfData.perceptualRoughness, bsdfData.anisotropy, bsdfData.roughnessT, bsdfData.roughnessB);
if (bsdfData.materialId == MATERIALID_LIT_STANDARD && HasMaterialFeatureFlag(MATERIALFEATUREFLAGS_LIT_STANDARD))
{
if (materialIdExtent == GBUFFER_LIT_STANDARD_SPECULAR_COLOR_ID)
[flatten] 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
// GGX
float partLambdaV;
float energyCompensation;
float TdotV;
float BdotV;
// Clear coat
float coatNdotV;
NdotV = saturate(dot(N, V));
preLightData.NdotV = NdotV;
float3 iblR;
float3 iblN, iblR;
preLightData.TdotV = dot(bsdfData.tangentWS, V);
preLightData.BdotV = dot(bsdfData.bitangentWS, V);
preLightData.partLambdaV = GetSmithJointGGXAnisoPartLambdaV(preLightData.TdotV, preLightData.BdotV, NdotV, bsdfData.roughnessT, bsdfData.roughnessB);
float TdotV = dot(bsdfData.tangentWS, V);
float BdotV = dot(bsdfData.bitangentWS, V);
preLightData.partLambdaV = GetSmithJointGGXAnisoPartLambdaV(TdotV, BdotV, NdotV, bsdfData.roughnessT, bsdfData.roughnessB);
// For GGX aniso and IBL we have done an empirical (eye balled) approximation compare to the reference.
// We use a single fetch, and we stretch the normal to use based on various criteria.
// NOTE: If we follow the theory we should use the modified normal for the different calculation implying a normal (like NdotV) and use 'anisoIblNormalWS'
// into function like GetSpecularDominantDir(). However modified normal is just a hack. The goal is just to stretch a cubemap, no accuracy here.
// With this in mind and for performance reasons we chose to only use modified normal to calculate R.
float3 anisoIblNormalWS = GetAnisotropicModifiedNormal(grainDirWS, N, V, stretch);
iblR = reflect(-V, anisoIblNormalWS);
iblN = GetAnisotropicModifiedNormal(grainDirWS, N, V, stretch);
preLightData.TdotV = 0;
preLightData.BdotV = 0;
preLightData.partLambdaV = GetSmithJointGGXPartLambdaV(NdotV, bsdfData.roughness);
iblR = reflect(-V, N);
preLightData.partLambdaV = GetSmithJointGGXPartLambdaV(NdotV, bsdfData.roughnessT);
iblN = N;
iblR = reflect(-V, iblN);
float reflectivity;
else
{
// Note: this is a ad-hoc tweak.
float iblRoughness, iblPerceptualRoughness;
if (bsdfData.materialId == MATERIALID_LIT_ANISO && HasMaterialFeatureFlag(MATERIALFEATUREFLAGS_LIT_ANISO))
{
// Use the min roughness, and bias it for higher values of anisotropy and roughness.
float roughnessBias = 0.075 * bsdfData.anisotropy * bsdfData.roughness;
iblRoughness = saturate(min(bsdfData.roughnessT, bsdfData.roughnessB) + roughnessBias);
iblPerceptualRoughness = RoughnessToPerceptualRoughness(iblRoughness);
}
else
{
iblRoughness = bsdfData.roughness;
iblPerceptualRoughness = bsdfData.perceptualRoughness;
}
preLightData.iblDirWS = GetSpecularDominantDir(N, iblR, iblRoughness, NdotV);
preLightData.iblMipLevel = PerceptualRoughnessToMipmapLevel(iblPerceptualRoughness);
// TODO: we need a better hack.
float iblPerceptualRoughness = bsdfData.perceptualRoughness * saturate(1.2 - bsdfData.anisotropy);
float iblRoughness = PerceptualRoughnessToRoughness(iblPerceptualRoughness);
preLightData.iblDirWS = GetSpecularDominantDir(N, iblR, iblRoughness, NdotV);
preLightData.iblMipLevel = PerceptualRoughnessToMipmapLevel(iblPerceptualRoughness);
}
#ifdef LIT_USE_GGX_ENERGY_COMPENSATION
// But rough metals (black diffuse) still scatter quite a lot of light around, so
// we want to take some of that into account too.
lightTransportData.diffuseColor = bsdfData.diffuseColor + bsdfData.fresnel0 * bsdfData.roughness * 0.5 * surfaceData.metallic;
float roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
lightTransportData.diffuseColor = bsdfData.diffuseColor + bsdfData.fresnel0 * roughness * 0.5 * surfaceData.metallic;
lightTransportData.emissiveColor = builtinData.emissiveColor;
return lightTransportData;
float BdotH = dot(bsdfData.bitangentWS, H);
float BdotL = dot(bsdfData.bitangentWS, L);
bsdfData.roughnessT = ClampRoughnessForAnalyticalLights(bsdfData.roughnessT);
bsdfData.roughnessB = ClampRoughnessForAnalyticalLights(bsdfData.roughnessB);
DV = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH,
preLightData.TdotV, preLightData.BdotV, preLightData.NdotV,
TdotL, BdotL, NdotL,
bsdfData.roughnessT, bsdfData.roughnessB
#ifdef LIT_USE_BSDF_PRE_LAMBDAV
, preLightData.partLambdaV);
#else
);
#endif
DV = DV_SmithJointGGXAniso(TdotH, BdotH, NdotH, NdotV, TdotL, BdotL, NdotL,
bsdfData.roughnessT, bsdfData.roughnessB, preLightData.partLambdaV);
bsdfData.roughness = ClampRoughnessForAnalyticalLights(bsdfData.roughness);
DV = DV_SmithJointGGX(NdotH, NdotL, NdotV, bsdfData.roughness
#ifdef LIT_USE_BSDF_PRE_LAMBDAV
, preLightData partLambdaV);
#else
);
#endif
DV = DV_SmithJointGGX(NdotH, NdotL, NdotV, bsdfData.roughnessT, preLightData.partLambdaV);
}
specularLighting += F * DV;
float3 diffuseTerm = DiffuseGGX(bsdfData.diffuseColor, NdotV, NdotL, NdotH, LdotV, bsdfData.roughness);
float roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
float3 diffuseTerm = DiffuseGGX(bsdfData.diffuseColor, NdotV, NdotL, NdotH, LdotV, roughness);
#else
// A note on subsurface scattering: [SSS-NOTE-TRSM]
// The correct way to handle SSS is to transmit light inside the surface, perform SSS,
float3 positionLS = mul(lighToSample, transpose(lightToWorld));
float2 positionCS = positionLS.xy;
bool isInBounds;
// Tile the texture if the 'repeat' wrap mode is enabled.
bool isInBounds = lightData.tileCookie || max(abs(positionCS.x), abs(positionCS.y)) <= 1.0;
float2 positionNDC = positionCS * 0.5 + 0.5;
if (lightData.tileCookie)
{
// Tile the texture if the 'repeat' wrap mode is enabled.
positionNDC = frac(positionNDC);
isInBounds = true;
}
else
{
isInBounds = Max3(abs(positionCS.x), abs(positionCS.y), 1.0 - positionLS.z) <= 1.0;
}
float2 positionNDC = frac(positionCS * 0.5 + 0.5);
// We let the sampler handle tiling or clamping to border.
// Note: tiling (the repeat mode) is not currently supported.
// We let the sampler handle clamping to border.
float4 cookie = SampleCookie2D(lightLoopContext, positionNDC, lightData.cookieIndex);
cookie.a = isInBounds ? cookie.a : 0;
[branch] if (intensity > 0.0)
{
bsdfData.roughness = max(bsdfData.roughness, lightData.minRoughness); // Simulate that a punctual light have a radius with this hack
// Simulate a sphere light with this hack.
bsdfData.roughnessT = max(bsdfData.roughnessT, lightData.minRoughness);
bsdfData.roughnessB = max(bsdfData.roughnessB, lightData.minRoughness);
BSDF(V, L, positionWS, preLightData, bsdfData, lighting.diffuse, lighting.specular);
lighting.diffuse *= intensity * lightData.diffuseScale;
// TODO: factor this code in common, so other material authoring don't require to rewrite everything,
// TODO: test the strech from Tomasz
// float shrinkedRoughness = AnisotropicStrechAtGrazingAngle(bsdfData.roughness, bsdfData.perceptualRoughness, NdotV);
// float roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
// float shrunkRoughness = AnisotropicStrechAtGrazingAngle(roughness, roughness, NdotV);
// Guideline for reflection volume: In HDRenderPipeline we separate the projection volume (the proxy of the scene) from the influence volume (what pixel on the screen is affected)
// However we add the constrain that the shape of the projection and influence volume is the same (i.e if we have a sphere shape projection volume, we have a shape influence).
bakeDiffuseLighting *= lerp(_AmbientOcclusionParam.rgb, float3(1.0, 1.0, 1.0), indirectAmbientOcclusion);
#endif
float specularOcclusion = GetSpecularOcclusionFromAmbientOcclusion(preLightData.NdotV, indirectAmbientOcclusion, bsdfData.roughness);
float roughness = PerceptualRoughnessToRoughness(bsdfData.perceptualRoughness);
float specularOcclusion = GetSpecularOcclusionFromAmbientOcclusion(preLightData.NdotV, indirectAmbientOcclusion, roughness);
// Try to mimic multibounce with specular color. Not the point of the original formula but ok result.
// Take the min of screenspace specular occlusion and visibility cone specular occlusion
#if GTAO_MULTIBOUNCE_APPROX
}
else if (_DebugLightingMode == DEBUGLIGHTINGMODE_INDIRECT_SPECULAR_OCCLUSION_FROM_SSAO)
{
diffuseLighting = GetSpecularOcclusionFromAmbientOcclusion(preLightData.NdotV, indirectAmbientOcclusion, bsdfData.roughness) ;
diffuseLighting = specularOcclusion ;
specularLighting = float3(0.0, 0.0, 0.0); // Disable specular lighting
}
#if GTAO_MULTIBOUNCE_APPROX