// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt) #ifndef UNITY_GLOBAL_ILLUMINATION_INCLUDED #define UNITY_GLOBAL_ILLUMINATION_INCLUDED // Functions sampling light environment data (lightmaps, light probes, reflection probes), which is then returned as the UnityGI struct. #include "UnityImageBasedLighting.cginc" #include "UnityStandardUtils.cginc" #include "UnityShadowLibrary.cginc" inline half3 DecodeDirectionalSpecularLightmap (half3 color, half4 dirTex, half3 normalWorld, bool isRealtimeLightmap, fixed4 realtimeNormalTex, out UnityLight o_light) { o_light.color = color; o_light.dir = dirTex.xyz * 2 - 1; o_light.ndotl = 0; // Not use; // 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.dir)); o_light.dir /= 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.color /= max(0.125, dot(realtimeNormal, o_light.dir)); } #endif // Split light into the directional and ambient parts, according to the directionality factor. half3 ambient = o_light.color * (1 - directionality); o_light.color = o_light.color * directionality; // Technically this is incorrect, but helps hide jagged light edge at the object silhouettes and // makes normalmaps show up. ambient *= saturate(dot(normalWorld, o_light.dir)); return ambient; } inline void ResetUnityLight(out UnityLight outLight) { outLight.color = half3(0, 0, 0); outLight.dir = half3(0, 1, 0); // Irrelevant direction, just not null outLight.ndotl = 0; // Not used } inline half3 SubtractMainLightWithRealtimeAttenuationFromLightmap (half3 lightmap, half attenuation, half4 bakedColorTex, half3 normalWorld) { // Let's try to make realtime shadows work on a surface, which already contains // baked lighting and shadowing from the main sun light. half3 shadowColor = unity_ShadowColor.rgb; half shadowStrength = _LightShadowData.x; // Summary: // 1) Calculate possible value in the shadow by subtracting estimated light contribution from the places occluded by realtime shadow: // a) preserves other baked lights and light bounces // b) eliminates shadows on the geometry facing away from the light // 2) Clamp against user defined ShadowColor. // 3) Pick original lightmap value, if it is the darkest one. // 1) Gives good estimate of illumination as if light would've been shadowed during the bake. // Preserves bounce and other baked lights // No shadows on the geometry facing away from the light half ndotl = LambertTerm (normalWorld, _WorldSpaceLightPos0.xyz); half3 estimatedLightContributionMaskedByInverseOfShadow = ndotl * (1- attenuation) * _LightColor0.rgb; half3 subtractedLightmap = lightmap - estimatedLightContributionMaskedByInverseOfShadow; // 2) Allows user to define overall ambient of the scene and control situation when realtime shadow becomes too dark. half3 realtimeShadow = max(subtractedLightmap, shadowColor); realtimeShadow = lerp(realtimeShadow, lightmap, shadowStrength); // 3) Pick darkest color return min(lightmap, realtimeShadow); } inline void ResetUnityGI(out UnityGI outGI) { ResetUnityLight(outGI.light); outGI.indirect.diffuse = 0; outGI.indirect.specular = 0; } inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld) { UnityGI o_gi; ResetUnityGI(o_gi); // Base pass with Lightmap support is responsible for handling ShadowMask / blending here for performance reason #if defined(HANDLE_SHADOWS_BLENDING_IN_GI) half bakedAtten = UnitySampleBakedOcclusion(data.lightmapUV.xy, data.worldPos); float zDist = dot(_WorldSpaceCameraPos - data.worldPos, UNITY_MATRIX_V[2].xyz); float fadeDist = UnityComputeShadowFadeDistance(data.worldPos, zDist); data.atten = UnityMixRealtimeAndBakedShadows(data.atten, bakedAtten, UnityComputeShadowFade(fadeDist)); #endif o_gi.light = data.light; o_gi.light.color *= data.atten; #if UNITY_SHOULD_SAMPLE_SH o_gi.indirect.diffuse = ShadeSHPerPixel(normalWorld, data.ambient, data.worldPos); #endif #if defined(LIGHTMAP_ON) // Baked lightmaps half4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy); half3 bakedColor = DecodeLightmap(bakedColorTex); #ifdef DIRLIGHTMAP_COMBINED fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy); o_gi.indirect.diffuse += DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld); #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN) ResetUnityLight(o_gi.light); o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap (o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld); #endif #else // not directional lightmap o_gi.indirect.diffuse += bakedColor; #if defined(LIGHTMAP_SHADOW_MIXING) && !defined(SHADOWS_SHADOWMASK) && defined(SHADOWS_SCREEN) ResetUnityLight(o_gi.light); o_gi.indirect.diffuse = SubtractMainLightWithRealtimeAttenuationFromLightmap(o_gi.indirect.diffuse, data.atten, bakedColorTex, normalWorld); #endif #endif #endif #ifdef DYNAMICLIGHTMAP_ON // Dynamic lightmaps fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw); half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex); #ifdef DIRLIGHTMAP_COMBINED half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw); o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld); #else o_gi.indirect.diffuse += realtimeColor; #endif #endif o_gi.indirect.diffuse *= occlusion; return o_gi; } inline half3 UnityGI_IndirectSpecular(UnityGIInput data, half occlusion, Unity_GlossyEnvironmentData glossIn) { half3 specular; #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 (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 (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 * occlusion; } // Deprecated old prototype but can't be move to Deprecated.cginc file due to order dependency inline half3 UnityGI_IndirectSpecular(UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn) { // normalWorld is not used return UnityGI_IndirectSpecular(data, occlusion, glossIn); } inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld) { return UnityGI_Base(data, occlusion, normalWorld); } inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld, Unity_GlossyEnvironmentData glossIn) { UnityGI o_gi = UnityGI_Base(data, occlusion, normalWorld); o_gi.indirect.specular = UnityGI_IndirectSpecular(data, occlusion, glossIn); return o_gi; } // // Old UnityGlobalIllumination signatures. Kept only for backward compatibility and will be removed soon // inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half smoothness, half3 normalWorld, bool reflections) { if(reflections) { Unity_GlossyEnvironmentData g = UnityGlossyEnvironmentSetup(smoothness, data.worldViewDir, normalWorld, float3(0, 0, 0)); return UnityGlobalIllumination(data, occlusion, normalWorld, g); } else { return UnityGlobalIllumination(data, occlusion, normalWorld); } } inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half smoothness, half3 normalWorld) { #if defined(UNITY_PASS_DEFERRED) && UNITY_ENABLE_REFLECTION_BUFFERS // No need to sample reflection probes during deferred G-buffer pass bool sampleReflections = false; #else bool sampleReflections = true; #endif return UnityGlobalIllumination (data, occlusion, smoothness, normalWorld, sampleReflections); } #endif