#ifndef CUSTOM_LIGHTING_INCLUDED #define CUSTOM_LIGHTING_INCLUDED // @Cyanilux | https://github.com/Cyanilux/URP_ShaderGraphCustomLighting // Note this version of the package assumes v12+ due to usage of "Branch on Input Connection" node // For older versions, see branches on github repo! //------------------------------------------------------------------------------------------------------ // Main Light //------------------------------------------------------------------------------------------------------ /* - Obtains the Direction, Color and Distance Atten for the Main Light. - (DistanceAtten is either 0 or 1 for directional light, depending if the light is in the culling mask or not) - If you want shadow attenutation, see MainLightShadows_float, or use MainLightFull_float instead */ void MainLight_float (out float3 Direction, out float3 Color, out float DistanceAtten){ #ifdef SHADERGRAPH_PREVIEW Direction = normalize(float3(1,1,-0.4)); Color = float4(1,1,1,1); DistanceAtten = 1; #else Light mainLight = GetMainLight(); Direction = mainLight.direction; Color = mainLight.color; DistanceAtten = mainLight.distanceAttenuation; #endif } //------------------------------------------------------------------------------------------------------ // Main Light Layer Test //------------------------------------------------------------------------------------------------------ #ifndef SHADERGRAPH_PREVIEW #if UNITY_VERSION < 202220 /* GetMeshRenderingLayer() is only available in 2022.2+ Previous versions need to use GetMeshRenderingLightLayer() */ uint GetMeshRenderingLayer(){ return GetMeshRenderingLightLayer(); } #endif #endif /* - Tests whether the Main Light Layer Mask appears in the Rendering Layers from renderer - (Used to support Light Layers, pass your shading from Main Light into this) - To work in an Unlit Graph, requires keywords : - Boolean Keyword, Global Multi-Compile "_LIGHT_LAYERS" */ void MainLightLayer_float(float3 Shading, out float3 Out){ #ifdef SHADERGRAPH_PREVIEW Out = Shading; #else Out = 0; uint meshRenderingLayers = GetMeshRenderingLayer(); #ifdef _LIGHT_LAYERS if (IsMatchingLightLayer(GetMainLight().layerMask, meshRenderingLayers)) #endif { Out = Shading; } #endif } /* - Obtains the Light Cookie assigned to the Main Light - (For usage, You'd want to Multiply the result with your Light Colour) - To work in an Unlit Graph, requires keywords : - Boolean Keyword, Global Multi-Compile "_LIGHT_COOKIES" */ void MainLightCookie_float(float3 WorldPos, out float3 Cookie){ Cookie = 1; #if defined(_LIGHT_COOKIES) Cookie = SampleMainLightCookie(WorldPos); #endif } //------------------------------------------------------------------------------------------------------ // Main Light Shadows //------------------------------------------------------------------------------------------------------ /* - This undef (un-define) is required to prevent the "invalid subscript 'shadowCoord'" error, which occurs when _MAIN_LIGHT_SHADOWS is used with 1/No Shadow Cascades with the Unlit Graph. - It's not required for the PBR/Lit graph, so I'm using the SHADERPASS_FORWARD to ignore it for that pass */ #ifndef SHADERGRAPH_PREVIEW #include "Packages/com.unity.render-pipelines.universal/Editor/ShaderGraph/Includes/ShaderPass.hlsl" #if (SHADERPASS != SHADERPASS_FORWARD) #undef REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR #endif #endif /* - Samples the Shadowmap for the Main Light, based on the World Position passed in. (Position node) - For shadows to work in the Unlit Graph, the following keywords must be defined in the blackboard : - Enum Keyword, Global Multi-Compile "_MAIN_LIGHT", with entries : - "SHADOWS" - "SHADOWS_CASCADE" - "SHADOWS_SCREEN" - Boolean Keyword, Global Multi-Compile "_SHADOWS_SOFT" - For a PBR/Lit Graph, these keywords are already handled for you. */ void MainLightShadows_float (float3 WorldPos, half4 Shadowmask, out float ShadowAtten){ #ifdef SHADERGRAPH_PREVIEW ShadowAtten = 1; #else #if defined(_MAIN_LIGHT_SHADOWS_SCREEN) && !defined(_SURFACE_TYPE_TRANSPARENT) float4 shadowCoord = ComputeScreenPos(TransformWorldToHClip(WorldPos)); #else float4 shadowCoord = TransformWorldToShadowCoord(WorldPos); #endif ShadowAtten = MainLightShadow(shadowCoord, WorldPos, Shadowmask, _MainLightOcclusionProbes); #endif } void MainLightShadows_float (float3 WorldPos, out float ShadowAtten){ MainLightShadows_float(WorldPos, half4(1,1,1,1), ShadowAtten); } //------------------------------------------------------------------------------------------------------ // Shadowmask (v10+) //------------------------------------------------------------------------------------------------------ /* - Used to support "Shadowmask" mode in Lighting window. - Should be sampled once in graph, then input into the Main Light Shadows and/or Additional Light subgraphs/functions. - To work in an Unlit Graph, likely requires keywords : - Boolean Keyword, Global Multi-Compile "SHADOWS_SHADOWMASK" - Boolean Keyword, Global Multi-Compile "LIGHTMAP_SHADOW_MIXING" - (also LIGHTMAP_ON, but I believe Shader Graph is already defining this one) */ void Shadowmask_half (float2 lightmapUV, out half4 Shadowmask){ #ifdef SHADERGRAPH_PREVIEW Shadowmask = half4(1,1,1,1); #else OUTPUT_LIGHTMAP_UV(lightmapUV, unity_LightmapST, lightmapUV); Shadowmask = SAMPLE_SHADOWMASK(lightmapUV); #endif } //------------------------------------------------------------------------------------------------------ // Ambient Lighting //------------------------------------------------------------------------------------------------------ /* - Uses "SampleSH", the spherical harmonic stuff that ambient lighting / light probes uses. - Will likely be used in the fragment, so will be per-pixel. - Alternatively could use the Baked GI node, as it'll also handle this for you. - Could also use the Ambient node, would be cheaper but the result won't automatically adapt based on the Environmental Lighting Source (Lighting tab). */ void AmbientSampleSH_float (float3 WorldNormal, out float3 Ambient){ #ifdef SHADERGRAPH_PREVIEW Ambient = float3(0.1, 0.1, 0.1); // Default ambient colour for previews #else Ambient = SampleSH(WorldNormal); #endif } //------------------------------------------------------------------------------------------------------ // Subtractive Baked GI //------------------------------------------------------------------------------------------------------ /* - Used to support "Subtractive" mode in Lighting window. - To work in an Unlit Graph, likely requires keywords : - Boolean Keyword, Global Multi-Compile "LIGHTMAP_SHADOW_MIXING" - (also LIGHTMAP_ON, but I believe Shader Graph is already defining this one) */ void SubtractiveGI_float (float ShadowAtten, float3 normalWS, float3 bakedGI, out half3 result){ #ifdef SHADERGRAPH_PREVIEW result = half3(1,1,1); #else Light mainLight = GetMainLight(); mainLight.shadowAttenuation = ShadowAtten; MixRealtimeAndBakedGI(mainLight, normalWS, bakedGI); result = bakedGI; #endif } //------------------------------------------------------------------------------------------------------ // Mix Fog //------------------------------------------------------------------------------------------------------ /* - Adds fog to the colour, based on the Fog settings in the Lighting tab. - Note : Not required for v12, can use Lerp instead. See "Mix Fog" SubGraph */ void MixFog_float (float3 Colour, float Fog, out float3 Out){ #ifdef SHADERGRAPH_PREVIEW Out = Colour; #else Out = MixFog(Colour, Fog); #endif } //------------------------------------------------------------------------------------------------------ // Default Additional Lights //------------------------------------------------------------------------------------------------------ /* - Handles additional lights (e.g. additional directional, point, spotlights) - For custom lighting, you may want to duplicate this and swap the LightingLambert / LightingSpecular functions out. See Toon Example below! - To work in the Unlit Graph, the following keywords must be defined in the blackboard : - Boolean Keyword, Global Multi-Compile "_ADDITIONAL_LIGHT_SHADOWS" - Boolean Keyword, Global Multi-Compile "_ADDITIONAL_LIGHTS" - To support Forward+ path, - Boolean Keyword, Global Multi-Compile "_FORWARD_PLUS" (2022.2+) */ void AdditionalLights_float(float3 SpecColor, float Smoothness, float3 WorldPosition, float3 WorldNormal, float3 WorldView, half4 Shadowmask, out float3 Diffuse, out float3 Specular) { float3 diffuseColor = 0; float3 specularColor = 0; #ifndef SHADERGRAPH_PREVIEW Smoothness = exp2(10 * Smoothness + 1); uint pixelLightCount = GetAdditionalLightsCount(); uint meshRenderingLayers = GetMeshRenderingLayer(); #if USE_FORWARD_PLUS for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++) { FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK Light light = GetAdditionalLight(lightIndex, WorldPosition, Shadowmask); #ifdef _LIGHT_LAYERS if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers)) #endif { // Blinn-Phong float3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation); diffuseColor += LightingLambert(attenuatedLightColor, light.direction, WorldNormal); specularColor += LightingSpecular(attenuatedLightColor, light.direction, WorldNormal, WorldView, float4(SpecColor, 0), Smoothness); } } #endif // For Foward+ the LIGHT_LOOP_BEGIN macro will use inputData.normalizedScreenSpaceUV, inputData.positionWS, so create that: InputData inputData = (InputData)0; float4 screenPos = ComputeScreenPos(TransformWorldToHClip(WorldPosition)); inputData.normalizedScreenSpaceUV = screenPos.xy / screenPos.w; inputData.positionWS = WorldPosition; LIGHT_LOOP_BEGIN(pixelLightCount) Light light = GetAdditionalLight(lightIndex, WorldPosition, Shadowmask); #ifdef _LIGHT_LAYERS if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers)) #endif { // Blinn-Phong float3 attenuatedLightColor = light.color * (light.distanceAttenuation * light.shadowAttenuation); diffuseColor += LightingLambert(attenuatedLightColor, light.direction, WorldNormal); specularColor += LightingSpecular(attenuatedLightColor, light.direction, WorldNormal, WorldView, float4(SpecColor, 0), Smoothness); } LIGHT_LOOP_END #endif Diffuse = diffuseColor; Specular = specularColor; } // For backwards compatibility (before Shadowmask was introduced) void AdditionalLights_float(float3 SpecColor, float Smoothness, float3 WorldPosition, float3 WorldNormal, float3 WorldView, out float3 Diffuse, out float3 Specular) { AdditionalLights_float(SpecColor, Smoothness, WorldPosition, WorldNormal, WorldView, half4(1,1,1,1), Diffuse, Specular); } //------------------------------------------------------------------------------------------------------ // Additional Lights Toon Example //------------------------------------------------------------------------------------------------------ /* - Calculates light attenuation values to produce multiple bands for a toon effect. See AdditionalLightsToon function below */ #ifndef SHADERGRAPH_PREVIEW float ToonAttenuation(int lightIndex, float3 positionWS, float pointBands, float spotBands){ #if !USE_FORWARD_PLUS lightIndex = GetPerObjectLightIndex(lightIndex); #endif #if USE_STRUCTURED_BUFFER_FOR_LIGHT_DATA float4 lightPositionWS = _AdditionalLightsBuffer[lightIndex].position; half4 spotDirection = _AdditionalLightsBuffer[lightIndex].spotDirection; half4 distanceAndSpotAttenuation = _AdditionalLightsBuffer[lightIndex].attenuation; #else float4 lightPositionWS = _AdditionalLightsPosition[lightIndex]; half4 spotDirection = _AdditionalLightsSpotDir[lightIndex]; half4 distanceAndSpotAttenuation = _AdditionalLightsAttenuation[lightIndex]; #endif // Point float3 lightVector = lightPositionWS.xyz - positionWS * lightPositionWS.w; float distanceSqr = max(dot(lightVector, lightVector), HALF_MIN); float range = rsqrt(distanceAndSpotAttenuation.x); float dist = sqrt(distanceSqr) / range; // Spot half3 lightDirection = half3(lightVector * rsqrt(distanceSqr)); half SdotL = dot(spotDirection.xyz, lightDirection); half spotAtten = saturate(SdotL * distanceAndSpotAttenuation.z + distanceAndSpotAttenuation.w); spotAtten *= spotAtten; float maskSpotToRange = step(dist, 1); // Atten bool isSpot = (distanceAndSpotAttenuation.z > 0); return isSpot ? //step(0.01, spotAtten) : // cheaper if you just want "1" band for spot lights (floor(spotAtten * spotBands) / spotBands) * maskSpotToRange : saturate(1.0 - floor(dist * pointBands) / pointBands); } #endif /* - Handles additional lights (e.g. point, spotlights) with banded toon effect - For shadows to work in the Unlit Graph, the following keywords must be defined in the blackboard : - Boolean Keyword, Global Multi-Compile "_ADDITIONAL_LIGHT_SHADOWS" - Boolean Keyword, Global Multi-Compile "_ADDITIONAL_LIGHTS" (required to prevent the one above from being stripped from builds) - For a PBR/Lit Graph, these keywords are already handled for you. */ void AdditionalLightsToon_float(float3 SpecColor, float Smoothness, float3 WorldPosition, float3 WorldNormal, float3 WorldView, half4 Shadowmask, float PointLightBands, float SpotLightBands, out float3 Diffuse, out float3 Specular) { float3 diffuseColor = 0; float3 specularColor = 0; #ifndef SHADERGRAPH_PREVIEW Smoothness = exp2(10 * Smoothness + 1); uint pixelLightCount = GetAdditionalLightsCount(); uint meshRenderingLayers = GetMeshRenderingLayer(); #if USE_FORWARD_PLUS for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++) { FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK Light light = GetAdditionalLight(lightIndex, WorldPosition, Shadowmask); #ifdef _LIGHT_LAYERS if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers)) #endif { if (PointLightBands <= 1 && SpotLightBands <= 1){ // Solid colour lights diffuseColor += light.color * step(0.0001, light.distanceAttenuation * light.shadowAttenuation); }else{ // Multiple bands diffuseColor += light.color * light.shadowAttenuation * ToonAttenuation(lightIndex, WorldPosition, PointLightBands, SpotLightBands); } } } #endif // For Foward+ the LIGHT_LOOP_BEGIN macro will use inputData.normalizedScreenSpaceUV, inputData.positionWS, so create that: InputData inputData = (InputData)0; float4 screenPos = ComputeScreenPos(TransformWorldToHClip(WorldPosition)); inputData.normalizedScreenSpaceUV = screenPos.xy / screenPos.w; inputData.positionWS = WorldPosition; LIGHT_LOOP_BEGIN(pixelLightCount) Light light = GetAdditionalLight(lightIndex, WorldPosition, Shadowmask); #ifdef _LIGHT_LAYERS if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers)) #endif { if (PointLightBands <= 1 && SpotLightBands <= 1){ // Solid colour lights diffuseColor += light.color * step(0.0001, light.distanceAttenuation * light.shadowAttenuation); }else{ // Multiple bands diffuseColor += light.color * light.shadowAttenuation * ToonAttenuation(lightIndex, WorldPosition, PointLightBands, SpotLightBands); } } LIGHT_LOOP_END #endif /* #ifndef SHADERGRAPH_PREVIEW Smoothness = exp2(10 * Smoothness + 1); WorldNormal = normalize(WorldNormal); WorldView = SafeNormalize(WorldView); int pixelLightCount = GetAdditionalLightsCount(); for (int i = 0; i < pixelLightCount; ++i) { Light light = GetAdditionalLight(i, WorldPosition, Shadowmask); // DIFFUSE if (PointLightBands <= 1 && SpotLightBands <= 1){ // Solid colour lights diffuseColor += light.color * step(0.0001, light.distanceAttenuation * light.shadowAttenuation); }else{ // Multiple bands : diffuseColor += light.color * light.shadowAttenuation * ToonAttenuation(i, WorldPosition, PointLightBands, SpotLightBands); } } #endif */ Diffuse = diffuseColor; Specular = specularColor; // Didn't really like the look of specular lighting in the toon shader here, so just keeping it at 0 } // For backwards compatibility (before Shadowmask was introduced) void AdditionalLightsToon_float(float3 SpecColor, float Smoothness, float3 WorldPosition, float3 WorldNormal, float3 WorldView, float PointLightBands, float SpotLightBands, out float3 Diffuse, out float3 Specular) { AdditionalLightsToon_float(SpecColor, Smoothness, WorldPosition, WorldNormal, WorldView, half4(1,1,1,1), PointLightBands, SpotLightBands,Diffuse, Specular); } #endif // CUSTOM_LIGHTING_INCLUDED