Erstellen eines Gliederungseffekts in der Unity Universal Render-Pipeline

In der Universal Render Pipeline können Sie durch Erstellen Ihrer eigenen RendererFeature die Rendering-Funktionen problemlos erweitern. Durch Hinzufügen neuer Durchgänge zur Rendering-Pipeline können Sie verschiedene Effekte erstellen. In diesem Artikel erstellen wir mithilfe von ScriptableRendererFeature und ScriptableRenderPass den Gliederungseffekt des Objekts und berücksichtigen einige Funktionen seiner Implementierung.

Gliederungseffekt


Intro oder ein paar Worte zur Render Pipeline


Mit der Scriptable Render Pipeline können Sie das Rendern von Grafiken über Skripte in C # steuern und die Verarbeitungsreihenfolge von Objekten, Lichtern, Schatten und mehr steuern. Die Universal Render Pipeline ist eine vorgefertigte skriptfähige Render-Pipeline, die von Unity entwickelt wurde und die alte integrierte RP ersetzen soll.

Die Funktionen von Universal RP können durch Erstellen und Hinzufügen eigener Zeichnungsdurchläufe (ScriptableRendererFeature und ScriptableRenderPass) erweitert werden. Dies wird der aktuelle Artikel sein. Dies ist nützlich für diejenigen, die zu Universal RP wechseln und möglicherweise dazu beitragen, die Arbeit von ScriptableRenderPass'ov in Universal RP besser zu verstehen.

Dieser Artikel wurde in Unity 2019.3 und Universal RP 7.1.8 geschrieben.

Aktionsplan


Wir werden verstehen, wie ScriptableRendererFeature und ScriptableRenderPass am Beispiel der Erstellung des Stricheffekts undurchsichtiger Objekte funktionieren.

Erstellen Sie dazu eine ScriptableRendererFeature, die die folgenden Aktionen ausführt:

  • Zeichnen bestimmter Objekte
  • Unscharfe gezeichnete Objekte
  • Erhalten von Konturen von Objekten aus Bildern, die in früheren Durchgängen erhalten wurden

Quellrahmen -

Und die Reihenfolge der Ergebnisse, die wir erreichen müssen:


Im Laufe der Arbeit werden wir einen Shader erstellen, in dessen globalen Eigenschaften die Ergebnisse des ersten und zweiten Durchlaufs gespeichert werden. Der letzte Durchgang zeigt das Ergebnis des Shaders selbst auf dem Bildschirm an.

Globale Eigenschaften
, , Properties. — .

— . .

Erstellen Sie OutlineFeature


ScriptableRendererFeature wird verwendet, um Universal RP benutzerdefinierte Rendering-Passagen (ScriptableRenderPass) hinzuzufügen. Erstellen Sie eine OutlineFeature-Klasse, die von ScriptableRenderFeature erbt, und implementieren Sie deren Methoden.

using UnityEngine;
using UnityEngine.Rendering.Universal;

public class OutlineFeature : ScriptableRendererFeature
{
    public override void Create()
    { }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    { }
}

Die Create () -Methode wird zum Erstellen und Konfigurieren von Durchläufen verwendet. Und die AddRenderPasses () -Methode zum Einfügen der erstellten Durchgänge in die Rendering-Warteschlange.

Argumente AddRenderPasses ()
ScriptableRenderer — Universal RP. Universal RP Forward Rendering.

RenderingData — , , .

Beginnen wir nun mit der Erstellung der Rendering-Durchgänge, und wir kehren nach der Implementierung der einzelnen Klassen zur aktuellen Klasse zurück.

Renderobjekte werden übergeben


Die Aufgabe dieser Passage besteht darin, Objekte aus einer bestimmten Ebene zu zeichnen, wobei das Material in der globalen Textur-Eigenschaft des Shaders ersetzt wird. Dies ist eine vereinfachte Version der in Universal RP verfügbaren RenderObjectsPass-Passage, mit dem einzigen Unterschied im Ziel (RenderTarget), in dem das Rendern ausgeführt wird.

Erstellen Sie eine Klasse MyRenderObjectsPass, die von ScriptableRenderPass geerbt wurde. Wir implementieren die Execute () -Methode, die die gesamte Logik der Passage enthält, und definieren die Configure () -Methode neu.

using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class MyRenderObjectsPass : ScriptableRenderPass
{
    {
        public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
        { }
        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        { }
    }
}

