【Unity URP】探讨描边方案 自定义后处理Volume

news2024/11/27 12:30:53

写在前面

本篇内容实现了在URP下获取深度、法线实现描边的后处理描边之前做的工作,包括讨论描边方案,以及写shader之前的自定义renderFeature和Volume组件的过程。

由于是想复刻《SCHiM》游戏里的画面风格,所以本篇文章的需求很明确,会夹杂一些自己的分析思考,并不是严格意义上的分享某一种描边技术的文章,更多的是个人的记录。

由于URP各个版本更新换代太快了,贴一下项目环境,给后面看到这篇文章的小伙伴提个醒,我的项目环境:

URP12.1.7

Unity2021.3.8f1


 1 明确描边需求

1.1 分析

 之前学习《入门精要》的时候就实现过基于Sobel算子的边缘检测描边效果:【Unity Shader】屏幕后处理2.0:实现Sobel边缘检测,这是一种基于颜色信息进行描边的方法,再来回顾一下效果:

简单总结一下这个实现的效果:

  • 除了边缘,物体的纹理有明显过渡的地方也会描边
  •  阴影也会被描边

在实现任何效果之前,我们需要明确需求,再提出合理的渲染方案,才是一个正确的思路。

这里再明确一下需求,由于我是有针对性地复刻游戏画面,我希望:

  • 最基础的,给物体边缘描边
  • 阴影虽然也有描边,但是阴影描边颜色是可控的,粗细也是额外控制的,因此阴影不能被后处理描上边
  • 最后,其实也是最特别的,游戏中出现了很多如下的平面的、简单的描边效果:

进行场景分析的时候也总结过:

所以上述需求,单纯的Sobel算子边缘检测无法满足需求。

1.2 提出实现方案

场景中阴影描边自己来,通过shadow值step就行,不赘述。

主要是场景中的那些装饰性的框框怎么实现。想了很久,最后定了一种可行的方案——基于Mask图进行Sobel算子边缘检测描边,然后场景中的物体描边采用深度+法线纹理后处理描边法解决。

基于Mask图的描边

原理大概是:场景中色彩不是很复杂,是单色Shading,按理来说纹理是不需要的。这里我们就不传递sRGB的颜色纹理,选择传递储存Mask信息的单通道纹理。

纹理需要在建模阶段,给场景中对应的物件进行特别的绘制,例如地面的斑马线、花坛的小砖块等等,纹理类似这样:

由于我还没开始准备场景中的模型贴图等资产,只能先随便简单画几个框框,看看铺在地面上的效果。

接下来我们进行正常的Sobel算子边缘检测,完全跟之前的实现过程一样,最后也是获得一个edge参数:

接下来

中间还需要把阴影考虑进去,再得到最后的值:

最后的效果(观察地板上的描边):

这样,场景中装饰性的平面上的描边效果,就实现了,并且还不是后处理,而是包含在了基本着色的Pass里。

接下来就是基于深度和法线的描边了,这里就开始了后处理描边的实现。我希望给他写成一个可以在Volume面板看到的一个后处理效果,所以可能步骤相对繁琐,需要脚本和shader之间的参数传递。先来回顾一下Volume组件:

3 URP下的后处理

URP下后处理都塞在了一个叫做Global Volume的组件中,我们右键可以创建出来:

挂到场景中后,可以在Volume下Add Override添加一些URP内置的后处理效果:

这些内置的后处理效果,Volume控制脚本都放在了这儿:

打开个Bloom后处理面板跟脚本对着看看:

会发现仅仅是可视化了面板,这个cs脚本再跟相应的RenderFeature想匹配,我们就可以实现Volume组件里控制后处理效果了!

4 自定义Volume

我们可以仿照这自定义一个Outline Volume组件,当然,这个Outline组件具体需要什么参数,只有写完shader之后才能明确知道,文章其实也是写完pass之后再回来补充的,所以直接给出Volume的脚本:

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

namespace UnityEngine.Rendering.Universal
{
    [Serializable,VolumeComponentMenu("My-post-processing/Outline")]
    public class OutlineVolume : VolumeComponent, IPostProcessComponent
{
    [Tooltip("边缘颜色")]
    public ColorParameter  OutlineColor = new ColorParameter(Color.white);
    [Tooltip("边缘检测大小")]
    public ClampedFloatParameter Scale = new ClampedFloatParameter(1f, 0f, 10f);
    [Tooltip("深度")]
    public ClampedFloatParameter DepthThreshold = new ClampedFloatParameter(0.2f, 0f, 10f);

