UnityShader学习笔记——深度与法线纹理

——内容源自唐老狮的shader课程


目录

1.概述

1.1.分别指什么

1.2.如何获取

1.2.1.对摄像机赋值

1.2.2.在Shader中声明

1.2.3.获取深度值

1.2.4.获取法线纹理

1.3.背后的原理

1.3.1.深度纹理中存储的是什么信息

1.3.2.法线纹理中存储的是什么信息

1.3.3.unity是如何得到深度和法线纹理的

1.3.4.深度和法线纹理使用时调用的函数原理

1.3.5.注

2.查看深度和法线纹理

2.1.查看深度信息

2.2.查看法线信息

3.后处理效果——运动模糊

3.1.概述

3.2.基本原理

3.2.1.得到像素上一帧和当前帧在裁剪空间的位置

3.2.2.运动方向

3.2.3.如何模拟运动模糊效果

3.3.实现

 3.4.问题

4.后处理效果——全局雾效

4.1.是什么

4.2.unity自带的全局雾效

4.2.1.开启

4.2.2.雾气的计算模式(也是自带雾效的重要参数)

4.3.基于深度纹理实现全局雾效

4.3.1.为什么要自己实现

4.3.2.基本原理(计算像素在世界空间下的位置是为了计算其离摄像机的距离)

 4.3.3.获取摄像机指向像素的世界坐标的方向向量

4.4.实现

5.后处理效果——边缘检测

5.1.为什么这么干

5.2.基本原理(不会进行卷积运算)

5.3.关键步骤

5.3.1.得到对角线上的像素

5.3.2.进行深度和法线值的比较

5.3.3.具体的比较

5.4.实现

6.如有疏漏,还请指出


1.概述

1.1.分别指什么

        1.深度纹理屏幕空间的深度纹理,用于存储屏幕图像每个像素深度信息的纹理。可以利用其中存储的每个像素的深度信息

        2.法线纹理屏幕空间的法线纹理,用于存储屏幕图像中每个像素法线信息的纹理。可用来制作屏幕空间环境遮挡,基于屏幕空间的反射等

1.2.如何获取

1.2.1.对摄像机赋值

        在c#代码中对主摄像机进行赋值,让其知道我们要使用

Camera.main.depthTextureMode = DepthTextureMode.Depth;

        赋值有三种(常用):

        1.Depth:获取一张深度纹理

        2.DepthNormals:获取一张纹理,其同时包含深度和法线

        3.Depth | Normals:获取两张纹理,分别为深度和法线

1.2.2.在Shader中声明

        1.深度纹理的声明:

sampler2D _CameraDepthTexture

        2.深度 + 法线纹理的声明:

sampler2D _CameraDepthNormalsTexture //一般RG通道存法线,BA通道存深度

1.2.3.获取深度值

        1.用SAMPLE_DEPTH_TEXTURE(深度纹理,uv坐标)对深度纹理进行采样,所得结果是非线性的

        2.使用LinearEyeDepth将非线性的深度值转换到观察空间下;

           或用Linear01Depth将非线性的深度值,转换到01区间内的线性深度值,同样是观察空间

float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
float viewDepth = LinearEyeDepth(depth);
float linearDepth = Linear01Depth(depth);

1.2.4.获取法线纹理

        1.先使用tex2D对深度法线纹理进行采样得到depthNormal的float4变量

        2.使用DecodeDepthNormal获取01间的深度值和观察空间下的法线信息,想当于一次处理深度和法线。其参数为(depthNormal,depth(float类型),normal(float3类型)),后两个参数需要自己声明,名字自定义

        3.也可单独获取某一个量:

           DecodeFloatRG(depthNormal.zw):单独获取深度

           DecodeViewNormalStereo(depthNormal):单独获取法线

1.3.背后的原理

1.3.1.深度纹理中存储的是什么信息

        是进行裁剪空间变换后的z分量再转换到01之后的结果,因为齐次裁剪空间坐标范围为-1 ~ 1,纹理中存储的信息范围需要是0 ~ 1,所以进行如下变换:

        深度纹理值 = 0.5 * z + 0.5;

1.3.2.法线纹理中存储的是什么信息

        是观察空间下的 法线 再转换到0-1之后的结果。同样的,由于观察空间下范围为 -1 ~ 1(前提它是个单位向量),而纹理中要存储0 ~ 1,所以会进行如下变换:

        法线纹理值 = (观察空间下法线 + 1)* 0.5