Die Configure () -Methode wird verwendet, um den Zweck des Renderns und Erstellens temporärer Texturen anzugeben. Standardmäßig ist das Ziel das Ziel der aktuellen Kamera. Nach Abschluss des Durchlaufs wird es standardmäßig wieder angezeigt. Diese Methode wird aufgerufen, bevor die Basislogik ausgeführt wird.

Zielersatz rendern


Deklarieren Sie ein RenderTargetHandle für ein neues Rendering-Ziel. Erstellen Sie damit eine temporäre Textur und geben Sie sie als Ziel an. RenderTargetHandle enthält die Kennung der verwendeten temporären RenderTexture. Außerdem können Sie einen RenderTargetIdentifier abrufen, mit dem Sie ein Rendering-Ziel identifizieren, das beispielsweise als RenderTexture, Texture-Objekt, temporäre RenderTexture oder integriert (von der Kamera beim Rendern eines Frames verwendet) festgelegt werden kann.

Ein RenderTargetHandle-Objekt wird in der OutlineFeature erstellt und beim Erstellen an unseren Pass übergeben.

private RenderTargetHandle _destination;

public MyRenderObjectsPass(RenderTargetHandle destination)
{
    _destination = destination;
}

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{  
    cmd.GetTemporaryRT(_destination.id, cameraTextureDescriptor);
}

Die GetTemporaryRT () -Methode erstellt eine temporäre RenderTexture mit den angegebenen Parametern und legt sie als globale Shader-Eigenschaft mit dem angegebenen Namen fest (der Name wird im Feature festgelegt).

Entfernen Sie RenderTexture
ReleaseTemporaryRT() RenderTexture. Execute() FrameCleanup.
, RenderTexture, , .

Um eine temporäre RenderTexture zu erstellen, verwenden wir den Deskriptor der aktuellen Kamera, der Informationen zu Größe, Format und anderen Parametern des Kameraziels enthält.

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{        
    cmd.GetTemporaryRT(_destination.id, cameraTextureDescriptor);
    ConfigureTarget(_destination.Identifier());
    ConfigureClear(ClearFlag.All, Color.clear);
}

Der Zweck des Ziels und seine Bereinigung sollten nur in Configure () mit den Methoden ConfigureTarget () und ClearTarget () erfolgen.

Machen


Wir werden das Rendern nicht im Detail betrachten, weil Dies kann uns weit und breit vom Hauptthema wegführen. Zum Rendern verwenden wir die ScriptableRenderContext.DrawRenderers () -Methode. Erstellen Sie Einstellungen, um nur undurchsichtige Objekte nur aus den angegebenen Ebenen zu rendern. Die Ebenenmaske wird an den Konstruktor übergeben.

...
private List<ShaderTagId> _shaderTagIdList = new List<ShaderTagId>() { new ShaderTagId("UniversalForward") };
private FilteringSettings _filteringSettings;
private RenderStateBlock _renderStateBlock;
...
public MyRenderObjectsPass(RenderTargetHandle destination, int layerMask)
{
    _destination = destination;

    _filteringSettings = new FilteringSettings(RenderQueueRange.opaque, layerMask);
    _renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
    SortingCriteria sortingCriteria = renderingData.cameraData.defaultOpaqueSortFlags;
    DrawingSettings drawingSettings = CreateDrawingSettings(_shaderTagIdList, ref renderingData, sortingCriteria);

    context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _filteringSettings, ref _renderStateBlock);
}

Sehr kurz über die Parameter
CullingResults — ( RenderingData)
FilteringSettings — .
DrawingSettings — .
RenderStateBlock — , (, ..)

Über den fertigen RenderObjectsPass in UniversalRP
, . . RenderObjectsPass, Universal RP, . RenderFeature - :)

Materialersatz


Wir definieren die für das Rendern verwendeten Materialien neu, da wir nur die Konturen der Objekte benötigen.

private Material _overrideMaterial;

public MyRenderObjectsPass(RenderTargetHandle destination, int layerMask,, Material overrideMaterial)
{
...
    _overrideMaterial = overrideMaterial;
...
}

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
...
    DrawingSettings drawingSettings = CreateDrawingSettings(_shaderTagIdList, ref renderingData, sortingCriteria);
    drawingSettings.overrideMaterial = _overrideMaterial;
...
}

Shader zum Rendern


Erstellen Sie in ShaderGraph einen Material-Shader, der beim Zeichnen von Objekten im aktuellen Durchgang verwendet wird.


