- PVSM.RU - https://www.pvsm.ru -
Здравствуйте.
Я поведаю о том, как создать простой outline effect на новом Lightweight Render Pipeline(LWRP) в Unity. Для этого нужна версия Unity 2018.3 и выше, а так же LWRP версии 4.0.0 и выше.
Классический outline состоит из двух-проходного шейдера (two pass shader), но LWRP поддерживает только одно-проходные шейдера (single pass shader). Для исправления этого недостатка в LWRP появилась возможность добавлять пользовательские pass в определенные этапы рендеринга, используя интерфейсы:
IAfterDepthPrePass
IAfterOpaquePass
IAfterOpaquePostProcess
IAfterSkyboxPass
IAfterTransparentPass
IAfterRender
Нам потребуется два шейдера.
Первым я буду использовать Unlit Color. Вместо него можно использовать другой, главное добавить в шейдер конструкцию Stencil.
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
}
}
}
Второй — непосредственно простейший 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
}
}
}
Написание пользовательского pass начинается с создания обычного MonoBehaviour и реализации в нем одного из интерфейсов, указанных выше. Используем IAfterOpaquePass, так как outline будет применяться только к оpaque объектам.
public class OutlinePass : MonoBehaviour, IAfterOpaquePass
{
public ScriptableRenderPass GetPassToEnqueue(RenderTextureDescriptor baseDescriptor, RenderTargetHandle colorAttachmentHandle, RenderTargetHandle depthAttachmentHandle)
{
//...
}
}
Этот скрипт должен быть добавлен на камеру. И через него мы будем организовывать взаимодействие нашего прохода с игровой логикой, но об этом чуть позже.
Теперь приступим к написанию самого прохода. Для этого создадим класс, наследуемый от ScriptableRenderPass
public class OutlinePassImpl : ScriptableRenderPass
{
public OutlinePassImpl()
{
//...
}
public override void Execute(ScriptableRenderer renderer, ScriptableRenderContext context, ref RenderingData renderingData)
{
//...
}
}
В конструкторе мы зарегистрируем имя прохода, создадим материал и настройки для фильтрации видимых объектов после кулинга. В фильтре установим только opaque объекты, так как свой проход добавим после Opaque pass.
Функция Execute — это функция рендеринга для прохода. В ней мы создаём настройки для рендерига, устанавливаем материал, созданный в конструкторе, и рендерим все видимые объекты, удовлетворяющие созданному фильтру.
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);
}
}
Теперь дополним класс OutlinePass. Тут все очень просто создаем экземпляр класса OutlinePassImpl и через ссылку можно будут взаимодействовать с пользовательским pass в режиме runtime. Например для изменения цвета outline.
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));
}
}
Теперь настроим сцену для теста.
Outline будет виден только в Game View.
Вот такой результат должен получиться.

Используя настройку для фильтрации видимых объектов, можно указать слой или рендерный слой для того, чтобы применить этот проход для конкретного объекта или группы объектов и связать её с логикой игры.
Изменим наш pass так, что все объекты со слоем «Friend» будут иметь зеленый outline, а со слоем «Enemy» красный.
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]);
}
}
}
На сцене добавим слои «Friend» и «Enemy», продублируем куб несколько раз, назначим им слои на «Friend» или «Enemy», настроим Outline Pass и запустим.

И вот что получим.

Новый рендериг в Unity отлично расширяется, что позволяет создавать интересные эффекты очень просто.
Надеюсь статья оказалась полезной для прочтения. Если у кого возникнут вопросы — до встречи в комментах.
Автор: ArtemSekretov
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/c-2/299971
Ссылки в тексте:
[1] Источник: https://habr.com/post/430792/?utm_campaign=430792
Нажмите здесь для печати.