Announcement

Collapse
No announcement yet.

Implementing Exponential Height Fog in a Custom Shader

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    Implementing Exponential Height Fog in a Custom Shader

    I need to be able to control Exponential Height Fog on a per-pixel basis, so I am trying to implement it into an HLSL custom node inside a material. I feel like I'm on the right track, but struggling to figure out how it's actually done in the engine version (tried to sniff out where the equation/s are lurking in the source but didn't get very far). I'm wondering if anyone else has ever tried this, or can point me in the right direction. Here is what I have so far (not looking so great):

    Click image for larger version

Name:	Fog_Versions.jpg
Views:	1
Size:	460.2 KB
ID:	1208583

    As you can see, (among many other things!) I'm having trouble getting a nice falloff between the scattering and inscattering colours. Here is my network:

    Click image for larger version

Name:	Shader_Network.jpg
Views:	1
Size:	497.2 KB
ID:	1208584

    And here is the code in the custom node:

    Code:
    float distance = length( Pixel_Position - Camera_Position );
    
    float fog_amount = 1 - exp( - distance * Fog_Intensity);
    
    float3 pixel_to_cam = normalize ( Camera_Position - Pixel_Position );
    
    float sun_dot = dot ( pixel_to_cam, Sun_Direction );
    
    float sun_dot_ranged = ( ( ( sun_dot - 1 ) / ( Sun_Intensity - 1 ) ) * ( 0 - 1 ) ) + 1;
    
    return lerp (Scattering_Colour.xyz, Inscattering_Colour.xyz, sun_dot_pow) * fog_amount;
    I absolutely love the look of the standard UE4 Exponential Height Fog, can anyone help me out for implementing this in a shader?

    I should mention, I haven't tried to actually implement the code for height falloff yet, I'm currently just trying to get the scattering/inscattering setup working.

    Thanks!

    #2
    https://github.com/EpicGames/UnrealE...tFogCommon.usf

    Comment


      #3
      https://github.com/EpicGames/UnrealE...tFogCommon.usf

      Comment


        #4
        Thanks! Looking through that now. Will see if I can get it working!

        Comment


          #5
          Hmmm.... implementing the code from HeightFogCommon isn't working out quite as easily as I had hoped.

          Below is the adjusted code. All I have done is:

          1) Strip out the branch for USE_GLOBAL_CLIP_PLANE
          2) Added a line for defining ExponentialFogParameters.x using the definition in the code comment

          The rest is the same.

          Code:
          /*=============================================================================
          	HeightFog - HLSL Version
          =============================================================================*/
          
          float FLT_EPSILON = 0.001f;
          float FLT_EPSILON2 = 0.01f;
          
          ExponentialFogParameters.x =  ExponentialFogParameters3.x * exp2(-ExponentialFogParameters.y * (CameraWorldPosition.z - ExponentialFogParameters3.y));
          
          /** Color to use. */
          float3 ExponentialFogColor = ExponentialFogColorParameter.xyz;
          float MinFogOpacity = ExponentialFogColorParameter.w;
          
          float3 CameraToReceiver = PixelWorldPosition - CameraWorldPosition;
          float CameraToReceiverLengthSqr = dot(CameraToReceiver, CameraToReceiver);
          float CameraToReceiverLengthInv = rsqrt(CameraToReceiverLengthSqr);
          float CameraToReceiverLength = CameraToReceiverLengthSqr * CameraToReceiverLengthInv;
          half3 CameraToReceiverNormalized = CameraToReceiver * CameraToReceiverLengthInv;
          
          float RayOriginTerms = ExponentialFogParameters.x;
          float RayLength = CameraToReceiverLength;
          float RayDirectionZ = CameraToReceiver.z;
          
          // Calculate the line integral of the ray from the camera to the receiver position through the fog density function
          // The exponential fog density function is d = GlobalDensity * exp(-HeightFalloff * z)
          float EffectiveZ = (abs(RayDirectionZ) > FLT_EPSILON2) ? RayDirectionZ : FLT_EPSILON2;
          float Falloff = max(-127.0f, ExponentialFogParameters.y * EffectiveZ);	// if it's lower than -127.0, then exp2() goes crazy in OpenGL's GLSL.
          float ExponentialHeightLineIntegralShared = RayOriginTerms * (1.0f - exp2(-Falloff) ) / Falloff;
          float ExponentialHeightLineIntegral = ExponentialHeightLineIntegralShared * max(RayLength - ExponentialFogParameters.w, 0.0f);
          
          float3 DirectionalInscattering = 0;
          
          if (InscatteringLightDirection.w > 0)
          {
          	// Setup a cosine lobe around the light direction to approximate inscattering from the directional light off of the ambient haze;
          	float3 DirectionalLightInscattering = DirectionalInscatteringColor.xyz * pow(saturate(dot(CameraToReceiverNormalized, InscatteringLightDirection.xyz)), DirectionalInscatteringColor.w);
          
          	// Calculate the line integral of the eye ray through the haze, using a special starting distance to limit the inscattering to the distance
          	float DirExponentialHeightLineIntegral = ExponentialHeightLineIntegralShared * max(RayLength - DirectionalInscatteringStartDistance, 0.0f);
          	// Calculate the amount of light that made it through the fog using the transmission equation
          	float DirectionalInscatteringFogFactor = saturate(exp2(-DirExponentialHeightLineIntegral));
          	// Final inscattering from the light
          	DirectionalInscattering = DirectionalLightInscattering * (1 - DirectionalInscatteringFogFactor);
          }
          
          // Calculate the amount of light that made it through the fog using the transmission equation
          float ExpFogFactor = max(saturate(exp2(-ExponentialHeightLineIntegral)), MinFogOpacity);
          
          return float4((ExponentialFogColor) * (1 - ExpFogFactor) + DirectionalInscattering, ExpFogFactor);
          Here is the shader network, into which I am feeding in the same values I am using in the standard height fog:

          Click image for larger version