Fügen Sie eine Passage zu OutlineFeature hinzu


Zurück zu OutlieFeature. Erstellen Sie zunächst eine Klasse für die Einstellungen unserer Passage.

public class OutlineFeature : ScriptableRendererFeature
{
    [Serializable]
    public class RenderSettings
    {
        public Material OverrideMaterial = null;
        public LayerMask LayerMask = 0;
    }
    ...
}

Deklarieren Sie die Felder für die MyRenderPass-Einstellungen und den Namen der globalen Textur-Eigenschaft, die von unserem Pass als Rendering-Ziel verwendet wird.

[SerializeField] private string _renderTextureName;
[SerializeField] private RenderSettings _renderSettings;

Erstellen Sie einen Bezeichner für die Textur-Eigenschaft und eine Instanz von MyRenderPass.

private RenderTargetHandle _renderTexture;
private MyRenderObjectsPass _renderPass;

public override void Create()
{
    _renderTexture.Init(_renderTextureName);

    _renderPass = new MyRenderObjectsPass(_renderTexture, _renderSettings.LayerMask, _renderSettings.OverrideMaterial);
}

In der AddRendererPass-Methode fügen wir unseren Pass zur Ausführungswarteschlange hinzu.

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

Vorausschauen
. , .

Das Ergebnis des Durchlaufs für die Quellenszene sollte wie folgt sein:


Debuggen
Frame Debug (Windows-Analysis-FrameDebugger).

Unschärfepass


Der Zweck dieser Passage besteht darin, das im vorherigen Schritt erhaltene Bild zu verwischen und auf die globale Shader-Eigenschaft zu setzen.

Zu diesem Zweck kopieren wir die ursprüngliche Textur mehrmals in eine temporäre Textur, wobei wir den Unschärfeshader verwenden. In diesem Fall kann das Originalbild verkleinert werden (eine verkleinerte Kopie erstellen), wodurch die Berechnungen beschleunigt werden und die Qualität des Ergebnisses nicht beeinträchtigt wird.

Lassen Sie uns die von ScriptableRenderPass geerbte BlurPass-Klasse erstellen.

using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class BlurPass : ScriptableRenderPass
{
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    { }
}

Lassen Sie uns die Variablen für die Quell-, Ziel- und temporären Texturen (und deren ID) abrufen.

private int _tmpBlurRTId1 = Shader.PropertyToID("_TempBlurTexture1");
private int _tmpBlurRTId2 = Shader.PropertyToID("_TempBlurTexture2");

private RenderTargetIdentifier _tmpBlurRT1;
private RenderTargetIdentifier _tmpBlurRT2;

private RenderTargetIdentifier _source;
private RenderTargetHandle _destination;

Alle IDs für RenderTexture werden über Shader.PropertyID () festgelegt. Dies bedeutet nicht, dass irgendwo solche Shader-Eigenschaften unbedingt vorhanden sein müssen.

Fügen Sie Felder für die verbleibenden Parameter hinzu, die wir sofort im Konstruktor initialisieren.

private int _passesCount;
private int _downSample;
private Material _blurMaterial;

public BlurPass(Material blurMaterial, int downSample, int passesCount)
{
    _blurMaterial = blurMaterial;
    _downSample = downSample;
    _passesCount = passesCount;
}

_blurMaterial - Material mit einem Blur Shader.
_downSample - Koeffizient zum Reduzieren der Texturgröße
_passesCount - Die Anzahl der anzuwendenden Unschärfedurchgänge.

Um temporäre Texturen zu erstellen, erstellen Sie einen Deskriptor mit allen erforderlichen Informationen dazu - Größe, Format und mehr. Die Höhe und Größe werden relativ zum Kameradeskriptor skaliert.

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
    var width = Mathf.Max(1, cameraTextureDescriptor.width >> _downSample);
    var height = Mathf.Max(1, cameraTextureDescriptor.height >> _downSample);
    var blurTextureDesc = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32, 0, 0);
Wir werden auch die Bezeichner und die temporäre RenderTexture selbst erstellen.
    _tmpBlurRT1 = new RenderTargetIdentifier(_tmpBlurRTId1);
    _tmpBlurRT2 = new RenderTargetIdentifier(_tmpBlurRTId2);

    cmd.GetTemporaryRT(_tmpBlurRTId1, blurTextureDesc, FilterMode.Bilinear);
    cmd.GetTemporaryRT(_tmpBlurRTId2, blurTextureDesc, FilterMode.Bilinear);

