#ifndef __LIGHTING_INC #define __LIGHTING_INC #include "UnityCG.cginc" #include "AutoLight.cginc" #include "UnityPBSLighting.cginc" #include "UnityLightingCommon.cginc" #include "UnityStandardCoreMinimal.cginc" #include "burley.cginc" #include "data.cginc" #include "features.cginc" #include "filamented.cginc" #include "interpolators.cginc" #include "LightVolumes.cginc" #include "glitter.cginc" #include "poi.cginc" float3 getDirectLightDirection(v2f i) { #if defined(POINT) || defined(POINT_COOKIE) || defined(SPOT) return normalize((_WorldSpaceLightPos0 - i.worldPos).xyz); #else return _WorldSpaceLightPos0; #endif } float getShadowAttenuation(v2f i) { UNITY_LIGHT_ATTENUATION(attenuation, i, i.worldPos); return attenuation; } float4 getDirectLightColorIntensity() { // Properly separate light color from intensity like filamented if (_LightColor0.w <= 0) return float4(0, 0, 0, 0); return float4(_LightColor0.xyz, _LightColor0.w); } float3 getIndirectSpecular(v2f i, float perceptual_roughness, float3 view_dir, float3 reflect_dir, float3 indirect_diffuse) { UnityGIInput data = InitialiseUnityGIInput(i.worldPos, view_dir); float3 env_refl = UnityGI_prefilteredRadiance(data, perceptual_roughness, reflect_dir); return env_refl; } float3 getAverageSHDirection(float3 L1r, float3 L1g, float3 L1b, float3 fallback_dir) { float3 raw_dir = L1r + L1g + L1b; float raw_dir_len = length(raw_dir); if (abs(raw_dir_len) < 1e-3) { return fallback_dir; } return raw_dir / raw_dir_len; } // Geomerics SH evaluation // https://community.arm.com/cfs-file/__key/telligent-evolution-components-attachments/01-2066-00-00-00-01-27-70/Simplifying_2D00_Spherical_2D00_Harmonics_2D00_for_2D00_Lighting.pdf float shEvaluateDiffuseL1Geomerics(float L0, float3 L1, float3 n) { // average energy 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); // 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 yumSH9(float4 n, float3 worldPos, inout LightIndirect light) { [branch] if (_UdonLightVolumeEnabled) { LightVolumeSH(worldPos, light.L00, light.L01r, light.L01g, light.L01b); return light.L00 + float3( dot(light.L01r, n.xyz), dot(light.L01g, n.xyz), dot(light.L01b, n.xyz)); } // Unity gives us the first three bands (L0-L2) of SH coefficients as follows: // unity_SHA*.w: L0 coefficients // unity_SHA*.xyz: L1 coefficients // unity_SHB*: first four of the L2 coefficients // unity_SHC: last L2 coefficient // Equation 13 from "An Efficient Representation for Irradiance Environment // Maps" by Ramamoorthi and Hanrahan. Normalization constants have been // premultiplied by Unity into the coefficient buffers. // // L0+L1: dot4 per channel (n.w=1 picks up the L0 term from SHA*.w) // L2: four quadratic terms packed into vB via swizzle multiply, plus L22 float3 L0 = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w); float3 L1 = float3(dot(unity_SHAr, n.xyz), dot(unity_SHAg, n.xyz), dot(unity_SHAb, n.xyz)); float4 vB = n.xyzz * n.yzzx; float3 L2 = float3(dot(unity_SHBr, vB), dot(unity_SHBg, vB), dot(unity_SHBb, vB)) + unity_SHC * (n.x * n.x - n.y * n.y); #if defined(_WRAPPED_LIGHTING) // Original coefficients: 1, 2/3, 1/4. // Wrapped coefficients: 1, (2-w)/3, ((1-w)^2)/4. // Setting w=0, the l1 band is: // (2-w)/3 = 2/3 // 2-w = 2 // 1-w/2 = 1 float wrap_amount = _Wrapped_Lighting_Amount; float l1_wrap = 1.0f - wrap_amount * 0.75f; L1 *= l1_wrap; // The l2 band is: // ((1-w)^2)/4 = 1/4 // (1-w)^2 = 1 float l2_wrap = (1.0f-wrap_amount); l2_wrap *= l2_wrap; L2 *= l2_wrap; #else float l1_wrap = 1.0f; #endif // _WRAPPED_LIGHTING light.L00 = L0; light.L01r = unity_SHAr.xyz; light.L01g = unity_SHAg.xyz; light.L01b = unity_SHAb.xyz; return L0 + L1 + L2; } float4 getIndirectDiffuse(v2f i, Pbr pbr, inout LightData light) { float4 diffuse = 0; #if defined(FORWARD_BASE_PASS) || defined(OUTLINES_PASS) #if defined(_BENT_NORMALS) diffuse.xyz += max(0, yumSH9(float4(pbr.bent_normal, 1.0), i.worldPos, light.indirect)); #else diffuse.xyz += max(0, yumSH9(float4(i.normal, 1.0), i.worldPos, light.indirect)); #endif #endif #if defined(_SHADOWS) || defined(_SSFD) float3 dom_dir = getAverageSHDirection( light.indirect.L01r, light.indirect.L01g, light.indirect.L01b, light.direct.dir); light.indirect.diffuse_dominant_dir = dom_dir; #endif #if defined(_SHADOWS) float light_amount = dot(dom_dir, pbr.normal); float3 shadow_color = lerp( _Shadow_0_Color.rgb, 1, smoothstep(_Shadow_0_Threshold - _Shadow_0_Blur, _Shadow_0_Threshold + _Shadow_0_Blur, light_amount)); #if defined(_SHADOW_1) shadow_color = lerp( _Shadow_1_Color.rgb, shadow_color, smoothstep(_Shadow_1_Threshold - _Shadow_1_Blur, _Shadow_1_Threshold + _Shadow_1_Blur, light_amount)); #endif // _SHADOW_1 diffuse.xyz *= shadow_color; #endif // _SHADOWS return diffuse; } float getAO(v2f i) { float ao = 1; #if defined(_BURLEY_TILING) && defined(_BURLEY_TILING_AMBIENT_OCCLUSION) ao = saturate(lerp( 1.0, burley_sample_scalar( _Burley_Tiling_Ambient_Occlusion_Map, _Burley_Tiling_Ambient_Occlusion_Map_LUT), _OcclusionStrength)); #elif defined(_AMBIENT_OCCLUSION) ao = saturate(lerp(1.0, _OcclusionMap.Sample(bilinear_repeat_s, i.uv01.xy).r, _OcclusionStrength)); #endif return ao; } float getSpecularAO(v2f i, Pbr pbr, LightData data, float3 reflect_dir) { float ao_vis = 1.0; #if defined(_AMBIENT_OCCLUSION) ao_vis = data.common.ao; #endif // Exposure occlusion: derive specular AO from diffuse irradiance magnitude. // When IBL diffuse goes dark, attenuate specular to avoid implausible // reflections. Based on filamented's IrradianceToExposureOcclusion. float exposure_ao = saturate(length(data.indirect.diffuse) / _Exposure_Occlusion); ao_vis *= exposure_ao; #if defined(_BENT_NORMALS) float3 spec_ao_normal = pbr.bent_normal; #else float3 spec_ao_normal = pbr.normal; #endif float spec_ao = computeSpecularAO(data.common.NoV, ao_vis, pbr.roughness, spec_ao_normal, -data.indirect.dir); #if defined(_BENT_NORMALS) spec_ao = saturate(lerp(1.0, spec_ao, _Bent_Normals_Strength)); #endif return spec_ao; } void GetLighting(v2f i, Pbr pbr, out LightData data) { data = (LightData) 0; data.common.ao = 1.0f; data.common.spec_ao = 1.0f; float3 view_dir = normalize(i.eyeVec.xyz); data.common.V = -view_dir; data.common.N = pbr.normal; data.common.NoV = max(1e-4, dot(pbr.normal, data.common.V)); data.common.ao = getAO(i); #if defined(_CLEARCOAT) data.common.NoV_cc = max(1e-4, dot(pbr.cc_normal, data.common.V)); #endif // Direct lighting data.direct.dir = getDirectLightDirection(i); data.direct.H = normalize(data.common.V + data.direct.dir); #if defined(_WRAPPED_LIGHTING) data.direct.NoL = max(1e-4, wrapNoL(saturate(dot(pbr.normal, data.direct.dir)), _Wrapped_Lighting_Amount)); #else data.direct.NoL = max(1e-4, dot(pbr.normal, data.direct.dir)); #endif data.direct.NoH = max(1e-4, dot(pbr.normal, data.direct.H)); data.direct.LoH = max(1e-4, dot(data.direct.dir, data.direct.H)); #if defined(_CLEARCOAT) data.direct.NoH_cc = max(1e-4, dot(pbr.cc_normal, data.direct.H)); data.direct.NoL_cc = max(1e-4, dot(pbr.cc_normal, data.direct.dir)); #endif float direct_LoV = dot(data.direct.dir, data.common.V); data.direct.LoV = max(1e-4, direct_LoV); data.direct.double_LoV = max(1e-4, 2.0f * direct_LoV * direct_LoV - 1.0f); float4 lightColorIntensity = getDirectLightColorIntensity(); data.direct.color = lightColorIntensity.rgb * (lightColorIntensity.w * getShadowAttenuation(i)); // Indirect lighting float3 reflect_dir = reflect(-data.common.V, pbr.normal); float3 dominant_dir = getSpecularDominantDirection(pbr.normal, reflect_dir, pbr.roughness); data.indirect.dir = normalize(dominant_dir); data.indirect.H = normalize(data.common.V + data.indirect.dir); data.indirect.NoL = max(1e-4, dot(pbr.normal, data.indirect.dir)); data.indirect.NoH = max(1e-4, dot(pbr.normal, data.indirect.H)); #if defined(_CLEARCOAT) float3 cc_reflect_dir = reflect(-data.common.V, pbr.cc_normal); float3 cc_dominant_dir = getSpecularDominantDirection(pbr.cc_normal, cc_reflect_dir, pbr.cc_roughness); float3 dir_cc = normalize(cc_dominant_dir); float3 H_cc = normalize(data.common.V + dir_cc); #endif data.indirect.LoH = max(1e-4, dot(data.indirect.dir, data.indirect.H)); float indirect_LoV = dot(data.indirect.dir, data.common.V); data.indirect.LoV = max(1e-4, indirect_LoV); data.indirect.double_LoV = max(1e-4, 2.0f * indirect_LoV * indirect_LoV - 1.0f); data.indirect.diffuse = getIndirectDiffuse(i, pbr, data); data.indirect.specular = getIndirectSpecular(i, pbr.roughness_perceptual, view_dir, data.indirect.dir, data.indirect.diffuse); #if defined(_GLITTER) float3 glitter_indirect_dir = getAverageSHDirection( data.indirect.L01r, data.indirect.L01g, data.indirect.L01b, data.indirect.dir); #if defined(_GLITTER_BASE_ROUGHNESS_OVERRIDE) float glitter_roughness = _Glitter_Base_Roughness_Override; #else float glitter_roughness = pbr.roughness; #endif data.glitter = GetGlitterLighting( _Glitter_Amount, _Glitter_Roughness, _Glitter_Angular_Cells, _Glitter_Filter_Size, UV_SCOFF(i, _Glitter_Mask_ST, _Glitter_UV_Channel), pbr.tbn, glitter_roughness, pbr.normal, data.common.V, data.direct.H, glitter_indirect_dir); #endif data.common.spec_ao = getSpecularAO(i, pbr, data, reflect_dir); #if defined(_CLEARCOAT) data.indirect.specular_cc = getIndirectSpecular(i, pbr.cc_roughness_perceptual, view_dir, dir_cc, data.indirect.diffuse); #if defined(_CLEARCOAT_MASK) float cc_mask = _Clearcoat_Mask.Sample(bilinear_clamp_s, i.uv01.xy).r; data.indirect.specular_cc *= cc_mask; #endif #endif #if defined(_BRIGHTNESS_CLAMP) float3 tmpHSV = RGBtoHSV(data.direct.color); tmpHSV[2] = clamp(tmpHSV[2], _Brightness_Clamp_Min, _Brightness_Clamp_Max); data.direct.color = HSVtoRGB(tmpHSV); tmpHSV = RGBtoHSV(data.indirect.diffuse); tmpHSV[2] = clamp(tmpHSV[2], _Brightness_Clamp_Min, _Brightness_Clamp_Max); data.indirect.diffuse = HSVtoRGB(tmpHSV); // No minimum for specular lighting. It would look awful. tmpHSV = RGBtoHSV(data.indirect.specular); tmpHSV[2] = clamp(tmpHSV[2], 0, _Brightness_Clamp_Max); data.indirect.specular = HSVtoRGB(tmpHSV); #if defined(_GLITTER) tmpHSV = RGBtoHSV(data.indirect.L00); tmpHSV[2] = clamp(tmpHSV[2], 0, _Brightness_Clamp_Max); data.indirect.L00 = HSVtoRGB(tmpHSV); #endif #if defined(_CLEARCOAT) tmpHSV = RGBtoHSV(data.indirect.specular_cc); tmpHSV[2] = clamp(tmpHSV[2], 0, _Brightness_Clamp_Max); data.indirect.specular_cc = HSVtoRGB(tmpHSV); #endif #endif } #endif // __LIGHTING_INC