Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Join us on Thursday, June 8, for a Q&A with Unity's Content Pipeline group here on the forum, and on the Unity Discord, and discuss topics around Content Build, Import Workflows, Asset Database, and Addressables!
    Dismiss Notice

Question Forward+ not working with custom shader

Discussion in 'Universal Render Pipeline' started by Lazy_Evaluation, Mar 21, 2023.

  1. Lazy_Evaluation

    Lazy_Evaluation

    Joined:
    Nov 16, 2019
    Posts:
    14
    Hello! I'm building a game with a toon graphic style which requires many lights to be displayed, more than 8 per object (so I can't use neither deferred nor Forward rendering). I updated Unity to 2022.2.10f1 and URP to 14.0.6, and set the rendering setting to Forward+. I had to adapt my hlsl code to make it work with Forward+, and I modified NedMakesGame's Lighting.hlsl script (from a toon tutorial video) to make it work with Forward+:
    Code (CSharp):
    1. #ifndef CUSTOM_LIGHTING_INCLUDED
    2. #define CUSTOM_LIGHTING_INCLUDED
    3.  
    4.  
    5. float GetLightIntensity(float3 color)
    6. {
    7.     return max(color.r, max(color.g, color.b));
    8. }
    9.  
    10. void CalculateMainLight_float(float3 WorldPos, out float3 Direction, out float3 Color, out half DistanceAtten, out half ShadowAtten, out float Intensity)
    11. {
    12. #ifdef SHADERGRAPH_PREVIEW
    13.     Direction = half3(0.5, 0.5, 0);
    14.     Color = 1;
    15.     Intensity = 0;
    16.     DistanceAtten = 1;
    17.     ShadowAtten = 1;
    18. #else
    19. #if SHADOWS_SCREEN
    20.     half4 clipPos = TransformWorldToHClip(WorldPos);
    21.     half4 shadowCoord = ComputeScreenPos(clipPos);
    22. #else
    23.     half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
    24. #endif
    25.     Light mainLight = GetMainLight(shadowCoord);
    26.     Direction = mainLight.direction;
    27.     Color = mainLight.color;
    28.     DistanceAtten = mainLight.distanceAttenuation;
    29.     ShadowAtten = mainLight.shadowAttenuation;
    30.     Intensity = GetLightIntensity(mainLight.color);
    31. #endif
    32. }
    33.  
    34.  
    35. #ifndef SHADERGRAPH_PREVIEW
    36. // This function gets additional light data and calculates realtime shadows
    37. Light GetAdditionalLightForToon(int pixelLightIndex, float3 worldPosition)
    38. {
    39.     // Convert the pixel light index to the light data index
    40.     int perObjectLightIndex = GetPerObjectLightIndex(pixelLightIndex);
    41.     // Call the URP additional light algorithm. This will not calculate shadows, since we don't pass a shadow mask value
    42.     Light light = GetAdditionalPerObjectLight(perObjectLightIndex, worldPosition);
    43.     // Manually set the shadow attenuation by calculating realtime shadows
    44.     light.shadowAttenuation = AdditionalLightRealtimeShadow(perObjectLightIndex, worldPosition);
    45.     return light;
    46. }
    47. #endif
    48. void AddAdditionalLights_float(float Smoothness, float3 WorldPosition, float3 WorldNormal, float3 WorldView,
    49.     float MainDiffuse, float MainSpecular, float3 MainColor, float3 ScreenPosition,
    50.     out float Diffuse, out float Specular, out float3 Color)
    51. {
    52.  
    53.     float mainIntensity = GetLightIntensity(MainColor);
    54.     Diffuse = 0;
    55.     Specular = 0;
    56.     Color = MainColor;
    57.  
    58.  
    59.     float totalDiffuse = 0; // Aggiunto per accumulare i valori di thisDiffuse
    60.  
    61. #ifndef SHADERGRAPH_PREVIEW
    62.     InputData inputData = (InputData) 0;
    63.     float highestDiffuse = Diffuse;
    64.     float maxIntensity = 0;
    65.     float intensity = 0;
    66.     uint meshRenderingLayers = GetMeshRenderingLayer();
    67.  
    68.     inputData.normalizedScreenSpaceUV = ScreenPosition;
    69.     inputData.positionWS = WorldPosition;
    70.     uint lightsCount = GetAdditionalLightsCount();
    71.     LIGHT_LOOP_BEGIN(lightsCount)
    72.     Light light = GetAdditionalLight(lightIndex, WorldPosition);
    73.     #ifdef _LIGHT_LAYERS
    74.         if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
    75. #endif
    76.     {
    77.         half NdotL = saturate(dot(WorldNormal, light.direction));
    78.         half atten = light.distanceAttenuation * light.shadowAttenuation * GetLightIntensity(light.color);
    79.         half thisDiffuse = atten * NdotL;
    80.         half thisSpecular = LightingSpecular(thisDiffuse, light.direction, WorldNormal, WorldView, 1, Smoothness);
    81.         Diffuse += thisDiffuse;
    82.         Specular += thisSpecular;
    83.         intensity = GetLightIntensity(light.color);
    84.  
    85.         if (thisDiffuse > 0)
    86.         {
    87.             if (intensity > maxIntensity)
    88.             {
    89.                 maxIntensity = intensity;
    90.                 Color = light.color;
    91.             }
    92.         }
    93.     }
    94.         LIGHT_LOOP_END
    95. #endif
    96.     }
    97. #endif
    98.  
    As you can see I'm using the LIGHT_LOOP system and I changed the GetAdditionalPerObjectLight function, and it works. When there are few objects and lights.
    If I have many objects and many lights, the game crashes with this error:

    This is a big problem and it should mean that the GPU is somehow under great stress, but switching to Forward or Deferred rendering, with the same amount of objects and lights in the same scene, doesn't make the game crash. So I find a bit weird that a feature that has been literally implemented to handle more lights can't even handle as many lights as Forward or Deferred do. I guess I'm doing something wrong with the code...
    In small scenes with few objects, the Forward+ works correctly even with 20/30 lights, as it should. The scene where the game crashes has thousand of objects (not all of them are nearby a light, I'd say that there are about 30-40 objects inside the lights ranges) and about 4-8 lights.
    Could somebody help? My GPU drivers are updated and I'm using a RX480 4GB card.
     
    Last edited: Mar 21, 2023
  2. wwWwwwW1

    wwWwwwW1

    Joined:
    Oct 31, 2021
    Posts:
    522
    Will this issue still happen if switching to URP's default shader rather than custom shader graph?

    If yes, I think you can submit a bug report with a reproduceable project.
     
  3. Lazy_Evaluation

    Lazy_Evaluation

    Joined:
    Nov 16, 2019
    Posts:
    14
    No, it doesn't.
    Digging more, I managed to find what could be the problem: the LIGHT_LOOP might not be supported on Forward+.
    I changed the code to have this:

    Code (CSharp):
    1. #if USE_FORWARD_PLUS
    2.     for(uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
    3.     {
    4.     Light light = GetAdditionalLight(lightIndex, WorldPosition);
    5.         half NdotL = max(0, dot(WorldNormal, light.direction));
    6.         half atten = light.distanceAttenuation * light.shadowAttenuation * GetLightIntensity(light.color);
    7.         half thisDiffuse = atten * NdotL;
    8.         thisSpecular += LightingSpecular(thisDiffuse, light.direction, WorldNormal, WorldView, 1, Smoothness);
    9.         Diffuse += thisDiffuse;
    10.  
    11.         Color = (thisDiffuse > 0 && GetLightIntensity(light.color) > maxIntensity) ? light.color : Color;
    12.         maxIntensity = (thisDiffuse > 0 && GetLightIntensity(light.color) > maxIntensity) ? GetLightIntensity(light.color) : maxIntensity;
    13.     }
    14. #endif
    Now, the game doesn't crash. But the problem is that there's no light shown, because the loop takes a minimum between MAX_VISIBLE_LIGHTS and URP_FP_DIRECTIONAL_LIGHTS_COUNT, which should be 1 with only one directional light. If I, for example, set a static number instead of URP_FP_DIRECTIONAL_LIGHTS_COUNT, the lights are shown correctly and the game doesn't crash, but for obvious reasons everything is a big buggy because the number will most likely be greater than the lights count in my scene, resulting in the lights staying visible even when they are not enabled.

    The solution to this problem seems now to be finding the total additional lights count, so I can place it in the loop condition. GetAdditionalLightsCount(); doesn't work with Forward+. Any alternative? Also looked for URP_FP_LIGHTS_COUNT but it doesn't exist :-(
     
  4. wwWwwwW1

    wwWwwwW1

    Joined:
    Oct 31, 2021
    Posts:
    522
    Then it should be a custom shader issue, you can refer to how Lit shader calculates lighting in Forward+ in "URP-Package/ShaderLibrary/Lighting.hlsl" (link is for 2023.2a).

    Example from the link:
    Code (CSharp):
    1.     #if defined(_ADDITIONAL_LIGHTS)
    2.     uint pixelLightCount = GetAdditionalLightsCount();
    3.  
    4.     #if USE_FORWARD_PLUS
    5.     for (uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
    6.     {
    7.         FORWARD_PLUS_SUBTRACTIVE_LIGHT_CHECK
    8.  
    9.         Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor);
    10.  
    11. #ifdef _LIGHT_LAYERS
    12.         if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
    13. #endif
    14.         {
    15.             // Do custom lighting here.
    16.         }
    17.     }
    18.     #endif
    19.  
    20.     LIGHT_LOOP_BEGIN(pixelLightCount)
    21.         Light light = GetAdditionalLight(lightIndex, inputData, shadowMask, aoFactor);
    22.  
    23. #ifdef _LIGHT_LAYERS
    24.         if (IsMatchingLightLayer(light.layerMask, meshRenderingLayers))
    25. #endif
    26.         {
    27.             // Repeat the same custom lighting here.
    28.         }
    29.     LIGHT_LOOP_END
    30.     #endif
    It's quite confusing and I'm also looking forward to the SRP shader documentation improvements.
     
  5. Lazy_Evaluation

    Lazy_Evaluation

    Joined:
    Nov 16, 2019
    Posts:
    14

    It's the code which I've taken as an example for my shader. The only difference that I do is that I don't use the four argument GetAdditionalLight method because I can't have InputData and SurfaceData completely loaded without passing about 20 variables in my shader graph and spending hours setting them up, creating a huge spaghetti code. The rest, is pretty much the same. The LIGHT_LOOP works, in small scenes, but in the game it crashes with a D3D11 error as another post (without solution) in the forum reports. The for loop, doesn't crash, but until I find a way to count all the lights in a scene I can't use it.
     
  6. wwWwwwW1

    wwWwwwW1

    Joined:
    Oct 31, 2021
    Posts:
    522
    You can replace it with the one that doesn't handle shadows.

    What I usually do to solve an unknown shader problem (for me) is to make it work first (maybe from an example) and then clean it up until I found out where the problem is.

    You can also try reporting this as a bug and they should point out what went wrong later as long as it's confirmed by QA.
     
  7. ZbigniewBrzozowski

    ZbigniewBrzozowski

    Joined:
    Jan 26, 2019
    Posts:
    4
    Hello. Did you find solution to your problem? I'm noob when it comes to coding shaders - could you please give me some tutorials to match Unity Toon Shader with Forward+ ? I already research NedMakesGame channel thanks to you. Thank you.
     
  8. Lazy_Evaluation

    Lazy_Evaluation

    Joined:
    Nov 16, 2019
    Posts:
    14
    Yes! First, you have to add _FORWARD_PLUS as a boolean keyword, like every other special keyword used in the shader. Be sure to use it in every custom shader that you're using! upload_2023-4-15_11-46-14.png

    Second, use the LIGHT_LOOP_BEGIN and LIGHT_LOOP_END to loop between all the lights. My C# code is this:
    Code (CSharp):
    1.  
    2. #ifndef CUSTOM_LIGHTING_INCLUDED
    3. #define CUSTOM_LIGHTING_INCLUDED
    4.  
    5.  
    6. float max3(float a, float b, float c)
    7. {
    8.     return max(a, max(b, c));
    9. }
    10.  
    11. float GetLightIntensity(float3 color)
    12. {
    13.     return max3(color.r, color.g, color.b);
    14. }
    15.  
    16.  
    17.  
    18. void CalculateMainLight_float(float3 WorldPos, out float3 Direction, out float3 Color, out half DistanceAtten, out half ShadowAtten, out float Intensity)
    19. {
    20. #ifdef SHADERGRAPH_PREVIEW
    21.     Direction = half3(0.5, 0.5, 0);
    22.     Color = 1;
    23.     Intensity = 0;
    24.     DistanceAtten = 1;
    25.     ShadowAtten = 1;
    26. #else
    27. #if SHADOWS_SCREEN
    28.     half4 clipPos = TransformWorldToHClip(WorldPos);
    29.     half4 shadowCoord = ComputeScreenPos(clipPos);
    30. #else
    31.     half4 shadowCoord = TransformWorldToShadowCoord(WorldPos);
    32. #endif
    33.     Light mainLight = GetMainLight(shadowCoord);
    34.     Direction = mainLight.direction;
    35.     Color = mainLight.color;
    36.     DistanceAtten = mainLight.distanceAttenuation;
    37.     ShadowAtten = mainLight.shadowAttenuation;
    38.     Intensity = GetLightIntensity(mainLight.color);
    39. #endif
    40. }
    41.  
    42.  
    43. void AddAdditionalLights_float(float Smoothness, float3 WorldPosition, float3 WorldNormal, float3 WorldView,
    44.     float MainDiffuse, float MainSpecular, float3 MainColor, float3 ScreenPosition,
    45.     out float Diffuse, out float Specular, out float3 Color)
    46. {
    47.     float mainIntensity = GetLightIntensity(MainColor);
    48.     Diffuse = 0;
    49.     Specular = 0;
    50.     Color = MainColor;
    51.  
    52. #ifndef SHADERGRAPH_PREVIEW
    53.     InputData inputData = (InputData) 0;
    54.     SurfaceData surfaceData;
    55.     AmbientOcclusionFactor aoFactor = CreateAmbientOcclusionFactor(inputData, surfaceData);
    56.     half thisSpecular = 0;
    57.     half maxIntensity = 0;
    58.     uint meshRenderingLayers = GetMeshRenderingLayer();
    59.  
    60.     inputData.normalizedScreenSpaceUV = ScreenPosition;
    61.     inputData.positionWS = WorldPosition;
    62.     uint lightsCount = GetAdditionalLightsCount();
    63.     half4 shadowMask = CalculateShadowMask(inputData);
    64.     uint _AdditionalLightsDirectionalCount = 128;
    65.  
    66. #if USE_FORWARD_PLUS
    67.     for(uint lightIndex = 0; lightIndex < min(URP_FP_DIRECTIONAL_LIGHTS_COUNT, MAX_VISIBLE_LIGHTS); lightIndex++)
    68.     {
    69.     Light light = GetAdditionalLight(lightIndex, WorldPosition);
    70.         half NdotL = max(0, dot(WorldNormal, light.direction));
    71.         half atten = light.distanceAttenuation * light.shadowAttenuation * GetLightIntensity(light.color);
    72.         half thisDiffuse = atten * NdotL;
    73.         thisSpecular += LightingSpecular(thisDiffuse, light.direction, WorldNormal, WorldView, 1, Smoothness);
    74.         Diffuse += thisDiffuse;
    75.  
    76.         Color = (thisDiffuse > 0 && GetLightIntensity(light.color) > maxIntensity) ? light.color : Color;
    77.         maxIntensity = (thisDiffuse > 0 && GetLightIntensity(light.color) > maxIntensity) ? GetLightIntensity(light.color) : maxIntensity;
    78.     }
    79. #endif
    80.    
    81.    
    82.     LIGHT_LOOP_BEGIN(lightsCount)
    83.  
    84.     Light light = GetAdditionalLight(lightIndex, WorldPosition);
    85.         half NdotL = max(0, dot(WorldNormal, light.direction));
    86.         half atten = light.distanceAttenuation * light.shadowAttenuation * GetLightIntensity(light.color);
    87.         half thisDiffuse = atten * NdotL;
    88.         thisSpecular += LightingSpecular(thisDiffuse, light.direction, WorldNormal, WorldView, 1, Smoothness);
    89.         Diffuse += thisDiffuse;
    90.  
    91.         Color = (thisDiffuse > 0 && GetLightIntensity(light.color) > maxIntensity) ? light.color : Color;
    92.         maxIntensity = (thisDiffuse > 0 && GetLightIntensity(light.color) > maxIntensity) ? GetLightIntensity(light.color) : maxIntensity;
    93.     LIGHT_LOOP_END
    94.  
    95.     Specular = thisSpecular;
    96. #endif
    97. }
    98. #endif
    It's still based on NedMakesGames code. It still uses all the subshaders and most of the tutorial's graph structure. You can see the product of my shader here: https://twitter.com/senfinecogames/status/1637766562378661888?s=20
     
    Reanimate_L and HollyRivay like this.