Wir ändern das Rendering-Ziel erneut. Erstellen Sie daher eine weitere temporäre Textur und geben Sie sie als Ziel an.

    cmd.GetTemporaryRT(_destination.id, blurTextureDesc, FilterMode.Bilinear);
    ConfigureTarget(_destination.Identifier());
}

Verwischen


Einige Rendering-Aufgaben können mit speziellen ScriptableRenderContext-Methoden ausgeführt werden, mit denen Befehle konfiguriert und hinzugefügt werden. Um andere Befehle auszuführen, müssen Sie den CommandBuffer verwenden, der aus dem Pool abgerufen werden kann.
Nachdem Sie Befehle hinzugefügt und an den Kontext gesendet haben, muss der Puffer wieder in den Pool zurückgegeben werden.

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
    var cmd = CommandBufferPool.Get("BlurPass");
    ...
    context.ExecuteCommandBuffer(cmd);
    CommandBufferPool.Release(cmd);
}

Die endgültige Implementierung der Execute () -Methode lautet wie folgt.

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

    if (_passesCount > 0)
    {
        cmd.Blit(_source, _tmpBlurRT1, _blurMaterial, 0);
        for (int i = 0; i < _passesCount - 1; i++)
        {
            cmd.Blit(_tmpBlurRT1, _tmpBlurRT2, _blurMaterial, 0);
            var t = _tmpBlurRT1;
            _tmpBlurRT1 = _tmpBlurRT2;
            _tmpBlurRT2 = t;
        }
        cmd.Blit(_tmpBlurRT1, _destination.Identifier());
    }
    else
        cmd.Blit(_source, _destination.Identifier());
    context.ExecuteCommandBuffer(cmd);
    CommandBufferPool.Release(cmd);
}

Blit
Blit() .

Shader


Erstellen Sie zum Verwischen einen einfachen Shader, der die Farbe des Pixels unter Berücksichtigung seiner nächsten Nachbarn berechnet (der durchschnittliche Farbwert von fünf Pixeln).

Verwischen Sie den Shader
Shader "Custom/Blur"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}

SubShader
{
HLSLINCLUDE

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

struct Attributes
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD0;
};

struct Varyings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
};

TEXTURE2D_X(_MainTex);
SAMPLER(sampler_MainTex);
float4 _MainTex_TexelSize;

Varyings Vert(Attributes input)
{
Varyings output;
output.positionCS = TransformObjectToHClip(input.positionOS.xyz);
output.uv = input.uv;
return output;
}

half4 Frag(Varyings input) : SV_Target
{
float2 offset = _MainTex_TexelSize.xy;
float2 uv = UnityStereoTransformScreenSpaceTex(input.uv);

half4 color = SAMPLE_TEXTURE2D_X(_MainTex, sampler_MainTex, input.uv);
color += SAMPLE_TEXTURE2D_X(_MainTex, sampler_MainTex, input.uv + float2(-1, 1) * offset);
color += SAMPLE_TEXTURE2D_X(_MainTex, sampler_MainTex, input.uv + float2( 1, 1) * offset);
color += SAMPLE_TEXTURE2D_X(_MainTex, sampler_MainTex, input.uv + float2( 1,-1) * offset);
color += SAMPLE_TEXTURE2D_X(_MainTex, sampler_MainTex, input.uv + float2(-1,-1) * offset);

return color/5.0;
}

ENDHLSL

Pass
{
HLSLPROGRAM

#pragma vertex Vert
#pragma fragment Frag

ENDHLSL
}
}
}

Warum nicht ShaderGraph?
ShaderGraph , 13 :)

Fügen Sie eine Passage zu OutlineFeature hinzu


Das Verfahren ähnelt dem Hinzufügen unseres ersten Durchgangs. Erstellen Sie zunächst die Einstellungen.

    [Serializable]
    public class BlurSettings
    {
        public Material BlurMaterial;
        public int DownSample = 1;
        public int PassesCount = 1;
    }

Dann die Felder.

[SerializeField] private string _bluredTextureName;
[SerializeField] private BlurSettings _blurSettings;
private RenderTargetHandle _bluredTexture;
private BlurPass _blurPass;

...