Name:	Shader_Network_Fog_HLSL.PNG
Views:	1
Size:	288.6 KB
ID:	1122318

          And here is the current result compared with the standard one:

          Click image for larger version

Name:	Comparison.jpg
Views:	1
Size:	134.9 KB
ID:	1122319

          Obviously I am not expecting any of the fog effect outside of the cube itself at this point, but all I am getting is a very straight line across the geometry. Additionally, if I tilt the camera up slightly, the entire box snaps to becomes the fog colour, and if I tilt downwards, it snaps to have no fog colour.

          Unfortunately, since I don't really understand most of the code in the function, I can't understand what's actually happening here, or what's going wrong, what I'm missing.

          Does anyone know if this is even possible?

          Comment


            #6
            It would probably be easier if you would edit the existing shader. With the forward renderer you can do the fog per vertex I think, and then it has to run in the vertex shader where you should have access to the vertex color and UVs. So you could modify the code to set the fog intensity to 0 on some predefined vertex colors
            Easy to use UMG Mini Map on the UE4 Marketplace.
            Forum thread: https://forums.unrealengine.com/show...-Plug-and-Play

            Comment


              #7
              Phew, got it working in the end!

              Just thought I would share an example:

              Click image for larger version

Name:	Example.PNG
Views:	1
Size:	697.7 KB
ID:	1122337

              So this is all taking place within the material itself. I need this for such a weird and specific purpose in my project, so I can't imagine anyone will actually need to be able to do such a thing, but on the off-chance anyone needs height fog inside a shader, let me know and I can clean it up and share it!

              Comment


                #8
                Can you post the final material + custom code. I can double check that everything is right.

                Comment


                  #9
                  Sure, here is the material setup:

                  Click image for larger version

