【Unity3D】基于深度和法线纹理的边缘检测方法
1 前言
边缘检测特效中使用屏后处理技术,通过卷积运算计算梯度,检测每个像素周围像素的亮度差异,以识别是否是边缘像素;选中物体描边特效中也使用了屏后处理技术,通过 CommandBuffer 获取目标物体渲染后的模板纹理,并将模板纹理模糊化,使得模板纹理的边缘向外扩张,再将模糊化后的模板纹理与原纹理进行合成,得到描边后的纹理;基于模板测试和顶点膨胀的描边方法中开 2 个 Pass 渲染通道,第一个 Pass 通道将待描边物体的屏幕区域像素对应的模板值标记为 1,第二个 Pass 通道将待描边物体的顶点向外膨胀,绘制模板值为非 1 的膨胀区域(即外环区域),实现描边。
边缘检测特效边缘检测特效中容易将物体的影子等误识别为边缘,选中物体描边特效和基于模板测试和顶点膨胀的描边方法中存在层叠消融现象。本文将实现另一张边缘检测算法:基于深度和法线纹理的边缘检测算法。
本文完整资源见→Unity3D基于深度和法线纹理的边缘检测方法。
2 边缘检测原理
对渲染后的屏幕纹理进行二次渲染,根据像素点周围深度和法线的差异判断是否是物体边缘像素,如果是边缘,需要重新进行边缘着色。判断边缘的具体做法是:对该像素点周围的像素点的深度和法线进行卷积运算,得到该点的梯度(反映该点附近深度和法线突变的强度),根据梯度阈值判断该点是否是边缘。 深度和法线纹理介绍见→屏幕深度和法线纹理简介。
1)边缘检测算子
边缘检测算子和梯度的定义见→边缘检测特效。本文将使用 Roberts 边缘检测算子,如下:

使用 1 范式梯度,如下:

2)深度差异计算
深度差异计算如下,depth1、depth2 分别为待检测点左上和右下(或右上和左下)像素点的深度值,_DepthScale 为深度缩放系数。
float depthDelta = abs(depth1 - depth2) * depth1 * _DepthScale; // 深度差异(距离相机越远, 像素点越少, 对深度差越敏感, 所以乘了depth1)
3)法线差异计算
法线差异计算如下,normal1、normal2 分别为待检测点左上和右下(或右上和左下)像素点的法线向量(已归一化),_NormalScale 为法线缩放系数。
float normalDelta = (1 - abs(dot(normal1, normal2))) * _NormalScale; // 法线差异
3 边缘检测实现
EdgeDetection.cs
using UnityEngine;
[RequireComponent(typeof(Camera))] // 需要相机组件
public class EdgeDetection : MonoBehaviour {
[Range(0.0f, 1.0f)]
public float edgesOnly = 0.0f; // 是否仅显示边缘
public Color edgeColor = Color.black; // 边缘颜色
public Color backgroundColor = Color.white; // 背景颜色
public float sampleScale = 1.0f; // 采样缩放系数(值越大, 描边越宽)
public float depthScale = 1.0f; // 深度缩放系数(值越大, 越易识别为边缘)
public float normalScale = 1.0f; // 法线缩放系数(值越大, 越易识别为边缘)
private Material material; // 材质
private void Start() {
material = new Material(Shader.Find("MyShader/EdgeDetect"));
material.hideFlags = HideFlags.DontSave;
}
private void OnEnable() {
GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
}
//[ImageEffectOpaque] // 在不透明的 Pass 执行完毕后立即调用 OnRenderImage 方法(透明物体不需要描边)
private void OnRenderImage(RenderTexture src, RenderTexture dest) {
if (material != null) {
material.SetFloat("_EdgeOnly", edgesOnly);
material.SetColor("_EdgeColor", edgeColor);
material.SetColor("_BackgroundColor", backgroundColor);
material.SetFloat("_SampleScale", sampleScale);
material.SetFloat("_DepthScale", depthScale);
material.SetFloat("_NormalScale", normalScale);
Graphics.Blit(src, dest, material);
} else {
Graphics.Blit(src, dest);
}
}
}
EdgeDetection.shader
Shader "MyShader/EdgeDetect" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {} // 主纹理
_EdgeOnly ("Edge Only", Float) = 1.0 // 是否仅显示边缘
_EdgeColor ("Edge Color", Color) = (0, 0, 0, 1) // 边缘颜色
_BackgroundColor ("Background Color", Color) = (1, 1, 1, 1) // 背景颜色
_SampleScale("Sample Scale", Float) = 1.0 // 采样缩放系数(值越大, 描边越宽)
_DepthScale("Depth Scale", Float) = 1.0 // 深度缩放系数(值越大, 越易识别为边缘)
_NormalScale("Normal Scale", Float) = 1.0 // 法线缩放系数(值越大, 越易识别为边缘)
}
SubShader {
Pass {
// 深度测试始终通过, 关闭深度写入
ZTest Always ZWrite Off
CGPROGRAM
#include "UnityCG.cginc"
#pragma vertex vert
#pragma fragment frag
sampler2D _MainTex; // 主纹理
sampler2D _CameraDepthNormalsTexture; // 深度&法线纹理
uniform half4 _MainTex_TexelSize; // _MainTex的像素尺寸大小, float4(1/width, 1/height, width, height)
fixed _EdgeOnly; // 是否仅显示边缘
fixed4 _EdgeColor; // 边缘颜色
fixed4 _BackgroundColor; // 背景颜色
float _SampleScale; // 采样缩放系数(值越大, 描边越宽)
float _DepthScale; // 深度缩放系数(值越大, 越易识别为边缘)
float _NormalScale; // 法线缩放系数(值越大, 越易识别为边缘)
struct v2f {
float4 pos : SV_POSITION; // 裁剪空间中顶点坐标
half2 uv[5] : TEXCOORD0; // 顶点及其周围4个角的uv坐标
};
v2f vert(appdata_img v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv[0] = v.texcoord;
o.uv[1] = v.texcoord + _MainTex_TexelSize.xy * half2(1, 1) * _SampleScale;
o.uv[2] = v.texcoord + _MainTex_TexelSize.xy * half2(-1, -1) * _SampleScale;
o.uv[3] = v.texcoord + _MainTex_TexelSize.xy * half2(-1, 1) * _SampleScale;
o.uv[4] = v.texcoord + _MainTex_TexelSize.xy * half2(1, -1) * _SampleScale;
return o;
}
bool isEdge(half4 sample1, half4 sample2) { // 是否是边缘(1: 是, 0: 否)
// 计算深度差
float depth1 = DecodeFloatRG(sample1.zw); // 观察空间中的线性且归一化的深度
float depth2 = DecodeFloatRG(sample2.zw); // 观察空间中的线性且归一化的深度
float depthDelta = abs(depth1 - depth2) * depth1 * _DepthScale; // 深度差异(距离相机越远, 像素点越少, 对深度差越敏感, 所以乘了depth1)
bool isDepthDiff = depthDelta > 0.01; // 深度是否不同
// 计算法线差
float3 normal1 = DecodeViewNormalStereo(sample1); // 观察空间中的法线向量
float3 normal2 = DecodeViewNormalStereo(sample2); // 观察空间中的法线向量
float normalDelta = (1 - abs(dot(normal1, normal2))) * _NormalScale; // 法线差异
bool isNormalDiff = normalDelta > 0.14; // cos(30°)=0.86, 法线夹角小于30°
return isDepthDiff || isNormalDiff;
}
fixed4 frag(v2f i) : SV_Target {
half4 sample1 = tex2D(_CameraDepthNormalsTexture, i.uv[1]);
half4 sample2 = tex2D(_CameraDepthNormalsTexture, i.uv[2]);
half4 sample3 = tex2D(_CameraDepthNormalsTexture, i.uv[3]);
half4 sample4 = tex2D(_CameraDepthNormalsTexture, i.uv[4]);
bool isDiff = isEdge(sample1, sample2); // 是否是边缘
isDiff = isDiff && isEdge(sample3, sample4);
if (isDiff) {
return _EdgeColor;
}
fixed4 tex = tex2D(_MainTex, i.uv[0]);
return lerp(tex, _BackgroundColor, _EdgeOnly);
}
ENDCG
}
}
FallBack Off
}
3 运行效果
1)原图

2) Edges Only 设置为 0,Edge Color 设置为绿色

3)Edges Only 设置为 1,Edge Color 设置为黑色,Background Color 设置为白色