public override void Create()
{
    _bluredTexture.Init(_bluredTextureName);

    _blurPass = new BlurPass(_blurSettings.BlurMaterial, _blurSettings.DownSample, _blurSettings.PassesCount);
}

Und zur Ausführung in die Warteschlange stellen.

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

Pass Ergebnis:


Umrisspass


Das endgültige Bild mit dem Strich der Objekte wird mit dem Shader erhalten. Das Ergebnis seiner Arbeit wird über dem aktuellen Bild auf dem Bildschirm angezeigt.

Unten ist der gesamte Passcode auf einmal als Die gesamte Logik besteht aus zwei Zeilen.

public class OutlinePass : ScriptableRenderPass
{
    private string _profilerTag = "Outline";
    private Material _material;

    public OutlinePass(Material material)
    {
        _material = material;
    }

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

        using (new ProfilingSample(cmd, _profilerTag))
        {
            var mesh = RenderingUtils.fullscreenMesh;
            cmd.DrawMesh(mesh, Matrix4x4.identity, _material, 0, 0);
        }

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

RenderingUtils.fullscreenMesh gibt ein 1 x 1-Netz zurück.

Shader


Erstellen Sie einen Shader, um die Gliederung zu erhalten. Es sollte zwei globale Textureigenschaften enthalten. _OutlineRenderTexture und _OutlineBluredTexture für das Bild der angegebenen Objekte und deren verschwommene Version.

Shader-Code
Shader "Custom/Outline"
{
    Properties
    {
        _Color ("Glow Color", Color ) = ( 1, 1, 1, 1)
        _Intensity ("Intensity", Float) = 2
        [Enum(UnityEngine.Rendering.BlendMode)] _SrcBlend ("Src Blend", Float) = 1
        [Enum(UnityEngine.Rendering.BlendMode)] _DstBlend ("Dst Blend", Float) = 0
    }
    
    SubShader
    {       
        HLSLINCLUDE
 
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        
        struct Attributes
        {
            float4 positionOS   : POSITION;
            float2 uv           : TEXCOORD0;
        };
        
        struct Varyings
        {
            half4 positionCS    : SV_POSITION;
            half2 uv            : TEXCOORD0;
        };
 
        TEXTURE2D_X(_OutlineRenderTexture);
        SAMPLER(sampler_OutlineRenderTexture);
 
        TEXTURE2D_X(_OutlineBluredTexture);
        SAMPLER(sampler_OutlineBluredTexture);
 
        half4 _Color;
        half _Intensity;
 
        Varyings Vertex(Attributes input)
        {
            Varyings output;
            output.positionCS = float4(input.positionOS.xy, 0.0, 1.0); 
            output.uv = input.uv;
            if (_ProjectionParams.x < 0.0) 
                output.uv.y = 1.0 - output.uv.y;    
            return output;      
        }
 
        half4 Fragment(Varyings input) : SV_Target
        {
            float2 uv = UnityStereoTransformScreenSpaceTex(input.uv);
            half4 prepassColor = SAMPLE_TEXTURE2D_X(_OutlineRenderTexture, sampler_OutlineRenderTexture, uv);
            half4 bluredColor = SAMPLE_TEXTURE2D_X(_OutlineBluredTexture, sampler_OutlineBluredTexture,uv);
            half4 difColor = max( 0, bluredColor - prepassColor);
            half4 color = difColor* _Color * _Intensity;
            color.a = 1;    
            return color;
        }
        
        ENDHLSL        
     
        Pass
        {
            Blend [_SrcBlend] [_DstBlend]
            ZTest Always    //  ,      
            ZWrite Off      //      
            Cull Off        //    
 
            HLSLPROGRAM
           
            #pragma vertex Vertex
            #pragma fragment Fragment        
 
            ENDHLSL         
        }
    }
}

, . Unity . , _ProjectionParams..

Das Ergebnis des Shaders für zwei zuvor erhaltene Bilder:



Fügen Sie eine Passage zu OutlineFeature hinzu


Alle Aktionen ähneln den vorherigen Durchgängen.

[SerializeField] private Material _outlineMaterial;
private OutlinePass _outlinePass;

public override void Create()
{
    ...
    _outlinePass = new OutlinePass(_outlineMaterial);
    ....
}

public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
    renderer.EnqueuePass(_renderPass);
    renderer.EnqueuePass(_blurPass);
    renderer.EnqueuePass(_outlinePass);
}

RenderPassEvent


