


前言
为了更加熟练的掌握这个功能,我们来做一个描边的效果吧。
方便其他人,也方便自己查询。
描边效果
看了下这几篇文章,大致理解了描边行为的实现思路
https://zhuanlan.zhihu.com/p/396965255
https://zhuanlan.zhihu.com/p/64138757
我们先来捋一遍大致的实现思路和需要前置了解的知识:
首先,我们需要做的就是用RenderFeature在渲染流程的某个时机插入一次渲染命令,即ScriptableRenderContext.DrawRenderers;
之前可能是不透明-天空盒-透明物体-后处理的绘制顺序。
现在则可能是不透明-描个边-天空盒-透明物体-后处理这样的绘制顺序。
当然,这个渲染命令需要需要一定的约束条件,规定在什么时机渲染,渲染什么内容,那么约束条件则是FilteringSettings,FilteringSettings中参数的意义建议去 Unity手册查看一下:https://docs.unity3d.com/2020.2/Documentation/ScriptReference/Rendering.FilteringSettings.html
总而言之,我们通过上面的命令可以得知,我们在这个渲染通道中需要渲染哪些物体。
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref m_FilteringSettings); 当然,我们还需要知道,用哪个Shader来渲染,用Shader的第几个Pass来渲染,因为,一个ScriptablePass只能调用一个Pass进行渲染。
var drawingSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, sortingCriteria);
drawingSettings.overrideMaterial = overrideMaterial;
drawingSettings.overrideMaterialPassIndex = overrideMaterialPassIndex;
上文中的sortingCriteria代表的是物体的渲染顺序,比如按照深度值从前到后,还是从后到前。
SortingCriteria sortingCriteria = (m_renderQueueType == RenderQueueType.Transparent)? SortingCriteria.CommonTransparent: renderingData.cameraData.defaultOpaqueSortFlags; 此外,我们的Shader实现描边效果需要两个Pass,第一个Pass是模板测试,改变物体的模板值,第二个Pass则是用模板值过滤,然后让顶点沿着法线扩张并渲染纯色,就得到了描边效果。
直接看代码吧。
Shader源码如下:
Shader "URP/URPOutlineStencil"
{
Properties{
_MainTex("MainTex",2D) = "white"{}
_OutlineWidth("Outline Width", Range(0, 1.0)) = 1.0
_OutlineColor("Outline Color", Color) = (1,1,1,1)
}
SubShader
{
Tags{
"RenderPipeLine"="UniversalRenderPipeline"
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float _OutlineWidth;
float4 _OutlineColor;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
struct a2v{
float4 positionOS: POSITION;
float3 normalOS: NORMAL;
};
struct v2f{
float4 positionCS: SV_POSITION;
};
ENDHLSL
Pass
{
ZWrite Off
ZTest Always
ColorMask 0
Stencil
{
Ref 1
Pass Replace
}
}
Pass
{
ZWrite Off
ZTest Always
Stencil
{
Ref 1
Comp NotEqual
}
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
v2f vert(a2v v)
{
v2f o;
v.positionOS.xyz += v.normalOS * _OutlineWidth;
o.positionCS = TransformObjectToHClip(v.positionOS);
return o;
}
real4 frag(v2f i):SV_TARGET
{
return _OutlineColor;
}
ENDHLSL
}
}
} 这个比较简单,核心就是模板测试。
模板测试相关内容请看这里:模板测试
接下来我们来写RenderPass,要点太多,直接看代码备注吧。
using System.Collections.Generic;
using UnityEngine;
namespace UnityEngine.Rendering.Universal
{
public class OutlineRenderPass : ScriptableRenderPass
{
//标签名,用于续帧调试器中显示缓冲区名称
const string CommandBufferTag = "Outline Render Pass";
// 用于后处理的材质
public Material m_Material;
// 材质中第几个pass来渲染
public int m_PassID;
// 颜色渲染标识符
RenderTargetIdentifier m_ColorAttachment;
// 过滤设置
private RenderQueueRange m_renderQueueRange; //渲染队列的范围
private FilteringSettings m_FilteringSettings; //渲染过滤设置
private RenderStateBlock m_RenderStateBlock; //渲染状态的设置,比如深度测试,开启混合等等
private SortingCriteria m_SortingCriterial; //渲染物体的渲染顺序,从前往后,还是从后往前
// 轮廓线颜色
private Color m_OutlineColor;
// 轮廓线宽度
private float m_OutlineWidth;
// 多Pass渲染需要不同的ShaderTagId,应该是这样吧?懒得验证了。
private List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>()
{
new ShaderTagId("SRPDefaultUnlit"),
new ShaderTagId("UniversalForward"),
};
// 设置渲染参数
public void Setup(Material Material, int passID,
RenderPassEvent renderPassEvent, FilterSettings filterSettings)
{
//this.m_ColorAttachment = _ColorAttachment;
this.m_Material = Material;
this.m_PassID = passID;
this.m_OutlineColor = filterSettings.outlineColor;
this.m_OutlineWidth = filterSettings.outlineWidth;
// 渲染时机
this.renderPassEvent = renderPassEvent;
// 过滤范围,处于哪些渲染队列的可以被渲染
this.m_renderQueueRange =
filterSettings.renderQueue == RenderQueue.Geometry?
RenderQueueRange.opaque: RenderQueueRange.transparent;
// render layer,这里基本上不靠这个过滤,靠物体的object layer过滤
uint renderingLayerMask = (uint)1 << filterSettings.renderingLayerMask - 1;
// 整合一下,得到一个全新的参数
this.m_FilteringSettings = new FilteringSettings(m_renderQueueRange, filterSettings.layerMask, renderingLayerMask);
// 渲染状态的设置,比如是否写入深度呀,是否开启混合呀,等等
this.m_RenderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);
// 排序,不多哔哔
this.m_SortingCriterial =
filterSettings.renderQueue == RenderQueue.Geometry ?
SortingCriteria.CommonOpaque : SortingCriteria.CommonTransparent;
}
// 重新设置渲染状态
public void SetDepthState(bool writeEnable, CompareFunction function = CompareFunction.Less)
{
m_RenderStateBlock.mask |= RenderStateMask.Depth;
m_RenderStateBlock.depthState = new DepthState(writeEnable, function);
}
/// <summary>
/// URP会自动调用该执行方法
/// </summary>
/// <param name="context"></param>
/// <param name="renderingData"></param>
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
m_Material.SetColor("_OutlineColor", m_OutlineColor);
m_Material.SetFloat("_OutlineWidth", m_OutlineWidth);
var cmd = CommandBufferPool.Get(CommandBufferTag);
var drawingSettings = CreateDrawingSettings
(m_ShaderTagIdList, ref renderingData, m_SortingCriterial);
drawingSettings.overrideMaterial = m_Material;
drawingSettings.overrideMaterialPassIndex = m_PassID;
context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref m_FilteringSettings);
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
}
} 接下来是Render Feature,写的不是很严谨,可以优化归类的地方有很多,但是功能可以正常使用,嘿嘿。
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UnityEngine.Rendering.Universal
{
[Serializable] // 参数面板设置
public class FilterSettings
{
// 轮廓线颜色
public Color outlineColor = Color.blue;
// 轮廓线宽度
[Range(0, 0.1f)] public float outlineWidth = 0.04f;
// 过滤用的渲染队列
public RenderQueue renderQueue = RenderQueue.Geometry;
// 过滤用的物体层
public LayerMask layerMask = -1;
// 过滤用的渲染层
[Range(1, 32)] public int renderingLayerMask;
}
public class OutlineRenderFeature : ScriptableRendererFeature
{
// 渲染时机
public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingSkybox;
// 参数面板设置实例化
public FilterSettings filterSettings;
// 渲染用shader
public Shader shader;
// 材质
private Material m_Material;
// 使用第几个pass的参数列表
public int[] passes;
[Space(10)]
// 是否重新写入渲染状态
public bool overrideDepthState = false;
// 深度比较参数
public CompareFunction depthCompareFunction = CompareFunction.LessEqual;
// 是否开启深度写入
public bool enableWrite = true;
// 我们一共要渲染几个Pass
List<ScriptableRenderPass> m_ScriptablePasses = new List<ScriptableRenderPass>();
// 将Pass添加到渲染列表
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
if (passes==null || shader==null)
{
return;
}
foreach (OutlineRenderPass item in m_ScriptablePasses)
{
var cameraColorTarget = renderer.cameraColorTarget;
renderer.EnqueuePass(item);
}
}
// 创建Passes
public override void Create()
{
if (shader == null || passes == null)
return;
// 创建材质
if (m_Material == null)
m_Material = CoreUtils.CreateEngineMaterial(shader);
m_ScriptablePasses.Clear();
for (int i = 0; i < passes.Length; i++)
{
// Pass参数的初始化
var outlineRenderPass = new OutlineRenderPass();
outlineRenderPass.Setup(m_Material, passes[i], passEvent, filterSettings);
if (overrideDepthState)
{
outlineRenderPass.SetDepthState(enableWrite, depthCompareFunction);
}
m_ScriptablePasses.Add(outlineRenderPass);
}
}
}
} 如果不出意外,调整好参数以后,我们就可以得到下面的结果了。

