Jamie Greunbaum 8eaef49f2e - Added game room, including pool and skee-ball.
- Moved video screen into its own separate movie tent.
- Adjusted stable post-processing volume.
- Chickens are now at full volume.
- Added button to toggle chickens off and on.
2026-02-09 03:49:54 -05:00

1138 lines
43 KiB
HLSL

#ifndef FILAMENT_LIGHT_INDIRECT
#define FILAMENT_LIGHT_INDIRECT
#include "FilamentCommonOcclusion.cginc"
#include "FilamentCommonGraphics.cginc"
#include "FilamentBRDF.cginc"
#include "UnityImageBasedLightingMinimal.cginc"
#include "UnityStandardUtils.cginc"
#include "UnityLightingCommon.cginc"
#include "SharedFilteringLib.hlsl"
#include "FilamentLightLTCGI.cginc"
//------------------------------------------------------------------------------
// Image based lighting configuration
//------------------------------------------------------------------------------
// Whether to use Geometrics' deringing lightprobe sampling.
#define SPHERICAL_HARMONICS_DEFAULT 0
#define SPHERICAL_HARMONICS_GEOMETRICS 1
#define SPHERICAL_HARMONICS SPHERICAL_HARMONICS_GEOMETRICS
// IBL integration algorithm
#define IBL_INTEGRATION_PREFILTERED_CUBEMAP 0
#define IBL_INTEGRATION_IMPORTANCE_SAMPLING 1 // Not supported!
#define IBL_INTEGRATION IBL_INTEGRATION_PREFILTERED_CUBEMAP
#define IBL_INTEGRATION_IMPORTANCE_SAMPLING_COUNT 64
// Refraction defines
#define REFRACTION_TYPE_SOLID 0
#define REFRACTION_TYPE_THIN 1
#ifndef REFRACTION_TYPE
#define REFRACTION_TYPE REFRACTION_TYPE_SOLID
#endif
#define REFRACTION_MODE_CUBEMAP 0
#define REFRACTION_MODE_SCREEN 1
#ifndef REFRACTION_MODE
#define REFRACTION_MODE REFRACTION_MODE_CUBEMAP
#endif
//------------------------------------------------------------------------------
// IBL prefiltered DFG term implementations
//------------------------------------------------------------------------------
float3 PrefilteredDFG_LUT(float lod, float NoV) {
#if defined(USE_DFG_LUT)
// coord = sqrt(linear_roughness), which is the mapping used by cmgen.
return UNITY_SAMPLE_TEX2D(_DFG, float2(NoV, lod));
#else
// Texture not available
return float3(1.0, 0.0, 0.0);
#endif
}
//------------------------------------------------------------------------------
// IBL environment BRDF dispatch
//------------------------------------------------------------------------------
float3 prefilteredDFG(float perceptualRoughness, float NoV) {
#if defined(USE_DFG_LUT)
// PrefilteredDFG_LUT() takes a LOD, which is sqrt(roughness) = perceptualRoughness
return PrefilteredDFG_LUT(perceptualRoughness, NoV);
#else
#if 0
// Karis' approximation based on Lazarov's
const float4 c0 = float4(-1.0, -0.0275, -0.572, 0.022);
const float4 c1 = float4( 1.0, 0.0425, 1.040, -0.040);
float4 r = perceptualRoughness * c0 + c1;
float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
return (float3(float2(-1.04, 1.04) * a004 + r.zw, 0.0));
#endif
#if 0
// Zioma's approximation based on Karis
return float3(float2(1.0, pow(1.0 - max(perceptualRoughness, NoV), 3.0)), 0.0);
#endif
return float3(0, 1, 1);
#endif
}
//------------------------------------------------------------------------------
// IBL irradiance implementations
//------------------------------------------------------------------------------
/* http://www.geomerics.com/wp-content/uploads/2015/08/CEDEC_Geomerics_ReconstructingDiffuseLighting1.pdf */
float shEvaluateDiffuseL1Geomerics_local(float L0, float3 L1, float3 n)
{
// average energy
// Add max0 to fix an issue caused by probes having a negative ambient component (???)
// I'm not sure how normal that is but this can't handle it
float R0 = max(L0, 0);
// avg direction of incoming light
float3 R1 = 0.5f * L1;
// directional brightness
float lenR1 = length(R1);
// linear angle between normal and direction 0-1
float q = dot(normalize(R1), n) * 0.5 + 0.5;
q = saturate(q); // Thanks to ScruffyRuffles for the bug identity.
// power for q
// lerps from 1 (linear) to 3 (cubic) based on directionality
float p = 1.0f + 2.0f * lenR1 / R0;
// dynamic range constant
// should vary between 4 (highly directional) and 0 (ambient)
float a = (1.0f - lenR1 / R0) / (1.0f + lenR1 / R0);
return R0 * (a + (1.0f - a) * (p + 1.0f) * pow(q, p));
}
float3 Irradiance_SphericalHarmonics(const float3 n, const bool useL2) {
// Uses Unity's functions for reading SH.
// However, this function is currently unused.
float3 finalSH = float3(0,0,0);
#if (SPHERICAL_HARMONICS == SPHERICAL_HARMONICS_DEFAULT)
finalSH = SHEvalLinearL0L1(half4(n, 1.0));
if (useL2) finalSH += SHEvalLinearL2(half4(n, 1.0));
#endif
#if (SPHERICAL_HARMONICS == SPHERICAL_HARMONICS_GEOMETRICS)
float3 L0 = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);
float3 L0L2 = float3(unity_SHBr.z, unity_SHBg.z, unity_SHBb.z) / 3.0;
L0 = (useL2) ? L0+L0L2 : L0-L0L2;
finalSH.r = shEvaluateDiffuseL1Geomerics_local(L0.r, unity_SHAr.xyz, n);
finalSH.g = shEvaluateDiffuseL1Geomerics_local(L0.g, unity_SHAg.xyz, n);
finalSH.b = shEvaluateDiffuseL1Geomerics_local(L0.b, unity_SHAb.xyz, n);
// Quadratic polynomials
if (useL2) finalSH += SHEvalLinearL2 (float4(n, 1));
#endif
return finalSH;
}
float3 Irradiance_SphericalHarmonics(const float3 n) {
// Assume L2 is wanted
return Irradiance_SphericalHarmonics(n, true);
}
#if UNITY_LIGHT_PROBE_PROXY_VOLUME
// normal should be normalized, w=1.0
half3 Irradiance_SampleProbeVolume (half4 normal, float3 worldPos)
{
const float transformToLocal = unity_ProbeVolumeParams.y;
const float texelSizeX = unity_ProbeVolumeParams.z;
//The SH coefficients textures and probe occlusion are packed into 1 atlas.
//-------------------------
//| ShR | ShG | ShB | Occ |
//-------------------------
float3 position = (transformToLocal == 1.0f) ? mul(unity_ProbeVolumeWorldToObject, float4(worldPos, 1.0)).xyz : worldPos;
float3 texCoord = (position - unity_ProbeVolumeMin.xyz) * unity_ProbeVolumeSizeInv.xyz;
texCoord.x = texCoord.x * 0.25f;
// We need to compute proper X coordinate to sample.
// Clamp the coordinate otherwize we'll have leaking between RGB coefficients
float texCoordX = clamp(texCoord.x, 0.5f * texelSizeX, 0.25f - 0.5f * texelSizeX);
// sampler state comes from SHr (all SH textures share the same sampler)
texCoord.x = texCoordX;
half4 SHAr = UNITY_SAMPLE_TEX3D_SAMPLER(unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord);
texCoord.x = texCoordX + 0.25f;
half4 SHAg = UNITY_SAMPLE_TEX3D_SAMPLER(unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord);
texCoord.x = texCoordX + 0.5f;
half4 SHAb = UNITY_SAMPLE_TEX3D_SAMPLER(unity_ProbeVolumeSH, unity_ProbeVolumeSH, texCoord);
// Linear + constant polynomial terms
half3 x1;
#if (SPHERICAL_HARMONICS == SPHERICAL_HARMONICS_DEFAULT)
x1.r = dot(SHAr, normal);
x1.g = dot(SHAg, normal);
x1.b = dot(SHAb, normal);
#endif
#if (SPHERICAL_HARMONICS == SPHERICAL_HARMONICS_GEOMETRICS)
x1.r = shEvaluateDiffuseL1Geomerics_local(SHAr.w, SHAr.rgb, normal);
x1.g = shEvaluateDiffuseL1Geomerics_local(SHAg.w, SHAg.rgb, normal);
x1.b = shEvaluateDiffuseL1Geomerics_local(SHAb.w, SHAb.rgb, normal);
#endif
return x1;
}
#endif
half3 Irradiance_SphericalHarmonicsUnity (half3 normal, half3 ambient, float3 worldPos)
{
half3 ambient_contrib = 0.0;
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL
// Completely per-pixel
#if UNITY_LIGHT_PROBE_PROXY_VOLUME
if (unity_ProbeVolumeParams.x == 1.0)
ambient_contrib = Irradiance_SampleProbeVolume(half4(normal, 1.0), worldPos);
else
ambient_contrib = Irradiance_SphericalHarmonics(normal, true);
#else
ambient_contrib = Irradiance_SphericalHarmonics(normal, true);
#endif
ambient += max(half3(0, 0, 0), ambient_contrib);
#ifdef UNITY_COLORSPACE_GAMMA
ambient = LinearToGammaSpace(ambient);
#endif
#elif (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
// Completely per-vertex
// nothing to do here. Gamma conversion on ambient from SH takes place in the vertex shader, see ShadeSHPerVertex.
#else
// L2 per-vertex, L0..L1 & gamma-correction per-pixel
// Ambient in this case is expected to be always Linear, see ShadeSHPerVertex()
#if UNITY_LIGHT_PROBE_PROXY_VOLUME
if (unity_ProbeVolumeParams.x == 1.0)
ambient_contrib = Irradiance_SampleProbeVolume (half4(normal, 1.0), worldPos);
else
ambient_contrib = Irradiance_SphericalHarmonics(normal, false);
#else
ambient_contrib = Irradiance_SphericalHarmonics(normal, false);
#endif
ambient = max(half3(0, 0, 0), ambient+ambient_contrib); // include L2 contribution in vertex shader before clamp.
#ifdef UNITY_COLORSPACE_GAMMA
ambient = LinearToGammaSpace (ambient);
#endif
#endif
return ambient;
}
/*
float3 Irradiance_RoughnessOne(const float3 n) {
// note: lod used is always integer, hopefully the hardware skips tri-linear filtering
return decodeDataForIBL(textureLod(light_iblSpecular, n, frameUniforms.iblRoughnessOneLevel));
}
*/
float4 UnityLightmap_ColorIntensitySeperated(float3 lightmap) {
lightmap += 0.000001;
return float4(lightmap.xyz / 1, 1);
}
float4 SampleLightmapBicubic(float2 uv)
{
#ifdef SHADER_API_D3D11
float width, height;
unity_Lightmap.GetDimensions(width, height);
float4 unity_Lightmap_TexelSize = float4(width, height, 1.0/width, 1.0/height);
return SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(unity_Lightmap, samplerunity_Lightmap),
uv, unity_Lightmap_TexelSize);
#else
return SAMPLE_TEXTURE2D(unity_Lightmap, samplerunity_Lightmap, uv);
#endif
}
float4 SampleLightmapDirBicubic(float2 uv)
{
#ifdef SHADER_API_D3D11
float width, height;
unity_LightmapInd.GetDimensions(width, height);
float4 unity_LightmapInd_TexelSize = float4(width, height, 1.0/width, 1.0/height);
return SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(unity_LightmapInd, samplerunity_Lightmap),
uv, unity_LightmapInd_TexelSize);
#else
return SAMPLE_TEXTURE2D(unity_LightmapInd, samplerunity_Lightmap, uv);
#endif
}
float4 SampleDynamicLightmapBicubic(float2 uv)
{
#ifdef SHADER_API_D3D11
float width, height;
unity_DynamicLightmap.GetDimensions(width, height);
float4 unity_DynamicLightmap_TexelSize = float4(width, height, 1.0/width, 1.0/height);
return SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(unity_DynamicLightmap, samplerunity_DynamicLightmap),
uv, unity_DynamicLightmap_TexelSize);
#else
return SAMPLE_TEXTURE2D(unity_DynamicLightmap, samplerunity_DynamicLightmap, uv);
#endif
}
float4 SampleDynamicLightmapDirBicubic(float2 uv)
{
#ifdef SHADER_API_D3D11
float width, height;
unity_DynamicDirectionality.GetDimensions(width, height);
float4 unity_DynamicDirectionality_TexelSize = float4(width, height, 1.0/width, 1.0/height);
return SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(unity_DynamicDirectionality, samplerunity_DynamicLightmap),
uv, unity_DynamicDirectionality_TexelSize);
#else
return SAMPLE_TEXTURE2D(unity_DynamicDirectionality, samplerunity_DynamicLightmap, uv);
#endif
}
inline float3 DecodeDirectionalLightmapSpecular(half3 color, half4 dirTex, half3 normalWorld,
const bool isRealtimeLightmap, fixed4 realtimeNormalTex, out Light o_light)
{
o_light = (Light)0;
o_light.colorIntensity = float4(color, 1.0);
o_light.l = dirTex.xyz * 2 - 1;
// The length of the direction vector is the light's "directionality", i.e. 1 for all light coming from this direction,
// lower values for more spread out, ambient light.
half directionality = max(0.001, length(o_light.l));
o_light.l /= directionality;
#ifdef DYNAMICLIGHTMAP_ON
if (isRealtimeLightmap)
{
// Realtime directional lightmaps' intensity needs to be divided by N.L
// to get the incoming light intensity. Baked directional lightmaps are already
// output like that (including the max() to prevent div by zero).
half3 realtimeNormal = realtimeNormalTex.xyz * 2 - 1;
o_light.colorIntensity /= max(0.125, dot(realtimeNormal, o_light.l));
}
#endif
// Split light into the directional and ambient parts, according to the directionality factor.
half3 ambient = o_light.colorIntensity * (1 - directionality);
o_light.colorIntensity = o_light.colorIntensity * directionality;
o_light.attenuation = directionality;
o_light.NoL = saturate(dot(normalWorld, o_light.l));
return ambient;
}
#if defined(USING_BAKERY) && defined(LIGHTMAP_ON)
// needs specular variant?
float3 DecodeRNMLightmap(half3 color, half2 lightmapUV, half3 normalTangent, float3x3 tangentToWorld, out Light o_light)
{
const float rnmBasis0 = float3(0.816496580927726f, 0, 0.5773502691896258f);
const float rnmBasis1 = float3(-0.4082482904638631f, 0.7071067811865475f, 0.5773502691896258f);
const float rnmBasis2 = float3(-0.4082482904638631f, -0.7071067811865475f, 0.5773502691896258f);
float3 irradiance;
o_light = (Light)0;
#ifdef SHADER_API_D3D11
float width, height;
_RNM0.GetDimensions(width, height);
float4 rnm_TexelSize = float4(width, height, 1.0/width, 1.0/height);
float3 rnm0 = DecodeLightmap(SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(_RNM0, sampler_RNM0), lightmapUV, rnm_TexelSize));
float3 rnm1 = DecodeLightmap(SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(_RNM1, sampler_RNM0), lightmapUV, rnm_TexelSize));
float3 rnm2 = DecodeLightmap(SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(_RNM2, sampler_RNM0), lightmapUV, rnm_TexelSize));
#else
float3 rnm0 = DecodeLightmap(SAMPLE_TEXTURE2D(_RNM0, sampler_RNM0, lightmapUV));
float3 rnm1 = DecodeLightmap(SAMPLE_TEXTURE2D(_RNM1, sampler_RNM0, lightmapUV));
float3 rnm2 = DecodeLightmap(SAMPLE_TEXTURE2D(_RNM2, sampler_RNM0, lightmapUV));
#endif
normalTangent.g *= -1;
irradiance = saturate(dot(rnmBasis0, normalTangent)) * rnm0
+ saturate(dot(rnmBasis1, normalTangent)) * rnm1
+ saturate(dot(rnmBasis2, normalTangent)) * rnm2;
#if defined(LIGHTMAP_SPECULAR)
float3 dominantDirT = rnmBasis0 * luminance(rnm0) +
rnmBasis1 * luminance(rnm1) +
rnmBasis2 * luminance(rnm2);
float3 dominantDirTN = normalize(dominantDirT);
float3 specColor = saturate(dot(rnmBasis0, dominantDirTN)) * rnm0 +
saturate(dot(rnmBasis1, dominantDirTN)) * rnm1 +
saturate(dot(rnmBasis2, dominantDirTN)) * rnm2;
o_light.l = normalize(mul(tangentToWorld, dominantDirT));
half directionality = max(0.001, length(o_light.l));
o_light.l /= directionality;
// Split light into the directional and ambient parts, according to the directionality factor.
o_light.colorIntensity = float4(specColor * directionality, 1.0);
o_light.attenuation = directionality;
o_light.NoL = saturate(dot(normalTangent, dominantDirTN));
#endif
return irradiance;
}
float3 DecodeSHLightmap(half3 L0, half2 lightmapUV, half3 normalWorld, out Light o_light)
{
float3 irradiance;
o_light = (Light)0;
#ifdef SHADER_API_D3D11
float width, height;
_RNM0.GetDimensions(width, height);
float4 rnm_TexelSize = float4(width, height, 1.0/width, 1.0/height);
float3 nL1x = SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(_RNM0, sampler_RNM0), lightmapUV, rnm_TexelSize);
float3 nL1y = SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(_RNM1, sampler_RNM0), lightmapUV, rnm_TexelSize);
float3 nL1z = SampleTexture2DBicubicFilter(TEXTURE2D_ARGS(_RNM2, sampler_RNM0), lightmapUV, rnm_TexelSize);
#else
float3 nL1x = SAMPLE_TEXTURE2D(_RNM0, sampler_RNM0, lightmapUV);
float3 nL1y = SAMPLE_TEXTURE2D(_RNM1, sampler_RNM0, lightmapUV);
float3 nL1z = SAMPLE_TEXTURE2D(_RNM2, sampler_RNM0, lightmapUV);
#endif
nL1x = nL1x * 2 - 1;
nL1y = nL1y * 2 - 1;
nL1z = nL1z * 2 - 1;
float3 L1x = nL1x * L0 * 2;
float3 L1y = nL1y * L0 * 2;
float3 L1z = nL1z * L0 * 2;
#ifdef BAKERY_SHNONLINEAR
float lumaL0 = dot(L0, float(1));
float lumaL1x = dot(L1x, float(1));
float lumaL1y = dot(L1y, float(1));
float lumaL1z = dot(L1z, float(1));
float lumaSH = shEvaluateDiffuseL1Geomerics_local(lumaL0, float3(lumaL1x, lumaL1y, lumaL1z), normalWorld);
irradiance = L0 + normalWorld.x * L1x + normalWorld.y * L1y + normalWorld.z * L1z;
float regularLumaSH = dot(irradiance, 1);
irradiance *= lerp(1, lumaSH / regularLumaSH, saturate(regularLumaSH*16));
#else
irradiance = L0 + normalWorld.x * L1x + normalWorld.y * L1y + normalWorld.z * L1z;
#endif
#if defined(LIGHTMAP_SPECULAR)
float3 dominantDir = float3(luminance(nL1x), luminance(nL1y), luminance(nL1z));
o_light.l = dominantDir;
half directionality = max(0.001, length(o_light.l));
o_light.l /= directionality;
// Split light into the directional and ambient parts, according to the directionality factor.
o_light.colorIntensity = float4(irradiance * directionality, 1.0);
o_light.attenuation = directionality;
o_light.NoL = saturate(dot(normalWorld, o_light.l));
#endif
return irradiance;
}
#endif
#if defined(_BAKERY_MONOSH)
float3 DecodeMonoSHLightmap(half3 L0, half2 lightmapUV, half3 normalWorld, out Light o_light)
{
o_light = (Light)0;
float3 dominantDir = SampleLightmapDirBicubic (lightmapUV);
float3 nL1 = dominantDir * 2 - 1;
float3 L1x = nL1.x * L0 * 2;
float3 L1y = nL1.y * L0 * 2;
float3 L1z = nL1.z * L0 * 2;
float3 sh;
#if BAKERY_SHNONLINEAR
float lumaL0 = dot(L0, 1);
float lumaL1x = dot(L1x, 1);
float lumaL1y = dot(L1y, 1);
float lumaL1z = dot(L1z, 1);
float lumaSH = shEvaluateDiffuseL1Geomerics_local(lumaL0, float3(lumaL1x, lumaL1y, lumaL1z), normalWorld);
sh = L0 + normalWorld.x * L1x + normalWorld.y * L1y + normalWorld.z * L1z;
float regularLumaSH = dot(sh, 1);
sh *= lerp(1, lumaSH / regularLumaSH, saturate(regularLumaSH*16));
#else
sh = L0 + normalWorld.x * L1x + normalWorld.y * L1y + normalWorld.z * L1z;
#endif
#if defined(LIGHTMAP_SPECULAR)
dominantDir = nL1;
o_light.l = dominantDir;
half directionality = max(0.001, length(o_light.l));
o_light.l /= directionality;
// Split light into the directional and ambient parts, according to the directionality factor.
o_light.colorIntensity = float4(L0 * directionality, 1.0);
o_light.attenuation = directionality;
o_light.NoL = saturate(dot(normalWorld, o_light.l));
#endif
return sh;
}
#endif
float IrradianceToExposureOcclusion(float3 irradiance)
{
return saturate(length(irradiance + FLT_EPS) * getExposureOcclusionBias());
}
// Return light probes or lightmap.
float3 UnityGI_Irradiance(ShadingParams shading, float3 tangentNormal, out float occlusion, out Light derivedLight)
{
float3 irradiance = shading.ambient;
float3 irradianceForAO;
occlusion = 1.0;
derivedLight = (Light)0;
#if UNITY_SHOULD_SAMPLE_SH
irradiance = Irradiance_SphericalHarmonicsUnity(shading.normal, shading.ambient, shading.position);
occlusion = saturate(length(irradiance) * getExposureOcclusionBias());
#endif
irradianceForAO = irradiance;
#if defined(LIGHTMAP_ON)
// Baked lightmaps
half4 bakedColorTex = SampleLightmapBicubic(shading.lightmapUV.xy);
half3 bakedColor = DecodeLightmap(bakedColorTex);
#ifdef DIRLIGHTMAP_COMBINED
fixed4 bakedDirTex = SampleLightmapDirBicubic (shading.lightmapUV.xy);
// Bakery's MonoSH mode replaces the regular directional lightmap
#if defined(_BAKERY_MONOSH)
irradiance = DecodeMonoSHLightmap (bakedColor, shading.lightmapUV.xy, shading.normal, derivedLight);
irradianceForAO = irradiance;
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
irradiance = SubtractMainLightWithRealtimeAttenuationFromLightmap (irradiance, shading.attenuation, bakedColorTex, shading.normal);
#endif
#else
irradiance = DecodeDirectionalLightmap (bakedColor, bakedDirTex, shading.normal);
irradianceForAO = irradiance;
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
irradiance = SubtractMainLightWithRealtimeAttenuationFromLightmap (irradiance, shading.attenuation, bakedColorTex, shading.normal);
#endif
#if defined(LIGHTMAP_SPECULAR)
irradiance = DecodeDirectionalLightmapSpecular(bakedColor, bakedDirTex, shading.normal, false, 0, derivedLight);
#endif
#endif
#else // not directional lightmap
#if defined(USING_BAKERY)
#if defined(_BAKERY_RNM)
// bakery rnm mode
irradiance = DecodeRNMLightmap(bakedColor, shading.lightmapUV.xy, tangentNormal, shading.tangentToWorld, derivedLight);
#endif
#if defined(_BAKERY_SH)
// bakery sh mode
irradiance = DecodeSHLightmap(bakedColor, shading.lightmapUV.xy, shading.normal, derivedLight);
#endif
irradianceForAO = irradiance;
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
irradiance = SubtractMainLightWithRealtimeAttenuationFromLightmap(irradiance, shading.attenuation, bakedColorTex, shading.normal);
#endif
#else
irradiance += bakedColor;
irradianceForAO = irradiance;
#if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN)
irradiance = SubtractMainLightWithRealtimeAttenuationFromLightmap(irradiance, shading.attenuation, bakedColorTex, shading.normal);
#endif
#endif
#endif
#endif
#if defined(DYNAMICLIGHTMAP_ON)
// Dynamic lightmaps
fixed4 realtimeColorTex = SampleDynamicLightmapBicubic(shading.lightmapUV.zw);
half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);
irradianceForAO += realtimeColor;
#ifdef DIRLIGHTMAP_COMBINED
half4 realtimeDirTex = SampleDynamicLightmapDirBicubic(shading.lightmapUV.zw);
irradiance += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, shading.normal);
#else
irradiance += realtimeColor;
#endif
#endif
occlusion = IrradianceToExposureOcclusion(irradianceForAO);
return irradiance;
}
//------------------------------------------------------------------------------
// IBL irradiance dispatch
//------------------------------------------------------------------------------
float3 get_diffuseIrradiance(const float3 n) {
return Irradiance_SphericalHarmonics(n);
}
//------------------------------------------------------------------------------
// IBL specular
//------------------------------------------------------------------------------
UnityGIInput InitialiseUnityGIInput(const ShadingParams shading, const PixelParams pixel)
{
UnityGIInput d;
d.worldPos = shading.position;
d.worldViewDir = -shading.view;
d.probeHDR[0] = unity_SpecCube0_HDR;
d.probeHDR[1] = unity_SpecCube1_HDR;
#if defined(UNITY_SPECCUBE_BLENDING) || defined(UNITY_SPECCUBE_BOX_PROJECTION)
d.boxMin[0] = unity_SpecCube0_BoxMin; // .w holds lerp value for blending
#endif
#ifdef UNITY_SPECCUBE_BOX_PROJECTION
d.boxMax[0] = unity_SpecCube0_BoxMax;
d.probePosition[0] = unity_SpecCube0_ProbePosition;
d.boxMax[1] = unity_SpecCube1_BoxMax;
d.boxMin[1] = unity_SpecCube1_BoxMin;
d.probePosition[1] = unity_SpecCube1_ProbePosition;
#endif
return d;
}
float perceptualRoughnessToLod(float perceptualRoughness) {
const float iblRoughnessOneLevel = 1.0/UNITY_SPECCUBE_LOD_STEPS;
// The mapping below is a quadratic fit for log2(perceptualRoughness)+iblRoughnessOneLevel when
// iblRoughnessOneLevel is 4. We found empirically that this mapping works very well for
// a 256 cubemap with 5 levels used. But also scales well for other iblRoughnessOneLevel values.
//return iblRoughnessOneLevel * perceptualRoughness * (2.0 - perceptualRoughness);
// Unity's remapping (UNITY_SPECCUBE_LOD_STEPS is 6, not 4)
return iblRoughnessOneLevel * perceptualRoughness * (1.7 - 0.7 * perceptualRoughness);
}
float3 prefilteredRadiance(const float3 r, float perceptualRoughness) {
float lod = perceptualRoughnessToLod(perceptualRoughness);
return DecodeHDR(UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, r, lod), unity_SpecCube0_HDR);
}
float3 prefilteredRadiance(const float3 r, float roughness, float offset) {
const float iblRoughnessOneLevel = 1.0/UNITY_SPECCUBE_LOD_STEPS;
float lod = iblRoughnessOneLevel * roughness;
return DecodeHDR(UNITY_SAMPLE_TEXCUBE_LOD(unity_SpecCube0, r, lod + offset), unity_SpecCube0_HDR);
}
half3 Unity_GlossyEnvironment_local (UNITY_ARGS_TEXCUBE(tex), half4 hdr, Unity_GlossyEnvironmentData glossIn)
{
half perceptualRoughness = glossIn.roughness /* perceptualRoughness */ ;
// Workaround for issue where objects are blurrier than they should be
// due to specular AA.
#if !defined(TARGET_MOBILE) && defined(GEOMETRIC_SPECULAR_AA)
float roughnessAdjustment = 1-perceptualRoughness;
roughnessAdjustment = MIN_PERCEPTUAL_ROUGHNESS * roughnessAdjustment * roughnessAdjustment;
perceptualRoughness = perceptualRoughness - roughnessAdjustment;
#endif
// Unity derivation
perceptualRoughness = perceptualRoughness*(1.7 - 0.7 * perceptualRoughness);
// Filament derivation
// perceptualRoughness = perceptualRoughness * (2.0 - perceptualRoughness);
half mip = perceptualRoughnessToMipmapLevel(perceptualRoughness);
half3 R = glossIn.reflUVW;
half4 rgbm = UNITY_SAMPLE_TEXCUBE_LOD(tex, R, mip);
return DecodeHDR(rgbm, hdr);
}
// Workaround: Construct the correct Unity variables and get the correct Unity spec values
inline half3 UnityGI_prefilteredRadiance(const UnityGIInput data, const float perceptualRoughness, const float3 r)
{
half3 specular;
Unity_GlossyEnvironmentData glossIn = (Unity_GlossyEnvironmentData)0;
glossIn.roughness = perceptualRoughness;
glossIn.reflUVW = r;
#ifdef UNITY_SPECCUBE_BOX_PROJECTION
// we will tweak reflUVW in glossIn directly (as we pass it to Unity_GlossyEnvironment twice for probe0 and probe1), so keep original to pass into BoxProjectedCubemapDirection
half3 originalReflUVW = glossIn.reflUVW;
glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[0], data.boxMin[0], data.boxMax[0]);
#endif
#ifdef _GLOSSYREFLECTIONS_OFF
specular = unity_IndirectSpecColor.rgb;
#else
half3 env0 = Unity_GlossyEnvironment_local (UNITY_PASS_TEXCUBE(unity_SpecCube0), data.probeHDR[0], glossIn);
#ifdef UNITY_SPECCUBE_BLENDING
const float kBlendFactor = 0.99999;
float blendLerp = data.boxMin[0].w;
UNITY_BRANCH
if (blendLerp < kBlendFactor)
{
#ifdef UNITY_SPECCUBE_BOX_PROJECTION
glossIn.reflUVW = BoxProjectedCubemapDirection (originalReflUVW, data.worldPos, data.probePosition[1], data.boxMin[1], data.boxMax[1]);
#endif
half3 env1 = Unity_GlossyEnvironment_local (UNITY_PASS_TEXCUBE_SAMPLER(unity_SpecCube1,unity_SpecCube0), data.probeHDR[1], glossIn);
specular = lerp(env1, env0, blendLerp);
}
else
{
specular = env0;
}
#else
specular = env0;
#endif
#endif
return specular;
}
float3 getSpecularDominantDirection(const float3 n, const float3 r, float roughness) {
return lerp(r, n, roughness * roughness);
}
float3 specularDFG(const PixelParams pixel) {
#if defined(SHADING_MODEL_CLOTH)
return pixel.f0 * pixel.dfg.z;
#else
return lerp(pixel.dfg.xxx, pixel.dfg.yyy, pixel.f0);
#endif
}
/**
* Returns the reflected vector at the current shading point. The reflected vector
* return by this function might be different from shading.reflected:
* - For anisotropic material, we bend the reflection vector to simulate
* anisotropic indirect lighting
* - The reflected vector may be modified to point towards the dominant specular
* direction to match reference renderings when the roughness increases
*/
float3 getReflectedVector(const PixelParams pixel, const float3 v, const float3 n) {
#if defined(MATERIAL_HAS_ANISOTROPY)
float3 anisotropyDirection = pixel.anisotropy >= 0.0 ? pixel.anisotropicB : pixel.anisotropicT;
float3 anisotropicTangent = cross(anisotropyDirection, v);
float3 anisotropicNormal = cross(anisotropicTangent, anisotropyDirection);
float bendFactor = abs(pixel.anisotropy) * saturate(5.0 * pixel.perceptualRoughness);
float3 bentNormal = normalize(lerp(n, anisotropicNormal, bendFactor));
float3 r = reflect(-v, bentNormal);
#else
float3 r = reflect(-v, n);
#endif
return r;
}
float3 getReflectedVector(const ShadingParams shading, const PixelParams pixel, const float3 n) {
#if defined(MATERIAL_HAS_ANISOTROPY)
float3 r = getReflectedVector(pixel, shading.view, n);
#else
float3 r = shading.reflected;
#endif
return getSpecularDominantDirection(n, r, pixel.roughness);
}
//------------------------------------------------------------------------------
// Prefiltered importance sampling
//------------------------------------------------------------------------------
#if IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING
void isEvaluateClearCoatIBL(const ShadingParams shading, const PixelParams pixel,
float specularAO, inout float3 Fd, inout float3 Fr) {
#if defined(MATERIAL_HAS_CLEAR_COAT)
#if defined(MATERIAL_HAS_NORMAL) || defined(MATERIAL_HAS_CLEAR_COAT_NORMAL)
// We want to use the geometric normal for the clear coat layer
float clearCoatNoV = clampNoV(dot(shading.clearCoatNormal, shading.view));
float3 clearCoatNormal = shading.clearCoatNormal;
#else
float clearCoatNoV = shading.NoV;
float3 clearCoatNormal = shading.normal;
#endif
// The clear coat layer assumes an IOR of 1.5 (4% reflectance)
float Fc = F_Schlick(0.04, 1.0, clearCoatNoV) * pixel.clearCoat;
float attenuation = 1.0 - Fc;
Fd *= attenuation;
Fr *= attenuation;
PixelParams p;
p.perceptualRoughness = pixel.clearCoatPerceptualRoughness;
p.f0 = 0.04;
p.roughness = perceptualRoughnessToRoughness(p.perceptualRoughness);
#if defined(MATERIAL_HAS_ANISOTROPY)
p.anisotropy = 0.0;
#endif
float3 clearCoatLobe = isEvaluateSpecularIBL(p, clearCoatNormal, shading.view, clearCoatNoV);
Fr += clearCoatLobe * (specularAO * pixel.clearCoat);
#endif
}
#endif
//------------------------------------------------------------------------------
// IBL evaluation
//------------------------------------------------------------------------------
void evaluateClothIndirectDiffuseBRDF(const ShadingParams shading, const PixelParams pixel,
inout float diffuse) {
#if defined(SHADING_MODEL_CLOTH)
#if defined(MATERIAL_HAS_SUBSURFACE_COLOR)
// Simulate subsurface scattering with a wrap diffuse term
diffuse *= Fd_Wrap(shading.NoV, 0.5);
#endif
#endif
}
void evaluateSheenIBL(const ShadingParams shading, const PixelParams pixel,
float diffuseAO, inout float3 Fd, inout float3 Fr) {
#if !defined(SHADING_MODEL_CLOTH) && !defined(SHADING_MODEL_SUBSURFACE)
#if defined(MATERIAL_HAS_SHEEN_COLOR)
// Albedo scaling of the base layer before we layer sheen on top
Fd *= pixel.sheenScaling;
Fr *= pixel.sheenScaling;
float3 reflectance = pixel.sheenDFG * pixel.sheenColor;
reflectance *= computeSpecularAO(shading.NoV, diffuseAO, pixel.sheenRoughness);
Fr += reflectance * prefilteredRadiance(shading.reflected, pixel.sheenPerceptualRoughness);
#endif
#endif
}
void evaluateClearCoatIBL(const ShadingParams shading, const PixelParams pixel,
float diffuseAO, inout float3 Fd, inout float3 Fr) {
#if IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING
float specularAO = computeSpecularAO(shading.NoV, diffuseAO, pixel.clearCoatRoughness);
isEvaluateClearCoatIBL(pixel, specularAO, Fd, Fr);
return;
#endif
#if defined(MATERIAL_HAS_CLEAR_COAT)
#if defined(MATERIAL_HAS_NORMAL) || defined(MATERIAL_HAS_CLEAR_COAT_NORMAL)
// We want to use the geometric normal for the clear coat layer
float clearCoatNoV = clampNoV(dot(shading.clearCoatNormal, shading.view));
float3 clearCoatR = reflect(-shading.view, shading.clearCoatNormal);
#else
float clearCoatNoV = shading.NoV;
float3 clearCoatR = shading.reflected;
#endif
// The clear coat layer assumes an IOR of 1.5 (4% reflectance)
float Fc = F_Schlick(0.04, 1.0, clearCoatNoV) * pixel.clearCoat;
float attenuation = 1.0 - Fc;
Fd *= attenuation;
Fr *= attenuation;
// TODO: Should we apply specularAO to the attenuation as well?
float specularAO = computeSpecularAO(clearCoatNoV, diffuseAO, pixel.clearCoatRoughness);
Fr += prefilteredRadiance(clearCoatR, pixel.clearCoatPerceptualRoughness) * (specularAO * Fc);
#endif
}
void evaluateSubsurfaceIBL(const ShadingParams shading, const PixelParams pixel,
const float3 diffuseIrradiance, inout float3 Fd, inout float3 Fr) {
#if defined(SHADING_MODEL_SUBSURFACE)
float3 viewIndependent = diffuseIrradiance;
float3 viewDependent = prefilteredRadiance(-shading.view, pixel.roughness, 1.0 + pixel.thickness);
float attenuation = (1.0 - pixel.thickness) / (2.0 * PI);
Fd += pixel.subsurfaceColor * (viewIndependent + viewDependent) * attenuation;
#elif defined(SHADING_MODEL_CLOTH) && defined(MATERIAL_HAS_SUBSURFACE_COLOR)
Fd *= saturate(pixel.subsurfaceColor + shading.NoV);
#endif
}
#if defined(HAS_REFRACTION)
struct Refraction {
float3 position;
float3 direction;
float d;
};
void refractionSolidSphere(const ShadingParams shading, const PixelParams pixel,
const float3 n, float3 r, out Refraction ray) {
r = refract(r, n, pixel.etaIR);
float NoR = dot(n, r);
float d = pixel.thickness * -NoR;
ray.position = float3(shading.position + r * d);
ray.d = d;
float3 n1 = normalize(NoR * r - n * 0.5);
ray.direction = refract(r, n1, pixel.etaRI);
}
void refractionSolidBox(const ShadingParams shading, const PixelParams pixel,
const float3 n, float3 r, out Refraction ray) {
float3 rr = refract(r, n, pixel.etaIR);
float NoR = dot(n, rr);
float d = pixel.thickness / max(-NoR, 0.001);
ray.position = float3(shading.position + rr * d);
ray.direction = r;
ray.d = d;
#if REFRACTION_MODE == REFRACTION_MODE_CUBEMAP
// fudge direction vector, so we see the offset due to the thickness of the object
float envDistance = 10.0; // this should come from a ubo
ray.direction = normalize((ray.position - shading.position) + ray.direction * envDistance);
#endif
}
void refractionThinSphere(const ShadingParams shading, const PixelParams pixel,
const float3 n, float3 r, out Refraction ray) {
float d = 0.0;
#if defined(MATERIAL_HAS_MICRO_THICKNESS)
// note: we need the refracted ray to calculate the distance traveled
// we could use shading.NoV, but we would lose the dependency on ior.
float3 rr = refract(r, n, pixel.etaIR);
float NoR = dot(n, rr);
d = pixel.uThickness / max(-NoR, 0.001);
ray.position = float3(shading.position + rr * d);
#else
ray.position = float3(shading.position);
#endif
ray.direction = r;
ray.d = d;
}
void applyRefraction(
const ShadingParams shading,
const PixelParams pixel,
float3 E, float3 Fd, float3 Fr,
inout float3 color) {
Refraction ray;
float iblLuminance = 1.0; // unused
float refractionLodOffset = 0.0; // unused
#if REFRACTION_TYPE == REFRACTION_TYPE_SOLID
refractionSolidSphere(shading, pixel, shading.normal, -shading.view, ray);
#elif REFRACTION_TYPE == REFRACTION_TYPE_THIN
refractionThinSphere(shading, pixel, shading.normal, -shading.view, ray);
#else
#error invalid REFRACTION_TYPE
#endif
// compute transmission T
#if defined(MATERIAL_HAS_ABSORPTION)
#if defined(MATERIAL_HAS_THICKNESS) || defined(MATERIAL_HAS_MICRO_THICKNESS)
float3 T = min(1.0, exp(-pixel.absorption * ray.d));
#else
float3 T = 1.0 - pixel.absorption;
#endif
#endif
// Roughness remapping so that an IOR of 1.0 means no microfacet refraction and an IOR
// of 1.5 has full microfacet refraction
float perceptualRoughness = lerp(pixel.perceptualRoughnessUnclamped, 0.0,
saturate(pixel.etaIR * 3.0 - 2.0));
#if REFRACTION_TYPE == REFRACTION_TYPE_THIN
// For thin surfaces, the light will bounce off at the second interface in the direction of
// the reflection, effectively adding to the specular, but this process will repeat itself.
// Each time the ray exits the surface on the front side after the first bounce,
// it's multiplied by E^2, and we get: E + E(1-E)^2 + E^3(1-E)^2 + ...
// This infinite series converges and is easy to simplify.
// Note: we calculate these bounces only on a single component,
// since it's a fairly subtle effect.
E *= 1.0 + pixel.transmission * (1.0 - E.g) / (1.0 + E.g);
#endif
/* sample the cubemap or screen-space */
#if REFRACTION_MODE == REFRACTION_MODE_CUBEMAP
// when reading from the cubemap, we are not pre-exposed so we apply iblLuminance
// which is not the case when we'll read from the screen-space buffer
// Gather Unity GI data
UnityGIInput unityData = InitialiseUnityGIInput(shading, pixel);
float3 Ft = UnityGI_prefilteredRadiance(unityData, perceptualRoughness, ray.direction) * iblLuminance;
#else
// compute the point where the ray exits the medium, if needed
//float4 p = float4(frameUniforms.clipFromWorldMatrix * float4(ray.position, 1.0));
//p.xy = uvToRenderTargetUV(p.xy * (0.5 / p.w) + 0.5);
float4 p = UnityWorldToClipPos(float4(ray.position, 1.0));
p.w = (0.5 / p.w);
p.xy = ComputeGrabScreenPos(p);
// perceptualRoughness to LOD
// Empirical factor to compensate for the gaussian approximation of Dggx, chosen so
// cubemap and screen-space modes match at perceptualRoughness 0.125
// TODO: Remove this factor temporarily until we find a better solution
// This overblurs many scenes and needs a more principled approach
// float tweakedPerceptualRoughness = perceptualRoughness * 1.74;
float tweakedPerceptualRoughness = perceptualRoughness;
float lod = max(0.0, 2.0 * log2(tweakedPerceptualRoughness) + refractionLodOffset);
float3 Ft = UNITY_SAMPLE_TEX2D_LOD(REFRACTION_SOURCE, p.xy, lod).rgb * REFRACTION_MULTIPLIER;
#endif
// base color changes the amount of light passing through the boundary
Ft *= pixel.diffuseColor;
// fresnel from the first interface
Ft *= 1.0 - E;
// apply absorption
#if defined(MATERIAL_HAS_ABSORPTION)
Ft *= T;
#endif
Fr *= iblLuminance;
Fd *= iblLuminance;
color.rgb += Fr + lerp(Fd, Ft, pixel.transmission);
}
#endif
void combineDiffuseAndSpecular(const ShadingParams shading, const PixelParams pixel,
const float3 E, const float3 Fd, const float3 Fr,
inout float3 color) {
const float iblLuminance = 1.0; // Unknown
#if defined(HAS_REFRACTION)
applyRefraction(shading, pixel, E, Fd, Fr, color);
#else
color.rgb += (Fd + Fr) * iblLuminance;
#endif
}
void evaluateIBL(const ShadingParams shading, const MaterialInputs material, const PixelParams pixel,
inout float3 color) {
float ssao = 1.0; // Not implemented
float lightmapAO = 1.0; //
float3 tangentNormal = float3(0, 0, 1);
Light derivedLight = (Light)0;
// Gather Unity GI data
UnityGIInput unityData = InitialiseUnityGIInput(shading, pixel);
#if defined(MATERIAL_HAS_NORMAL)
tangentNormal = material.normal;
#endif
float3 unityIrradiance = UnityGI_Irradiance(shading, tangentNormal, lightmapAO, derivedLight);
float diffuseAO = min(material.ambientOcclusion, ssao);
float specularAO = computeSpecularAO(shading.NoV, diffuseAO*lightmapAO, pixel.roughness);
// specular layer
float3 Fr;
#if IBL_INTEGRATION == IBL_INTEGRATION_PREFILTERED_CUBEMAP
float3 E = specularDFG(pixel);
float3 r = getReflectedVector(shading, pixel, shading.normal);
Fr = E * UnityGI_prefilteredRadiance(unityData, pixel.perceptualRoughness, r);
#elif IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING
// Not supported
float3 E = float3(0.0); // TODO: fix for importance sampling
Fr = isEvaluateSpecularIBL(pixel, shading.normal, shading.view, shading.NoV);
#endif
// Gather LTCGI data, if present.
#if defined(_LTCGI)
float3 ltcDiffuse = 0;
float3 ltcSpecular = 0;
float ltcSpecularIntensity = 0;
LTCGI_Contribution(
shading.position,
shading.normal,
shading.view,
pixel.perceptualRoughness,
(shading.lightmapUV.xy - unity_LightmapST.zw) / unity_LightmapST.xy,
/* out */ ltcDiffuse,
/* out */ ltcSpecular,
/* out */ ltcSpecularIntensity
);
#endif
#if defined(_LTCGI)
Fr = lerp(Fr, E * ltcSpecular, saturate(ltcSpecularIntensity));
#endif
Fr *= singleBounceAO(specularAO) * pixel.energyCompensation;
// diffuse layer
float diffuseBRDF = singleBounceAO(diffuseAO); // Fd_Lambert() is baked in the SH below
evaluateClothIndirectDiffuseBRDF(shading, pixel, diffuseBRDF);
#if defined(MATERIAL_HAS_BENT_NORMAL)
float3 diffuseNormal = shading.bentNormal;
#else
float3 diffuseNormal = shading.normal;
#endif
#if IBL_INTEGRATION == IBL_INTEGRATION_PREFILTERED_CUBEMAP
//float3 diffuseIrradiance = get_diffuseIrradiance(diffuseNormal);
float3 diffuseIrradiance = unityIrradiance;
#elif IBL_INTEGRATION == IBL_INTEGRATION_IMPORTANCE_SAMPLING
float3 diffuseIrradiance = isEvaluateDiffuseIBL(pixel, diffuseNormal, shading.view);
#endif
#if defined(_LTCGI)
diffuseIrradiance += ltcDiffuse;
#endif
float3 Fd = pixel.diffuseColor * diffuseIrradiance * (1.0 - E) * diffuseBRDF;
// subsurface layer
evaluateSubsurfaceIBL(shading, pixel, diffuseIrradiance, Fd, Fr);
// extra ambient occlusion term for the base and subsurface layers
multiBounceAO(diffuseAO, pixel.diffuseColor, Fd);
multiBounceSpecularAO(specularAO, pixel.f0, Fr);
// sheen layer
evaluateSheenIBL(shading, pixel, diffuseAO, Fd, Fr);
// clear coat layer
evaluateClearCoatIBL(shading, pixel, diffuseAO, Fd, Fr);
// Note: iblLuminance is already premultiplied by the exposure
combineDiffuseAndSpecular(shading, pixel, E, Fd, Fr, color);
#if defined(LIGHTMAP_SPECULAR)
PixelParams pixelForBakedSpecular = pixel;
// remap roughness to clamp at max roughness without a hard clamp
pixelForBakedSpecular.roughness = remap_almostIdentity(pixelForBakedSpecular.roughness,
1-getLightmapSpecularMaxSmoothness(), 1-getLightmapSpecularMaxSmoothness()+MIN_ROUGHNESS);
if (derivedLight.NoL >= 0.0) color += surfaceShading(shading, pixelForBakedSpecular, derivedLight,
computeMicroShadowing(derivedLight.NoL, material.ambientOcclusion * 0.8 + 0.3));
#endif
}
#endif // FILAMENT_LIGHT_INDIRECT