    [Tooltip("法线深度")]
    public ClampedFloatParameter NormalThreshold = new ClampedFloatParameter(0.4f, 0f, 1f);
    public ClampedFloatParameter DepthNormalThreshold = new ClampedFloatParameter(0.5f, 0f, 1f);
    public ClampedFloatParameter DepthNormalThresholdScale = new ClampedFloatParameter(7f, 0f, 10f);


    public bool IsActive() => Scale.value > 0;

    public bool IsTileCompatible() => false;

}
}

这样就能在自定义路径下添加组件了。

当然这仅仅是写参数,还需要自定义一个实现方法。我们用RenderFeature来实现,完全把URP内置的实现路径和我们自定义的后处理过程剥离开,下一步就是自定义RenderFeature了。

5 自定义RenderFeature

刚接触URP的时候,一直不想去用RenderFeature,,觉得很麻烦,这次静下心来扒了一下整个过程,感觉还是足以理解的!

学习,我主要参考unityURP管线学习+后处理这篇文章最后的Volume相关的内容,最后的定义过程,参考了URP | 后处理-描边和Unity Outline Shader Tutorial,学习并实现了RenderFeature和Volume面板,完成的话接下来就能安心写主要的shader内容了:

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;


public class OutlineRenderFeature : ScriptableRendererFeature
{
    [System.Serializable]
    // 定义3个共有变量
    public class Settings
    {
        //public Shader shader; // 设置后处理shader
        public Material material; //后处理Material
        public RenderPassEvent renderPassEvent = RenderPassEvent.BeforeRenderingPostProcessing; // 定义事件位置,放在了官方的后处理之前
    }

    // 初始化一个刚刚定义的Settings类
    public Settings settings = new Settings(); 
    // 初始化Pass
    OutlinePass outlinePass;

    // 给pass传递变量,并加入渲染管线中
    public override void Create()
    {
        this.name = "OutlinePass"; // 外部显示的名字
        this.
        outlinePass = new OutlinePass(settings.renderPassEvent, settings.material);
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(outlinePass);
    }

    
}

public class OutlinePass : ScriptableRenderPass
{
    static readonly string renderTag = "Post Effects"; // 定义渲染Tag
    Material tmaterial;
    OutlineVolume outlineVolume;  // 传递到volume,OutlineVolume是Volume那个类定义的类名
    public OutlinePass(RenderPassEvent evt, Material tmaterial)
    {
        renderPassEvent = evt; // 设置渲染事件位置
        //var shader = tshader;  // 输入shader信息
        var material = tmaterial;
        if (material == null)
        {
            Debug.LogError("没有指定Material");
            return;
        }
    }

    // 后处理逻辑和渲染核心函数,相当于build-in 的OnRenderImage()
    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        // 判断是否开启后处理
        if (!renderingData.cameraData.postProcessEnabled)
        {
            return;
        }

        // 渲染设置
        var stack = VolumeManager.instance.stack;             // 传入volume
        outlineVolume = stack.GetComponent<OutlineVolume>();  // 拿到我们的volume
        if (outlineVolume == null)
        {
            Debug.LogError("Volume组件获取失败");
            return;
        }

        var cmd = CommandBufferPool.Get(renderTag);     // 设置渲染标签
        Render(cmd, ref renderingData);                 // 设置渲染函数
        context.ExecuteCommandBuffer(cmd);              // 执行函数
        CommandBufferPool.Release(cmd);                 // 释放
	}
	void Render(CommandBuffer cmd, ref RenderingData renderingData)
    {
        RenderTargetIdentifier source = renderingData.cameraData.renderer.cameraColorTarget;                 // 定义RT
        RenderTextureDescriptor inRTDesc = renderingData.cameraData.cameraTargetDescriptor;
        inRTDesc.depthBufferBits = 0;                                                                          // 清除深度

        var camera = renderingData.cameraData.camera;                         // 传入摄像机
        Matrix4x4 clipToView = GL.GetGPUProjectionMatrix(camera.projectionMatrix, true).inverse;

        tmaterial.SetColor("_Color", outlineVolume.OutlineColor.value);   // 获取value 组件的颜色

        tmaterial.SetMatrix("_ClipToView", clipToView);   // 反向输出到Shader

        tmaterial.SetFloat("_Scale", outlineVolume.Scale.value);
        tmaterial.SetFloat("_DepthThreshold", outlineVolume.DepthThreshold.value);
        tmaterial.SetFloat("_NormalThreshold", outlineVolume.NormalThreshold.value);

        tmaterial.SetFloat("_DepthNormalThreshold", outlineVolume.DepthNormalThreshold.value);
        tmaterial.SetFloat("_DepthNormalThresholdScale", outlineVolume.DepthNormalThresholdScale.value);

        int destination = Shader.PropertyToID("Temp1");

        // 获取一张临时RT
        cmd.GetTemporaryRT(destination, inRTDesc.width, inRTDesc.height, 0, FilterMode.Bilinear, RenderTextureFormat.DefaultHDR); //申请一个临时图像,并设置相机rt的参数进去

        cmd.Blit(source, destination);                            // 设置后处理


        cmd.Blit(destination, source, tmaterial, 0);
    }
}

