描边需要深度+法线纹理的加持,效果才能达到最好,但URP下很多版本不支持直接获取_CameraNormalsTexture,而我本人也尝试了一下在12.1.7下偷懒直接拿SSAO里的Depth Normal图,
虽然也能实现吧,但是需要打开SSAO的同时,再在shader中加入指定的Tag为"DepthNormals"的Pass才能实现:
稍微有点麻烦,而且总有种用别人东西的感觉。
那就尝试一下自己动手吧!动手造一个获取深度法线纹理的轮子!
贴一下项目环境:
URP12.1.7
Unity2021.3.8f1
浅看两篇手动获取深度法线纹理的文章:URP深度法线纹理 - 简书 (jianshu.com)和雪风大佬的urp管线的自学hlsl之路 第二十四篇 科幻扫描效果后篇 - 哔哩哔哩 (bilibili.com),实现都是依靠build-in底下的shader,然后将绘制出来的纹理传递给URP下自己项目定义的shader使用。
1 定义RenderFeature获取法线深度图
这个是参考了上述的过程,说实话,内容太过复杂。只有不断多学习,多做,每次都好好做备注,总有一天会完全理解的:
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
public class DepthNormalsFeature : ScriptableRendererFeature
{
// 定义3个共有变量
public class Settings
{
//public Shader shader; // 设置后处理shader
public Material material; //后处理Material
public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; // 定义事件位置,放在了官方的后处理之前
}
// 初始化一个刚刚定义的Settings类
public Settings settings = new Settings();
// 初始化Pass
DepthNormalsPass depthNormalsPass;
// 初始化纹理
RenderTargetHandle depthNormalsTexture;
// 材质
Material depthNormalsMaterial;
// 给pass传递变量,并加入渲染管线中
public override void Create()
{
// 通过Built-it管线中的Shader创建材质,最重要的一步!
depthNormalsMaterial = CoreUtils.CreateEngineMaterial("Hidden/Internal-DepthNormalsTexture");
// 获取Pass(渲染队列,渲染对象,材质)
depthNormalsPass = new DepthNormalsPass(RenderQueueRange.opaque, -1, depthNormalsMaterial);
// 设置渲染时机 = 预渲染通道后
depthNormalsPass.renderPassEvent = RenderPassEvent.AfterRenderingPrePasses;
// 设置纹理名
depthNormalsTexture.Init("_CameraDepthNormalsTexture");
}
//这里你可以在渲染器中注入一个或多个渲染通道。
//这个方法在设置渲染器时被调用。
public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
{
// 对Pass进行参数设置(当前渲染相机信息,深度法线纹理)
depthNormalsPass.Setup(renderingData.cameraData.cameraTargetDescriptor, depthNormalsTexture);
// 写入渲染管线队列
renderer.EnqueuePass(depthNormalsPass);
}
}
public class DepthNormalsPass : ScriptableRenderPass
{
int kDepthBufferBits = 32; // 缓冲区大小
private RenderTargetHandle Destination { get; set; } // 深度法线纹理
private Material DepthNormalsMaterial = null; // 材质
private FilteringSettings m_FilteringSettings; // 筛选设置
static readonly string m_ProfilerTag = "Depth Normals Pre Pass"; // 定义渲染Tag
ShaderTagId m_ShaderTagId = new ShaderTagId("MyDepthOnly"); // 绘制标签,Shader需要声明这个标签的tag
/// <summary>
/// 构造函数Pass
/// </summary>
/// <param name="renderQueueRange"></param>
/// <param name="layerMask"></param>
/// <param name="material"></param>
public DepthNormalsPass(RenderQueueRange renderQueueRange, LayerMask layerMask, Material material)
{
m_FilteringSettings = new FilteringSettings(renderQueueRange, layerMask);
DepthNormalsMaterial = material;
}
/// <summary>
/// 参数设置
/// </summary>
/// <param name="baseDescriptor"></param>
/// <param name="Destination"></param>
public void Setup(RenderTextureDescriptor baseDescriptor, RenderTargetHandle Destination)
{
// 设置纹理
this.Destination = Destination;
}
/// <summary>
/// 配置渲染目标,可创建临时纹理
/// </summary>
/// <param name="cmd"></param>
/// <param name="cameraTextureDescriptor"></param>
public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)
{
// 设置渲染目标信息
RenderTextureDescriptor descriptor = cameraTextureDescriptor;
descriptor.depthBufferBits = kDepthBufferBits;
descriptor.colorFormat = RenderTextureFormat.ARGB32;
// 创建一个临时的RT(储存深度法线纹理、目标信息和滤波模式)
cmd.GetTemporaryRT(Destination.id, descriptor, FilterMode.Point);
// 配置
ConfigureTarget(Destination.Identifier());
// 清楚,未渲染时配置为黑色
ConfigureClear(ClearFlag.All, Color.black);
}
//
/// <summary>
/// 后处理逻辑和渲染核心函数,相当于build-in 的OnRenderImage()
/// 实现渲染逻辑
/// </summary>
/// <param name="context"></param>
/// <param name="renderingData"></param>
public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{
var cmd = CommandBufferPool.Get(m_ProfilerTag); // 设置渲染标签
using (new ProfilingSample(cmd, m_ProfilerTag))
{
// 执行命令缓存
context.ExecuteCommandBuffer(cmd);
// 清楚数据缓存
cmd.Clear();
// 相机的排序标志
var sortFlags = renderingData.cameraData.defaultOpaqueSortFlags;
// 创建绘制设置
var drawSettings = CreateDrawingSettings(m_ShaderTagId, ref renderingData, sortFlags);
// 设置对象数据
drawSettings.perObjectData = PerObjectData.None;
// 设置覆盖材质
drawSettings.overrideMaterial = DepthNormalsMaterial;
// 绘制渲染器
context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref m_FilteringSettings);
// 设置全局纹理
cmd.SetGlobalTexture("_CameraDepthNormalsTexture", Destination.id);
}
// 执行命令缓冲区
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}
// 清除此呈现传递执行期间创建的任何已分配资源。
public override void FrameCleanup(CommandBuffer cmd)
{
if (Destination != RenderTargetHandle.CameraTarget)
{
cmd.ReleaseTemporaryRT(Destination.id);
Destination = RenderTargetHandle.CameraTarget;
}
}
}
2 在Shader中使用
上述RenderFeature我们获得了一个全局的_CameraDepthNormalsTexture变量,我们就可以像Build-in下一样访问啦!
但是,一些之前固定管线下的一些采样、解码Texture函数在URP下不能直接用,要自己定义,主要需要一个解码函数。固定管线下函数:
其中:
直接搬运!完全没问题~
我给他合起来了,合成了一个函数,返回的时候用就行:
还要注意,采样要是屏幕空间的UV,不然乱七八糟。
然后shader后面必须也要加上一个自定义的LightTag:
突然发现这个复杂程度跟SSAO那个差不多。。。
看看效果,我们单独输出深度和法线:
一切正常!终于可以进行下一步了。
参考
URP深度法线纹理 - 简书 (jianshu.com)