1.3.3.unity是如何得到深度和法线纹理的

        通常分为两种途径(具体使用哪个,取决于使用的渲染路径和设备的硬件限制):

        1.从G-buffer几何缓冲区中获取

        2.由一个专门的Pass渲染而来

        当使用延迟渲染路径时,深度和法线纹理可以直接访问到。因为延迟渲染路径会把信息存储到G-buffer几何缓冲区中(深度和法线等信息都存储在其中)

        只有当无法直接获取到深度和法线纹理时,unity才会单独通过一个Pass来渲染,获取深度和法线信息

        使用单独的pass渲染获取深度和法线纹理时,二者有所区别。

        对于深度纹理:unity内部会使用 着色器替换技术 选择 渲染类型 RenderType = “Opaque”(不透明物体)的物体,然后判度胺它们的渲染队列Queue是否小于等于2500,如果满足这个条件,就会使用物体投射阴影时的Pass(LightMode = ShaderCaster)来得到深度纹理,若没有该Pass。则该物体不会出现在深度纹理中。

        即:获取深度纹理需要正确的RenderType标签 和 有阴影投射的Pass

        对于法线纹理:unity底层会使用一个单独的Pass把整个场景再渲染一次,从而得到深度和法线纹理。因此,获取法线纹理时,往往就会一块获取深度纹理

1.3.4.深度和法线纹理使用时调用的函数原理

        SAMPLE_DEPTH_TEXTURE:它是用来采样的,相较于tex2D,他会帮忙适配平台,他采样的深度值是裁剪空间下的z分量转换到01之间的结果,并且是非线性的,即在透视摄像机的裁剪空间中,深度值分布不均匀。

        深度值离近裁减面近时,深度值变化迅速,精度高。反之则缓慢,精度低。

        简而言之,远的东西变化不明显。

        而将裁剪空间下的深度值转换到观察空间下,才可以得到线性的深度值

        LinearEyeDepth:像素到摄像机的实际距离

        Linear01Depth:被压缩到01之间的值

1.3.5.注

        直接采样出来的深度和法线信息是不会直接使用的,我们需要将其转换。以得到得到我们最终会使用的 观察空间下的深度和法线信息


2.查看深度和法线纹理

2.1.查看深度信息

        将深度值作为颜色的RGB显示在屏幕上即可

//Shader部分Shader "Models_5/WatchDepth"
{Properties{_MainTex ("MainTex", 2D) = "white" {}}SubShader{Tags { "RenderType"="Opaque" }Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;};sampler2D _MainTex;float4 _MainTex_ST;sampler2D _CameraDepthTexture;v2f vert (appdata_base v){v2f data;data.pos = UnityObjectToClipPos(v.vertex);data.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;return data ;}fixed4 frag (v2f f) : SV_Target{float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, f.uv);float linearDepth = Linear01Depth(depth);return fixed4(linearDepth, linearDepth, linearDepth, 1);}ENDCG}}Fallback off
}
//c#部分using UnityEngine;public class Leeson77_WatchDepth : Lesson69_Basic
{/// <summary>/// 一上来就是纯黑和纯白的原因是:远裁剪面设置的太远了/// </summary>private void Start(){Camera.main.depthTextureMode = DepthTextureMode.Depth;}protected override void OnRenderImage(RenderTexture source, RenderTexture destination){base.OnRenderImage(source, destination);}
}
查看深度信息

2.2.查看法线信息

        同样,将法线作为RGB