Es bleibt anzugeben, wann die erstellten Pässe aufgerufen werden. Dazu muss jeder von ihnen den Parameter renderPassEvent angeben.

Veranstaltungsliste
Universal RP, .
BeforeRendering
BeforeRenderingShadows
AfterRenderingShadows
BeforeRenderingPrepasses
AfterRenderingPrePasses
BeforeRenderingOpaques
AfterRenderingOpaques
BeforeRenderingSkybox
AfterRenderingSkybox
BeforeRenderingTransparents
AfterRenderingTransparents
BeforeRenderingPostProcessing
AfterRenderingPostProcessing
AfterRendering

Erstellen Sie das entsprechende Feld in OutlineFeature.

[SerializeField] private RenderPassEvent _renderPassEvent;

Und wir werden es allen erstellten Passagen anzeigen.

public override void Create()
{
    ...
    _renderPass.renderPassEvent = _renderPassEvent;
    _blurPass.renderPassEvent = _renderPassEvent;
    _outlinePass.renderPassEvent = _renderPassEvent;
}

Anpassung


Fügen Sie eine Gliederungsebene hinzu und legen Sie sie für die Objekte fest, die wir umkreisen möchten.

Wir erstellen und konfigurieren alle erforderlichen Assets: UniversalRendererPipelineAsset und ForwardRendererData.



Ergebnis


Das Ergebnis für unseren Originalrahmen ist wie folgt!



Fertigstellung


Jetzt ist der Umriss des Objekts auch durch andere Objekte immer sichtbar. Damit unser Effekt die Tiefe der Szene berücksichtigt, müssen einige Änderungen vorgenommen werden.

RenderObjectsPass


Wenn Sie den Zweck unseres Renderings angeben, sollten Sie auch den aktuellen Tiefenpuffer angeben. Erstellen Sie das entsprechende Feld und die entsprechende Methode.

public class MyRenderObjectsPass : ScriptableRenderPass
{
    ...
    private RenderTargetIdentifier _depth;

    public void SetDepthTexture(RenderTargetIdentifier depth)
    { _depth = depth; }
    ...
}

Geben Sie in der Configure () -Methode die Tiefe beim Festlegen des Renderziels an.

public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
    cmd.GetTemporaryRT(_destination.id, cameraTextureDescriptor);
    ConfigureTarget(_destination.Identifier(), _depth);
    ConfigureClear(ClearFlag.Color, Color.clear);
}

Umrissfunktion


In OutlineFeature übergeben wir MyRenderObjectsPass die aktuelle Szenentiefe.

public class OutlineFeature : ScriptableRendererFeature
{
    ...
    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        var depthTexture = renderer.cameraDepth;
        _renderPass.SetDepthTexture(depthTexture);

        renderer.EnqueuePass(_renderPass);
        renderer.EnqueuePass(_blurPass);
        renderer.EnqueuePass(_outlinePass);
    }
    ...
}

UniversalRenderPipelineAsset


Aktivieren Sie im verwendeten UniversalRenderPipelineAsset das Kontrollkästchen neben DepthTexture.


Ergebnis


Ergebnis ohne Tiefe:


Ergebnis basierend auf der Tiefe:



Gesamt


ScriptableRendererFeature ist ein recht praktisches Tool zum Hinzufügen Ihrer Passagen zum RP.
Darin können Sie RenderObjectsPass einfach ersetzen und in anderen ScriptableRendererFeature verwenden. Sie müssen nicht tief in die Implementierung von Universal RP eintauchen und dessen Code ändern, um etwas hinzuzufügen.

PS


Um den allgemeinen Algorithmus für die Arbeit mit ScriptableRendererFeature und ScriptableRenderPass klarer zu gestalten und damit der Artikel nicht zu stark wächst, habe ich absichtlich versucht, den Passcode einfach zu erstellen, auch zum Nachteil ihrer Universalität und Optimalität.

Verweise


Der Quellcode ist ein Link zu gitlab. Die
Modelle und die Szene stammen aus dem Spiel Lander Missions: Planetentiefen.
Die folgende Strichimplementierung wurde als Grundlage für das Beispiel verwendet - Youtube- Link
Unitys eigene RenderFeature-Implementierungsbeispiele - Link zu Github .

Eine Reihe von Lektionen zum Erstellen Ihrer eigenen ScriptableRenderPipeline. Nach dem Lesen der allgemeinen Logik von RP und Shadern wird der Link zu den Tutorials klar .

All Articles