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. Dismiss Notice

Looking for help with post-processing shader

Discussion in 'Universal Render Pipeline' started by thomasluce, Jun 14, 2021.

  1. thomasluce

    thomasluce

    Joined:
    Mar 8, 2019
    Posts:
    16
    Hello!

    So I'm working on re-producing the atmosphere shader done by Sebastian Lague (
    ) in URP instead of HDRP. I've got a Blit-pass built and working for applying a material to a full-screen quad, copying back and forth between the camera buffer, etc. , have depth-textures turned on for my camera, and things are basically working. I've been beating my head against the wall for a few days now and just can't figure out what's going on. Here is a screenshot of the effect that I'm getting at the moment:



    As you can see, I'm getting the good volume rendering with the light scattering and the whole bit, but the direction from the atmospheric sampling points to the light source (that yellow "sun" in the background) is wonky as hell. BTW, this is a donut that surrounds the planet regardless of the direction that you view it from; in other words, it's in world-space like that, not just an artifact of the camera viewing direction.

    Here is my shader code:

    Code (HLSL):
    1.  
    2. Shader "Unlit/AtmosphereVolume"
    3. {
    4.     Properties
    5.     {
    6.         _MainTex("Texture", 2D) = "white" {}
    7.         _NumScatteringInPoints("Number of scattering points", Int) = 10
    8.         _NumOpticalDepthPoints("Number of optical depth sample points", Int) = 10
    9.         [HideInInspector] _PlanetCenter("Planet center", Vector) = (0,0,0) // World-space
    10.         [HideInInspector] _PlanetRadius("Planet radius", Float) = 1
    11.         [HideInInspector] _AtmosphereRadius("Atmosphere radius", Float) = 1
    12.         [HideInInspector] _DensityFalloff("Atmosphere density falloff", Float) = 1
    13.         [HideInInspector] _DirToSun("Direction towards the sun", Vector) = (0,0,0) // Direction from planet to the sun
    14.         [HideInInspector] _ScatteringCoefficients("Scattering wavelengths", Vector) = (0,0,0)
    15.     }
    16.     SubShader
    17.     {
    18.         // No culling or depth.
    19.         Cull Off ZWrite Off ZTest Always
    20.    
    21.         Pass
    22.         {
    23.             CGPROGRAM
    24.             #pragma vertex vert
    25.             #pragma fragment frag
    26.            
    27.             #include "UnityCG.cginc"
    28.  
    29.         #define FLT_MAX 3.402823466e+38
    30.  
    31.  
    32.             struct appdata
    33.             {
    34.                 float4 vertex : POSITION;
    35.                 float2 uv : TEXCOORD0;
    36.             };
    37.  
    38.             struct v2f
    39.             {
    40.                 float4 vertex : SV_POSITION;
    41.                 float2 uv : TEXCOORD0;
    42.                 float3 viewVector : TEXCOORD1;
    43.             };
    44.  
    45.             sampler2D _MainTex;
    46.             float4 _MainTex_ST;
    47.  
    48.             sampler2D _CameraDepthTexture;
    49.  
    50.             float3 _PlanetCenter;
    51.             float _AtmosphereRadius;
    52.             float _PlanetRadius;
    53.             float _DensityFalloff;
    54.             int _NumScatteringInPoints;
    55.             int _NumOpticalDepthPoints;
    56.             float3 _DirToSun;
    57.             float3 _ScatteringCoefficients;
    58.  
    59.             float2 raySphere(float3 sphereCentre, float sphereRadius, float3 rayOrigin, float3 rayDir) {
    60.                 float3 offset = rayOrigin - sphereCentre;
    61.                 float a = 1;// dot(rayDir, rayDir);// if rayDir might not be normalized
    62.                 float b = 2 * dot(offset, rayDir);
    63.                 float c = dot(offset, offset) - sphereRadius * sphereRadius;
    64.                 float d = b * b - 4 * a * c; // Discriminant from quadratic formula
    65.  
    66.                 // Number of intersections: 0 when d < 0; 1 when d = 0; 2 when d > 0
    67.                 if (d > 0) {
    68.                     float s = sqrt(d);
    69.                     float dstToSphereNear = max(0, (-b - s) / (2 * a));
    70.                     float dstToSphereFar = (-b + s) / (2 * a);
    71.  
    72.                     // Ignore intersections that occur behind the ray
    73.                     if (dstToSphereFar >= 0) {
    74.                         return float2(dstToSphereNear, dstToSphereFar - dstToSphereNear);
    75.                     }
    76.                 }
    77.                 // Ray did not intersect sphere
    78.                 return float2(FLT_MAX, 0);
    79.             }
    80.  
    81.             float densityAtPoint(float3 densitySamplePoint) {
    82.                 float heightAboveSurface = length(densitySamplePoint - _PlanetCenter) - _PlanetRadius;
    83.                 float height01 = heightAboveSurface / (_AtmosphereRadius - _PlanetRadius);
    84.                 float localDensity = exp(-height01 * _DensityFalloff) * (1 - height01);
    85.                 return localDensity;
    86.             }
    87.  
    88.             float opticalDepth(float3 rayOrigin, float3 rayDir, float rayLength) {
    89.                 float3 densitySamplePoint = rayOrigin;
    90.                 float stepSize = rayLength / (_NumOpticalDepthPoints - 1);
    91.                 float opticalDepth = 0;
    92.  
    93.                 for (int i = 0; i < _NumOpticalDepthPoints; i++) {
    94.                     float localDensity = densityAtPoint(densitySamplePoint);
    95.                     opticalDepth += localDensity * stepSize;
    96.                     densitySamplePoint += rayDir * stepSize;
    97.                 }
    98.                 return opticalDepth;
    99.             }
    100.  
    101.             float3 calculateLight(float3 rayOrigin, float3 rayDir, float rayLength, float3 originalColor) {
    102.                 float inScatterPoint = rayOrigin;
    103.                 float stepSize = rayLength / (_NumScatteringInPoints - 1);
    104.                 float3 inScatteredLight = 0;
    105.                 float viewRayOpticalDepth = 0;
    106.  
    107.                 for (int i = 0; i < _NumScatteringInPoints; i++) {
    108.                     float sunRayLength = raySphere(_PlanetCenter, _AtmosphereRadius, inScatterPoint, _DirToSun).y;
    109.                     float sunRayOpticalDepth = opticalDepth(inScatterPoint, _DirToSun, sunRayLength);
    110.                     viewRayOpticalDepth = opticalDepth(inScatterPoint, -rayDir, stepSize * i);
    111.                     float3 transmittance = exp(-(sunRayOpticalDepth + viewRayOpticalDepth) * _ScatteringCoefficients);
    112.                     float localDensity = densityAtPoint(inScatterPoint);
    113.  
    114.                     inScatteredLight += localDensity * transmittance * _ScatteringCoefficients * stepSize;
    115.                     inScatterPoint += rayDir * stepSize;
    116.                 }
    117.                 float originalColorTransmittance = exp(-viewRayOpticalDepth);
    118.  
    119.                 return originalColor * originalColorTransmittance + inScatteredLight;
    120.             }
    121.  
    122.             v2f vert (appdata v)
    123.             {
    124.                 v2f o;
    125.                 o.vertex = UnityObjectToClipPos(v.vertex);
    126.                 o.uv = v.uv;
    127.                
    128.                 // Camera space matches OpenGL convention where forward is -z. In unity forward is +z.
    129.                 float3 viewVector = mul(unity_CameraInvProjection, float4(v.uv.xy * 2 - 1, 0, -1));
    130.                 o.viewVector = mul(unity_CameraToWorld, float4(viewVector, 0));
    131.                
    132.                 return o;
    133.             }
    134.  
    135.             float4 frag (v2f i) : SV_Target
    136.             {
    137.                 float4 originalColor = tex2D(_MainTex, i.uv);
    138.                 float sceneDepthNonLinear = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
    139.                 float sceneDepth = LinearEyeDepth(sceneDepthNonLinear) * length(i.viewVector);
    140.                
    141.                 float3 rayOrigin = _WorldSpaceCameraPos;
    142.                 float3 rayDir = normalize(i.viewVector);
    143.  
    144.                 float2 hitInfo = raySphere(_PlanetCenter, _AtmosphereRadius, rayOrigin, rayDir);
    145.                 float dstToAtmosphere = hitInfo.x;
    146.                 float dstThroughAtmosphere = min(hitInfo.y, sceneDepth - dstToAtmosphere);
    147.  
    148.                 if (dstThroughAtmosphere > 0) {
    149.                     const float epsilon = 0.0001;
    150.                     float3 pointInAtmosphere = rayOrigin + rayDir * (dstToAtmosphere + epsilon);
    151.                     float3 light = calculateLight(pointInAtmosphere, rayDir, dstThroughAtmosphere - epsilon * 2, originalColor);
    152.                     return float4(light, 1);
    153.                 }
    154.                 return originalColor;
    155.             }
    156.  
    157.             ENDCG
    158.         }
    159.     }
    160. }
    161.  
    You can ignore all the [HideInInspector] crap at the top - I was using the material view for my inputs when I was first working on things, now I have a simple script to set the values:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Atmosphere : MonoBehaviour
    6. {
    7.     public Material atmosphereMaterial;
    8.     public Transform sun;
    9.     public Transform planet;
    10.     public float planetRadius = 1f;
    11.     public Vector3 waveLengths = new Vector3(700, 530, 440);
    12.     public float scatteringStrength = 1f;
    13.     public float densityFalloff = 2f;
    14.  
    15.     [Range(1, 10)]
    16.     public float atmosphereScale = 1;
    17.  
    18.     public void Start()
    19.     {
    20.         SetScatterings();
    21.     }
    22.  
    23.     void Update()
    24.     {
    25.         SetPositions();
    26.     }
    27.  
    28.     private void OnValidate()
    29.     {
    30.         SetScatterings();
    31.         SetPositions();
    32.     }
    33.  
    34.     void SetScatterings()
    35.     {
    36.         float scatterR = Mathf.Pow(400 / waveLengths.x, 4) * scatteringStrength;
    37.         float scatterG = Mathf.Pow(400 / waveLengths.y, 4) * scatteringStrength;
    38.         float scatterB = Mathf.Pow(400 / waveLengths.z, 4) * scatteringStrength;
    39.         Vector3 scatteringCoefficients = new Vector3(scatterR, scatterG, scatterB);
    40.         atmosphereMaterial.SetVector("_ScatteringCoefficients", scatteringCoefficients);
    41.         atmosphereMaterial.SetFloat("_DensityFalloff", densityFalloff);
    42.     }
    43.  
    44.     void SetPositions()
    45.     {
    46.         Vector3 directionToLight = (sun.position - planet.position).normalized;
    47.         atmosphereMaterial.SetVector("_DirToSun", directionToLight);
    48.         atmosphereMaterial.SetVector("_PlanetCenter", planet.position);
    49.  
    50.         float atmosphereSize = planetRadius * (1 + atmosphereScale) - planetRadius;
    51.         atmosphereMaterial.SetFloat("_PlanetRadius", planetRadius);
    52.         atmosphereMaterial.SetFloat("_AtmosphereRadius", atmosphereSize);
    53.     }
    54. }
    55.  
    So yeah... That's where I'm stuck. I'm hoping there is something about URP that isn't well documented, or at least that I don't know about, that will make this "click" for me, but so far I'm pretty dang confused as to why this is happening. Any help is much appreciated!
     
  2. Simon_E_Sorensen

    Simon_E_Sorensen

    Unity Technologies

    Joined:
    Feb 4, 2020
    Posts:
    10
    At a glance it looks correct(having implemented a similar solution).
    From your description, it sounds like one of your directions is off.
    Could you try, instead of using the _DirToSun you set in c#, and set a float3 to "normalize(_MainLightPosition.xyz)" in the frag function ? This is the direction to the sun, so it _should_ be interchangeable with the _DirToSun(Though it will be the direction that the sun is pointing in, instead of the direction from the planet to the sun...). This might be the culprit?
    Other than that, maybe try and debug it, and see where in the code it is failing - if it is in density/opticaldepth/calc light, by returning just those values?
    Looks like you're almost there though!
     
  3. thomasluce

    thomasluce

    Joined:
    Mar 8, 2019
    Posts:
    16
    I've been debugging it like that for a few days now, to no avail. Everything seems correct. I'll try switching out the _DirToSun to the normalized light position and see if that has an effect, though.
     
  4. thomasluce

    thomasluce

    Joined:
    Mar 8, 2019
    Posts:
    16
    Well it did end up looking the same change it to the position of the light, that is to say it continues to look wrong in the same way :) When debugging the lighting, there is only one light being exposed to the shader (which is correct), but when I move it around and rotate it, the information for it doesn't change at all. The value stays the same in the shader, which I thought was strange.

    I'm definitely setting some direction or position incorrectly somewhere but I'm running out of ideas where that might be...
     
  5. CaptainKirby

    CaptainKirby

    Joined:
    Dec 12, 2011
    Posts:
    36
    Hmmm could it be something with the scale of the planet if it doesnt move?
    This line seems kind of strange? float atmosphereSize = planetRadius * (1 + atmosphereScale) - planetRadius;
    Why are you removing the radius?
    What happens if you just strip it down to the sphere ray intersection part from the video?
     
  6. thomasluce

    thomasluce

    Joined:
    Mar 8, 2019
    Posts:
    16
    The atmosphere scale thing was just something I was screwing with and doesn't really matter. For completeness, I went ahead and changed it to a simple planetRadius * atmosphereScale and nothing really changed other than sensitivity to the slider. If in the shader I return float4(dstThroughAtmosphere, 0, 0, 0) as the only factor, I get the this:
    Screenshot 2021-06-15 121151.png
    which again, looks correct to me...

    I double checked with the exact code from the video and got this:
    Screenshot 2021-06-15 121437.png

    Again, that seems correct. I don't think that the atmosphere settings or the direction from the camera out into the scene is the culprit.
     
  7. thomasluce

    thomasluce

    Joined:
    Mar 8, 2019
    Posts:
    16
    Interestingly, I also made the vector for planet-to-sun something that I could change in my script to mess around with it and see what effect it had. The only effect it had was to change the look of the atmosphere color-wise, but not position-wise; it stayed the weird axis-aligned donut regardless of the planet-to-sun vector....
     
  8. thomasluce

    thomasluce

    Joined:
    Mar 8, 2019
    Posts:
    16
    Turns out the problem is that I'm a big fat-ole' dummy...

    Check the variable type on line 102 of the shader. Thanks, auto-casting type-systems! :)
     
  9. Simon_E_Sorensen

    Simon_E_Sorensen

    Unity Technologies

    Joined:
    Feb 4, 2020
    Posts:
    10
    Glad you got it working!