Name:	Fog_Shader_v5.PNG
Views:	1
Size:	279.1 KB
ID:	1122356

                  The Absolute World Position and Camera Position nodes aren't connected here because they are connected inside the Material Function you can see there, so don't need to be connected externally.

                  Here is the custom node code:

                  Code:
                  //////////////////////////////////////////////////
                  //
                  // Directional Inscattering Height Fog
                  //
                  // With imaginary-sun-based falloff
                  //
                  //////////////////////////////////////////////////
                  
                  float distance = length( Pixel_Position - Camera_Position );
                  
                  float3 ray_direction = normalize ( Pixel_Position - Camera_Position );
                  
                  float fog_amount = 1.0 - exp ( - distance * Fog_Density);
                  
                  float3 imaginary_sun_position = Directional_Inscattering_Light_Direction * Imaginary_Sun_Distance;
                  
                  float pixel_to_sun_distance = length ( Pixel_Position - imaginary_sun_position );
                  
                  // Correction multiplier to ensure inscattering is behind near objects
                  float distance_correction = clamp ( ( ( ( ( pixel_to_sun_distance - Directional_Inscattering_Start_Distance ) / ( Directional_Inscattering_End_Distance - Directional_Inscattering_Start_Distance ) ) * ( 0 - 1) ) + 1 ), 0, 1);
                  
                  float directional_inscattering_amount = max ( dot ( ray_direction, ( normalize ( Directional_Inscattering_Light_Direction ) * Directional_Inscattering_Multiplier ) ), 0.0 ) * distance_correction;
                  
                  float3 output_colour = lerp ( Inscattering_Colour, Directional_Inscattering_Colour, pow ( directional_inscattering_amount, Directional_Inscattering_Exponent ) );
                  
                  float falloff_correction = clamp ( ( ( ( ( Pixel_Position.z - Height_Falloff_Start ) / ( Height_Falloff_End - Height_Falloff_Start ) ) * ( 0 - 1) ) + 1 ), 0, 1 );
                  
                  return output_colour * fog_amount * pow ( falloff_correction, Height_Falloff_Exponent );
                  I couldn't get the HeightFogCommon one to work, so I built this one up from scratch. A lot of it comes from here, with various additions of my own in terms of control. I couldn't get the height fog implementation from that page to work, so I came up with my own. I've also done something which might be quite strange: setting the light position using a normalised direction vector (Directional_Inscattering_Light_Direction) and multiplying it by a number (Imaginary_Sun_Distance) to place it where I want it. Could easily be adapted to take positional and directional information from an actual light source in the scene (which would probably make more sense), but this is how I want it for my purposes right now. You can then control how much influence the directional scattering has over geometry behind it using Directional_Inscattering_End_Distance.

                  I'm pretty happy with the final result, as I lets me create exactly the look I was hoping for, but it's not necessarily completely optimised, so let me know what you think!

                  PS.
                  Just wanted to add, in case it just looks messy: I have a naming convention for my custom nodes where I used Capitalised_Variable_Names for variables that are coming from outside the node and uncapitalised_variable_names for variables created internally to the custom node.
                  Last edited by localstarlight; 02-01-2017, 06:52 AM.

                  Comment


                    #10
                    My question may be stupid but is this postprocess material or? Are all the objects on the scene using the same material or?

                    Comment


                      #11
                      Definitely not a stupid question. As I mentioned, I'm using this in a weird and specific way that currently requires me to use it in an ordinary material. So yes, all the materials implement this as part of their emissive channel, but have different diffuse/normal/etc.

                      I had the thought yesterday that perhaps I should be doing this as a post process material instead, so it is something I'm going to be looking at, but for my purposes I think it might still be better inside the normal material.

                      Comment


                        #12
                        Your fog isn't using extinction at all? When object is further a way fog should block visibility to that object based on fog amount.

                        Comment


                          #13
                          [MENTION=37019]Kalle-H[/MENTION]: I am doing that with fog_amount (float fog_amount = 1.0 - exp ( - distance * Fog_Density);), which is a multiplier for the final output. Here are some example settings for the Fog_Density input:

                          Click image for larger version

Name:	Fog_Density.jpg
Views:	1
Size:	374.2 KB
ID:	1122368

                          Is that what you mean?

                          Comment


                            #14
                            Original exp fog function outputs float4. Alpha channel is used to prevent you to see object behind fog. Ideally you should multiply base color, specular and material based emissive with that alpha value. You are not seeing problem probably because everything is unlit.

                            Comment


                              #15
                              Ah, interesting! My scene uses only baked lighting, so for my current purposes the setup should be fine, right? Should I ever update this for dynamic lighting I'll bear in mind what you said. Thanks!

                              Comment

                              Working...
                              X