Здравствуйте.
Я поведаю о том, как создать простой outline effect на новом Lightweight Render Pipeline(LWRP) в Unity. Для этого нужна версия Unity 2018.3 и выше, а так же LWRP версии 4.0.0 и выше.
Классический outline состоит из двух-проходного шейдера (two pass shader), но LWRP поддерживает только одно-проходные шейдера (single pass shader). Для исправления этого недостатка в LWRP появилась возможность добавлять пользовательские pass в определенные этапы рендеринга, используя интерфейсы:
Нам потребуется два шейдера.
Первым я буду использовать Unlit Color. Вместо него можно использовать другой, главное добавить в шейдер конструкцию Stencil.
Второй — непосредственно простейший outline шейдер.
Написание пользовательского pass начинается с создания обычного MonoBehaviour и реализации в нем одного из интерфейсов, указанных выше. Используем IAfterOpaquePass, так как outline будет применяться только к оpaque объектам.
Этот скрипт должен быть добавлен на камеру. И через него мы будем организовывать взаимодействие нашего прохода с игровой логикой, но об этом чуть позже.
Теперь приступим к написанию самого прохода. Для этого создадим класс, наследуемый от ScriptableRenderPass
В конструкторе мы зарегистрируем имя прохода, создадим материал и настройки для фильтрации видимых объектов после кулинга. В фильтре установим только opaque объекты, так как свой проход добавим после Opaque pass.
Функция Execute — это функция рендеринга для прохода. В ней мы создаём настройки для рендерига, устанавливаем материал, созданный в конструкторе, и рендерим все видимые объекты, удовлетворяющие созданному фильтру.
Теперь дополним класс OutlinePass. Тут все очень просто создаем экземпляр класса OutlinePassImpl и через ссылку можно будут взаимодействовать с пользовательским pass в режиме runtime. Например для изменения цвета outline.
Теперь настроим сцену для теста.
Outline будет виден только в Game View.
Вот такой результат должен получиться.
Используя настройку для фильтрации видимых объектов, можно указать слой или рендерный слой для того, чтобы применить этот проход для конкретного объекта или группы объектов и связать её с логикой игры.
Изменим наш pass так, что все объекты со слоем «Friend» будут иметь зеленый outline, а со слоем «Enemy» красный.
На сцене добавим слои «Friend» и «Enemy», продублируем куб несколько раз, назначим им слои на «Friend» или «Enemy», настроим Outline Pass и запустим.
И вот что получим.
Новый рендериг в Unity отлично расширяется, что позволяет создавать интересные эффекты очень просто.
Надеюсь статья оказалась полезной для прочтения. Если у кого возникнут вопросы — до встречи в комментах.
Я поведаю о том, как создать простой 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.
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
}
}
}
Второй — непосредственно простейший outline шейдер.
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
}
}
}
Пользовательский Pass
Написание пользовательского 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 — это функция рендеринга для прохода. В ней мы создаём настройки для рендерига, устанавливаем материал, созданный в конструкторе, и рендерим все видимые объекты, удовлетворяющие созданному фильтру.
OutlinePassImpl который получился у меня
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.
OutlinePass который получился у меня
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));
}
}
Теперь настроим сцену для теста.
- Создадим материал из шейдера SimpleColor
- Создадим куб и навесим на него материал
- На камеру добавим OutlinePass скрипт и установим цвет
- И нажимаем плей
Outline будет виден только в Game View.
Вот такой результат должен получиться.
Бонус: подсветка типа друг-враг
Используя настройку для фильтрации видимых объектов, можно указать слой или рендерный слой для того, чтобы применить этот проход для конкретного объекта или группы объектов и связать её с логикой игры.
Изменим наш pass так, что все объекты со слоем «Friend» будут иметь зеленый outline, а со слоем «Enemy» красный.
OutlinePass и 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]);
}
}
}
На сцене добавим слои «Friend» и «Enemy», продублируем куб несколько раз, назначим им слои на «Friend» или «Enemy», настроим Outline Pass и запустим.
И вот что получим.
Заключение
Новый рендериг в Unity отлично расширяется, что позволяет создавать интересные эффекты очень просто.
Надеюсь статья оказалась полезной для прочтения. Если у кого возникнут вопросы — до встречи в комментах.
Комментарии (6)
panteleymonov
23.11.2018 00:22Как бы, это тот же подход что и в стандартном «Toon/Basic Outline» и «Toon/Lighted Outline» (со времен появления unity), только там ни какие фильтры не требуются. Тут же дополнительный проход на отрисовку сетки.
DimPal
23.11.2018 08:45Что то последняя картинка c 4 кубами на outline вообще не похожа, почему нельзя сделать отступ в пиксельном пространстве экрана, рассчитав нормализованное направление проекции вектора нормали?
ArtemSekretov Автор
23.11.2018 10:08Конечно можно, но задача статьи не показать как делать сам effect, а как сделать его в рамках single pass LWRP. Не запрещается заменить vertex шейдер в outline pass на любой другой найденный на просторах интернета.
AgentFire
А как там дела обстоят с нормальным outline'ом, ну, который блюром делается?
ArtemSekretov Автор
Так же как и в простом варианте. Все дополнительные pass также добавляются на камеру. Grab texture нет в LWRP, но там сделали аналог Opaque texture(нужно включать в настройках рендеринга) и она доступна в pass после opaque pass.