#custom renderer feature q

1 messages ยท Page 1 of 1 (latest)

mossy oak
#

I have an article here which might be useful (can't remember if I linked it before) https://www.cyanilux.com/tutorials/custom-renderer-features/
Also links to some other examples, including blurring - though those might be a bit outdated. It can be difficult to find resources as things in URP keep changing - (even the code structure in the post has changed in Unity 6 due as you're supposed to use the RenderGraph api instead. It's hard to keep up ๐Ÿ˜ฎโ€๐Ÿ’จ )

A tilemap (or any object) can't be referenced directly afaik, but ScriptableRenderContext.DrawRenderers or CreateRendererList and cmd.DrawRendererList may be something to look into, can be filtered to a specific layer.
Or render it to a render texture first using a second camera instead.

keen flume
# mossy oak I have an article here which might be useful (can't remember if I linked it befo...

Oh yes! I've been referencing this article (it's great!) and I've definitely learned a lot of the basic concept of creating the render feature, however for the parts that don't really seem to have much documentation (mainly the tilemap situation), it seems REALLY hard to find a clear answer as to how I should approach it, would setting just the layer and drawing the RT be the best way to your knowledge (performance-wise)?

#

Also, another quick question regarding RTHandles that I am not 100% on, to my knowledge they are supposed to create a texture that references the cameras dimensions (presumably for fullscreen effects such as my own), however when declaring an RTHandle it seems to ask for argument declarations inside the RTHandle declaration itself?

keen flume
mossy oak
keen flume
mossy oak
keen flume
keen flume
keen flume
# mossy oak I have an article here which might be useful (can't remember if I linked it befo...

Hey, so I am currently creating my first pass for referencing both the tilemap and light texture, and for the tilemap I am trying to look for any documentation which references the ability to render a specific layer or even how to initialize a ScriptableRenderContext.DrawRenderer in general. Just wondering how exactly you'd go about creating a temporary RTHandle or RT of a tagged object in the scene, (for example my Tilemap has the tag "Ground" how would I reference all objects in camera to draw to an RTHandle or Texture)?

mossy oak
# keen flume Hey, so I am currently creating my first pass for referencing both the tilemap a...

I'd recommend looking at my feature example code here (click buttons to foldout)

That shows how to set up a custom RTHandle, and a DrawRenderers call with a given LayerMask. (Must use layers, not tags)
Also creates a second "_TemporaryColorTexture" RTHandle used in Blitter.BlitCameraTexture calls, which is similar to how you'll apply a blur (via the blitMaterial). Might need to expose another material for the second blit for your use-case, not sure off the top of my head.

