Unity Shader 屏幕后效果——边缘检测
关于屏幕后效果的控制类详细见之前写的另一篇博客:
https://www.cnblogs.com/koshio0219/p/11131619.html
这篇主要是基于之前的控制类,实现另一种常见的屏幕后效果——边缘检测。
概念和原理部分:
首先,我们需要知道在图形学中经常处理像素的一种操作——卷积。
卷积操作的实质在于,对于图像中的每个像素与其周围的像素进行的重新融合计算行为,以得到不同的像素处理效果,例如锐化图像,模糊图像,检测边缘等。
卷积操作通过不同的像素融合算法能得到各不相同的效果,这主要依赖于卷积核。
可以把卷积核看作是一个n行n列方阵,原始像素则位于方阵的中心。
边缘检测的卷积核也叫边缘检测算子,以Sobel算子为例,形如:
需要特别注意的是,这里的Sobel算子是基于坐标轴以屏幕左上为原点,右下分别为+x,+y方向的,而不是类似于uv坐标轴的以屏幕左下为原点,右上分别为+x,+y方向的。这一点需要特别注意,不然后面的程序很容易写错。
其中Gx和Gy分别是纵向和横向两个方向的边缘线检测,你可以通过去掉矩阵中的零元素来想象,因为零元素不会对像素产生任何影响。也就是说,Gx是为了计算横向的梯度值,Gy为了计算纵向的梯度值。
横向的梯度值检测出来的是纵向的边缘线,纵向的梯度值检测出来的是横向的边缘线。这一点非常容易混淆,需要特别注意。
利用边缘检测算子除了融合像素外,主要是为了计算出像素的梯度值。
一个像素和周围的像素之间梯度值很高,意味着它与周围的像素差异很大,我们可以想象这个像素和周围的像素格格不入,存在一个无法逾越的阶梯;那么就可以这么认为,这个像素可以作为一条边界中的值。
对图像中的每个像素都如此处理,最终就能得到图像的边缘。这也就是边缘检测的实质内容。
计算方法:
1.得到每个像素周围的8个像素的坐标位置以便与Sobel算子进行计算,类似于:(排列方式应该与Sobel算子的坐标轴保持一致)
uv[0] | uv[1] | uv[2] |
uv[3] | uv[4](原始像素点) | uv[5] |
uv[6] | uv[7] | uv[8] |
但因为uv坐标的原点在左下角,因此在计算uv[0]-uv[8]时,若依据uv[4]为原始像素点,则它们的偏移可以表示为如下情况:
(-1,1)uv[0] | (0,1)uv[1] | (1,1)uv[2] |
(-1,0)uv[3] | (0,0)uv[4] | (1,0)uv[5] |
(-1,-1)uv[6] | (0,-1)uv[7] | (1,-1)uv[8] |
2.通过偏移值可以很快计算出目标像素的周围像素位置坐标信息,随后与Gx和Gy对应元素分别进行横向和纵向的梯度值计算,也就是分别进行纵向和横向的边缘检测:
具体计算方法为:先对卷积核进行180度翻转,得到新的矩阵,随后各项对应元素相乘并相加,注意,不要与矩阵的乘法计算混淆。
但因为Sobel算子是否执行翻转操作对计算结果没有任何影响,故对于Sobel算子来说,翻转操作可以省略。
Gx和Gy计算结束后再将它们开平方和;但往往为了简化GPU的计算量,可以直接取各自的绝对值再相加,得到最终的梯度值G。
3.计算出梯度值后对原始的采样结果进行关于G的插值操作以得到最终的图像。
程序实现:
首先是参数调控的脚本:
- using UnityEngine;
- public class EdgeDetectionCtrl : ScreenEffectBase
- {
- private const string _EdgeOnly = "_EdgeOnly";
- private const string _EdgeColor = "_EdgeColor";
- private const string _BackgroundColor = "_BackgroundColor";
- [Range(,)]
- public float edgeOnly = 0.0f;
- public Color edgeColor = Color.black;
- public Color backgroundColor = Color.white;
- private void OnRenderImage(RenderTexture source, RenderTexture destination)
- {
- if (Material!=null)
- {
- Material.SetFloat(_EdgeOnly, edgeOnly);
- Material.SetColor(_EdgeColor, edgeColor);
- Material.SetColor(_BackgroundColor, backgroundColor);
- Graphics.Blit(source, destination, Material);
- }
- else
- Graphics.Blit(source, destination);
- }
- }
同样是继承自ScreenEffectBase基类,三个参数的意义分别如下:
edgeOnly(shader中:_EdgeOnly):边缘线的叠加程度,0表示完全叠加,1表示只显示边缘线,不显示原图
edgeColor(shader中:_EdgeColor):边缘线的颜色
backgroundColor(shader中:_BackgroundColor):背景颜色,当只显示边缘线时,可以很清晰看出
基类脚本见:
https://www.cnblogs.com/koshio0219/p/11131619.html
下面是Shader脚本:
- Shader "MyUnlit/EdgeDetection"
- {
- Properties
- {
- _MainTex ("Texture", 2D) = "white" {}
- }
- SubShader
- {
- Tags { "RenderType"="Opaque" }
- Pass
- {
- ZTest always
- Cull off
- ZWrite off
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- #pragma multi_compile_fog
- #include "UnityCG.cginc"
- struct appdata
- {
- float4 vertex : POSITION;
- float2 uv : TEXCOORD0;
- };
- struct v2f
- {
- half2 uv[] : TEXCOORD0;
- UNITY_FOG_COORDS()
- float4 pos : SV_POSITION;
- };
- sampler2D _MainTex;
- //纹理映射到[0,1]之后的大小,用于计算相邻区域的纹理坐标
- half4 _MainTex_TexelSize;
- //定义控制脚本中对应的参数
- fixed _EdgeOnly;
- fixed4 _EdgeColor;
- fixed4 _BackgroundColor;
- v2f vert (appdata v)
- {
- v2f o;
- o.pos = UnityObjectToClipPos(v.vertex);
- half2 uv = v.uv;
- half2 size = _MainTex_TexelSize;
- //计算周围像素的纹理坐标位置,其中4为原始点,右侧乘积因子为偏移的像素单位,坐标轴为左下角原点,右上为+x,+y方向,与uv的坐标轴匹配
- o.uv[] = uv + size * half2(-, );
- o.uv[] = uv + size * half2(, );
- o.uv[] = uv + size * half2(, );
- o.uv[] = uv + size * half2(-, );
- o.uv[] = uv + size * half2(, );
- o.uv[] = uv + size * half2(, );
- o.uv[] = uv + size * half2(-, -);
- o.uv[] = uv + size * half2(, -);
- o.uv[] = uv + size * half2(, -);
- UNITY_TRANSFER_FOG(o,o.pos);
- return o;
- }
- //计算对应像素的最低灰度值并返回
- fixed minGrayCompute(v2f i,int idx)
- {
- return Luminance(tex2D(_MainTex, i.uv[idx]));
- }
- //利用Sobel算子计算最终梯度值
- half sobel(v2f i)
- {
- const half Gx[] = {
- - ,,,
- - ,,,
- - ,,
- };
- const half Gy[] = {
- -,-,-,
- , , ,
- , ,
- };
- //分别计算横向和纵向的梯度值,方法为各项对应元素相乘并相加
- half graX = ;
- half graY = ;
- for (int it = ; it < ; it++)
- {
- graX += Gx[it] * minGrayCompute(i, it);
- graY += Gy[it] * minGrayCompute(i, it);
- }
- //绝对值相加近似模拟最终梯度值
- return abs(graX) + abs(graY);
- }
- fixed4 frag (v2f i) : SV_Target
- {
- half gra = sobel(i);
- fixed4 col = tex2D(_MainTex, i.uv[]);
- //利用得到的梯度值进行插值操作,其中梯度值越大,越接近边缘的颜色
- fixed4 withEdgeColor = lerp( col, _EdgeColor, gra);
- fixed4 onlyEdgeColor = lerp( _BackgroundColor, _EdgeColor, gra);
- fixed4 color = lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
- UNITY_APPLY_FOG(i.fogCoord, color);
- return color;
- }
- ENDCG
- }
- }
- }
效果如下:
Unity Shader 屏幕后效果——边缘检测的更多相关文章
- Unity Shader 屏幕后效果——颜色校正
屏幕后效果指的是,当前整个场景图已经渲染完成输出到屏幕后,再对输出的屏幕图像进行的操作. 在Unity中,一般过程通常是: 1.建立用于处理效果的shader和临时材质,给shader脚本传递需要控制 ...
- Unity Shader 屏幕后效果——全局雾
Unity内置的雾效需要在每个shader中分别编写,造成了极大的不便.这里利用屏幕后处理产生可单独控制且自由度更高的雾效. 屏幕后雾效的本质在于,通过深度纹理重构出每个像素在世界空间中的位置,根据得 ...
- Unity Shader 屏幕后效果——高斯模糊
高斯模糊是图像模糊处理中非常经典和常见的一种算法,也是Bloom屏幕效果的基础. 实现高斯模糊同样用到了卷积的概念,关于卷积的概念和原理详见我的另一篇博客: https://www.cnblogs.c ...
- Unity Shader 屏幕后效果——景深
景深效果的原理是,在摄像机的近裁剪平面和远裁剪平面之间可以设置一个焦距,在这个距离所在的平面上的物体最为清晰,而这个距离之前或之后的物体成像是一种模糊状态(根据距离逐渐模糊,最终达到最为模糊的状态). ...
- Unity Shader 屏幕后效果——Bloom外发光
Bloom的原理很简单,主要是提取渲染图像中的亮部区域,并对亮部区域进行模糊处理,再与原始图像混合而成. 一般对亮部进行模糊处理的部分采用高斯模糊,关于高斯模糊,详见之前的另一篇博客: https:/ ...
- Unity Shader 屏幕后效果——摄像机运动模糊(速度映射图实现)
速度映射图主要是为了得到每个像素相对于前一帧的运动矢量,其中一种方法是使用摄像机的深度纹理来推导. 推导过程如下: 先由深度纹理逆推出NDC(归一化的设备坐标)下的顶点坐标,利用VP矩阵(视角*投影矩 ...
- Unity Shader实现描边效果
http://gad.qq.com/article/detail/28346 描边效果是游戏里面非常常用的一种效果,一般是为了凸显游戏中的某个对象,会给对象增加一个描边效果.本篇文章和大家介绍下利用S ...
- Unity Shader 之 透明效果
透明效果 透明效果一般有两种实现方法: 第一种,使用透明度测试(Alpha Test) 第二种,使用透明度混合(Alpha Blending) 透明度测试和透明度混合机制: 透明度测试(Alpha T ...
- Unity实现屏幕抖动效果(通过Camera Viewpoint实现)
由于游戏死亡时一般都需要屏幕抖一下下. 所以百度了下相关写法,发现方法很多~~~ 找来找去,找到个简单粗暴地,啥都不需要,一个脚本拖动到Camera上就可以了 略微修改了一点点,share一下 usi ...
随机推荐
- matlab 矢量化编程(一)—— 计算 AUC
AUC = sum( (Y(2:end)+Y(1:end-1))/2 .* (X(2:end) - X(1:end-1)) X 和 Y 均是向量: Y(2:end) - Y(1:end-1),是 Y( ...
- JS-LINQ
JS中使用LINQ 详细使用方法可找相关资料 文件在这里 引用: <script type="text/javascript" src="@Url.Content( ...
- android反射组件 (一个)java 它们的定义annotation基础知识
它们的定义annotation它由三部分组成: 它们的定义annotation.使用annotation班.annotation的处理方法. 一.自己定义annotation 元注解包含下面: 1) ...
- node.js学习笔记之json数据转string
Node.js中的JSON问题 var str = '{"dir":"kunhony","param":"archive&qu ...
- NPM镜像设置方法!
使用npm安装一些包失败了的看过来(npm国内镜像介绍) 发布于 2012-4-26 04:19 最后一次编辑是 2013-12-11 23:21 这个也是网上搜的,亲自试过,非常好用! 镜像使用方法 ...
- 赵伟国辞去TCL集团董事等职位,紫光参与TCL定增浮盈已超7亿
集微网消息,TCL 集团于8月9日晚间发布公告称,公司董事会于近日收到董事赵伟国先生的书面辞职报告,赵伟国先生因个人原因申请辞去公司董事及公司战略委员会委员职务.辞任后,赵伟国先生不再担任公司任何职务 ...
- WPF支持OneWay,TwoWay,OneTime,Default和OneWayToSource
原文:WPF支持OneWay,TwoWay,OneTime,Default和OneWayToSource 无论是目标属性还是源属性,只要发生了更改,TwoWay 就会更新目标属性或源属性. OneWa ...
- WPF 流打印
原文:WPF 流打印 PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == true) { Syst ...
- php如何去掉二维数组中重复的元素
$arr=array( "1"=>array("a","b "), "2"=>array("a&q ...
- wcf服务端代码方式及客户端代码方式
ServiceHost host; // 全局 host = new ServiceHost(typeof(实现服务接口的类)); host.open(); 用代码配置端点的方法 host.add ...