Robinseibold / Unity-URP-Outlines

A custom renderer feature for screen space outlines
MIT License
590 stars 51 forks source link

ScriptableRenderPass.Blit is obsolete #15

Closed gigazelle closed 8 months ago

gigazelle commented 1 year ago

I'm currently on Unity 2022.1, and get the following warning (twice, for two lines of code):

Assets\BL\Scripts\Rendering\ScreenSpaceOutlines.cs(167,17): warning CS0618: 'ScriptableRenderPass.Blit(CommandBuffer, RenderTargetIdentifier, RenderTargetIdentifier, Material, int)' is obsolete: 'Use RTHandles for source and destination'

The culprit lines of code are the following:

Blit(cmd, cameraColorTarget, temporaryBuffer);
Blit(cmd, temporaryBuffer, cameraColorTarget, screenSpaceOutlineMaterial);
Robinseibold commented 1 year ago

Interesting! I'm still using Unity 2021.2.7f1 so URP 12.1.8, while Unity 2022.1 uses URP 13.1.9. The Blit method seems to change from:

Blit(CommandBuffer, RenderTargetIdentifier, RenderTargetIdentifier, Material, Int32)

Add a blit command to the context for execution. This changes the active render target in the ScriptableRenderer to destination.

Declaration

public void Blit(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier destination, 
    Material material = null, int passIndex = 0)
Parameters Type Name Description
CommandBuffer cmd Command buffer to record command for execution.
RenderTargetIdentifier source Source texture or target identifier to blit from.
RenderTargetIdentifier destination Destination texture or target identifier to blit into. This becomes the renderer active render target.
Material material Material to use.
Int32 passIndex Shader pass to use. Default is 0.

to

Blit(CommandBuffer, RTHandle, RTHandle, Material, Int32)

Add a blit command to the context for execution. This changes the active render target in the ScriptableRenderer to destination.

Declaration

public void Blit(CommandBuffer cmd, RTHandle source, RTHandle destination, 
    Material material = null, int passIndex = 0)
Parameters Type Name Description
CommandBuffer cmd Command buffer to record command for execution.
RTHandle source Source texture or target handle to blit from.
RTHandle destination Destination texture or target handle to blit into. This becomes the renderer active render target.
Material material Material to use.
Int32 passIndex Shader pass to use. Default is 0.

I haven't looked thoroughly through the changes, but after a quick glance it seems quite straight forward to change the code to use RTHandle instead of RenderTargetIdentifier. I won't have the time to look into it more before Christmas, since I've got a lot to do, but might dive into it after - it's time to upgrade my project soon anyway. Will comment here when I've looked into it, please do the same if anyone decides to do it earlier.

On another note, this might mean that the temporaryBuffer workaround in the current ScreenSpaceOutlinePass : ScriptableRenderPass implementation is no longer needed (should be possible to do Blit(cmd, cameraColorTarget, cameraColorTarget);), but one can only wish!

WolfEYc commented 1 year ago

+1

Pholith commented 1 year ago

+1, RenderTargetHandle instead of RTHandle can't be compiled in 2023.2.0b10

rkulwicki commented 11 months ago

+1, anybody get this to work in 2023.x? I've swapped for the new usage of Blit with RTHandles (eliminating the use of temporaryBuffer), but it doesn't appear to be working.

dasfoxx commented 11 months ago

+1 Same boat. Just restarted a project that was using this and upgraded it to 2023 and attempted the same kind of fixes. No luck yet though so I'll be coming back to it later

Pholith commented 11 months ago

