【Unity UPR】造个获取深度法线纹理的轮子

描边需要深度+法线纹理的加持,效果才能达到最好,但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)

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
手动计算网格的法线可以通过以下步骤实现: 1. 获取网格的顶点和三角形列表。可以使用 `Mesh.vertices` 和 `Mesh.triangles` 属性来获取它们。 ```csharp Vector3[] vertices = mesh.vertices; int[] triangles = mesh.triangles; ``` 2. 为每个顶点计算法线。遍历三角形列表,对于每个三角形,计算它的面法线,并将其添加到其三个顶点的法线总和。 ```csharp Vector3[] normals = new Vector3[vertices.Length]; for (int i = 0; i < triangles.Length; i += 3) { int i1 = triangles[i]; int i2 = triangles[i + 1]; int i3 = triangles[i + 2]; Vector3 v1 = vertices[i1]; Vector3 v2 = vertices[i2]; Vector3 v3 = vertices[i3]; Vector3 normal = Vector3.Cross(v2 - v1, v3 - v1).normalized; normals[i1] += normal; normals[i2] += normal; normals[i3] += normal; } ``` 3. 归一化每个顶点的法线。对于每个顶点,将其法线向量除以其长度,以使其长度为1。 ```csharp for (int i = 0; i < normals.Length; i++) { normals[i].Normalize(); } ``` 4. 将计算出的法线设置为网格的法线。使用 `Mesh.normals` 属性将计算出的法线向量数组分配给网格。 ```csharp mesh.normals = normals; ``` 完整的代码示例: ```csharp void CalculateNormals(Mesh mesh) { Vector3[] vertices = mesh.vertices; int[] triangles = mesh.triangles; Vector3[] normals = new Vector3[vertices.Length]; for (int i = 0; i < triangles.Length; i += 3) { int i1 = triangles[i]; int i2 = triangles[i + 1]; int i3 = triangles[i + 2]; Vector3 v1 = vertices[i1]; Vector3 v2 = vertices[i2]; Vector3 v3 = vertices[i3]; Vector3 normal = Vector3.Cross(v2 - v1, v3 - v1).normalized; normals[i1] += normal; normals[i2] += normal; normals[i3] += normal; } for (int i = 0; i < normals.Length; i++) { normals[i].Normalize(); } mesh.normals = normals; } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九九345

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值