Also notes :

  • 2023 is meant to use Renderer Lists instead, as briefly mentioned here
  • Override material is optional, kinda depends how you handle things. (If it's a mask you might want to use a fully white material?)
  • The example is meant for 3D you may need to edit the RenderQueueRange in FilteringSettings, possibly SortingCriteria too
#

custom renderer feature q

keen flume
# mossy oak I'd recommend looking at my feature example code [here](https://www.cyanilux.com...

I see, I do think I now understand how the LayerMask reference works using settings. However, I don't think I quite grasp a lot of the syntax of the reference example and how they apply to my own render feature, I really don't know what a "ShaderTag" is, or a ProfilingScope and from other explanations online of how to create a custom render feature I haven't seen these present, so I am honestly somehow more confused now just because of all these different approaches to what seems to be the same expected output. I am trying to find a way to take in all these concepts in a way that makes sense logically to my brain which I haven't accomplished yet, I'm not sure how to phrase it exactly but I can see and read and understand how this is a completed render feature I am looking at, but I couldn't even begin to explain or even apply many of these concepts to my own render feature, since to me it seems to lack a (if this, then output this as a variable "workflow"), which I'd like to try to overcome.

#

And I do sincerely apologize if it seems that you are trying to explain this to a brick wall notlikethis , because honestly my brain is very overwhelmed trying to learn this with very very limited documentation or any explanation as to what for example "RenderingData" or what "Context" work in practice ๐Ÿ˜ญ I can't really seem to find a concrete explanation as to what data is stored or pulled from them.

#

Also for "RTHandles" is there a way to dynamically create texture sizes based on the camera they are rendering to, like if I want to get all of my tilemap that is actually in the camera frame will it just automatically adjust to fill it's size?

mossy oak
# keen flume I see, I do think I now understand how the LayerMask reference works using setti...

The "ShaderTag" refers to the Tags { "LightMode"="" } in shaders. You shouldn't need to worry much about it, the SRPDefaultUnlit and UniversalForward handle most cases.
Could maybe add Universal2D as well, idk if it's needed though.

Using other tags instead can be useful if you have custom shaders with additional passes but it's more of a shader code thing. Graphs only generate specific passes based on the target/material mode.

ProfilingScope is related to making the rendering commands appear in the Profiler and FrameDebugger windows. Not necessarily required, but can be useful

keen flume
mossy oak
keen flume
#

And also thank you very much, this has been very helpful so far!

mossy oak
keen flume
#

Oh okay!

mossy oak
keen flume
#

So if I were to try to get a texture from all visible tilemap to camera, in pseudocode do I:

  • declare an empty RTHandle
  • Assign the TargetHandle to the empty RTHandle
  • then draw the scene using a LayerMask of just the layer with the tilemap (to the targethandle?)
    which should give me a blank RTHandle with just the visible TIlemap visible?
keen flume
#

Okay, I'll see what I can do ahah! But I am still a little iffy on where "OnCameraSetup" and the "Execute" methods differ. But I guess just getting into it might be my best bet now.

mossy oak
#

I think there can be a little bit of overlap, but in general OnCameraSetup is where you allocate/set render targets (i.e. using ConfigureTarget(..) as in my example) and Execute is where the context/cmd function calls go.

keen flume
#

I appreciate the continued help, I know it is probably quite annoying to repeat decently basic concepts to me over and over, but it does truly mean a ton. I've been trying to get this working for almost two full weeks now, (and had to deal with a break-in to my house while I was home during it haha) but I feel like I am finally starting to see the beginning of the end to a working solution.

keen flume
keen flume
#
public class MaskPass : ScriptableRenderPass // Initial Pass that will sample Tilemap Layer as Texture, and Light Texture and returned the masked value.
    {
        private MaskSettings settings;

        private FilteringSettings filteringSettings;
        private DrawingSettings drawingSettings;

        private RTHandle rtTilemapMaskTexture;
        public Material maskShader;

        public MaskPass(MaskSettings settings, string name)
        {
            this.settings = settings;
            filteringSettings = new FilteringSettings(RenderQueueRange.opaque, settings.layerMask);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            // reference to what the camera sees I assume:
            var colorDesc = renderingData.cameraData.cameraTargetDescriptor;


            // Draw the Tilemap to the empty RTHandle:
            RenderingUtils.ReAllocateIfNeeded(ref rtTilemapMaskTexture, colorDesc, name: "_TilemapMaskTexture");

        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get();

            // no idea what these do
            SortingCriteria sortingCriteria = renderingData.cameraData.defaultOpaqueSortFlags;
            DrawingSettings drawingSettings = CreateDrawingSettings(null, ref renderingData, sortingCriteria);

            // praying this just works
            context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);

        }
    }
    public MaskSettings settings = new MaskSettings();

    MaskPass maskPass;
    public override void Create()
    {
        maskPass = new MaskPass(settings, name);
        maskPass.renderPassEvent = settings._event;
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(maskPass);
    }
#

So if I have this pass and I would like to test to see if it is outputting the RTHandle I am aiming for, would that be logged in the Frame Debugger if it worked?

mossy oak
#

Mmm yes should do. Though not sure if you need a ProflingScope to make it appear correctly.
You also need to add the feature to the Renderer Features list on your Universal or 2D Renderer Asset or it won't actually be used.

keen flume
#

Actually wait, I am realizing I didn't set the layer of my tilemap

mossy oak
#

Yeah it'll only appear in the FrameDebugger if there's actually objects to render. The tilemap might be using a transparent shader, so I'd also try RenderQueueRange.transparent in the FilteringSettings

#

SortingCriteria.CommonTransparent is probably also more appropiate for tilemap/sprites

keen flume
keen flume
mossy oak
#

I'm also not sure what using null as the shadertags does in CreateDrawingSettings(). I would use the List<ShaderTagId> shaderTagsList like in my example.

mossy oak
keen flume
mossy oak
#

Either
SortingCriteria sortingCriteria = SortingCriteria.CommonTransparent
Or just SortingCriteria.CommonTransparent as the param and remove the variable

keen flume
#

would this be my renderer feature? ๐Ÿ˜ญ

mossy oak
#

Not sure, toggling the feature on/off might tell you. The frame debugger should also say what target it's rendering into somewhere on the right while it's selected. (should be something like _TilemapMaskTexture_(size))

keen flume
#

Yeah, I'm not seeing that anywhere :/

#