I tried to update the script using mainly urp doc and this forum thread. I made it build but doesn't work anymore for the moment, I don't really understand how it works but I think that this attempt is getting closer to the solution. New things are in UNITY_2023_1_OR_NEWER blocs for retro-compatibility and readability.

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class ScreenSpaceOutlines : ScriptableRendererFeature
{

    [System.Serializable]
    private class ScreenSpaceOutlineSettings
    {

        [Header("General Outline Settings")]
        public Color outlineColor = Color.black;
        [Range(0.0f, 20.0f)]
        public float outlineScale = 1.0f;

        [Header("Depth Settings")]
        [Range(0.0f, 100.0f)]
        public float depthThreshold = 1.5f;
        [Range(0.0f, 500.0f)]
        public float robertsCrossMultiplier = 100.0f;

        [Header("Normal Settings")]
        [Range(0.0f, 1.0f)]
        public float normalThreshold = 0.4f;

        [Header("Depth Normal Relation Settings")]
        [Range(0.0f, 2.0f)]
        public float steepAngleThreshold = 0.2f;
        [Range(0.0f, 500.0f)]
        public float steepAngleMultiplier = 25.0f;

    }

    [System.Serializable]
    private class ViewSpaceNormalsTextureSettings
    {

        [Header("General Scene View Space Normal Texture Settings")]
        public RenderTextureFormat colorFormat;
        public int depthBufferBits = 16;
        public FilterMode filterMode;
        public Color backgroundColor = Color.black;

        [Header("View Space Normal Texture Object Draw Settings")]
        public PerObjectData perObjectData;
        public bool enableDynamicBatching;
        public bool enableInstancing;

    }

    private class ViewSpaceNormalsTexturePass : ScriptableRenderPass
    {

        private ViewSpaceNormalsTextureSettings normalsTextureSettings;
        private FilteringSettings filteringSettings;
        private FilteringSettings occluderFilteringSettings;

        private readonly List<ShaderTagId> shaderTagIdList;
        private readonly Material normalsMaterial;
        private readonly Material occludersMaterial;

#if UNITY_2023_1_OR_NEWER
        private RTHandle normals;
#else
        private readonly RenderTargetHandle normals;
#endif
        public ViewSpaceNormalsTexturePass(RenderPassEvent renderPassEvent, LayerMask layerMask, LayerMask occluderLayerMask, ViewSpaceNormalsTextureSettings settings)
        {
            this.renderPassEvent = renderPassEvent;
            this.normalsTextureSettings = settings;
            filteringSettings = new FilteringSettings(RenderQueueRange.opaque, layerMask);
            occluderFilteringSettings = new FilteringSettings(RenderQueueRange.opaque, occluderLayerMask);

            shaderTagIdList = new List<ShaderTagId> {
                new ShaderTagId("UniversalForward"),
                new ShaderTagId("UniversalForwardOnly"),
                new ShaderTagId("LightweightForward"),
                new ShaderTagId("SRPDefaultUnlit")
            };

#if UNITY_2023_1_OR_NEWER
            normals = RTHandles.Alloc("_SceneViewSpaceNormals", name: "_SceneViewSpaceNormals");
#else
            normals.Init("_SceneViewSpaceNormals");
#endif
            normalsMaterial = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/ViewSpaceNormals"));
            occludersMaterial = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/UnlitColor"));
            occludersMaterial.SetColor("_Color", normalsTextureSettings.backgroundColor);
        }

        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        {
            RenderTextureDescriptor normalsTextureDescriptor = cameraTextureDescriptor;
            normalsTextureDescriptor.colorFormat = normalsTextureSettings.colorFormat;
            normalsTextureDescriptor.depthBufferBits = normalsTextureSettings.depthBufferBits;

            //RTHandles
#if UNITY_2023_1_OR_NEWER
            RenderingUtils.ReAllocateIfNeeded(ref normals, normalsTextureDescriptor, normalsTextureSettings.filterMode);
            ConfigureTarget(normals);
#else
            cmd.GetTemporaryRT(normals.id, normalsTextureDescriptor, normalsTextureSettings.filterMode);
            ConfigureTarget(normals.Identifier());
#endif
            ConfigureClear(ClearFlag.All, normalsTextureSettings.backgroundColor);
        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            if (!normalsMaterial || !occludersMaterial)
                return;

            CommandBuffer cmd = CommandBufferPool.Get();
            using (new ProfilingScope(cmd, new ProfilingSampler("SceneViewSpaceNormalsTextureCreation")))
            {
                context.ExecuteCommandBuffer(cmd);
                cmd.Clear();

                DrawingSettings drawSettings = CreateDrawingSettings(shaderTagIdList, ref renderingData, renderingData.cameraData.defaultOpaqueSortFlags);
                drawSettings.perObjectData = normalsTextureSettings.perObjectData;
                drawSettings.enableDynamicBatching = normalsTextureSettings.enableDynamicBatching;
                drawSettings.enableInstancing = normalsTextureSettings.enableInstancing;
                drawSettings.overrideMaterial = normalsMaterial;

                DrawingSettings occluderSettings = drawSettings;
                occluderSettings.overrideMaterial = occludersMaterial;
                context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filteringSettings);
                context.DrawRenderers(renderingData.cullResults, ref occluderSettings, ref occluderFilteringSettings);
            }

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        public override void OnCameraCleanup(CommandBuffer cmd)
        {
#if UNITY_2023_1_OR_NEWER
            if (cmd == null)
            {
                throw new System.ArgumentNullException("cmd");
            }
            normals = null;
#else
            cmd.ReleaseTemporaryRT(normals.id);
#endif
        }

#if UNITY_2023_1_OR_NEWER
        public void Dispose()
        {
            normals?.Release();
        }
#endif
    }

    private class ScreenSpaceOutlinePass : ScriptableRenderPass
    {

        private readonly Material screenSpaceOutlineMaterial;

#if UNITY_2023_1_OR_NEWER
        RTHandle cameraColorTarget;
        RTHandle temporaryBuffer;
#else
        RenderTargetIdentifier cameraColorTarget;
        RenderTargetIdentifier temporaryBuffer;
        int temporaryBufferID = Shader.PropertyToID("_TemporaryBuffer");
#endif

        public ScreenSpaceOutlinePass(RenderPassEvent renderPassEvent, ScreenSpaceOutlineSettings settings)
        {
            this.renderPassEvent = renderPassEvent;

            screenSpaceOutlineMaterial = CoreUtils.CreateEngineMaterial(Shader.Find("Hidden/Outlines"));
            screenSpaceOutlineMaterial.SetColor("_OutlineColor", settings.outlineColor);
            screenSpaceOutlineMaterial.SetFloat("_OutlineScale", settings.outlineScale);

            screenSpaceOutlineMaterial.SetFloat("_DepthThreshold", settings.depthThreshold);
            screenSpaceOutlineMaterial.SetFloat("_RobertsCrossMultiplier", settings.robertsCrossMultiplier);

            screenSpaceOutlineMaterial.SetFloat("_NormalThreshold", settings.normalThreshold);

            screenSpaceOutlineMaterial.SetFloat("_SteepAngleThreshold", settings.steepAngleThreshold);
            screenSpaceOutlineMaterial.SetFloat("_SteepAngleMultiplier", settings.steepAngleMultiplier);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            RenderTextureDescriptor temporaryTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;
            temporaryTargetDescriptor.depthBufferBits = 0;

#if UNITY_2023_1_OR_NEWER
            temporaryBuffer = RTHandles.Alloc("_TemporaryBuffer",name: "_TemporaryBuffer");
            RenderingUtils.ReAllocateIfNeeded(ref temporaryBuffer, temporaryTargetDescriptor, wrapMode: TextureWrapMode.Clamp, name: "_TemporaryBuffer", filterMode: FilterMode.Bilinear);
            cameraColorTarget = renderingData.cameraData.renderer.cameraColorTargetHandle;
#else
            cmd.GetTemporaryRT(temporaryBufferID, temporaryTargetDescriptor, FilterMode.Bilinear);
            temporaryBuffer = new RenderTargetIdentifier(temporaryBufferID);
            cameraColorTarget = renderingData.cameraData.renderer.cameraColorTarget;
#endif

        }
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            if (!screenSpaceOutlineMaterial)
                return;

            CommandBuffer cmd = CommandBufferPool.Get();
            using (new ProfilingScope(cmd, new ProfilingSampler("ScreenSpaceOutlines")))
            {

                Blit(cmd, cameraColorTarget, temporaryBuffer);
                Blit(cmd, temporaryBuffer, cameraColorTarget, screenSpaceOutlineMaterial);
            }

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }

        public override void OnCameraCleanup(CommandBuffer cmd)
        {
#if UNITY_2023_1_OR_NEWER
            if (cmd == null)
            {
                throw new System.ArgumentNullException("cmd");
            }
            temporaryBuffer = null;
            cameraColorTarget = null;
#else
            cmd.ReleaseTemporaryRT(temporaryBufferID);
#endif
        }
#if UNITY_2023_1_OR_NEWER
        public void Dispose()
        {
            temporaryBuffer?.Release();
            cameraColorTarget?.Release();
        }
#endif
    }

    [SerializeField] private RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    [SerializeField] private LayerMask outlinesLayerMask;
    [SerializeField] private LayerMask outlinesOccluderLayerMask;

    [SerializeField] private ScreenSpaceOutlineSettings outlineSettings = new ScreenSpaceOutlineSettings();
    [SerializeField] private ViewSpaceNormalsTextureSettings viewSpaceNormalsTextureSettings = new ViewSpaceNormalsTextureSettings();

    private ViewSpaceNormalsTexturePass viewSpaceNormalsTexturePass;
    private ScreenSpaceOutlinePass screenSpaceOutlinePass;

    public override void Create()
    {
        if (renderPassEvent < RenderPassEvent.BeforeRenderingPrePasses)
            renderPassEvent = RenderPassEvent.BeforeRenderingPrePasses;

        viewSpaceNormalsTexturePass = new ViewSpaceNormalsTexturePass(renderPassEvent, outlinesLayerMask, outlinesOccluderLayerMask, viewSpaceNormalsTextureSettings);
        screenSpaceOutlinePass = new ScreenSpaceOutlinePass(renderPassEvent, outlineSettings);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(viewSpaceNormalsTexturePass);
        renderer.EnqueuePass(screenSpaceOutlinePass);
    }
}
Chishikii commented 10 months ago

