📜 ⬆️ ⬇️

Creating an outline for LWRP in Unity

Hello.

I will tell you how to create a simple outline effect on the new Lightweight Render Pipeline (LWRP) in Unity. This requires a version of Unity 2018.3 or higher, as well as an LWRP version 4.0.0 or higher.

The classic outline consists of a two-pass shader (two pass shader), but the LWRP only supports a single pass shader. To fix this flaw in LWRP, it became possible to add user pass to certain rendering steps using interfaces:
')
IAfterDepthPrePass IAfterOpaquePass IAfterOpaquePostProcess IAfterSkyboxPass IAfterTransparentPass IAfterRender 

Training


We need two shaders.

First I will use Unlit Color. Instead, you can use another one, the main thing is to add the Stencil construct to the shader.

Unlit color
 Shader "Unlit/SimpleColor" { SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { Tags { "LightMode" = "LightweightForward" } Stencil { Ref 2 Comp always Pass replace } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.lightweight/ShaderLibrary/Core.hlsl" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex = TransformObjectToHClip(v.vertex.xyz); return o; } half4 frag (v2f i) : SV_Target { return half4(0.5h, 0.0h, 0.0h, 1.0h); } ENDHLSL } } } 


The second is the simplest outline shader itself.

Simple outline
 Shader "Unlit/SimpleOutline" { SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { Stencil { Ref 2 Comp notequal Pass keep } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.lightweight/ShaderLibrary/Core.hlsl" struct appdata { float4 vertex : POSITION; }; struct v2f { float4 vertex : SV_POSITION; }; half4 _OutlineColor; v2f vert (appdata v) { v2f o; v.vertex.xyz += 0.2 * normalize(v.vertex.xyz); o.vertex = TransformObjectToHClip(v.vertex.xyz); return o; } half4 frag (v2f i) : SV_Target { return _OutlineColor; } ENDHLSL } } } 


User Pass


Writing a user pass begins with the creation of the usual MonoBehaviour and the implementation of one of the interfaces mentioned above. Use IAfterOpaquePass, as the outline will apply only to orca objects.

 public class OutlinePass : MonoBehaviour, IAfterOpaquePass { public ScriptableRenderPass GetPassToEnqueue(RenderTextureDescriptor baseDescriptor, RenderTargetHandle colorAttachmentHandle, RenderTargetHandle depthAttachmentHandle) { //... } } 

This script must be added to the camera. And through it we will organize the interaction of our passage with the game logic, but more on that later.

Now let's start writing the passage itself. To do this, create a class that inherits from ScriptableRenderPass

 public class OutlinePassImpl : ScriptableRenderPass { public OutlinePassImpl() { //... } public override void Execute(ScriptableRenderer renderer, ScriptableRenderContext context, ref RenderingData renderingData) { //... } } 

In the constructor, we register the name of the passage, create the material and settings for filtering visible objects after cooling. In the filter, we will install only opaque objects, since we will add our pass after the Opaque pass.

The Execute function is a rendering function for the pass. In it, we create settings for rendering, set the material created in the constructor, and render all visible objects that satisfy the created filter.

OutlinePassImpl which turned out at me
 public class OutlinePassImpl : ScriptableRenderPass { private Material outlineMaterial; private FilterRenderersSettings m_OutlineFilterSettings; private int OutlineColorId; public OutlinePassImpl(Color outlineColor) { //      ,   ,    // SimpleColor RegisterShaderPassName("LightweightForward"); //   outline shader,   outlineMaterial = CoreUtils.CreateEngineMaterial("Unlit/SimpleOutline"); OutlineColorId = Shader.PropertyToID("_OutlineColor"); outlineMaterial.SetColor(OutlineColorId, outlineColor); m_OutlineFilterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque, }; } public override void Execute(ScriptableRenderer renderer, ScriptableRenderContext context, ref RenderingData renderingData) { Camera camera = renderingData.cameraData.camera; SortFlags sortFlags = renderingData.cameraData.defaultOpaqueSortFlags; // a       DrawRendererSettings drawSettings = CreateDrawRendererSettings(camera, sortFlags, RendererConfiguration.None, renderingData.supportsDynamicBatching); drawSettings.SetOverrideMaterial(outlineMaterial, 0); context.DrawRenderers(renderingData.cullResults.visibleRenderers, ref drawSettings, m_OutlineFilterSettings); } } 


Now we add the class OutlinePass. It's all very easy to create an instance of the class OutlinePassImpl and through the link you can interact with the user pass in runtime mode. For example, to change the color of the outline.

OutlinePass which turned out at me
 public class OutlinePass : MonoBehaviour, IAfterOpaquePass { public Color OutlineColor; private OutlinePassImpl outlinePass; public ScriptableRenderPass GetPassToEnqueue(RenderTextureDescriptor baseDescriptor, RenderTargetHandle colorAttachmentHandle, RenderTargetHandle depthAttachmentHandle) { return outlinePass ?? (outlinePass = new OutlinePassImpl(OutlineColor)); } } 


Now set up the scene for the test.

  1. Create material from the SimpleColor shader.
  2. Create a cube and hang material on it
  3. Add the OutlinePass script to the camera and set the color
  4. And click play

Outline will be visible only in Game View.

That is the result should be obtained.



Bonus: friend-enemy type lights


Using the setting to filter visible objects, you can specify a layer or render layer in order to apply this pass to a specific object or group of objects and associate it with the logic of the game.

Let's change our pass so that all objects with the “Friend” layer will have a green outline, and with the “Enemy” layer red.

OutlinePass and OutlinePassImpl
 public class OutlinePass : MonoBehaviour, IAfterOpaquePass { [System.Serializable] public class OutlineData { public Color Color; public LayerMask Layer; } public List<OutlineData> outlineDatas = new List<OutlineData>(); private OutlinePassImpl outlinePass; public ScriptableRenderPass GetPassToEnqueue(RenderTextureDescriptor baseDescriptor, RenderTargetHandle colorAttachmentHandle, RenderTargetHandle depthAttachmentHandle) { return outlinePass ?? (outlinePass = new OutlinePassImpl(outlineDatas)); } } public class OutlinePassImpl : ScriptableRenderPass { private Material[] outlineMaterial; private FilterRenderersSettings[] m_OutlineFilterSettings; public OutlinePassImpl(List<OutlinePass.OutlineData> outlineDatas) { RegisterShaderPassName("LightweightForward"); outlineMaterial = new Material[outlineDatas.Count]; m_OutlineFilterSettings = new FilterRenderersSettings[outlineDatas.Count]; Shader outlineShader = Shader.Find("Unlit/SimpleOutline"); int OutlineColorId = Shader.PropertyToID("_OutlineColor"); for (int i = 0; i < outlineDatas.Count; i++) { OutlinePass.OutlineData outline = outlineDatas[i]; Material material = CoreUtils.CreateEngineMaterial(outlineShader); material.SetColor(OutlineColorId, outline.Color); outlineMaterial[i] = material; m_OutlineFilterSettings[i] = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque, layerMask = outline.Layer }; } } public override void Execute(ScriptableRenderer renderer, ScriptableRenderContext context, ref RenderingData renderingData) { Camera camera = renderingData.cameraData.camera; SortFlags sortFlags = renderingData.cameraData.defaultOpaqueSortFlags; DrawRendererSettings drawSettings = CreateDrawRendererSettings(camera, sortFlags, RendererConfiguration.None, renderingData.supportsDynamicBatching); for (int i = 0; i < outlineMaterial.Length; i++) { drawSettings.SetOverrideMaterial(outlineMaterial[i], 0); context.DrawRenderers(renderingData.cullResults.visibleRenderers, ref drawSettings, m_OutlineFilterSettings[i]); } } } 


On the scene, add the “Friend” and “Enemy” layers, duplicate the cube several times, assign them layers to “Friend” or “Enemy”, configure the Outline Pass and launch it.



And that's what we get.



Conclusion


The new rendering in Unity expands perfectly, which makes it possible to create interesting effects very simply.

I hope the article was useful for reading. If anyone has any questions - see you in the comments.

Source: https://habr.com/ru/post/430792/


All Articles