体现在面板上就是:

关于展示到面板部分的内容,需要给定义的结构体前加上[System.Serializable]

我发现,如果只是创建一个RenderFeature脚本,跟URP下创建shader一样,函数啥的都缺胳膊少腿的,为什么不像创建URP Shader模板那样,也创建一个带有Pass的RenderFeature脚本模板呢!

然后我就写了个模板:

用的话Asset->Rendering->MyRenderFeature,就能创建自定义的模板啦! 

那么下一步,就是写shader了!明天继续!

参考

如何扩展Unity URP的后处理Volume组件 (zhihu.com)

Unity Outline Shader Tutorial - Roystan

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/423607.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

国网B接口调阅实时视频规范解读和代码示例分析

接口描述 国网B接口调阅实时视频&#xff0c;相关规范写的比较粗略&#xff1a; 调阅实时视频包括信令接口和媒体流接口&#xff0c;采用标准的SIP INVITESDP流程&#xff0c;媒体传输使用RTP/RTCP。 SDP 中 RTP Payload 的取值应遵守下面接口参数中的定义&#xff1a; a&a…

用低代码平台可视化设计表单

表单在前端可谓是非常常见的场景&#xff0c;而且通常需要花费开发非常多的时间来处理各种复杂的逻辑。特别是在企业中后台的业务中&#xff0c;存在着大量的表单&#xff0c;比如客户的订单&#xff0c;投诉的问题单&#xff0c;服务跟进过程每个流程的流转。凡是存在用户输入…

西安五日游规划

文章目录前言一、行前准备二、必带清单三、打卡美食四、景点地理坐标五、旅游时间轴六、景点小巴士第一天第二天第三天第四天第五天其他七、住宿八、小贴士总结前言 西安五日游。计划从北京出发&#xff0c;游玩五天&#xff0c;第五天回京。 一、行前准备 计划行程 票务预订 …

雅思资料汇总

关于雅思 流程&#xff1a; 雅思考试将全面启用现场照相和生物识别技术, 包括指纹采集和验证,考生将无需提供个人照片。我们建议考生在考试当天提前到场以完成个人物品放置&#xff0c;身份证件验证&#xff0c;检录入场等一系列重要考前准备工作。大厅在当天会贴出考生的考号…

【网络原理】应用层协议 与 传输层协议

✨个人主页&#xff1a;bit me&#x1f447; ✨当前专栏&#xff1a;Java EE初阶&#x1f447; 目 录&#x1f3c9;一. 应用层协议⚾️二. 传输层协议&#x1f452;1. UDP 协议&#x1f302;2. 校验和&#x1f453;3. TCP 协议&#x1f3c9;一. 应用层协议 我们自己写的应用程…

2023年MathorCup数模B题赛题

B 题 城市轨道交通列车时刻表优化问题 列车时刻表优化问题是轨道交通领域行车组织方式的经典问题之一。 列车时刻表规定了列车在每个车站的到达和出发(或通过)时刻&#xff0c;其在实 际运用过程中&#xff0c;通常用列车运行图来表示。图 1 为某一运行图的示例&#xff0c;图 …

代码随想录算法训练营第五十九天 | 503. 下一个更大元素 II、42. 接雨水

503. 下一个更大元素 II 方法一&#xff1a;将两个nums数组放在一起&#xff0c;使用单调栈求下一个更大元素&#xff0c;最后再把结果集即result数组resize到原数组大小就可以了。 方法二&#xff1a;在遍历的过程中模拟走了两遍nums class Solution { public:vector<in…

5G-OAI关于物理层中PDCCH源码解析

5G物理层是指5G网络的传输技术&#xff0c;包括无线帧、子帧、时隙、符号等方面的定义和规范。具体来说&#xff0c;5G物理层定义了无线帧的长度、帧结构、子帧结构、传输速率、带宽、时间同步等方面的参数&#xff0c;以及物理层信道的编码、调制和解调方式等方面的规范。5G物…

k8s 滚动部署学习总结

k8s 滚动部署学习总结 滚动发布 滚动发布配置总结 定义&#xff1a; 滚动升级&#xff08;Rolling update&#xff09; 就是指每次更新部分Pod&#xff0c;而不是在同一时刻将该Service下面的所有Pod shutdown&#xff0c;然后去更新逐个更新可以避免将业务中断 使用Deploy…