Anyone here that got this to work? I've tried and failed miserably.

rkulwicki commented 10 months ago

Haha unfortunately not. I ended up using a different Outline method.

On Thu, Jan 11, 2024, 5:13 AM Finn Pelzer @.***> wrote:

Anyone here that got this to work? I've tried and failed miserably.

— Reply to this email directly, view it on GitHub https://github.com/Robinseibold/Unity-URP-Outlines/issues/15#issuecomment-1886910700, or unsubscribe https://github.com/notifications/unsubscribe-auth/AGDBXEIXP2W2SDOU23WS6MDYN7CL5AVCNFSM6AAAAAAS3F5GJ6VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQOBWHEYTANZQGA . You are receiving this because you commented.Message ID: @.***>

adrian-miasik commented 10 months ago

Anyone here that got this to work? I've tried and failed miserably.

Heya, I'm trying to get this to work in 2023.2.0f1.

Everyone seems to be on a similar track. Decided to throw my hat into the ring but unfortunately...no luck either.

I've tried converting the render feature in different ways and have managed to get rid of compilation errors, but then my visuals were entirely incorrect when the renderer feature was on, so my conversions weren't successful either.

@rkulwicki, which outline method did you end up using?

Chishikii commented 10 months ago

Alright so after some figuring out how the new API works I have managed to update the feature. Quite a lot has changed and ill link a repository later when i get the time to create it.