声明:本文转自【Unity3D】基于深度和法线纹理的边缘检测方法。
【Unity3D】基于深度和法线纹理的边缘检测方法的更多相关文章
- Unity Shader入门精要学习笔记 - 第13章 使用深度和法线纹理
线纹理的代码非常简单,但是我们有必要在这之前首先了解它们背后的实现原理. 深度纹理实际上就是一张渲染纹理,只不过它里面存储的像素值不是颜色值而是一个高精度的深度值.由于被存储在一张纹理中,深度纹理里的 ...
- 《Unity shader入门精要》复习<第13章 关于NDC坐标和深度/法线纹理>
分为三个地方讲解. NDC(Normalize Device Coordinates)归一化的设备坐标 NDC坐标是世界空间坐标通过MVP变换之后再进行归一化得到的坐标.只需要再一步变换就能得到屏幕空 ...
- 【Unity Shaders】法线纹理(Normal Mapping)的实现细节
写在前面 写这篇的目的是为了总结我长期以来的混乱.虽然题目是"法线纹理的实现细节",但其实我想讲的是如何在shader中编程正确使用法线进行光照计算.这里面最让人头大的就是各种矩阵 ...
- 腾讯优图&港科大提出一种基于深度学习的非光流 HDR 成像方法
目前最好的高动态范围(HDR)成像方法通常是先利用光流将输入图像对齐,随后再合成 HDR 图像.然而由于输入图像存在遮挡和较大运动,这种方法生成的图像仍然有很多缺陷.最近,腾讯优图和香港科技大学的研究 ...
- #Deep Learning回顾#之基于深度学习的目标检测(阅读小结)
原文链接:https://www.52ml.net/20287.html 这篇博文主要讲了深度学习在目标检测中的发展. 博文首先介绍了传统的目标检测算法过程: 传统的目标检测一般使用滑动窗口的框架,主 ...
- Unity3d 基于物理渲染Physically-Based Rendering之最终篇
前情提要: 讲求基本算法 Unity3d 基于物理渲染Physically-Based Rendering之specular BRDF plus篇 Unity3d 基于物理渲染Physically-B ...
- 【OCR技术系列之四】基于深度学习的文字识别(3755个汉字)
上一篇提到文字数据集的合成,现在我们手头上已经得到了3755个汉字(一级字库)的印刷体图像数据集,我们可以利用它们进行接下来的3755个汉字的识别系统的搭建.用深度学习做文字识别,用的网络当然是CNN ...
- OCR技术浅探:基于深度学习和语言模型的印刷文字OCR系统
作者: 苏剑林 系列博文: 科学空间 OCR技术浅探:1. 全文简述 OCR技术浅探:2. 背景与假设 OCR技术浅探:3. 特征提取(1) OCR技术浅探:3. 特征提取(2) OCR技术浅探:4. ...
- 基于深度学习的目标检测技术演进:R-CNN、Fast R-CNN、Faster R-CNN
object detection我的理解,就是在给定的图片中精确找到物体所在位置,并标注出物体的类别.object detection要解决的问题就是物体在哪里,是什么这整个流程的问题.然而,这个问题 ...
- 深度学习与计算机视觉(12)_tensorflow实现基于深度学习的图像补全
深度学习与计算机视觉(12)_tensorflow实现基于深度学习的图像补全 原文地址:Image Completion with Deep Learning in TensorFlow by Bra ...
随机推荐
- pycharm设置保存时自动格式化代码(Auto Reformat Code)
原文:https://blog.csdn.net/qq_41906934/article/details/124631826 1.手动格式化代码 Code -> Reformat Code 格式 ...
- 【C】《C专家编程》阅读体会
[来源]https://mp.weixin.qq.com/s/0kmN5knql4yrOuUcnebwIQ
- [转帖]Prometheus Shell Exporter
Shell Exporter can execute Powershell or Bash scripts and transform its output to Prometheus metrics ...
- [转帖]xtrabackup2.4备份恢复脚本
https://developer.aliyun.com/article/534230#:~:text=xtrabackup2.4%E5%A4%87%E4%BB%BD%E6%81%A2%E5%A4%8 ...
- [转帖]关系模型到 Key-Value 模型的映射
https://cn.pingcap.com/blog/tidb-internal-2 在这我们将关系模型简单理解为 Table 和 SQL 语句,那么问题变为如何在 KV 结构上保存 Table 以 ...
- [转帖]Python-Mock接口测试
https://www.cnblogs.com/zhangwuxuan/p/12928850.html 前言 今天跟小伙伴们一起来学习一下如何编写Python脚本进行mock测试. 什么是mock? ...
- MySQL查询聚合函数与分组查询
连接数据库 mysql -hlocalhost -uroot -proot 聚合函数 聚合函数:作用于某一列,对数据进行计算. ps: 所有的null值是不参与聚合函数的运算的. 06 常见的聚合函数 ...
- py 学习(c++ to py)
py1: print 2024-01-27 23:18:57 星期六 #这里是注释 # py1 : 基础print总结 ''' aaa 有时候也用三个单引号当注释 但其实是字符串 交互式会输出 ''' ...
- 从Spring源码看Spring如何解决循环引用的问题
Spring如何解决循环引用的问题 关于循环引用,首先说一个结论: Spring能够解决的情况为:两个对象都是单实例.且通过set方法进行注入. 两个对象都是单实例,通过构造方法进行注入,Spring ...
- PaddleNLP基于ERNIR3.0文本分类以中医疗搜索检索词意图分类(KUAKE-QIC)为例【多分类(单标签)】
相关项目链接: Paddlenlp之UIE模型实战实体抽取任务[打车数据.快递单] Paddlenlp之UIE分类模型[以情感倾向分析新闻分类为例]含智能标注方案) 应用实践:分类模型大集成者[Pad ...