I should probably head to bed now anyways and pick this back up tomorrow, I think I've got the basic concepts down better now so hopefully I can get this working tomorrow on a good night's sleep!

#

I really appreciate the help though truly you are a real one for that, it's pretty hard to find people who even know enough to help so it really does mean a lot.

keen flume
# mossy oak Not sure, toggling the feature on/off might tell you. The frame debugger should ...

Haha, just found something you might find cool. Your website is the default source for Google's "Gemini" AI on Custom Render Features. Even before Unity's official documentation. Gemini does a really great job of explaining all of my dumb questions so I don't have to bother you or anyone else. I've got a lot of the basic principles down I think now besides having the render feature actually show up in the frame debugger, still not too sure about that one.

keen flume
#
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System.Collections.Generic;

public class EternalBlockShadingRenderFeature : ScriptableRendererFeature
{
    //public Material maskPassShaderMaterial;
    //public Material blurPassShaderMaterial;

    // Settings for MaskPass
    public MaskSettings settings = new MaskSettings();


    public class MaskSettings
    {
        public bool showInSceneView = true;
        public RenderPassEvent _event = RenderPassEvent.AfterRenderingOpaques;

        [Header("Draw Renderers Settings")]
        public LayerMask layerMask = 2;
        public string colorTargetDestinationID = "";
        //public List<ShaderTagId> shaderTagsList = new List<ShaderTagId>();

        [Header("Blit Settings")]
        public Material blitMaterial;
    }
    // End of Settings for MaskPass

#
public class MaskPass : ScriptableRenderPass
    {
        private MaskSettings settings;
        private ProfilingSampler _profilingSampler;

        //private FilteringSettings filteringSettings;
        private DrawingSettings drawingSettings;

        private RTHandle rtTilemapMaskTexture;

        public MaskPass(MaskSettings settings, string name)
        {
            this.settings = settings;

            _profilingSampler = new ProfilingSampler(name);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            var colorDesc = renderingData.cameraData.cameraTargetDescriptor;

            // Create empty RTHandle if not currently Allocated:
            RenderingUtils.ReAllocateIfNeeded(ref rtTilemapMaskTexture, colorDesc, name: "_TilemapMaskTexture");

    }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get();
            using (new ProfilingScope(cmd, _profilingSampler))
            {
                drawingSettings = new DrawingSettings();

                // Set the render target to our RTHandle
                cmd.SetRenderTarget(rtTilemapMaskTexture); // Use our declared RTHandle directly

                // Filtering settings to capture only layer "2"
                var filterSettings = new FilteringSettings(RenderQueueRange.opaque);

                // No shader needed here, just draw renderers on layer "2"
                context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filterSettings);

            }

            context.ExecuteCommandBuffer(cmd);
            CommandBufferPool.Release(cmd);
        }
    }
#
    MaskPass maskPass;
    public override void Create()
    {
        maskPass = new MaskPass(settings, name);
        Debug.Log(name.ToString());
        maskPass.renderPassEvent = settings._event;
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(maskPass);
    }
}
#

In this render feature I've created, I've noticed that nothing is logging in my frame debugger, although I do believe the code is executing based on the Debug.Log statements running, just wondering if you see any blatantly obvious reasons as to why my "_TilemapMaskTexture" RT wouldn't be showing up, my Tilemap is set to rendering layer 2 as-well as actual layer 2, but Gemini says everything looks fine from whatever it's referencing however, it didn't seem to know or understand much about ProfilingScopes at all. Any tips or suggestions would be very appreciated as always!

mossy oak
# keen flume In this render feature I've created, I've noticed that nothing is logging in my ...

It's recommended to use ConfigureTarget(rtTilemapMaskTexture) method inside OnCameraSetup over cmd.SetRenderTarget. (I think because it allows the feature to properly reset the target for rendering things in later events)

Also of note, while you've used cmd.SetRenderTarget, you would have needed to call context.ExecuteCommandBuffer(cmd); before context.DrawRenderers for it to actually take effect. Keep that in mind when using context. methods.

You should also use cmd.SetGlobalTexture("_SomeGlobalTexture", rtTilemapMaskTexture); to pass the result into shaders as a global texture. You can then sample it in any shader which may help with debugging. (In graphs, untick the "Exposed" tickbox on the texture property to make it global)

keen flume
keen flume
#

