【Unity Shader入门精要 第12章】屏幕后处理效果(二)

1. 卷积

在图像处理中,卷积操作就是使用一个卷积核对一张图像中的每个像素做一系列的操作。

卷积核通常是一个四方形网格结构,如2x2、3x3的方形区域,该区域内每个方格都有一个权重值。

当对图像中的某个像素进行卷积操作时,将卷积核的中心放置于要处理的像素上,按照卷积核每个格子中的权重值,将卷积核覆盖到的像素的进行加权计算,得到最终结果。

下图为用一个3x3的卷积核对原始图像中A像素进行卷积操作时的示意:

在这里插入图片描述

2. 边缘检测

在这个例子中,我们实现一个简单边缘检测效果。使用的方法是,通过边缘检测算子直接对屏幕图像进行卷积操作,基于卷积结果判断某像素是否位于边缘上。

边缘检测算子就是一个预先定义好的卷积核,本次使用的为Sobel算子,如下:

在这里插入图片描述
原理为对于每个像素,按照算子的范围,计算该像素左右和上下一定范围内的相邻像素的灰度值差异大小——也就是卷积的结果,我们称之为梯度。梯度越大,代表像素两侧的灰度值差异越大,当前像素就越可能是某个边缘上的像素。

因此,在片元着色器中,对于每个处理中的片元,需要采样该片元相邻的9个像素,并按照算子中的权重值计算最终梯度。为了减少在片元着色器中逐像素计算uv偏移的消耗,我们把这个计算uv偏移的过程移到了顶点着色器中,在 v2f 结构中通过一个长度为9的uv数组传递给片元着色器。

在计算得到梯度值之后,按照梯度值在原始采样颜色和边缘颜色之间进行插值,完成描边效果。

另外,也提供了一个只显示描边的展示效果,通过 _OnlyEdge 变量控制是否只显示描边,通过 _BackgroundColor 变量控制当只显示描边时非边缘区域的颜色。

最后,实现新增一个 PostEffect_EdgeDetection 脚本用于调用描边处理效果,继承自前文的后处理父类 PostEffectBase,并在 OnRenderImage 方法中为Shader中的变量赋值及调用Blit方法。

测试脚本如下:

using UnityEngine;[ExecuteInEditMode]
[RequireComponent(typeof(Camera))]
public class PostEffect_EdgeDetection : PostEffectBase
{public Shader EdShader;public Material EdMat;public Color EdgeColor = Color.black;public Color BackgroundColor = Color.white;[Range(0.0f, 1.0f)]public float OnlyEdge = 0f;private void OnRenderImage(RenderTexture src, RenderTexture dest){Material _mat = CheckShaderAndMaterial(EdShader, EdMat);if (null == _mat) Graphics.Blit(src, dest);else{_mat.SetColor("_EdgeColor", EdgeColor);_mat.SetColor("_BackgroundColor", BackgroundColor);_mat.SetFloat("_OnlyEdge", OnlyEdge);Graphics.Blit(src, dest, _mat);}}
}

测试Shader如下:

Shader "MyShader/Chapter_12/Chapter_12_EdgeDetection_Shader"
{Properties{_MainTex("MainTex", 2D) = "white"{}}SubShader{Pass{Tags{"LightMode" = "ForwardBase"}ZTest AlwaysZWrite OffCull OffCGPROGRAM#pragma vertex vert#pragma fragment frag#pragma multi_compile_fwdbase#include "UnityCG.cginc"struct v2f{float4 pos : SV_POSITION;float2 uv[9] : TEXCOORD0;};sampler2D _MainTex;float4 _MainTex_TexelSize;fixed4 _EdgeColor;fixed _OnlyEdge;fixed4 _BackgroundColor;v2f vert(appdata_img v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);half2 _uv = v.texcoord;//计算sobel算子覆盖范围的uv偏移o.uv[0] = _uv + _MainTex_TexelSize.xy * half2(-1, -1);o.uv[1] = _uv + _MainTex_TexelSize.xy * half2(0, -1);o.uv[2] = _uv + _MainTex_TexelSize.xy * half2(1, -1);o.uv[3] = _uv + _MainTex_TexelSize.xy * half2(-1, 0);o.uv[4] = _uv + _MainTex_TexelSize.xy * half2(0, 0);o.uv[5] = _uv + _MainTex_TexelSize.xy * half2(1, 0);o.uv[6] = _uv + _MainTex_TexelSize.xy * half2(-1, 1);o.uv[7] = _uv + _MainTex_TexelSize.xy * half2(0, 1);o.uv[8] = _uv + _MainTex_TexelSize.xy * half2(1, 1);return o;}fixed Luminance(fixed4 _color){return 0.2125 * _color.r + 0.7154 * _color.g + 0.0721 * _color.b;}half Sobel(v2f i){//Sobel算子const half Gx[9] = {-1, -2, -1,0,  0,  0,1,  2,  1};const half Gy[9] = {-1,  0,  1,-2,  0,  2,-1,  0,  1};fixed _luminance;half _edgeX = 0;half _edgeY = 0;for(int it = 0; it < 9; it++){_luminance = Luminance(tex2D(_MainTex, i.uv[it]));_edgeX += _luminance * Gx[it];_edgeY += _luminance * Gy[it];}//差异越大,值越小,越可能是边界return 1 - abs(_edgeX) - abs(_edgeY);}fixed4 frag(v2f i) : SV_Target{half _edge = Sobel(i);fixed4 _samplerColor = tex2D(_MainTex, i.uv[4]);fixed4 _withEdgeColor = lerp(_EdgeColor, _samplerColor, _edge);fixed4 _onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, _edge);return lerp(_withEdgeColor, _onlyEdgeColor, _OnlyEdge);}ENDCG}}
}

效果如下:
在这里插入图片描述

3. 高斯模糊

3.1 高斯核

常见的几种通过卷积操作实现模糊效果的方式:

  • 均值模糊:通过一个各项权值相等且经过归一化处理的卷积核对图像进行卷积,也就是用像素周围一定范围相邻像素的均值作为颜色
  • 中值模糊:将相邻像素进行排序,用中值作为颜色
  • 高斯模糊:通过高斯核对图像进行卷积操作,高斯核中每项的权值通过高斯方程获得(归一化处理)

使用的高斯方程:
在这里插入图片描述
其中:

  • σ是标准方差,通常取1
  • xy分别代表横纵坐标到当前像素的整数距离

从上述公式可以看出,当σ为1时,只有xy会影响最终的模糊效果:距离越大影响越大、维度越高模糊程度越高。

由于高斯核中的权重都是由该公式求出的,因此只要确定了高斯核的维度,则其中的每一项就已经固定了,比如下面的例子中使用的5x5高斯核为:
在这里插入图片描述

3.2 优化手段

拆分高斯核

对于一个 WxH 的纹理,如果用 NxN 的高斯核直接进行滤波,则采样次数为:NxNxWxH

可以将高斯核拆分成一横一纵两个一维的高斯核,用拆分后的高斯核分别进行滤波,则采样次数就可以降低为:2xNxWxH

注意,是经过两个PASS分别滤波,也就是先用横向的高斯核对原始纹理进行滤波得到中间纹理,再用纵向的高斯核对中间纹理进行滤波得到最终的效果。并不是在一个PASS中同时用横纵高斯核进行处理,因为这样横纵高斯核采样的都是原始纹理的颜色。

降采样

由于卷积操作是在片元着色器中对纹理中的每个像素进行处理,因此,纹理的像素数量(或者说纹理的尺寸)就直接影响到计算的次数

我们可以先按照一定的比例对原始的纹理进行降采样处理,生成一个尺寸较小的临时纹理,然后对临时纹理进行高斯滤波,这样可以大大降低计算的数量。

扩大步长

要达到更好的模糊效果,最只管的方式是提高高斯核的维度,但是提高维度也意味着增加计算量。基于上文对高斯方程的分析,XY的范围越大影响越大,因此,在不提高维度的情况下,要获得更好的模糊效果,我们还可以增加XY的采样步长,也就是扩大XY的范围。

增加迭代次数

另外,还可以设置进行滤波的迭代次数,这样可以动态修改迭代次数,在满足性能要求的前提下,获得更好的模糊效果。

3.3 示例

下面的例子中,我们将5X5的高斯核拆分成如下两个一维的高斯核:
在这里插入图片描述
观察拆分后的高斯核,可以发现无论横纵,高斯权值都是对称的,且横纵高斯核中相同距离的权值相等,因此在Shader中只需要用一个长度为3的数组就可以表达拆分后的高斯核。

测试脚本

using UnityEngine;public class PostEffect_GaussianBlur : PostEffectBase
{public Shader GaussianBlurShader;public Material GaussianBlurMat;[Range(1, 8)]public float DownSampler = 1;[Range(0.2f, 3f)]public float SmaplerStep = 1;[Range(1, 4)]public float BlurRound = 1;private void OnRenderImage(RenderTexture src, RenderTexture dest){Material _mat = CheckShaderAndMaterial(GaussianBlurShader, GaussianBlurMat);if(null == _mat) Graphics.Blit(src, dest);else{int _width = (int)(src.width / DownSampler);int _height = (int)(src.height / DownSampler);RenderTexture _buffer_0 = RenderTexture.GetTemporary(_width, _height, 0);_buffer_0.filterMode = FilterMode.Bilinear;//原始图像降采样至 _buffer_0,并保持后续每次操作中 _buffer_0 都是待处理的图像Graphics.Blit(src, _buffer_0);for (int i = 0; i < BlurRound; i++){_mat.SetFloat("_SmaplerStep", SmaplerStep * i);//横向滤波RenderTexture _buffer_1 = RenderTexture.GetTemporary(_width, _height, 0);Graphics.Blit(_buffer_0, _buffer_1, _mat, 0);RenderTexture.ReleaseTemporary(_buffer_0);_buffer_0 = _buffer_1;//纵向滤波_buffer_1 = RenderTexture.GetTemporary(_width, _height, 0);Graphics.Blit(_buffer_0, _buffer_1, _mat, 1);RenderTexture.ReleaseTemporary(_buffer_0);_buffer_0 = _buffer_1;}//将最终结果输出Graphics.Blit(_buffer_0, dest);RenderTexture.ReleaseTemporary(_buffer_0);}}
}

测试Shader

Shader "MyShader/Chapter_12/Chapter_12_GaussianBlur_Shader"
{Properties{_MainTex("MainTex", 2D) = "white"{}}SubShader{ZTest Always ZWrite Off Cull Off//预定义着色器函数,可以在后续的Pass中直接调用//避免在多个Pass中重复实现同样的代码CGINCLUDE#include "UnityCG.cginc"sampler2D _MainTex;float4 _MainTex_TexelSize;half _SmaplerStep;struct v2f{float4 pos : SV_POSITION;float2 uv[5] : TEXCOORD0;};//横向滤波顶点着色函数v2f vertHorizental(appdata_img v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv[0] = v.texcoord;o.uv[1] = v.texcoord + _MainTex_TexelSize.xy * float2(-1, 0) * _SmaplerStep;o.uv[2] = v.texcoord + _MainTex_TexelSize.xy * float2(1, 0) * _SmaplerStep;o.uv[3] = v.texcoord + _MainTex_TexelSize.xy * float2(-2, 0) * _SmaplerStep;o.uv[4] = v.texcoord + _MainTex_TexelSize.xy * float2(2, 0) * _SmaplerStep;return o;}//纵向滤波顶点着色函数v2f vertVertical(appdata_img v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv[0] = v.texcoord;o.uv[1] = v.texcoord + _MainTex_TexelSize.xy * float2(0, -1) * _SmaplerStep;o.uv[2] = v.texcoord + _MainTex_TexelSize.xy * float2(0, 1) * _SmaplerStep;o.uv[3] = v.texcoord + _MainTex_TexelSize.xy * float2(0, -2) * _SmaplerStep;o.uv[4] = v.texcoord + _MainTex_TexelSize.xy * float2(0, 2) * _SmaplerStep;return o;}//通用滤波片元着色函数fixed4 frag(v2f i) : SV_Target{const half G[3] = {0.4026, 0.2442, 0.0545};fixed3 _color = tex2D(_MainTex, i.uv[0]) * G[0];for(int it = 1; it < 3; it++){_color += tex2D(_MainTex, i.uv[it * 2]) * G[it];_color += tex2D(_MainTex, i.uv[it * 2 - 1]) * G[it];}return fixed4(_color, 1);}ENDCG//横向滤波PassPass{//将Pass命名,方便后续其他效果使用Name "GAUSSIAN_BLUR_HORIZENTAL"CGPROGRAM#pragma vertex vertHorizental#pragma fragment fragENDCG}//纵向滤波PassPass{Name "GAUSSIAN_BLUR_VERTICAL"CGPROGRAM#pragma vertex vertVertical#pragma fragment fragENDCG}}
}

高斯:
在这里插入图片描述

高斯模糊:
在这里插入图片描述

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

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

相关文章

Linux域名解析不了/网络不可达/虚拟机连接不了的问题

记录域名解析不了/网络不可达/虚拟机连接不了的问题问题 目录 文章目录 记录域名解析不了/网络不可达/虚拟机连接不了的问题问题1.首先确定已经连接上路由器(我的就是在这嗝屁了....)1.1 查看路由表1.2查看当前的网络连接状态&#xff0c;包括网关1.3查看网络接口的状态&…

如何解决研发数据传输层面安全可控、可追溯的共性需求?

研发数据在企业内部跨网文件交换&#xff0c;是相对较为普遍而频繁的文件流转需求&#xff0c;基于国家法律法规要求及自身安全管理需要&#xff0c;许多企业进行内部网络隔离。不同企业隔离方案各不相同&#xff0c;比如银行内部将网络隔离为生产网、办公网、DMZ区&#xff0c…

十四天学会Vue——Vue核心下篇(理论+实战)(第三天)

一、Vue核心下篇 1.15 常用的内置指令 1. v-text <!--准备好一个容器 --><div id"root"><!-- 1.v-text中的字符替换掉div整个字符 --><div v-text"name">你好,{{name}}</div><!-- 2.将标签当做字符串解析 --><di…

Vue Router (创建 挂载)

创建路由模块 在src目录下创建router.js文件作为模块&#xff0c;该文件中按照如下步骤进行操作 1.导入路由相关函数&#xff0c;具体代码如下&#xff1a; import{ createRouter&#xff0c;createWebHashHistory } from Vue-router在上述代码中&#xff0c;从Vue-router中…

网络原理-TCP/IP --应用层

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 目录 3.网络原理 -TCP/IP3.1 应用层 3.网络原理 -TCP/IP 3.1 应用层 应用层是程序员打交道最多的一层,与应用程序直接相关 而应用层的协议,实际上就规定了你写的程序,通过网络传输的时候,按…

2928. 给小朋友们分糖果 I

给你两个正整数 n 和 limit 。 请你将 n 颗糖果分给 3 位小朋友&#xff0c;确保没有任何小朋友得到超过 limit 颗糖果&#xff0c;请你返回满足此条件下的 总方案数 。 示例 1&#xff1a; 输入&#xff1a;n 5, limit 2 输出&#xff1a;3 解释&#xff1a;总共有 3 种方…

FX2N用什么编程软件:深入解析与选择指南

FX2N用什么编程软件&#xff1a;深入解析与选择指南 在工业自动化领域&#xff0c;FX2N系列PLC因其卓越的性能和广泛的应用而备受瞩目。然而&#xff0c;对于许多初学者和工程师来说&#xff0c;如何选择合适的编程软件来开发FX2N系列PLC却是一个令人困惑的问题。本文将从四个…

【LINUX】LINUX基础(目录结构、基本权限、基本命令)

文章目录 LINUX的目录结构LINUX的基本权限LINUX基本命令 LINUX的目录结构 /&#xff1a;表示根目录bin&#xff1a;存放二进制可执行文件(命令ls、cat、mkdir等)boot&#xff1a;存放系统引导文件dev&#xff1a;存放设备文件etc&#xff1a;存放系统配置文件home&#xff1a;…

LeeCode热题100(爬楼梯)

爬楼梯这个题我断断续续看了不下5遍&#xff0c;哪次看都是懵逼的&#xff0c;就会说是满足动态规划&#xff0c;满足斐波那契数列&#xff0c;也不说为什么。 本文一定让你明白怎么分析这个题的规律&#xff08;利用数学的递推思想来分析&#xff09;&#xff0c;看不懂来打我…

Ubuntu 22.04 .NET8 程序 环境安装和运行

前言 我们需要将.NET8编写的console控制台程序&#xff0c;部署在Ubuntu服务器上运行。 安装.NET运行时 1.增加微软包安装源 wget https://packages.microsoft.com/config/ubuntu/22.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb sudo dpkg -i packages…

JAVA学习-练习试用Java实现“Z字形变换”

问题&#xff1a; 将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时&#xff0c;排列如下&#xff1a; P A H N A P L S I I G Y I R 之后&#xff0c;你…

1114 全素日

你好哇&#xff0c;新的一天开始啦&#xff01; solution 取数值的不同部分&#xff0c;联想到借助string #include<iostream> #include<string> using namespace std; bool judge(string s){int n atoi(s.c_str());if(n 1 || n 0) return false;for(int i 2…

FT编程语言:探索其深度、广度与未来潜力

FT编程语言&#xff1a;探索其深度、广度与未来潜力 FT编程语言&#xff0c;作为一个新兴且充满潜力的编程领域&#xff0c;正逐渐引起开发者和研究者的广泛关注。其独特的设计理念和强大的功能使得它在多个方面展现出独特的魅力。本文将从四个方面、五个方面、六个方面和七个…

“浔川AI翻译”正式上线!——浔川AI社

1前言&#xff1a; 浔川AI翻译机是一种基于人工智能技术开发的翻译设备。它能够实时将一种语言的文本或口语翻译成另一种语言&#xff0c;使得不同语言之间的交流更加便捷。浔川AI翻译机利用深度学习算法和大数据训练模型&#xff0c;能够自动识别和理解输入的语言&#xff0c…

618大促买什么数码最划算?数码好物清单整理,买到就是赚到!

618年中大促数码产品爱好者们纷纷摩拳擦掌&#xff0c;准备在这个购物狂欢节里抢购心仪已久的数码好物&#xff0c;在繁多的品牌和型号中挑选出最划算的产品&#xff0c;并不是一件容易的事情&#xff0c;为了帮助大家更好地把握这次购物机会&#xff0c;我们精心整理了一份数码…

基于java的CRM客户关系管理系统(一)

目录 第一章 绪论 1.1 研究背景和意义 1.1.1 企业管理信息化 1.1.2 CRM的概念 1.1.3 CRM客户管理系统的意义 1.2 国内外研究 1.3 论文结构 1.4 本章小结 资源毕业设计毕业论文源代码请移步 CRM客户关系管理系统毕业论文毕业设计源代码 第一章 绪论 1.1 研究背景…

【qt】多窗口开发

多窗口开发 一.应用场景二.嵌入的窗口1.设计Widget窗口2.创建窗口3.添加窗口4.总代码 三.独立的窗口1.创建窗口2.显示窗口 四.总结 一.应用场景 多窗口,顾名思义,有多个窗口可以供我们进行操作! 截个小图,你应该就知道了 OK,话不多说,直接开干,先来设计我们的主窗口 需要蔬菜…

《异常检测——从经典算法到深度学习》29 EasyTSAD: 用于时间序列异常检测模型的工业级基准

《异常检测——从经典算法到深度学习》 0 概论1 基于隔离森林的异常检测算法 2 基于LOF的异常检测算法3 基于One-Class SVM的异常检测算法4 基于高斯概率密度异常检测算法5 Opprentice——异常检测经典算法最终篇6 基于重构概率的 VAE 异常检测7 基于条件VAE异常检测8 Donut: …

MySQL的一些高频面试题汇总(持续补充)

1.事务4大特性 事务4大特性&#xff1a;原子性、一致性、隔离性、持久性 原⼦性&#xff1a;事务是最⼩的执⾏单位&#xff0c;不允许分割。事务的原⼦性确保动作要么全部完成&#xff0c;要么全不执行 一致性&#xff1a;执⾏事务前后&#xff0c;数据保持⼀致&#xff0c;多…

2-链表-51-删除链表的倒数第 N 个结点-LeetCode19

2-链表-51-删除链表的倒数第 N 个结点-LeetCode19 参考:代码随想录 LeetCode: 题目序号19 更多内容欢迎关注我(持续更新中,欢迎Star✨) Github:CodeZeng1998/Java-Developer-Work-Note 技术公众号:CodeZeng1998(纯纯技术文) 生活公众号:好锅(Life is more than co…