在物体后面也不会被挡住

但是很明显由硬边造成了割裂的感觉,所以再优化一下shader
Shader "URP/URPOutlineStencil"
{
Properties{
_MainTex("MainTex",2D) = "white"{}
_OutlineWidth("Outline Width", Range(0, 1.0)) = 1.0
_OutlineColor("Outline Color", Color) = (1,1,1,1)
}
SubShader
{
Tags{
"RenderPipeLine"="UniversalRenderPipeline"
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
float _OutlineWidth;
float4 _OutlineColor;
CBUFFER_END
TEXTURE2D(_MainTex);
SAMPLER(sampler_MainTex);
struct a2v{
float4 positionOS: POSITION;
float3 normalOS: NORMAL;
};
struct v2f{
float4 positionCS: SV_POSITION;
};
ENDHLSL
Pass
{
ZWrite Off
ZTest Always
ColorMask 0
Stencil
{
Ref 1
Pass Replace
}
}
Pass
{
ZWrite Off
ZTest Always
Cull Off
Stencil
{
Ref 1
Comp NotEqual
}
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
v2f vert(a2v v)
{
v2f o;
float3 normalVS = mul((float3x3)UNITY_MATRIX_IT_MV, v.normalOS);
normalVS.z = -0.5;
float4 positionVS = mul(UNITY_MATRIX_MV,v.positionOS);
positionVS.xyz += normalVS * _OutlineWidth;
o.positionCS = mul(UNITY_MATRIX_P,positionVS);
return o;
}
real4 frag(v2f i):SV_TARGET
{
return _OutlineColor;
}
ENDHLSL
}
}
} 最后得到下面的效果,其实还是有瑕疵,如果想得到光滑组的法线,请看这里吧:https://zhuanlan.zhihu.com/p/130921684