Okay so now my render feature is present inside my frame debugger, however it is the parent to all already present renderers / rendered shaders, and I don't see any reference to the texture it should be generating, and shader graph doesn't appear to be receiving it either. I am currently still using the "cmd.SetRenderTarget" for testing purposes as I wasn't getting any reference with the "ConfigureTarget" (haven't tested that much). the Execute before the DrawRenderers seems to be what exposed the Feature name in the debugger so I think I'm on the right track. Do you know which "Layer" I need to set for the LayerMask to be referencing it? It seems there are Sorting Layers, Layers, and Rendering Layers. I'll send my updated relevant code here again, I really appreciate the help!

#
public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            var colorDesc = renderingData.cameraData.cameraTargetDescriptor;

            RenderingUtils.ReAllocateIfNeeded(ref rtTilemapMaskTexture, colorDesc, name: "_TilemapMaskTexture");

            cmd.SetGlobalTexture("_GlobalTilemapMaskTexture", rtTilemapMaskTexture);

        }

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get();
            using (new ProfilingScope(cmd, _profilingSampler))
            {
                drawingSettings = new DrawingSettings();
                var filterSettings = new FilteringSettings(RenderQueueRange.opaque);

                context.ExecuteCommandBuffer(cmd);
                context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filterSettings);

                CommandBufferPool.Release(cmd);
            }

        }
    }
mossy oak
# keen flume Okay so now my render feature is present inside my frame debugger, however it is...

I would try this

public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData) {
    var colorDesc = renderingData.cameraData.cameraTargetDescriptor;

    RenderingUtils.ReAllocateIfNeeded(ref rtTilemapMaskTexture, colorDesc, name: "_TilemapMaskTexture");

    ConfigureTarget(rtTilemapMaskTexture);
    ConfigureClear(ClearFlag.Color, Color.black);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData) {
    CommandBuffer cmd = CommandBufferPool.Get();
    using (new ProfilingScope(cmd, _profilingSampler))
    {
        context.ExecuteCommandBuffer(cmd);
        cmd.Clear();

        drawingSettings = new DrawingSettings();
        var filterSettings = new FilteringSettings(RenderQueueRange.all);
        context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filterSettings);

        cmd.SetGlobalTexture("_GlobalTilemapMaskTexture", rtTilemapMaskTexture);
    }
    context.ExecuteCommandBuffer(cmd);
    cmd.Clear();
    CommandBufferPool.Release(cmd);
}
#

If still nothing appears, new DrawingSettings() might not work. Can try using CreateDrawingSettings(...) similar to the example here.
But maybe add shaderTagsList.Add(new ShaderTagId("Universal2D")); in the constuctor and use SortingCriteria sortingCriteria = SortingCriteria.CommonTransparent instead of defaultOpaqueSortFlags.

keen flume
keen flume
#
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using System.Collections.Generic;

public class EternalBlockShadingRenderFeature : ScriptableRendererFeature
{
    public class Settings
    {
        [Header("Draw Renderers Settings")]
        public LayerMask layerMask = 2;
        public string colorTargetDestinationID = "";

        public bool showInSceneView = true;


        public RenderPassEvent _event = RenderPassEvent.AfterRenderingOpaques;

        [Header("Blit Settings")]
        public Material blitMaterial;
    }
    // End of Settings for MaskPass

    public Settings settings = new Settings();

    private MaskPass m_maskPass;

    public override void Create()
    {
        m_maskPass = new MaskPass(settings, name);
        //Debug.Log(name.ToString());
        m_maskPass.renderPassEvent = settings._event;
    }
#
public class MaskPass : ScriptableRenderPass // Initial Pass that will sample Tilemap Layer as Texture, and Light Texture and returned the masked value.
    {

        private List<ShaderTagId> shaderTagsList = new List<ShaderTagId>();
        private FilteringSettings filteringSettings;
        private ProfilingSampler _profilingSampler;

        private RTHandle rtTilemapMaskTexture;

        private Settings settings;
        public MaskPass(Settings settings, string name) // PASS "CONSTRUCTOR"
        {
            this.settings = settings;
            filteringSettings = new FilteringSettings(RenderQueueRange.opaque, settings.layerMask);

            shaderTagsList.Add(new ShaderTagId("Universal2D"));

            _profilingSampler = new ProfilingSampler(name);
        }