GEE初学者笔记之快速上手篇

1.基础概念 (1)谷歌云平台 整个GEE是基于Google Cloud云平台的一整套API开发环境。因此整个数据的处理全部都是在Google Cloud平台上实现的&#xff0c;无需本地机器参与运算。一般开发流程是在线/离线编辑代码&#xff0c;然后提交服务器端运行&#xff0c;完成之后会输出给我…

【Jenkins 2.x 实践指南】1.4 软件工程生产力--章节小结

目录 一、生产力三要素 1. 生产力 2. IT 中的生产力 二、Devops 和 Jenkins 1. DevOps 模式定义(AWS官方定义) 2. DevOps 实践经验 2.1 持续集成 2.2 持续交付 2.3 微服务 2.4 基础设施即代码 2.5 监控和日志记录 2.6 沟通与合作 一、生产力三要素 1. 生产力 劳动…

GPT系列简介与gpt训练(nanoGPT)

generateivelt pre-trained transformer ,GPT使用transformer做特征提取行&#xff0c;单项语言模型作为训练任务 gpt 1.0 通过自左向右生成式的构建预训练任务&#xff0c;然后得到一个通用的预训练模型&#xff0c;这个模型和BERT一样都可用来做下游任务的微调。GPT-1当时在…

Firefly-rk3288 开发板Linux系统编译

前言 手上的一块Firefly-RK3288开发板&#xff0c;看了下Firefly提供的SDK&#xff0c;压缩包就有15个多G&#xff0c;直接吓退。还好最近看到了韦东山老师提供的教学资料。记下学习步骤及遇到的问题解决办法。 1、开发环境 资料提供的有百问网制作的 ubuntu18.04 虚拟机镜像…

卷积计算转换为矩阵乘计算的几种场景和方法

本文默认卷积的输入输出数据格式为NHWC。 1x1卷积 输入shape为[N, H, W, C] , filter为[Hf, Wf, Ci, Co] FH, FW都为1&#xff0c;直接把输入shape reshape为[N, H * W, C], filter reshape为[[Hf * Wf * Ci, Co],然后进行矩阵乘得到[N, H * W, Co]&#xff0c;再reshape为卷…

ChatGPT 本地部署及搭建

这篇简要说下清华开源项目 ChatGLM 本地部署的详细教程。清华开源项目 ChatGLM-6B 已发布开源版本&#xff0c;这一项目可以直接部署在本地计算机上做测试&#xff0c;无需联网即可体验与 AI 聊天的乐趣。 项目地址&#xff1a;GitHub - THUDM/ChatGLM-6B: ChatGLM-6B&#xf…

一次小破站JS代码审计出XSS漏洞思路学习

今天看了小破站一个大佬的分析&#xff0c;感觉思路很有意思&#xff0c;感兴趣的xdm可以到大佬视频下提供的链接进行测试&#xff08;传送门&#xff09;这类社交平台的XSS漏洞利用起来其实危害是特别大的&#xff0c;利用XSS能在社交平台上呈现蠕虫式的扩散&#xff0c;大部分…

redis内存回收——过期、淘汰

DB结构删除策略惰性删除周期删除SLOWFAST淘汰策略redis内存设置过大时会增加同步等操作的复杂度 DB结构 /* Redis database representation. There are multiple databases identified* by integers from 0 (the default database) up to the max configured* database. The …

es 搜索中同时包含 “query“ 和 “filter“ 子句

Elasticsearch支持很多查询方式&#xff0c;其中一种就是DSL&#xff0c;它是把请求写在JSON里面&#xff0c;然后进行相关的查询。 一、Query DSL 与 Filter DSL DSL查询语言中存在两种&#xff1a;查询DSL&#xff08;query DSL&#xff09;和过滤DSL&#xff08;filter DSL…

数据库管理-第六十八期 Oracle 23c的其他(20230417)

数据库管理 2023-04-17第六十八期 Oracle 23c的其他1 DGPDB2 无锁并发总结第六十八期 Oracle 23c的其他 由于Oracle 23c的文档相对较少&#xff0c;一是当前文档主要面向开发人员&#xff0c;二是感觉实际内容还在不断增加&#xff0c;主要还有一点就是各种新特性的在官方文档…

几分种学会React Router v6使用

React路由可以实现页面间的切换。 传送门&#xff1a;英文文档 中文教程&#xff1a; https://www.reactrouter.cn/docs/getting-started/tutorial 1.基础使用 react 需求&#xff1a;实现一个普通的底部导航切换 1.安装react-router npm i react-router-dom62.配置根组件…