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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Additional Light Data Fails at Certain View Angles

Discussion in 'Shaders' started by swardson, Jan 16, 2023.

  1. swardson

    swardson

    Joined:
    Mar 22, 2013
    Posts:
    4
    Hey all,

    I have a problem getting additional lights working for my volumetric fog shader. I have it working great with directional lights, but the additional light stuff seems to have some issues.

    How the system currently works is that I have a function that draws a sphere using ray marching at a point and with a certain radius. This was working great, so I began looking through the URP source code to find how additional lights are handled. I found the functions GetAdditionalLightsCount() and GetAdditionalLight() among several others that seemed promising.

    After running some tests though, I found an issue where I would only be able to get the light data at certain camera angles (See attached GIF). LightIssue.gif LightIssue.gif LightIssue.gif LightIssue.gif LightIssue.gif

    I have no idea why this is happening and I am starting to lose my mind lol.

    Thanks so much for any help. :)

    here is my code (ignore the class names, I haven't updated them yet... I was preoccupied with the issue lol):

    Code (HLSL):
    1. Shader "Hidden/Worlds End/VolumeSpotLights"
    2. {
    3.     Properties
    4.     {
    5.         _MainTex ("Texture", 2D) = "white" {}
    6.     }
    7.     SubShader
    8.     {
    9.         // No culling or depth
    10.         Cull Off ZWrite Off ZTest Always
    11.  
    12.         Pass
    13.         {
    14.             HLSLPROGRAM
    15.  
    16.             #pragma vertex vert
    17.             #pragma fragment frag
    18.  
    19.             #pragma multi_compile _  _MAIN_LIGHT_SHADOWS_CASCADE
    20.  
    21.             #pragma prefer_hlslcc gles
    22.             #pragma exclude_renderers d3d11_9x
    23.  
    24.             #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
    25.             #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    26.  
    27.             struct appdata
    28.             {
    29.                 float4 vertex : POSITION;
    30.                 float2 uv : TEXCOORD0;
    31.             };
    32.  
    33.             struct v2f
    34.             {
    35.                 float4 vertex : SV_POSITION;
    36.                 float2 uv : TEXCOORD0;
    37.             };
    38.  
    39.             struct SpotLight
    40.             {
    41.                 float3 pos;
    42.                 float3 dir;
    43.                 float4 color;
    44.             };
    45.  
    46.             sampler2D _MainTex;
    47.  
    48.             float3 _Pos;
    49.  
    50.             float _Intensity;
    51.             float _Scattering;
    52.             float _Steps;
    53.             float _JitterVolumetric;
    54.             float _MaxDistance;
    55.  
    56.             float GetDepth(float2 uv)
    57.             {
    58.                 #if UNITY_REVERSED_Z
    59.                     float depth = SampleSceneDepth(uv);
    60.                 #else
    61.                     // Adjust z to match NDC for OpenGL
    62.                     float depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(uv));
    63.                 #endif
    64.  
    65.                 return depth;
    66.             }
    67.  
    68.             //Unity already has a function that can reconstruct world space position from depth
    69.             float3 GetWorldPos(float2 uv)
    70.             {
    71.                 float depth = GetDepth(uv);
    72.                 return ComputeWorldSpacePosition(uv, depth, UNITY_MATRIX_I_VP);
    73.             }
    74.  
    75.             float random( float2 p )
    76.             {
    77.                 return frac(sin(dot(p, float2(41, 289)))*45758.5453 )-0.5;
    78.             }
    79.  
    80.             float random01( float2 p )
    81.             {
    82.                 return frac(sin(dot(p, float2(41, 289)))*45758.5453 );
    83.             }
    84.            
    85.             //from Ronja https://www.ronja-tutorials.com/post/047-invlerp_remap/
    86.             float invLerp(float from, float to, float value)
    87.             {
    88.                 return (value - from) / (to - from);
    89.             }
    90.  
    91.             float remap(float origFrom, float origTo, float targetFrom, float targetTo, float value)
    92.             {
    93.                 float rel = invLerp(origFrom, origTo, value);
    94.                 return lerp(targetFrom, targetTo, rel);
    95.             }
    96.  
    97.             float2 raySphere(float3 sphereCentre, float sphereRadius, float3 rayOrigin, float3 rayDir)
    98.             {
    99.                 float3 offset = rayOrigin - sphereCentre;
    100.                 float a = 1; // Set to dot(rayDir, rayDir) if rayDir might not be normalized
    101.                 float b = 2 * dot(offset, rayDir);
    102.                 float c = dot (offset, offset) - sphereRadius * sphereRadius;
    103.                 float d = b * b - 4 * a * c; // Discriminant from quadratic formula
    104.  
    105.                 // Number of intersections: 0 when d < 0; 1 when d = 0; 2 when d > 0
    106.                 if (d > 0) {
    107.                     float s = sqrt(d);
    108.                     float dstToSphereNear = max(0, (-b - s) / (2 * a));
    109.                     float dstToSphereFar = (-b + s) / (2 * a);
    110.  
    111.                     // Ignore intersections that occur behind the ray
    112.                     if (dstToSphereFar >= 0) {
    113.                         return float2(dstToSphereNear, dstToSphereFar - dstToSphereNear);
    114.                     }
    115.                 }
    116.                 // Ray did not intersect sphere
    117.                 return float2(1.#INF, 0);
    118.             }
    119.  
    120.             float ShadowAtten(int lightIndex, float3 samplePoint, half3 lightDir)
    121.             {
    122.                 return AdditionalLightRealtimeShadow(lightIndex, TransformWorldToShadowCoord(samplePoint), lightDir);
    123.             }
    124.  
    125.  
    126.             float pointLightFogAtten(in float2 hitData, float3 lightPos, float lightRange, float3 samplePos, float intensity)
    127.             {
    128.                 float dstToLight = length(samplePos - lightPos);
    129.  
    130.                 // float accumFog = 1;
    131.  
    132.                 if(dstToLight > lightRange)
    133.                 {
    134.                     dstToLight = lightRange;
    135.                 }
    136.                 float accumFog = exp(-dstToLight / (lightRange / 10)) * intensity;
    137.                
    138.                 return accumFog;
    139.             }
    140.  
    141.             float fogMarch(in float2 uv, in float3 spherePos, int index)
    142.             {
    143.                 float3 worldPos = GetWorldPos(uv);
    144.  
    145.                 //we find out our ray info, that depends on the distance to the camera
    146.                 float3 startPosition = _WorldSpaceCameraPos;
    147.                 float3 rayVector = worldPos - startPosition;
    148.                 float3 rayDirection =  normalize(rayVector);
    149.                 float rayLength = length(rayVector);
    150.  
    151.                 //Depth
    152.                 float nonlin_depth = SampleSceneDepth(uv);
    153.                 float depth = LinearEyeDepth(nonlin_depth, _ZBufferParams.y) * rayLength;
    154.  
    155.                 //Sphere info
    156.                 float2 hitData = raySphere(spherePos, _Scattering, startPosition, rayDirection);
    157.                 float dstToSphere = hitData.x;
    158.                 float dstInsideSphere = hitData.y;
    159.  
    160.                 float stepLength = dstInsideSphere / _Steps;
    161.                 float dstLimit = min(depth - dstToSphere, dstInsideSphere);
    162.                
    163.                 float randomOffset = random01(uv) * stepLength * _JitterVolumetric / 100;
    164.  
    165.                 float dstTravelled = randomOffset;
    166.  
    167.                 float accumFog = 0;
    168.                 while(dstTravelled < dstLimit)
    169.                 {
    170.                     float3 samplePoint = startPosition + rayDirection * (dstToSphere + dstTravelled);
    171.  
    172.                     float shadowAtten = ShadowAtten(index, samplePoint, 0);
    173.                    
    174.                     if(shadowAtten > 0)
    175.                     {
    176.                         accumFog += pointLightFogAtten(hitData, spherePos, _Scattering, samplePoint, _Intensity);
    177.                     }
    178.                    
    179.                     dstTravelled += stepLength;
    180.                 }
    181.  
    182.                 //we need the average value, so we divide between the amount of samples
    183.                 accumFog /= _Steps;
    184.  
    185.                 return accumFog;
    186.             }
    187.  
    188.  
    189.             v2f vert (appdata v)
    190.             {
    191.                 v2f o;
    192.                 o.vertex = TransformWorldToHClip(v.vertex.xyz);
    193.                 o.uv = v.uv;
    194.                 return o;
    195.             }
    196.  
    197.             float4 frag (v2f i) : SV_Target
    198.             {
    199.                 float worldPos = GetWorldPos(i.uv);
    200.                 float4 col = tex2D(_MainTex, i.uv);
    201.  
    202.                 float fog = 0;
    203.                 half3 color = half3(0, 0, 0);
    204.                 uint lightCount = GetAdditionalLightsCount();
    205.                 for(uint l = 0; l < lightCount; l++)
    206.                 {
    207.                     Light light = GetAdditionalLight(l, worldPos, 1);
    208.                     color += light.color;
    209.                     fog += fogMarch(i.uv, _Pos, l);
    210.                 }
    211.  
    212.                 return col + (fog * float4(color.r, color.g, color.b, 1));
    213.             }
    214.             ENDHLSL
    215.         }
    216.     }
    217. }
    218.  
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Rendering;
    3. using UnityEngine.Rendering.Universal;
    4.  
    5. public class VolumeSpotLights : ScriptableRendererFeature
    6. {
    7.     public enum DownSample
    8.     {
    9.         off = 1,
    10.         half = 2,
    11.         third = 3,
    12.         quarter = 4
    13.     };
    14.  
    15.     [System.Serializable]
    16.     public class Settings
    17.     {
    18.         public DownSample downsampling = DownSample.off;
    19.  
    20.         [Space(10)]
    21.         [Header("Apperance")]
    22.         public Color tint = Color.white;
    23.         public float intensity = 1;
    24.         public float scattering = 0;
    25.  
    26.         public Vector3 position;
    27.  
    28.         [Space(10)]
    29.         [Header("Performance")]
    30.         [Min(0)] public float steps = 24;
    31.         public float maxDistance = 75;
    32.         [Min(0)] public float jitter = 250;
    33.  
    34.         [Space(10)]
    35.         [Header("Initialization")]
    36.         public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingPostProcessing;
    37.     }
    38.  
    39.     public Settings settings = new Settings();
    40.  
    41.     class Pass : ScriptableRenderPass
    42.     {
    43.         public Settings settings;
    44.         private RenderTargetIdentifier source;
    45.         RenderTargetHandle tempTexture;
    46.         // RenderTargetHandle lowResDepthRT;
    47.  
    48.         Material material;
    49.  
    50.         private string profilerTag;
    51.  
    52.         public void Setup(RenderTargetIdentifier source)
    53.         {
    54.             this.source = source;
    55.         }
    56.  
    57.         public Pass(string profilerTag)
    58.         {
    59.             this.profilerTag = profilerTag;
    60.         }
    61.  
    62.         public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
    63.         {
    64.             var original = cameraTextureDescriptor;
    65.             int divider = (int)settings.downsampling;
    66.  
    67.             if (Camera.current != null) //This is necessary so it uses the proper resolution in the scene window
    68.             {
    69.                 cameraTextureDescriptor.width = (int)Camera.current.pixelRect.width / divider;
    70.                 cameraTextureDescriptor.height = (int)Camera.current.pixelRect.height / divider;
    71.                 original.width = (int)Camera.current.pixelRect.width;
    72.                 original.height = (int)Camera.current.pixelRect.height;
    73.             }
    74.             else //regular game window
    75.             {
    76.                 cameraTextureDescriptor.width /= divider;
    77.                 cameraTextureDescriptor.height /= divider;
    78.             }
    79.  
    80.             //R8 has noticeable banding
    81.             cameraTextureDescriptor.colorFormat = RenderTextureFormat.ARGB32;
    82.  
    83.             //we dont need to resolve AA in every single Blit
    84.             cameraTextureDescriptor.msaaSamples = 1;
    85.  
    86.             //we need to assing a different id for every render texture
    87.             // lowResDepthRT.id = 1;
    88.  
    89.             cmd.GetTemporaryRT(tempTexture.id, cameraTextureDescriptor);
    90.             ConfigureTarget(tempTexture.Identifier());
    91.  
    92.             // cmd.GetTemporaryRT(lowResDepthRT.id, cameraTextureDescriptor);
    93.             // ConfigureTarget(lowResDepthRT.Identifier());
    94.            
    95.             ConfigureClear(ClearFlag.All, Color.black);
    96.         }
    97.  
    98.         public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    99.         {
    100.             CommandBuffer cmd = CommandBufferPool.Get(profilerTag);
    101.             cmd.Clear();
    102.  
    103.             //it is very important that if something fails our code still calls
    104.             //CommandBufferPool.Release(cmd) or we will have a HUGE memory leak
    105.             try
    106.             {
    107.                 material = new Material(Shader.Find("Hidden/Worlds End/VolumeSpotLights"));
    108.                
    109.                 material.SetVector("_Pos", settings.position);
    110.  
    111.                 material.SetFloat("_Scattering", settings.scattering);
    112.                 material.SetFloat("_Steps", settings.steps);
    113.                 material.SetFloat("_JitterVolumetric", settings.jitter);
    114.                 material.SetFloat("_MaxDistance", settings.maxDistance);
    115.                 material.SetFloat("_Intensity", settings.intensity);
    116.                 // material.SetFloat("_GaussSamples", settings.gaussBlur.samples);
    117.                 // material.SetFloat("_GaussAmount", settings.gaussBlur.amount);
    118.                 // material.SetColor("_Tint", settings.tint);
    119.  
    120.                 //raymarch
    121.                 cmd.Blit(source, tempTexture.Identifier(), material, 0);
    122.  
    123.                 //bilateral blu X, we use the lowresdepth render texture for other things too, it is just a name
    124.                 // cmd.Blit(tempTexture.Identifier(), lowResDepthRT.Identifier(), material, 1);
    125.  
    126.                 //bilateral blur Y
    127.                 // cmd.Blit(lowResDepthRT.Identifier(), tempTexture.Identifier(), material, 2);
    128.  
    129.                 //save it in a global texture
    130.                 // cmd.SetGlobalTexture("_volumetricTexture", tempTexture.Identifier());
    131.                
    132.                 //downsample depth
    133.                 // cmd.Blit(source, lowResDepthRT.Identifier(), material, 4);
    134.                 // cmd.SetGlobalTexture("_LowResDepth", lowResDepthRT.Identifier());
    135.  
    136.                 //upsample and composite
    137.                 // cmd.Blit(source, temptexture3.Identifier(), material, 3);
    138.                 cmd.Blit(tempTexture.Identifier(), source);
    139.  
    140.                 context.ExecuteCommandBuffer(cmd);
    141.             }
    142.             catch
    143.             {
    144.                 Debug.LogError("Error");
    145.             }
    146.  
    147.             cmd.Clear();
    148.             CommandBufferPool.Release(cmd);
    149.         }
    150.     }
    151.  
    152.     Pass pass;
    153.     RenderTargetHandle renderTextureHandle;
    154.     public override void Create()
    155.     {
    156.         pass = new Pass("Volumetric Spot Light");
    157.         name = "Volumetric Spot Light";
    158.         pass.settings = settings;
    159.         pass.renderPassEvent = settings.renderPassEvent;
    160.     }
    161.  
    162.     public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    163.     {
    164.         var cameraColorTargetIdent = renderer.cameraColorTarget;
    165.         pass.Setup(cameraColorTargetIdent);
    166.         renderer.EnqueuePass(pass);
    167.     }
    168. }
    169.  
    170.  
    171.  
     
  2. swardson

    swardson

    Joined:
    Mar 22, 2013
    Posts:
    4
    Also, that is only with point lights, spotlights don't work at all, and directional lights work perfectly.
     
  3. swardson

    swardson

    Joined:
    Mar 22, 2013
    Posts:
    4
    After further testing in a blank project with none of the ray march stuff, the additional light stuff fails completely with all of the light types.