Edit: The repository can be found here. @Robinseibold I currently don't have the time to write a pull request for this repo but you're free to use whatever you need if you want to update it.

adrian-miasik commented 9 months ago

Oh my gosh.... @Chishikii you're a life saver! This is great!

I'm playing around with your implementation right now, and it's really clean. I like the way you've structured it too.

Surprisingly enough, this actually also fixes a couple issues I've had with the previous iteration:

I'm off to go workout now, but thanks for posting your repo @Chishikii . I'll likely try to implement this into the project later in a PR unless someone beats me to it. Now I don't have to look into a different solution! I'm happy 😄

adrian-miasik commented 9 months ago

Hmmm still running into memory leaks and crashes. High memory usage.

Chishikii commented 9 months ago

I've found some resources i didn't release correctly and updated the project. I am not seeing any memory leaks.

XtroTheArctic commented 9 months ago

@Chishikii Thank you again but I am confused on something...

Can you explain how Render Layer Mask works? When I change it's value like 0,1,2 etc... The different objects has outlines in your feature scene. I can't figure out what's the rule about that.

Chishikii commented 9 months ago

@XtroTheArctic There are two different systems for layers in unity. One is the regular layer you define at the very top of objects next to tags Documentation. And the other one is defined in the MeshRenderer of objects. This layer was introduced with SRPs to help layer rendering such as light or in this case render passes Documentation

So if you want to combine both layers you can achieve some very specific filtering. You could use both or a combination of some of them. So if you put a layer in only objects by that layer get affected by the feature (such as sphere or cubes in my repo) and then further specify it by setting the render layer.

XtroTheArctic commented 9 months ago

I didn't know about the second layer system which came with SRP. Thank you for teaching me!