        public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)
        {
            var colorDesc = renderingData.cameraData.cameraTargetDescriptor;


            RenderingUtils.ReAllocateIfNeeded(ref rtTilemapMaskTexture, colorDesc, name: "_TilemapMaskTexture");

            ConfigureTarget(rtTilemapMaskTexture);
            ConfigureClear(ClearFlag.Color, Color.black);
        }
#
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            CommandBuffer cmd = CommandBufferPool.Get();
            using (new ProfilingScope(cmd, _profilingSampler))
            {
                context.ExecuteCommandBuffer(cmd);
                cmd.Clear();

                SortingCriteria sortingCriteria = SortingCriteria.CommonTransparent;
                DrawingSettings drawingSettings = CreateDrawingSettings(shaderTagsList, ref renderingData, sortingCriteria);

                context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
                cmd.SetGlobalTexture("_GlobalTilemapMaskTexture", rtTilemapMaskTexture);

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

        public override void OnCameraCleanup(CommandBuffer cmd) { }

    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(m_maskPass);
    }
}
#

I have tried to replicate your example as best as possible to unfortunately no avail :/ it currently seems like the "private Settings settings;" line in the pass is not being used and I am not sure as to why that would be necessarily, I would assume it should be the preferred declaration as it is in the same method as the reference below.

mossy oak
#

You use RenderQueueRange.opaque in your MaskPass constructor, but if you're dealing with tilemaps it might be using a shader in the transparent queue instead. Either try RenderQueueRange.transparent or RenderQueueRange.all for both queues.

#

I would also add SRPDefaultUnlit to the shader tags list. Maybe even keep UniversalForward still.

mossy oak
keen flume
keen flume
#

I've now added all those changes and still not having any luck with the frame debug or the global reference in a shader graph, should my tilemap's main layer or the rendering layer be matching the layerMask definition do you think?

mossy oak
#

Layermasks refer to what Layer gameobjects are on

keen flume
mossy oak
keen flume
#

so if my "layerMask" is set to int 2 should my tilemap be on the "ignore raycast" layer?

mossy oak
#

Eh, not sure. It's more complicated than that. See LayerMask
But you shouldn't need to set the value directly anyway

#

Have you actually added the feature to the renderer asset used by URP?

keen flume
mossy oak
#

Hm, there should be settings exposed there

#

Do you still have public Settings settings; in the feature?

keen flume
mossy oak
#

Oh maybe [System.Serializable] before the public class Settings

keen flume
#

Okay, good news!

#

It is actually showing in my Frame Debugger now, it wasn't set to the right Layer all along. Revealing it allowed me to set it to the same layer and now it is showing properly!

#

It's not actually drawing anything of use, but it's definitely a push in the right direction

#

The "Draw Dynamic" is the right resolution but fully black, however you don't understand how happy I am to even have it showing anything haha!

mossy oak
#

Is the camera looking at the tilemap? The window in the frame debugger shows the game view, not scene

keen flume
#

I don't necessarily know why but my camera is now fully black, let me try to troubleshoot that.

#

Okay so, the reason the camera was black is because I had that "Draw Dynamic" selected in the frame debugger, so the camera is actually viewing the tilemap properly when resetting it, but the draw render is still black unfortunately.

#

"full render"

#

Render Feature Selected (in frame debugger)

mossy oak
#

I'd maybe try changing the Color used in the ConfigureClear(..) function in OnCameraSetup. That would confirm whether the tilemap is just rendering black or not at all

keen flume
#

still rendering black

#

would that mean it is working and not seeing any tilemap or vise versa?

mossy oak
#

It should set the whole render target to red. It's a little odd that it's not

keen flume
#

Hmm, strange. I'll try resetting it.

#

Yeah, still black ๐Ÿ˜•

mossy oak
#

Might be something with the 2D renderer that means the ConfigureClear is ignored. Not sure. I'm hoping there isn't other issues just because it's 2D.

#

I'm thinking the tilemap could be rendering black if it's regular shader is lit. Could try an unlit one for debugging (or add an overrideMaterial to the DrawingsSettings)

keen flume
#

Yeah the regular shader for it is sprite default lit if I am correct. I'll try to set it to unlit

#

Okay I honestly don't even know how to describe what is going on anymore other than maybe with a video

#

when I switch the material of the tilemap, seemingly randomly it will result in a desired way

#

however in the scene view my tilemap looks like this and gets more red the more I move the scene view

#

I am noticing a seemingly common theme where I don't really have an explanation as to what is going on lol

mossy oak
#

I think you're rendering the result of the feature to the feature itself - bit of a weird rendering loop.
I would look into adding an overrideMaterial, so you can just render an unlit white material in the feature

keen flume
#

I'll try to look further into the overrideMaterial tomorrow as it's 2 PM and I haven't slept haha, I really appreciate all the help you've been providing, this topic in particular seems quite hard to find much good documentation for (other than yours ironically), so this whole ordeal would've probably taken me potentially weeks to troubleshoot, so thank you very much!

keen flume