//Shader部分Shader "Models_5/WatchNormal"
{Properties{_MainTex ("MainTex", 2D) = "white" {}}SubShader{Tags { "RenderType"="Opaque" }Pass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;};sampler2D _MainTex;float4 _MainTex_ST;sampler2D _CameraDepthNormalsTexture;v2f vert (appdata_base v){v2f data;data.pos = UnityObjectToClipPos(v.vertex);data.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;return data;}fixed4 frag (v2f f) : SV_Target{float depth;float3 normals;float4 depthNormal = tex2D(_CameraDepthNormalsTexture, f.uv);DecodeDepthNormal(depthNormal, depth, normals);depth = DecodeFloatRG(depthNormal.zw); //zw是深度normals = DecodeViewNormalStereo(depthNormal); //xy是法线return fixed4(normals, 1);}ENDCG}}Fallback off
}
//c#部分using UnityEngine;public class Lesson77_WatchNormal : Lesson69_Basic
{private void Start(){Camera.main.depthTextureMode = DepthTextureMode.DepthNormals;}protected override void OnRenderImage(RenderTexture source, RenderTexture destination){base.OnRenderImage(source, destination);}
}
查看法线信息

3.后处理效果——运动模糊

3.1.概述

        之前采取的是:将之前的图像不断叠加的方式。而这里我们要使用 速度缓存 的方式来进行,但会对其进行修改。

        只需要用当前帧位置和上一帧位置进行计算,得到位置差,从而得到该像素的速度矢量。而想要得到位置差,可以利用深度纹理中的信息来进行计算

        需要注意的是:

        1.这种方式只适合场景静止,即摄像机快速移动的情况

        2.该实现方式并不是基于真实的物理运动规律来计算的,只是一种近似计算

3.2.基本原理

        得到像素当前帧和上一帧在裁剪空间下的位置,利用两个位置计算出物体的运动方向,从而模拟出运动模糊的效果。

3.2.1.得到像素上一帧和当前帧在裁剪空间的位置

        1.利用uv坐标和深度值组合成一个裁剪空间下的组合坐标 nowClipPos,即:

float4 clipPos = float4(uv.x, ux.y, depth, 1);

           而为了让0~1映射到-1~1,还需要对其进行变换:

float4 clipPos = float4(uv.x * 2 - 1, uv.y * 2 - 1, depth * 2 - 1, 1);

        2.利用这一帧的世界空间到裁剪空间的变换矩阵 nowM 的逆矩阵 nowM^-1,将刚才所得的裁剪空间下的点转换到世界空间。

           可以使用c#中摄像机的一些内置函数来获取世界空间到裁剪空间的变换矩阵:

//相机投影矩阵(观察空间)到裁剪空间的变换矩阵
camera.projectionMatrix;
//世界空间到观察空间的变换矩阵
camera.worldToCameraMatrix;
//世界空间 到 裁剪空间的变换矩阵
Matrix4x4 worldToClipNatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
//裁剪空间 到 世界空间的变换矩阵
worldToClipMatrix.inverse

           用上面的矩阵就可实现坐标的相互变换

        3.利用上一帧的世界空间到裁剪空间的变换矩阵 oldM 得到上一帧下,nowClipPos所在的位置oldClipPos(使用nowClipPos获取对应世界坐标,因为只有摄像机动,所以其世界坐标不变,然后该世界坐标与oldM计算得到oldClipPos)

3.2.2.运动方向

        当前位置.xy - 上一帧位置.xy 便可以得到移动方向

3.2.3.如何模拟运动模糊效果

        利用这个方向在纹理中进行多次uv坐标偏移采样,将得到的颜色累加起来,最后进行算数平均值计算即可。

        我们会加入一个模糊偏移量来控制模糊程度。只要在每次采样时进行 方向 * 模糊偏移量 的偏移采样即可。

uv += 方向 * 模糊偏移量

        还可以加入一个次数变量,来控制uv坐标偏移采样的次数

        需要注意的是:ShaderLab没有矩阵类型的变量,所以直接在CG中声明对应属性,然后在c#代码中设置就行

3.3.实现

//Shader部分Shader "Unlit/Lesson78_MotionBlur2"
{Properties{_MainTex ("MainTex", 2D) = "white" {}_BlurOffset("BlurOffset", Range(0, 1)) = 0.1_LoopTimes("LoopTimes", Int) = 3}SubShader{Tags { "RenderType"="Opaque" }ZTest AlwaysCull OffZWrite OffPass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;float2 uv_depth : TEXCOORD1;};sampler2D _MainTex;float4 _MainTex_ST;float4 _MainTex_TexelSize;float _BlurOffset;int _LoopTimes;sampler2D _CameraDepthTexture;float4x4 _ClipToWorldMatrix;float4x4 _FrontWorldToClipMatrix;v2f vert (appdata_base v){v2f data;data.pos = UnityObjectToClipPos(v.vertex);data.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;data.uv_depth = v.texcoord;#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0){data.uv_depth.y = 1 - data.uv_depth.y;}#endifreturn data;}fixed4 frag (v2f f) : SV_Target{float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, f.uv_depth);float linearDepth = Linear01Depth(depth);//当前帧的坐标float4 nowClipPos = float4(f.uv.x * 2 - 1, f.uv.y * 2 - 1, depth * 2 - 1, 1);float4 nowWorldPos = mul(_ClipToWorldMatrix, nowClipPos);//透视除法nowWorldPos /= nowWorldPos.w;float4 oldClipPos = mul(_FrontWorldToClipMatrix, nowWorldPos);//透视除法oldClipPos /= oldClipPos.w;float2 moveDir = nowClipPos.xy - oldClipPos.xy; ////不直接使用f的uv,别的地方可能会用float2 uv = f.uv;float4 blurColor = float4(0, 0, 0, 0);for (int i = 0; i < _LoopTimes; i++){blurColor += tex2D(_MainTex, uv);uv += moveDir * _BlurOffset;}blurColor /= _LoopTimes;return fixed4(blurColor.rgb, 1);}ENDCG}}Fallback off
}
//c#部分using UnityEngine;public class Lesson78_MotionBlur2 : Lesson69_Basic
{[Range(0, 1)]public float blurOffset;[Range(1, 10)]public int loopTimes;private Matrix4x4 frontWorldToClipMatrix;private void Start(){Camera.main.depthTextureMode = DepthTextureMode.Depth;}private void OnEnable(){frontWorldToClipMatrix = Camera.main.worldToCameraMatrix * Camera.main.projectionMatrix;}protected override void UpdateProperty(){Matrix4x4 worldToClipMatrix = Camera.main.worldToCameraMatrix * Camera.main.projectionMatrix;Mat.SetMatrix("_FrontWorldToClipMatrix", frontWorldToClipMatrix);Mat.SetMatrix("_ClipToWorldMatrix", worldToClipMatrix.inverse);frontWorldToClipMatrix = worldToClipMatrix;Mat.SetFloat("_BlurOffset", blurOffset);Mat.SetInt("_LoopTimes", loopTimes);}protected override void OnRenderImage(RenderTexture source, RenderTexture destination){if (Mat != null){UpdateProperty();Graphics.Blit(source, destination, Mat);}else{Graphics.Blit(source, destination);}}
}
运动模糊(摄像机旋转)

 3.4.问题

        这个方法有个严重的问题:移动摄像机的位置(不是旋转)时,会出现严重的鬼畜(?)

鬼畜

4.后处理效果——全局雾效

4.1.是什么

        是一种视觉效果,用于在3d场景中模拟大气中的雾气对远处物体的遮挡。其会使离摄像机较远的物体看起来逐渐被雾气遮盖。

4.2.unity自带的全局雾效

4.2.1.开启

        在Window->Rendering->Lighting窗口中的 Environment 环境页签中开启,勾选fog

        然后在shader代码中实现UNITY_FOG_COORDS(),UNITY_TRANSFER_FOG(),UNITY_APPLY_FOG(),

        实际上就是一个Unlit着色器被创建出来时候自带的那几句。

4.2.2.雾气的计算模式(也是自带雾效的重要参数)

        首先要说明,几种计算模式都是在计算雾的混合因子 f,然后才会根据公式

最终颜色 = (1 - f) * 物体颜色 + f * 雾颜色

       来计算雾

        1.Linear:f = (end - |d|)/(end - start)->线性

           d代表距离摄像机的距离,end和start分别代表雾最强和开始的距离,均是相对摄像机而言

        2.Exponential:f = 1 - e^(-density * |d|) ->指数

           density代表雾的浓度

        3.Exponential Squared

           跟2差不多,就是变成二次方了

4.3.基于深度纹理实现全局雾效

4.3.1.为什么要自己实现

        unity自带的很多效果做不了,而且需要对每个物体的shader都实现对应代码

4.3.2.基本原理(计算像素在世界空间下的位置是为了计算其离摄像机的距离)

        首先,我们抛弃之前用矩阵来获取像素在世界坐标下的位置。使用另一种会获取像素世界坐标的方式:通过坐标偏移。

像素的世界坐标 = 摄像机位置 + 观察空间线性深度值 * 摄像机指向像素世界坐标的方向向量

 4.3.3.获取摄像机指向像素的世界坐标的方向向量

        1.屏幕后处理中处理的内容是一张抓取的屏幕图像,相当于是一个具有四个顶点的面片,摄像机近裁减面的四个角相当于是屏幕图像(后处理要处理的图像)四个顶点在世界空间中的位置。

        2.我们需要通过c#代码计算四个顶点在世界空间下的射线方向后传递给顶点着色器因为当数据传递到片元着色器时,每个像素会基于4个顶点的射线插值计算像素出对应的射线方向),四个点记为TL(top left),BL(buttom left),TR,BR 则:

        TL = Camera.forward * Near + toTop - toRight;

        TR = Camera.forward * Near + toTop + toRight;

        BL = Camera.forward * Near - toTop - toRight;

        BR = Camera.forward * Near - toTop + toRight;

        其中,

        Near:摄像机到近裁减面的距离

        toTop:近裁减面中心点到顶边的向量      toTop = Camera.up * halfH

        toRight:近裁减面中心点到右边的向量   toRight = Camera.right * halfW

        halfH:近裁减面高度的一半                     halfH = Near * tan(FOV / 2)

        halfW:近裁减面宽度的一半                    halfW = halfH * aspect

        aspect:Game窗口的宽高比(宽:高),其通过 Camera.main.aspect 获取

        FOV:摄像机到顶边与摄像机到底边之间夹角(摄像机的竖直夹角),直接点出来即可

        

        需要知道的是:这时不能直接通过公式进行计算得到对应点在世界空间下的坐标,因为这时的深度值只是该点离摄像机z轴方向的距离,而非到摄像机的距离(两点之间的距离称为欧氏距离),所以需要进行处理。

        根据相似三角形易得:Depth / Near = dis(两点距离) / |TL|

                                     即:dis = Depth * (|TL| / Near)

        对于近裁减面上的四个点,|TL| / Near是通用的(四个点对称,其模长都一样)

        所以最后缩减为:Scale = |TL| / Near

                                     RayTL = TL.normalized * Scale

        最后使用下面公式计算即可得到世界坐标:

像素的世界坐标 = 摄像机位置 + 观察空间线性深度值 * RayTL

        需要注意的是:这里的 "观察空间线性深度值" 是用LinearEyeDepth计算所得

4.4.实现

        

//Shader部分Shader "Models_5/GlobalFog"
{Properties{_MainTex("MainTex", 2D) = "white"{}_FogColor("FogColor", Color) = (1, 1, 1, 1)_FogDensity("FogDensity", Float) = 1_FogStart("FogStart", Float) = 0_FogEnd("FogEnd", Float) = 10}SubShader{Tags { "RenderType"="Opaque" }ZTest AlwaysCull OffZWrite OffPass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"sampler2D _MainTex;float4 _MainTex_TexelSize;sampler2D _CameraDepthTexture;float4 _FogColor;float _FogDensity;float _FogStart;float _FogEnd;float4x4 _RayMatrix;struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;//考虑翻转的深度纹理float2 uv_depth : TEXCOORD1;//顶点射线 指向四个角的方向向量 传递到片元时,会自动进行插值运算float4 ray : TEXCOORD2;};//顶点着色器函数 每一个顶点就会执行一次//对于屏幕后处理来说,就会执行四次,因为就四个点v2f vert (appdata_base v){v2f data;data.pos = UnityObjectToClipPos(v.vertex);data.uv = v.texcoord.xy;data.uv_depth = v.texcoord.xy;//方便赋值int index = 0;//因为就四个顶点,所以通过中心点判断if (v.texcoord.x < 0.5 ){//0, 0if (v.texcoord.y < 0.5){index = 0;}//0, 1else{index = 3;}}else{//1, 0if (v.texcoord.y < 0.5){index = 1;}//1, 1else{index = 2;}}#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0){data.uv_depth = 1 - data.uv_depth;//翻转顶点的射线(或许可以通过翻转uv的方式,而不是在这里翻转index)index = 3 - index;}#endif//赋值data.ray = _RayMatrix[index];return data;}fixed4 frag (v2f f) : SV_Target{float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, f.uv_depth);//此乃z分量float realDepth = LinearEyeDepth(depth);float3 fragWorldPos = _WorldSpaceCameraPos + realDepth * f.ray;float fogFactor = (_FogEnd - fragWorldPos.y) / (_FogEnd - _FogStart);fogFactor = saturate(fogFactor * _FogDensity);fixed3 finalColor = lerp(tex2D(_MainTex, f.uv).rgb, _FogColor.rgb, fogFactor);return fixed4(finalColor, 1);}ENDCG}}Fallback off
}

        这个计算雾气的方式结合了线性以及指数

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;public class Lesson79_GlobalFog : Lesson69_Basic
{public Color fogColor;public float fogDensity;public float fogStart;public float fogEnd;private void Start(){Camera.main.depthTextureMode = DepthTextureMode.Depth;}protected override void UpdateProperty(){float FOV = Camera.main.fieldOfView;float aspect = Camera.main.aspect;float near = Camera.main.nearClipPlane;//tan用的是弧度,但是fov是角度,所以将其乘以对应变量float halfH = near * Mathf.Tan((FOV * Mathf.Deg2Rad) / 2f);float halfW = halfH * aspect;Vector3 toTop = Camera.main.transform.up * halfH;Vector3 toRight = Camera.main.transform.right * halfW;Vector3 TL = Camera.main.transform.forward * near + toTop - toRight;Vector3 TR = Camera.main.transform.forward * near + toTop + toRight;Vector3 BL = Camera.main.transform.forward * near - toTop - toRight;Vector3 BR = Camera.main.transform.forward * near - toTop + toRight;//w为了让深度值计算出来是两点间距离,所以需要乘以一个缩放值float scale = TL.magnitude / near;//这里按照左下开始逆时针的顺序存储Matrix4x4 RayMatrix = new Matrix4x4();RayMatrix.SetRow(0, BL.normalized * scale);RayMatrix.SetRow(1, BR.normalized * scale);RayMatrix.SetRow(2, TR.normalized * scale);RayMatrix.SetRow(3, TL.normalized * scale);Mat.SetMatrix("_RayMatrix", RayMatrix);Mat.SetColor("_FogColor", fogColor);Mat.SetFloat("_FogDensity", fogDensity);Mat.SetFloat("_FogStart", fogStart);Mat.SetFloat("_FogEnd", fogEnd);}protected override void OnRenderImage(RenderTexture source, RenderTexture destination){if (Mat != null){UpdateProperty();Graphics.Blit(source, destination, Mat);}else{Graphics.Blit(source, destination);}}
}
全局雾效

 


5.后处理效果——边缘检测

5.1.为什么这么干

        基于灰度值会很乱,原因是他会将光照,阴影等也算进去,但是基于深度法线不会,只会出现基本的描边

        3d推荐深度法线,2d推荐灰度值(因为2d图片深度值和法线都是一样的)

5.2.基本原理(不会进行卷积运算)

        简而言之:基于 Roberts(罗伯兹)交叉算子,通过比较对角线上的像素的深度和法线值,判断是否在边缘上(不过我们不用这个交叉算子计算)

        Roberts算子:Gx: -1  0   Gy:  0  -1 (虽然不会用这个就是了)
                                       0  1           1   0

5.3.关键步骤

        我们是对一个像素的两条对角线上的像素的法线值和深度值进行判断,如果两条对角线有一条的变化过大,那么就说明该像素位于边缘。

5.3.1.得到对角线上的像素

        同样是利用纹素进行uv坐标偏移(类似之前的描边)。我们还可以声明一个可控的 采样偏移距离变量 _SampleDistance,他会决定描边的粗细。

        其原理是:采样离中心像素越近,检测的变化越细微,深度和法线值变化小,边缘会更细

5.3.2.进行深度和法线值的比较

        首先对深度和法线采样获取对应的值,再求出对角线上对角两个像素的 深度值差 和 法线值差

如果其中一个的差值大于自定义的差值(满足其一即可),那么我们认为该像素在物体的边缘上。

5.3.3.具体的比较

        一次对一条对角线上的一对点(注意是一对,不算中心点)进行处理。

        深度值:深度值求出来之后,直接相减求绝对值,然后乘以自定义深度敏感度变量进行比较(一般同 0.1乘以第一个点的深度 进行比较,小于就不是边缘,因为差异很小)即可

        法线值:对两个点的法线进行采样,然后相减取绝对值,用该结果乘以自定义敏感度得到法线差值,最后将该法线插值的三个分量相加与0.1进行比较,小于则证明差异小,不在边缘,即为1

        最后用两次比较结果相乘,但凡有一个0,那就处于边缘

5.4.实现

//Shader部分
Shader "Models_5/StrokeWithDepthNormal"
{Properties{_MainTex("MainTex", 2D) = "white"{}_DepthSensitivity("DepthSensitivity", Float) = 1_NormalSensitivity("NormalSensitivity", Float) = 1_EdgeColor("EdgeColor", Color) = (0, 0, 0, 0)_BackgroundExtent("BackgroundExtent", Range(0, 1)) = 1_BackgroundColor("BackgroundColor", Color) = (1, 1, 1, 1)_SampleDistance("SampleDistance", Int) = 1}SubShader{Tags { "RenderType"="Opaque" }ZTest AlwaysCull OffZWrite OffPass{CGPROGRAM#pragma vertex vert#pragma fragment frag#include "UnityCG.cginc"struct v2f{float4 pos : SV_POSITION;float2 uv[5] : TEXCOORD0;};sampler2D _MainTex;float4 _MainTex_ST;float4 _MainTex_TexelSize;float _DepthSensitivity;float _NormalSensitivity;float4 _EdgeColor;float _BackgroundExtent;float4 _BackgroundColor;int _SampleDistance;sampler2D _CameraDepthNormalsTexture;v2f vert (appdata_base v){v2f data;data.pos = UnityObjectToClipPos(v.vertex);data.uv[0] = v.texcoord.xy;//右上和左下data.uv[1] = data.uv[0] + _MainTex_TexelSize.xy * float2(-1,  1) * _SampleDistance;data.uv[2] = data.uv[0] + _MainTex_TexelSize.xy * float2( 1, -1) * _SampleDistance;//左上和右下,这个顺序没什么,反正会取绝对值,不在乎正负data.uv[3] = data.uv[0] + _MainTex_TexelSize.xy * float2( 1,  1) * _SampleDistance;data.uv[4] = data.uv[0] + _MainTex_TexelSize.xy * float2(-1, -1) * _SampleDistance;return data;}//这个点是对 深度法线纹理 采样所得的点float CheckSame(float4 depthNormal_1, float4 depthNormal_2){//后俩是深度float depth_1 = DecodeFloatRG(depthNormal_1.zw);float depth_2 = DecodeFloatRG(depthNormal_2.zw);float diffDepth = abs(depth_1 - depth_2) * _DepthSensitivity;//小于0.1为1,即不是边缘,很接近int depthSame = diffDepth < 0.1 * depth_1;//float2 diffNormal = abs(depthNormal_1.xy - depthNormal_2.xy) * _NormalSensitivity;//int normalSame = (diffNormal.x + diffNormal.y) < 0.1;float3 normal_1 = DecodeViewNormalStereo(depthNormal_1);float3 normal_2 = DecodeViewNormalStereo(depthNormal_2);float3 diffNormal = abs(normal_1 - normal_2) * _NormalSensitivity;int normalSame = (diffNormal.x + diffNormal.y + diffNormal.z) < 0.1;int res = depthSame * normalSame;//0为边缘,1代表这俩点相似return res;}fixed4 frag (v2f f) : SV_Target{//这俩有一个为0,那就代表是边缘float checkSame_1 = CheckSame(tex2D(_CameraDepthNormalsTexture, f.uv[1]), tex2D(_CameraDepthNormalsTexture, f.uv[2]));float checkSame_2 = CheckSame(tex2D(_CameraDepthNormalsTexture, f.uv[3]), tex2D(_CameraDepthNormalsTexture, f.uv[4]));float isEdge = 1;isEdge *= checkSame_1;isEdge *= checkSame_2;float4 mainTex = tex2D(_MainTex, f.uv[0]);fixed4 withEdgeColor = lerp(_EdgeColor, mainTex, isEdge);//纯色背景控制fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, isEdge);fixed4 finalColor = lerp(withEdgeColor, onlyEdgeColor, _BackgroundExtent);return fixed4(finalColor.rgb, 1.0);}ENDCG}}Fallback off
}
边缘检测(加入了纯色背景代码的)

 


6.如有疏漏,还请指出

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

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

相关文章

基于STM32的智能鱼缸水质净化系统设计

&#x1f91e;&#x1f91e;大家好&#xff0c;这里是5132单片机毕设设计项目分享&#xff0c;今天给大家分享的是智能鱼缸水质净化系统。 目录 1、设计要求 2、系统功能 3、演示视频和实物 4、系统设计框图 5、软件设计流程图 6、原理图 7、主程序 8、总结 1、设计要求…

如何打造一个更友好的网站结构?

在SEO优化中&#xff0c;网站的结构往往被忽略&#xff0c;但它其实是决定谷歌爬虫抓取效率的关键因素之一。一个清晰、逻辑合理的网站结构&#xff0c;不仅能让用户更方便地找到他们需要的信息&#xff0c;还能提升搜索引擎的抓取效率 理想的网站结构应该像一棵树&#xff0c;…

尝试在Excel里调用硅基流动上的免费大语言模型

我个人觉得通过api而不是直接浏览器客户端聊天调用大语言模型是使用人工智能大模型的一个相对进阶的阶段。 于是就尝试了一下。我用的是老师木 袁进辉博士新创的硅基流动云上的免费的大模型。——虽然自己获赠了不少免费token&#xff0c;但测试阶段用不上。 具体步骤如下&am…

“公路养护新利器!公路 AI 智慧巡检系统

家人们&#xff0c;咱日常开车出行&#xff0c;最烦的就是遇到路面坑洼、道路破损的情况&#xff0c;不仅颠簸难受&#xff0c;还存在安全隐患。其实&#xff0c;这些问题都得靠公路养护人员及时发现并处理。但以往的公路巡检工作可不容易&#xff0c;现在好了&#xff0c;有了…

【算法】动态规划专题⑧ —— 分组背包问题 python

目录 前置知识进入正题实战演练总结 前置知识 【算法】动态规划专题⑤ —— 0-1背包问题 滚动数组优化 python 进入正题 分组背包问题的详细解析 1. 问题定义 在 分组背包问题 中&#xff0c;物品被划分为若干组&#xff0c;每组内的物品 互斥&#xff08;只能选择其中一个或…

LLM:DeepSeek 系列(二)

原文链接 3、DeepSeek-V2 DeepSeek-V2 发布于 2024 年 5 月&#xff0c;为多领域专家&#xff08;MoE&#xff09;语言模型&#xff0c;包含总共 2360 亿个参数&#xff0c;其中每个词元激活 210 亿个参数&#xff0c;并支持 12.8 万个词元的上下文长度。DeepSeek-V2 采用包括…

AtCoder Beginner Contest 391(A~E题题解)

A - Lucky Direction 思路&#xff1a;纯模拟的一个水题 #include <bits/stdc.h> using namespace std; #define int long long string s; signed main() { cin>>s;for(int i0;i<s.size();i){char cs[i];if(cN){cout<<"S";}else if(c…

redis中的hash结构

hash类型也叫散列&#xff0c;其中value是一个无序字典&#xff0c;不用像string类型中的value用jason结构去存储&#xff0c;他的value可以将对象中的每个字段独立存储&#xff0c;而且有个好处&#xff0c;方便修改value值 类似于这样 hash类型的常见命令&#xff1a;

USB子系统学习(四)使用libusb读取鼠标数据

文章目录 1、声明2、HID协议2.1、描述符2.2、鼠标数据格式 3、应用程序4、编译应用程序5、测试 1、声明 本文是在学习韦东山《驱动大全》USB子系统时&#xff0c;为梳理知识点和自己回看而记录&#xff0c;全部内容高度复制粘贴。 韦老师的《驱动大全》&#xff1a;商品详情 …

02.08 多路文件IO

思维导图1&#xff1a; 思维导图2&#xff1a; 高效处理多路文件IO&#xff1a;select、poll和epoll模型详解 在现代网络编程中&#xff0c;高效地监视多个文件描述符的IO状态&#xff08;如可读、可写、异常&#xff09;是至关重要的。本文将详细介绍三种常用的多路文件IO模…

opentelemetry-collector 配置elasticsearch

一、修改otelcol-config.yaml receivers:otlp:protocols:grpc:endpoint: 0.0.0.0:4317http:endpoint: 0.0.0.0:4318 exporters:debug:verbosity: detailedotlp/jaeger: # Jaeger supports OTLP directlyendpoint: 192.168.31.161:4317tls:insecure: trueotlphttp/prometheus: …

Docker安装pypiserver私服

Docker安装pypiserver私服 1 简介 Python开源包管理工具有pypiserver、devpi和Nexus等&#xff0c;pypiserver安装部署比较简单&#xff0c;性能也不错。 搭建pypiserver私服&#xff0c;可以自己构建镜像&#xff0c;也可以使用官网的docker镜像。 # Github地址 https://g…

Java_双列集合

双列集合特点 存放的是键值对对象&#xff08;Entry&#xff09; Map 因为都是继承Map&#xff0c;所以要学会这些API&#xff0c;后面的类就都知道了 put 有两个操作&#xff0c;添加&#xff08;并返回null&#xff09;或者覆盖&#xff08;返回被覆盖的值&#xff09…

【AI学习】关于 DeepSeek-R1的几个流程图

遇见关于DeepSeek-R1的几个流程图&#xff0c;清晰易懂形象直观&#xff0c;记录于此。 流程图一 来自文章《Understanding Reasoning LLMs》&#xff0c; 文章链接&#xff1a;https://magazine.sebastianraschka.com/p/understanding-reasoning-llms?continueFlagaf07b1a0…

零基础都可以本地部署Deepseek R1

文章目录 一、硬件配置需求二、详细部署步骤1. 安装 Ollama 工具2. 部署 DeepSeek-R1 模型3. API使用4. 配置图形化交互界面&#xff08;可选&#xff09;5. 使用与注意事项 一、硬件配置需求 不同版本的 DeepSeek-R1 模型参数量不同&#xff0c;对硬件资源的要求也不尽相同。…

索引失效的场景

chatGpt 7. 使用 DISTINCT 或 GROUP BY 当查询中涉及 DISTINCT 或 GROUP BY 时&#xff0c;如果查询没有合适的索引支持&#xff0c;可能会导致性能问题&#xff0c;虽然不完全是索引失效&#xff0c;但会影响查询效率。 sql SELECT DISTINCT department_id FROM employees;…

Dockerfile 文件详解

在平常的开发工作中&#xff0c;我们经常需要部署项目&#xff0c;一个项目开发完成后&#xff0c;使用 Docker 方式部署&#xff0c;那么首先得构造镜像&#xff0c;构造镜像最主要的就是 Dockerfile 文件的编写&#xff0c;今天简单来总结下 Dockerfile 文件的编写以及有哪些…

天津三石峰科技——汽车生产厂的设备振动检测项目案例

汽车产线有很多传动设备需要长期在线运行&#xff0c;会出现老化、疲劳、磨损等 问题&#xff0c;为了避免意外停机造成损失&#xff0c;需要加装一些健康监测设备&#xff0c;监测设备运 行状态。天津三石峰科技采用 12 通道振动信号采集卡&#xff08;下图 1&#xff09;对…

SpringBoot教程(十四) SpringBoot之集成Redis

SpringBoot教程&#xff08;十四&#xff09; | SpringBoot之集成Redis 一、Redis集成简介二、集成步骤 2.1 添加依赖2.2 添加配置2.3 项目中使用之简单使用 &#xff08;举例讲解&#xff09;2.4 项目中使用之工具类封装 &#xff08;正式用这个&#xff09;2.5 序列化 &…

多智能体协作架构模式:驱动传统公司向AI智能公司转型

前言 在数字化浪潮的席卷下&#xff0c;传统公司的运营模式正面临着前所未有的挑战。随着市场竞争的日益激烈&#xff0c;客户需求的快速变化以及业务复杂度的不断攀升&#xff0c;传统公司在缺乏 AI 技术支撑的情况下&#xff0c;暴露出诸多痛点。在决策层面&#xff0c;由于…