Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feature Request A Solution for Overriding URP Post Processing and Terrain Shaders (and more)

Discussion in 'Shaders' started by jacques_unity12, Dec 7, 2023.

  1. jacques_unity12

    jacques_unity12

    Joined:
    Oct 1, 2023
    Posts:
    6
    After quite a deep dive into URP and how to possibly override Unity's built in URP shaders, I think I've come up with a solution that is quite lightweight and is (so far) quite stable what I've tested with on Unity 2022.3.10f1 running URP Package (14.0.8)

    I marked this Post as a Feature Request for Unity, which should become evident in the 2nd part of this post.

    I haven't tested on any other version yet, but I figured writing this solution out might inspire someone else on another version to give it a try and see if your milage varies.

    This solution is inspired by the original thread I followed here —

    https://forum.unity.com/threads/2019-2-overriding-shaders-of-terrain-grass.725294/

    There were different shaders I needed to override specifically.

    The easier ones —

    PostProcessing Shaders:

    In the URP package - Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/

    I wanted to have my own sampling implemented to replace the default Point Filtering (Nearest Neighbour) in FinalPost.shader
    1. First create a copy of the shader and all the included .hlsl files in your Assets folder, this is what you'll modify to whatever your needs would be. (Packages/com.unity.render-pipelines.universal/Shaders/PostProcessing/FinalPost.shader)
    2. Copy the Scriptable Object for PostProcessingData found in the URP package (Packages/com.unity.render-pipelines.universal/Runtime/Data/PostProcessData.asset)
    3. I placed it in Assets/Settings/Custom-PostProcessData.asset (but it doesn't matter what it is called or where it is placed)
    4. Change your URP-Renderer to now point to this custom PostProcessData asset (https://imgur.com/a/kfk8K1P)
    5. Make sure your Scene Camera has Post Processing enabled (just in case).
    6. Now the interesting bit - Click on your Custom PostProcessData Scriptable Object in the Project tree and you'll see nothing in the Inspector, then by the magic of Inspector Debug View you should see all the Post Processing Shaders configured. Now simply drag / or select & replace your Custom Shader to the one you want to replace. Example replacing FinalPost - (https://imgur.com/a/KwPzULD)
    7. That's it. I would recommend outputting simply a full screen half3 color = half3(1.0, 0.0, 0.0); or such to make sure your Custom Shader is being used.
    Now for the more tricky ones:

    The other shaders I wanted to override were the Terrain related shaders, found in URP Package:
    Packages/com.unity.render-pipelines.universal/Shaders/Terrain
    Packages/com.unity.render-pipelines.universal/Shaders/Nature

    Now these shaders are referenced from a SO found in the package location:
    Packages/com.unity.render-pipelines.universal/Runtime/Data/UniversalRenderPipelineEditorResources.asset

    This solution is heavily inspired by the original thread I mentioned above, particularly by the post by Tristan (https://forum.unity.com/threads/2019-2-overriding-shaders-of-terrain-grass.725294/#post-5414238)

    However, that solution stopped working in Unity 2021 or so (not sure) since the great Unity gods changed the referencing of UniversalRenderPipelineEditorResources by Asset GUID directly. Specifically in the code:

    URP Package (14.0.8) - Packages/com.unity.render-pipelines.universal/Runtime/Data/UniversalRenderPipelineAsset.cs (line: 596'ish)

    Code (CSharp):
    1.  
    2. [NonSerialized]
    3. internal UniversalRenderPipelineEditorResources m_EditorResourcesAsset;
    4.  
    5. public static readonly string packagePath = "Packages/com.unity.render-pipelines.universal";
    6. public static readonly string editorResourcesGUID = "a3d8d823eedde654bb4c11a1cfaf1abb";
    7.  
    Now for the love of gamers, Unity!! Please, why!? Why can't we specify our own UniversalRenderPipelineEditorResources the same as with PostProcessData in our URP Renderer?

    Anyway, in order to make this work, follow the same steps as with PostProcessData —
    1. Copy the shader you want to modify to your Assets folder, again with all .hlsl files it includes. I wanted to change WavingGrassBillboard.shader (Packages/com.unity.render-pipelines.universal/Shaders/Terrain/WavingGrassBillboard.shader)
    2. Copy the UniversalRenderPipelineEditorResources Scriptable Object (Packages/com.unity.render-pipelines.universal/Runtime/Data/UniversalRenderPipelineEditorResources.asset) I placed it in my Assets/Settings/ as well, the location path is important for our script later, so just make sure you reference it correctly in the script wherever you place it.
    3. Again, view the Debug Inspector on your custom SO and replace the shader reference with your custom shader. Example replacing the WavingGrassBillboard shader: (https://imgur.com/a/GcW4IIJ)
    Now the semi-hacky part...

    Place the following script in your Assets/Editor folder — Remember to change customAssetPath to your Custom UniversalRenderPipelineEditorResources SO path.

    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEngine.Rendering;
    4. using UnityEditor;
    5. using System.Reflection;
    6. using UnityEngine.Rendering.Universal;
    7.  
    8. public class CustomURPEditorResourcesLoader : MonoBehaviour
    9. {
    10.     [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    11.     private static void ReplaceURPEditorResources()
    12.     {
    13.         UniversalRenderPipelineAsset urpAsset = GraphicsSettings.renderPipelineAsset as UniversalRenderPipelineAsset;
    14.         if (urpAsset == null)
    15.         {
    16.             Debug.LogError("Not using URP.");
    17.             return;
    18.         }
    19.  
    20.         // Load your custom URP Editor Resources
    21.         string customAssetPath = "Assets/Settings/Custom-URP-EditorResources.asset";
    22.         var customEditorResources = AssetDatabase.LoadAssetAtPath<UniversalRenderPipelineEditorResources>(customAssetPath);
    23.  
    24.         if (customEditorResources == null)
    25.         {
    26.             Debug.LogError("Custom URP Editor Resources asset not found.");
    27.             return;
    28.         }
    29.  
    30.         // Use reflection to set the internal field
    31.         FieldInfo editorResourcesField = typeof(UniversalRenderPipelineAsset).GetField("m_EditorResourcesAsset", BindingFlags.NonPublic | BindingFlags.Instance);
    32.         if (editorResourcesField != null)
    33.         {
    34.             editorResourcesField.SetValue(urpAsset, customEditorResources);
    35.             Debug.Log("URP Editor Resources replaced successfully.");
    36.         }
    37.         else
    38.         {
    39.             Debug.LogError("Unable to find the internal field in URP Asset.");
    40.         }
    41.     }
    42. }
    43.  
    That's it. Note that after the script compiles, it didn't use my custom shader in the editor until the first run, but afterward the shader works. Again, I would test with just changing a color, or such to make sure it is running your custom shader.

    Few after thoughts and notes:

    • The main solution at the end of the original thread I followed, was to override UniversalRenderPipelineAsset itself and set the terrainDetailGrassBillboardShader (or any other shaders you want to override) directly. This did work, however in my case, only in the Editor. Once I tried an actual Build, it had errors because of serialization differences. I tried many things but could not figure it out, hence coming up with the alternative solution.
    • I have yet to run into any issues by replacing the UniversalRenderPipelineEditorResources reference in the editor, but it seems to be only an editor set reference, which then translates to build time. My Builds use my custom shader, so it seems pretty stable, no warnings / errors in Player log.
    • Please Unity, why would this be so difficult to change / fix? Allow your customers some flexibility here, your terrain shader is OK for a demo, but the wind is horribly implemented and what the F is the grass tint color @#%@... give us the freedom!
    • If this solution is already known, I don't know how I missed it, or if there is a WAY easier way to do this, please reply and show me the error of my stupid ways. XD
    Peace.
     
  2. GoGoGadget

    GoGoGadget

    Joined:
    Sep 23, 2013
    Posts:
    864
    What do you achieve by replacing FinalPost? It's not really performing a point filter, it's just sampling the last blit texture. For any other effects, you can inject your own postpro separately using Renderer Features.
     
  3. jacques_unity12

    jacques_unity12

    Joined:
    Oct 1, 2023
    Posts:
    6
    Hi, I'm not sure you understood the issue regarding replacing the Waving Grass Billboard shader. How would you do this with a Renderer Feature, whilst still using standard Unity Terrain Billboard Grass details? (edit: Apologies, I reread your post, you are only referring to post processing, not the grass or other terrain shaders, which still is the only valid solution that worked in my case)

    Also, Here is the replacement for Point Sampling (or Upscaling Filtering) which I replaced in FinalPost, I would be happy to hear about another solution to replace, not add, this kind of upscaling filtering. There is simply no way to achieve this without overriding the actual FinalPost shader in my opinion. I also don't see how you could do this with a Renderer Feature since you can't disable the default Upscaling Filtering, you can just select a type.



    Code (CSharp):
    1.          
    2. #if _POINT_SAMPLING
    3.             // Default nearest-neigbour (point sampling) upscaling
    4.             //half3 color = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_PointClamp, uv).xyz;
    5.          
    6.             // Custom pixel perfect upscaling (thanks tessel8r)
    7.             // box filter size in texel units
    8.             float2 boxSize = clamp(fwidth(uv) * _BlitTexture_TexelSize.zw, 1e-5, 1);
    9.             // scale uv by texture size to get texel coordinate
    10.             float2 tx = uv * _BlitTexture_TexelSize.zw - 0.5f * boxSize;
    11.             // compute offset for pixel-sized box filter
    12.             float2 txOffset = smoothstep(1-boxSize, 1, frac(tx));
    13.             // compute bilinear sample uv coordinates
    14.             float2 uv_bilinear = (floor(tx)+0.5+txOffset) * _BlitTexture_TexelSize.xy;
    15.             // sample the texture
    16.             half3 color = SAMPLE_TEXTURE2D_GRAD(_BlitTexture, sampler_LinearClamp, uv_bilinear, ddx(uv), ddy(uv)).rgb;
    17.  
     

    Attached Files:

    Last edited: Dec 12, 2023
  4. wwWwwwW1

    wwWwwwW1

    Joined:
    Oct 31, 2021
    Posts:
    744
    Hi, to customize URP, you can try moving the ".../Library/PackageCache/URP-package" to ".../Packages/URP-package".
     
  5. wwWwwwW1

    wwWwwwW1

    Joined:
    Oct 31, 2021
    Posts:
    744
  6. jacques_unity12

    jacques_unity12

    Joined:
    Oct 1, 2023
    Posts:
    6
    Hi!

    Thank you for this suggestion, I will definitely add my request on the roadmap!

    Yes, I'm aware the standard solution is just to place the URP package into your Assets and modify to your heart's content, but then you're stuck to that URP version or faced with porting your changes on each update. I specifically wanted to avoid this. My approach seems quite lightweight for the interim and I doubt it'll break on updates until Unity changes how the specific SO are referenced.

    I also feel not raising these issues for customisation and just modifying your own URP copy incentivises Unity not to address these issues. Hence the post being marked Feature Request and the reason for posting besides sharing my solution for anyone else in the same boat.

    I am jealous of HDRP's customisation in this regard. I will definitely request this for URP.

    Very much appreciate your suggestion.
     
    wwWwwwW1 likes this.