XtroTheArctic commented 9 months ago

Hello @Chishikii

I finally had time to try your version of the outline feature and unfortunately I have significant problems with it :( It doesn't render a type of outline similar to Robinseibold's outline. Can you help me somehow fix these problems of mine please?

I'm making a city builder game and I'm using the outline feature only for rendering the city borders. Inside of the borders is the player's buildable area. This buildable area is pre-baked as a planar mesh by the map designer. During game play, this mesh is rendered on top of the terrain but only using the outline effect. The material of the mesh itself is fully transparent so the build area mesh itself is not visible in the game.

When there is a big obstacle in the buildable area (like a huge rock), that obstacle create a hole on the build area mesh. This way I can render the internal borders around the obstacle but still as a planar shape. This is done automatically by the outline effect.

This is an example of a pre-baked buildable area mesh: image

This is how it was rendered in Unity 2022.2.13 using Robinseibold's outline effect: image

This is the outline settings using Robinseibold's outline effect: image

This is how it is rendered in Unity 2023.2.2 using your outline feature: image

This is the settings of your outline effect: image

I explained the 3 problems of mine on the screenshot of your outline feature above.

What do you think about this? Is this something you can try to help me some how?

adrian-miasik commented 9 months ago

Uh oh, this is not what I wanted to read after making a PR out of it 😅

XtroTheArctic commented 9 months ago

Hmmm. Yea. It sounds problematic. Sorry :(

I see that you made changes in the shader graph file too, not only in the feature script. I'm not a shader expert but I want to understand. Did the shader need to be updated along with the feature script or would it be possible to use the original shader with the updated feature script?

Asking this because, the problems I described may be caused by the shader changes; not the script.

adrian-miasik commented 9 months ago

Damn, sorry to see you're running into this problem.

Sadly it appears both shader and code changes are necessary. I've tried to break @Chishikii's approach bit by bit and wasn't able to get it outlines to render until I changed the graph settings to fullscreen material + adding in the sample buffer URP node. I wish I understood this better on a technical level to assist you

Chishikii commented 9 months ago

I'll have time to figure this out on Monday. Can you provide me with some minimum working example that showcases your problem, like a fork of my repo? That would help a lot. Ill also try to add occluder objects as well.

I assume this has either to do with transparency, or depth testing. I'm not entirely sure what exactly changed in the engine but the fullscreen shader graph is required.

Could you maybe share what the textures for the first blit pass has for textures. You can access this information in the frame debugger. I'm interested in these three textures.

Screenshot 2024-01-21 125233
XtroTheArctic commented 9 months ago

I'll try to gather info on what you need but in the meantime, I noticed another problem.

The underwater border problem I showed in the screenshot earlier goes away when I change the render pass event from "after transparents" to "after opaques" BUT this time... the foam effect on my water disappears.

image

XtroTheArctic commented 9 months ago

Full screen shader is causing another problem. The outline color is bleeding into the screen edges.

image

XtroTheArctic commented 9 months ago

Here are the screenshots of all outline entries 1 by 1.

image image image image image image

And these are the internal textures in the first Draw Procedural entry: Blit: image Camera depth: image Normals: image

XtroTheArctic commented 9 months ago

This is how the sceneviewspacenormals looked like in the old Unity and using Robinseibold's outline: image

Please notice that the entire "build area" mesh is rendered intact without any artifacts/holes caused by trees or terrain itself. The only hole it has is the hole I cut out during pre-baking the mesh itself for the big rock obstacle.

Chishikii commented 9 months ago

Alright this is definitely a issue with the depth evaluation. Sadly I'm currently not smart enough to understand the exact implication or specifics but I've tried to restore the same behavior.

Essentially, the renderer list get drawn the objects still consider the depth of all other objects in the scene. This can be prevented by removing the depth texture as a target but i assume that's not the correct approach.

As for the bleeding on the side i couldn't replicate that, could you provide some steps and open an issue?

If you have any more issues with my implementation I'd appreciate if we could also move the discussion over to my repo so we don't spam this one with unrelated info ;)

XtroTheArctic commented 9 months ago

Thank you for all your help. I created an issue on your repo. https://github.com/Chishikii/URP-Render-Features/issues/1

XtroTheArctic commented 9 months ago

I wanted to update this conversation about my problem. Chishikii fixed it!!!

image