Chapter13 深度和法线纹理——Shader入门精要学习笔记

Chapter13 深度和法线纹理

  • 一、深度和法线纹理的原理和获取
    • 1.背后的原理
      • ①深度纹理
      • ②法线纹理
    • 2.如何获取
    • 3.查看深度和法线纹理
  • 二、再谈运动模糊
    • 1.速度映射
    • 2.MotionBlurWithDepthTexture.cs
    • 3.MotionBlurShader
  • 三、全局雾效 —— 屏幕后处理
    • 1.重建世界坐标
      • interpolatedRay的求法
    • 2.雾的计算
    • 3.FogWithDepthTex.cs
    • 4.FogShader
  • 四、再谈边缘检测
    • 1.EdgeDetect.cs
    • 2.EdgeDetectShader

一、深度和法线纹理的原理和获取

1.背后的原理

①深度纹理

  • 定义:是一张存储深度信息的纹理图像,像素值代表像素点到摄像机的距离
  • 生成原理:
    • 深度值来源于顶点变换后得到的归一化设备坐标 NDC中顶点坐标的z分量的值
    • 如下图,最左侧为投影变换前;中间为应用透视裁剪矩阵后的变换结果,即顶点着色器输出的顶点变换结果;右侧是底层硬件进行了透视除法后得到的归一化设备坐标(透视投影是非线性的,正交投影是线性的)
    • 需要进行线性映射才能存储在纹理中: d = 0.5 ⋅ z n d c + 0.5 d = 0.5\cdot z_{ndc} + 0.5 d=0.5zndc+0.5 (d为深度纹理中的像素值, z n d c z_{ndc} zndc为NDC坐标中的z分量值在这里插入图片描述
  • 精度通常为24位或16位

②法线纹理

  • 定义:是一张存储法线信息的纹理图像,像素值代表像素点在世界空间中的法线方向
  • 生成原理:
    • 在延迟渲染中,法线信息存储在G-buffer中,可以直接获取
    • 在前向渲染中,Unity会使用单独的Pass对场景进行渲染,并生成法线纹理
  • 如果选择生成一张深度+法线纹理,Unity会创建一张与屏幕分辨率相同、精度为32位(每个通道为8位)的纹理,其中观察空间下的法线信息会被编码到纹理的R和G通道,深度信息会进B和A通道

2.如何获取

  • 设置摄像机:通过脚本设置摄像机的 depthTextureMode 属性,可以选择生成深度纹理或者深度+法线纹理
    • camera.depthTextureMode = DepthTextureMode.Depth;
    • camera.depthTextureMode = DepthTextureMode.DepthNormals;
  • 在Shader中访问:使用预定义变量访问 _CameraDepthTexture、_CameraDepthNormalsTexture
    • 绝大多数下可以直接使用 tex2D 函数进行采样,也可以使用SAMPLE_DEPTH_TEXTURE ,处理平台差异化float d = SAMPLE_DEPTH_TEXTURE (CameraDepthTexture, i.uv);
  • 深度值转换:在计算过程中通常需要线性的深度值,即要把投影后的深度值变换到线性空间下。可以使用 LinearEyeDepth 和 Linear01Depth 函数进行转换
    • LinearEyeDepth:负责把深度纹理采样结果转换到视角空间下的深度值——线性的
    • Linear01Depth:返回一个范围在[0,1]的线性深度值
  • 法线值解码:可以直接使用 tex2D 对 _CameraDepthNormalsTexture 直接进行采样,再使用 DecodeDepthNormal 函数进行解码,解码后得到的是 [0,1]范围内的线性深度值视角空间下的法线方向,也可以用DecodeFloatRG 和 DecodeViewNormalStereo 来解码深度+法线纹理中的信息

3.查看深度和法线纹理

  • 利用 frame debugger 帧调试器来查看(看到的是非线性空间的深度值)
  • 可以再片元着色器中输出转换或解码后的深度和法线值
float depth = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);
float linearDepth = Linear01Depth(depth);
return fixed4(linearDepth,linearDepth,linearDepth,1.0);
fixed3 normal = DecodeViewNormalStereo(tex2D(_CameraDepthNormalsTexture, i.uv).xy);
return fixed4(normal * 0.5 + 0.5, 1.0);

在这里插入图片描述

二、再谈运动模糊

1.速度映射

  • 步骤
    • 利用深度纹理再片元着色器中为每个像素计算其在世界空间中的位置——使用当前 视角 * 投影矩阵的逆矩阵 对NDC下的顶点坐标进行变换得到的
    • 得到世界空间中的位置后,使用 前一帧的视角*投影矩阵 对其进行变换,得到该位置在前一帧中的NDC坐标
    • 计算前一帧和当前帧的位置差,生成该像素的速度
  • 优点:可以在一个屏幕后处理步骤中完成整个效果模拟
  • 缺点:需要在片元着色器中进行两次矩阵乘法,性能有影响

2.MotionBlurWithDepthTexture.cs

  • 在上一章运动模糊的基础上
 private Camera myCamera;public Camera camera{get{if(myCamera == null) {myCamera = GetComponent<Camera>();}return myCamera;}}
  • 由于需要得到相机的视角和投影矩阵,定义一个Camera类型的变量,来获取该脚本所在的摄像机组件
 private Matrix4x4 previousViewProjectionMatrix;
  • 定义一个变量来保存上一帧摄像机的 视角*投影矩阵
private void OnEnable()
{camera.depthTextureMode |= DepthTextureMode.Depth;
}
  • 设置摄像机的状态——获取摄像机的深度纹理
 private void OnRenderImage(RenderTexture src, RenderTexture dest){if(material != null){material.SetFloat("_BlurSize", blurSize);material.SetMatrix("_PreviousViewProjectionMatrix", previousViewProjectionMatrix);Matrix4x4 currentViewProjectionMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;Matrix4x4 currentViewProjectionInverseMatrix = currentViewProjectionMatrix.inverse;material.SetMatrix("_CurrentViewProjectionInverseMatrix", currentViewProjectionInverseMatrix);previousViewProjectionMatrix = currentViewProjectionMatrix;Graphics.Blit(src, dest, material);}else{Graphics.Blit(src, dest);}}
  • 需要两个矩阵:前一帧的视角×投影矩阵 & 当前帧的(视角×投影矩阵)的逆矩阵
  • camera.projectionMatrix得到投影矩阵,camera.worldToCameraMatrix得到视角矩阵
  • 再把取逆结果前的结果存储再 previousViewProjectionMatrix 变量中,给下一帧用

3.MotionBlurShader

Properties
{_MainTex ("Base(RGB)",2D) = "white"{}_BlurSize ("Blur Size",Float) = 1.0
}
SubShader
{CGINCLUDE#include "UnityCG.cginc"sampler2D _MainTex;half4 _MainTex_TexelSize;sampler2D _CameraDepthTexture;float4x4 _CurrentViewProjectionInverseMatrix;float4x4 _PreviousViewProjectionMatrix;half _BlurSize;
  • 除了定义在Propertise里面的属性,还声明了另外三个变量。_CameraDepthTexture 是Unity传递给我们的深度纹理,而_CurrentViewProjectionInverseMatrix 和 _PreviousViewProjectionMatrix 是脚本传递来的矩阵(不支持在属性面板中设置)
 struct v2f{float4 pos : SV_POSITION;half2 uv : TEXCOORD0;half2 uv_depth : TEXCOORD1;};v2f vert(appdata_img v){v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = v.texcoord;o.uv_depth = v.texcoord;#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0)o.uv_depth.y = 1-o.uv_depth.y;#endifreturn o;}
  • 增加了专门用于对深度纹理采样的纹理坐标变量
fixed4 frag(v2f i) : SV_Target 
{//得到该像素的深度缓存的值float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);//H为像素的视角位置float4 H = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, d * 2 - 1, 1);//用视角投影逆矩阵变换float4 D = mul(_CurrentViewProjectionInverseMatrix, H);//除以w,得到世界位置float4 worldPos = D/D.w;//当前视角位置float4 currentPos = H;//用世界位置,以及前一帧的矩阵得到前一帧的视角位置float4 previousPos = mul(_PreviousViewProjectionMatrix, worldPos);//除以wpreviousPos /= previousPos.w;//用两帧的位置差得到速度float2 velocity = (currentPos.xy - previousPos.xy) / 2.0f;float2 uv = i.uv;float4 c = tex2D(_MainTex, uv);uv += velocity * _BlurSize;for(int it = 1; it < 3; it++, uv += velocity * _BlurSize){float4 currentColor = tex2D(_MainTex, uv);c += currentColor;}c /= 3;return fixed4(c.rgb, 1.0);
}
  • 先 SAMPLE_DEPTH_TEXTURE 对深度纹理进行采样,得到深度值d
  • d是由NDC映射来的,要构建NDC坐标H,就要把xy值和深度值d重新映射回NDC(即映射函数的反函数 z = 2*d+1)
  • 利用当前帧的视角投影矩阵的逆矩阵,并除以它的w分量来求该像素在世界空间下的位置worldPos
  • 得到后再用前一帧的视角投影矩阵来变换,得到previousPos
  • 计算位置差得到该像素的速度 velocity = (currentPos.xy - previousPos.xy) / 2
  • 邻域采样
    • uv += velocity * _BlurSize;:根据速度和模糊大小计算邻域像素的纹理坐标
    • float4 currentColor = tex2D(_MainTex, uv);:对邻域像素进行采样,得到颜色值
    • c += currentColor:将领域像素颜色累加到c中
  • 模糊效果
    • c /= 3; 将邻域像素颜色平均,得到模糊颜色

三、全局雾效 —— 屏幕后处理

  • 屏幕后处理生成的雾效不需要修改场景内物体的shader,自由度很高,方便模拟各种雾效——均匀的、基于距离的线性/指数雾效、基于高度的雾效等
  • 屏幕后处理的雾效关键:根据深度纹理来重建每个像素在世界空间下的位置。上一节中使用的是在shader中进行两次计算,性能消耗大
  • 本节方法:
    • 首先对图像空间下的视锥体射线(从摄像机出发,指向图像上某点的射线)进行插值,这条射线存储了该像素在世界空间下到摄像机的方向信息
    • 该射线和线性化后的视角空间下的深度值相乘,再加到摄像机的世界空间位置,即可得到该像素在世界空间下的位置

1.重建世界坐标

  • 思想:知道摄像机的世界坐标位置,以及世界空间下该像素相对于摄像机的偏移量,相加即可得到 float4 worldPos = _WorldSpaceCameraPos + linearDepth * interpolatedRay;
    • _WorldSpaceCameraPos 为Unity内置变量
    • linearDepth * interpolatedRay 可以计算偏移量

interpolatedRay的求法

  • 来源于对近平面的4个角的某个特定向量的插值,这4个向量包含了它们到摄像机方向的距离和信息,可以利用 摄像机近平面距离、FOV、纵横比来计算
    在这里插入图片描述
  • 可以先计算 toToptoRight 向量,起点位于近平面的中心,分别指向正上方和正右方
    • h a l f H e i g h t = N e a r × t a n ( F O V 2 ) halfHeight = Near \times tan(\frac{FOV}{2}) halfHeight=Near×tan(2FOV)
    • t o T o p = c a m e r a . u p × h a l f H e i g h t toTop = camera.up \times halfHeight toTop=camera.up×halfHeight
    • t o R i g h t = c a m e r a . r i g h t × h a l f H e i g h t ⋅ a s p e c t toRight = camera.right \times halfHeight \cdot aspect toRight=camera.right×halfHeightaspect
  • 得到后,根据矢量计算得到四个角的向量
    • T L = c a m e r a . f o w a r d ⋅ N e a r + t o T o p − t o R i g h t TL = camera.foward\cdot Near + toTop - toRight TL=camera.fowardNear+toToptoRight
    • T R = c a m e r a . f o w a r d ⋅ N e a r + t o T o p + t o R i g h t TR = camera.foward\cdot Near + toTop + toRight TR=camera.fowardNear+toTop+toRight
    • B L = c a m e r a . f o w a r d ⋅ N e a r − t o T o p − t o R i g h t BL = camera.foward\cdot Near - toTop - toRight BL=camera.fowardNeartoToptoRight
    • B R = c a m e r a . f o w a r d ⋅ N e a r − t o T o p + t o R i g h t BR = camera.foward\cdot Near - toTop + toRight BR=camera.fowardNeartoTop+toRight
      在这里插入图片描述
  • 因为像素的深度值是平行于z轴的,不是欧拉距离,现在需要把深度值转换为欧拉距离dist(由相似三角形可得)
    • d e p t h d i s t = N e a r ∣ T L ∣ \frac{depth}{dist} = \frac{Near}{|TL|} distdepth=TLNear
    • d i s t = ∣ T L ∣ N e a r × d e p t h dist = \frac{|TL|}{Near} \times depth dist=NearTL×depth
  • 由于四个点相互对称,因此其他三个向量的模和TL相等,即我们可以使用同一个因子和单位向量相乘,得到其对应的向量值
    • s c a l e = ∣ T L ∣ ∣ N e a r ∣ scale = \frac{|TL|}{|Near|} scale=NearTL
    • R a y T L = T L ∣ T L ∣ × s c a l e Ray_{TL} = \frac{TL}{|TL|} \times scale RayTL=TLTL×scale
    • R a y T R = T R ∣ T R ∣ × s c a l e Ray_{TR} = \frac{TR}{|TR|} \times scale RayTR=TRTR×scale
  • 这个四边形面片的4个顶点就对应了近裁剪平面的4个角。因此可以把上面的计算结果传递给顶点着色器,顶点着色器根据当前的位置选择所对应的向量,再将其输出,经过插值后传递给片元着色器得到 interpolatedRay

2.雾的计算

  • 在简单的雾效实现中,需要一个雾效系数f,作为混合原始颜色和雾的颜色的混合系数float3 afterFog = f * fogColor + (1-f) * origColor;
  • 三种雾的计算方式:线性、指数、指数的平方
    在这里插入图片描述
  • 本节利用线性,计算基于高度的雾效
  • 方法:给定一点在世界空间下的高度y后, f = H e n d − y H e n d − H s t a r t f = \frac{H_{end}-y}{H_{end}-H_{start}} f=HendHstartHendy H e n d H_{end} Hend H s t a r t H_{start} Hstart分别表示雾影响的终止高度和起始高度

3.FogWithDepthTex.cs

private Camera myCamera;
public Camera camera
{get{if (myCamera == null){myCamera = GetComponent<Camera>();}return myCamera;}
}
private Transform myCameraTransform;
public Transform cameraTransform
{get{if(myCameraTransform == null){myCameraTransform = camera.transform;}return myCameraTransform;}
}
  • 需要获取摄像机的相关参数,如近平面的距离、FOV等,同时还需要获取摄像机在世界空间下的前方、上方、右方等方向。因此用两个变量来存储camera和Transform组件
//定义模拟雾效时使用的参数
[Range(0.0f, 3.0f)]
public float fogDensity = 1.0f;public Color fogColor = Color.white;
public float fogStart = 0.0f;
public float fogEnd = 2.0f;
  • fogDensity控制雾的浓度,fogColor用于控制雾的颜色
private void OnEnable()
{camera.depthTextureMode |= DepthTextureMode.Depth;
}
  • 获取摄像机的深度纹理——设置摄像机的状态
 private void OnRenderImage(RenderTexture src, RenderTexture dest){if(material != null){Matrix4x4 frustumCorners = Matrix4x4.identity;float fov = camera.fieldOfView;float aspect = camera.aspect;float near = camera.nearClipPlane;float far = camera.farClipPlane;float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);Vector3 toRight = cameraTransform.right * halfHeight * aspect;Vector3 toTop = cameraTransform.up * halfHeight;Vector3 tL = cameraTransform.forward * near + toTop - toRight;Vector3 tR = cameraTransform.forward * near + toTop + toRight;Vector3 bL = cameraTransform.forward * near - toTop - toRight;Vector3 bR = cameraTransform.forward * near - toTop + toRight;float scale = tL.magnitude / near;tL.Normalize();tL *= scale;tR.Normalize();tR *= scale;bL.Normalize();bL *= scale;bR.Normalize();bR *= scale;frustumCorners.SetRow(0, bL);frustumCorners.SetRow(1, bR);frustumCorners.SetRow(2, tR);frustumCorners.SetRow(3, tL);material.SetMatrix("_FrustumCornersRay", frustumCorners);material.SetMatrix("_ViewProjectionInverseMatrix", (camera.projectionMatrix * camera.worldToCameraMatrix).inverse);material.SetFloat("_FogDensity", fogDensity);material.SetColor("_FogColor", fogColor);material.SetFloat("_FogStart", fogStart);material.SetFloat("_FogEnd", fogEnd);Graphics.Blit(src,dest, material);}else{Graphics.Blit(src,dest);}}
  • 首先计算近平面的四个角对应的向量,并存储在一个矩阵类型的变量中 frustumCorners 中,要按照顺序来存储(因为决定了我们在顶点着色器中使用哪一行作为该点的待插值向量)

4.FogShader

struct v2f{float4 pos : SV_POSITION;half2 uv : TEXCOORD0;half2 uv_depth : TEXCOORD1;float4 interpolatedRay : TEXCOORD2;
};v2f vert(appdata_img v)
{v2f o;o.pos = UnityObjectToClipPos(v.vertex);o.uv = v.texcoord;o.uv_depth = v.texcoord;#if UNITY_UV_STARTS_AT_TOPif(_MainTex_TexelSize.y < 0)o.uv_depth.y = 1-o.uv_depth.y;#endifint index = 0;if(v.texcoord.x < 0.5 && v.texcoord.y < 0.5){index = 0;}else if (v.texcoord.x>0.5 && v.texcoord.y <0.5){index = 1;}else if (v.texcoord.x>0.5 && v.texcoord.y >0.5){index = 2;}else{index = 3;}#if UNITY_UV_STARTS_AT_TOPif(_MainTex_TexelSize.y < 0)index = 3- index;#endifo.interpolatedRay = _FrustumCornersRay[index];return o;
}
  • 定义了interpolatedRay 变量存储插值后的像素向量
  • 通过判断纹理坐标来决定要用哪个角的向量插值
    • 纹理坐标的(0,0)点对应左下角,(1,1)对应了右上角
    • 使用索引值来获取_FrustumCornersRay 中对应的行为作为该顶点的interpolatedRay 值
 fixed4 frag(v2f i) : SV_Target {float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE (_CameraDepthTexture, i.uv_depth));float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xzy;float fogDensity = (_FogEnd-worldPos.z) / (_FogEnd - _FogStart);fogDensity = saturate(fogDensity * _FogDensity);fixed4 finalColor = tex2D(_MainTex,i.uv);finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);return finalColor;}
  • 首先要重建该像素在世界空间中的位置
    • 先对深度纹理进行采样,在用LinearEyeDepth得到视角空间下的线性深度值
    • 再与interpolatedRay相乘后再和世界空间下的摄像机位置相加,得到世界空间下的位置
  • 再计算 fogDensity,再与原始颜色混合后返回
    在这里插入图片描述

四、再谈边缘检测

  • 上一章中,直接利用sobel算子边缘检测会得到很多杂乱的边缘线
  • 本节再深度和法线纹理上进行边缘检测,不会受纹理和光照的影响,而仅仅保留了当前渲染物体的模型信息
  • 本节将使用 Roberts 算子来进行边缘检测
    • 本质上就是计算 左上角和右下角的插值 × 右上角与左下角的插值,作为评估边缘的依据
    • 在实现中,取对角线方向的深度或法线值,比较他们之间的插值,若超过某个阈值,就认为他们存在一条边
      在这里插入图片描述

1.EdgeDetect.cs

 [Range(0.0f, 1.0f)]public float edgesOnly = 0.0f;public Color edgeColor = Color.black;public Color backgroundColor = Color.white;public float sampleDistance = 1.0f;public float sensitivityDepth = 1.0f;public float sensitivityNormals = 1.0f;
  • sampleDistance 用于控制对深度+法线采样时,使用的采样距离,视觉上看,值越大,描边越宽
  • 灵敏度 sensitivityDepth 和 sensitivityNormals 会影响当邻域的深度值或法线值相差多少时认为存在一条边
    void OnEnable(){GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;}
  • 由于需要获得摄像机的深度+法线纹理
 [ImageEffectOpaque]void OnRenderImage(RenderTexture src, RenderTexture dest){if (material != null){material.SetFloat("_EdgeOnly", edgesOnly);material.SetColor("_EdgeColor", edgeColor);material.SetColor("_BackgroundColor", backgroundColor);material.SetFloat("_SampleDistance", sampleDistance);material.SetVector("_Sensitivity", new Vector4(sensitivityNormals, sensitivityDepth, 0.0f, 0.0f));Graphics.Blit(src, dest, material);}else{Graphics.Blit(src, dest);}}
  • [ImageEffectOpaque] 属性表示只在不透明物体上进行描边

2.EdgeDetectShader

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)_SampleDistance ("Sample Distance", Float) = 1.0_Sensitivity ("Sensitivity", Vector) = (1, 1, 1, 1)
}
SubShader {CGINCLUDE#include "UnityCG.cginc"sampler2D _MainTex;half4 _MainTex_TexelSize;fixed _EdgeOnly;fixed4 _EdgeColor;fixed4 _BackgroundColor;float _SampleDistance;half4 _Sensitivity;sampler2D _CameraDepthNormalsTexture;
  • 声明需要的属性
struct v2f {float4 pos : SV_POSITION;half2 uv[5]: TEXCOORD0;
};v2f vert(appdata_img v) {v2f o;o.pos = UnityObjectToClipPos(v.vertex);half2 uv = v.texcoord;o.uv[0] = uv;#if UNITY_UV_STARTS_AT_TOPif (_MainTex_TexelSize.y < 0)uv.y = 1 - uv.y;#endifo.uv[1] = uv + _MainTex_TexelSize.xy * half2(1,1) * _SampleDistance;o.uv[2] = uv + _MainTex_TexelSize.xy * half2(-1,-1) * _SampleDistance;o.uv[3] = uv + _MainTex_TexelSize.xy * half2(-1,1) * _SampleDistance;o.uv[4] = uv + _MainTex_TexelSize.xy * half2(1,-1) * _SampleDistance;return o;
}
  • 定义了一个5维数组,第一个坐标存储了屏幕颜色图像的采样纹理,对深度纹理采样坐标进行了平台差异化处理
  • 数组中剩余四个坐标则存储了Roberts 算子需要采样的纹理坐标,还使用了_SampleDistance来控制采样距离
  • 把计算采样纹理坐标的代码从片元着色器移到顶点中可以减少运算‘
fixed4 fragRobertsCrossDepthAndNormal(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]);half edge = 1.0;edge *= CheckSame(sample1, sample2);edge *= CheckSame(sample3, sample4);fixed4 withEdgeColor = lerp(_EdgeColor, tex2D(_MainTex, i.uv[0]), edge);fixed4 onlyEdgeColor = lerp(_EdgeColor, _BackgroundColor, edge);return lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
}
  • 首先使用4个纹理坐标对深度进行采样,再调用CheckSame函数来计算对角线上两个纹理值的差值。
  • CheckSame函数返回两个值,0是有边界,1是没有边界
half CheckSame(half4 center, half4 sample) {half2 centerNormal = center.xy;float centerDepth = DecodeFloatRG(center.zw);half2 sampleNormal = sample.xy;float sampleDepth = DecodeFloatRG(sample.zw);// difference in normals// do not bother decoding normals - there's no need herehalf2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x;int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;// difference in depthfloat diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y;// scale the required threshold by the distanceint isSameDepth = diffDepth < 0.1 * centerDepth;// return:// 1 - if normals and depth are similar enough// 0 - otherwisereturn isSameNormal * isSameDepth ? 1.0 : 0.0;
}
  • 首先从 center 和 sample 中分别提取法线和深度值
  • 计算两个像素的法线差异 diffNormal,并乘以法线灵敏度 _Sensitivity.x
  • 判断法线差异是否小于阈值 0.1,如果小于则认为法线相似,否则认为不相似
    • diffNormal.x 表示法线在 x 轴方向上的差异。
    • diffNormal.y 表示法线在 y 轴方向上的差异。
    • 将这两个分量相加,可以得到 法线差异向量在两个方向上的总差异
  • 计算两个像素的深度差异 diffDepth,并乘以深度灵敏度 _Sensitivity.y
  • 将深度差异乘以中心像素的深度值,得到距离缩放后的深度差异
  • 判断距离缩放后的深度差异是否小于阈值 0.1 * centerDepth,如果小于则认为深度相似,否则认为不相似
  • 如果法线和深度都相似,则返回 1,否则返回 0
    在这里插入图片描述

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

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

相关文章

电脑压缩软件有哪些?整理了6个常用的,总有一款满足你的需求 !

对于长期需要借助电脑来办公的小伙伴来说&#xff0c;电脑压缩软件是不可获取的办公软件之一。电脑压缩软件具有多种重要作用&#xff0c;它们在日常的计算机使用、文件管理、网络传输和存储中扮演着不可或缺的角色。 下是电脑压缩软件的主要作用&#xff1a; 1、减少文件大小…

【C++】C++ 学生信息管理系统(源码+面向对象)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;公众号&#x1f448;&#xff1a;测试开发自动化【获取源码商业合作】 &#x1f449;荣__誉&#x1f448;&#xff1a;阿里云博客专家博主、5…

C语言 ——— 在控制台上打印动态变化的菱形

目录 代码要求 代码实现 代码要求 输入 整数line &#xff0c;菱形的上半部分的长度就为line&#xff08;动态变化的菱形&#xff09; 菱形由 "*" 号构成 代码实现 #include<stdio.h> int main() {// 上半长int line 0;scanf("%d", &line)…

图解HTTP有感

目录 1、网络请求流程 2、HTTP报文结构 2.1、请求报文首部和响应报文首部 2.2、HTTP的首部字段有以下几种类型 3、HTTP的请求方式 4、响应状态码 5、HTTP安全 6、HTTP对用户身份的认证 7、全双工通信Websocket 7.1、什么是Websocket? 7.2、Websocket的主要特点&…

Linux系统及常用指令

目录 1、什么是Linux系统 2、为什么要用Linux系统 3、Linux系统的种类 4、如何安装Linux系统 5、常见的适配器种类 6、学习第一个Linux指令 7、安装ssh客户端软件 8、Linux系统的目录结构 9、Linux的常用命令 9.1 目录切换命令 9.2 查看目录下的内容 9.3 查看当前…

开源防病毒工具--ClamAV

产品文档&#xff1a;简介 - ClamAV 文档 开源地址&#xff1a;Cisco-Talos/clamav&#xff1a;ClamAV - 文档在这里&#xff1a;https://docs.clamav.net (github.com) 一、引言 ClamAV&#xff08;Clam AntiVirus&#xff09;是一个开源的防病毒工具&#xff0c;广泛应用…

MAVSKD-Java开源库mavsdk_server库macOS平台编译

1.下载源码 2.使用IDEA打开,进行mavsdk_server目录,使用gradle进行编译 3.开始编译时会自动下载依赖 4.下载完成后,会自动编译 5.编译成功 6.成功生成AAR文件

Linux TFTP服务搭建及使用

1、TFTP 服务器介绍 TFTP &#xff08; Trivial File Transfer Protocol &#xff09;即简单文件传输协议是 TCP/IP 协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议&#xff0c;提供不复杂、开销不大的文件传输服务。端口号为 69 2、TFTP 文件传输的特点 tftp…

vivado FFT IP Core

文章目录 前言FFT IP 接口介绍接口简介tdata 格式说明 其他细节关于计算精度及缩放系数计算溢出架构选择数据顺序实时/非实时模式数据输入输出时序关于配置信息的应用时间节点 FFT IP 例化介绍控制代码实现 & 测试参考文献 前言 由于计算资源受限&#xff0c;准备将上位机 …

机器学习第四十八周周报 IAGNN

文章目录 week48 IAGNN摘要Abstract0. 前言1. 题目2. Abstract3. 网络结构3.1 问题定义3.2 IAGNN 4. 文献解读4.1 Introduction4.2 创新点4.3 实验过程4.4 实验结果 5. 结论6.代码复现小结参考文献 week48 IAGNN 摘要 本周阅读了题为Interaction-Aware Graph Neural Networks…

<Qt> 信号和槽

目录 一、信号和槽概述 二、信号和槽的使用​​​​​​ &#xff08;一&#xff09;connect函数 &#xff08;二&#xff09;实现一个点击按钮关闭窗口的功能 &#xff08;三&#xff09;再谈connect 三、自定义槽函数 四、自定义信号 五、带参数的信号和槽 六、信号…

【从零开始实现stm32无刷电机FOC】【实践】【6/7 CMSIS-DSP】

目录 导入CMSIS-DSP库使用CMSIS-DSP 点击查看本文开源的完整FOC工程 CMSIS-DSP库是ARM开源的、对ARM处理器优化的数学库&#xff0c;本文使用了其提供的三角函数、反park变换函数、park变换函数、clarke变换函数、PID控制器。 CMSIS-DSP原始代码仓库是https://github.com/ARM-s…

双领域TOP10优秀安全企业!通付盾入选《嘶吼2024网络安全产业图谱》六大分类14项细分领域

7月16日&#xff0c;嘶吼安全产业研究院正式发布《嘶吼2024网络安全产业图谱》&#xff08;以下简称“图谱”&#xff09;&#xff0c;通过市场调研、数据精析、文献研究及政策参考等多方面的综合分析&#xff0c;全面展示网络安全产业的构成及其重要组成部分&#xff0c;探索网…

MongoDB教程(九):java集成mongoDB

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、环境准…

网安小贴士(19)入侵检测技术原理与应用

前言 入侵检测技术&#xff08;Intrusion Detection System, 简称IDS&#xff09;是一种用于监测和防止计算机网络中的恶意活动的安全系统。它通过收集系统状态信息、特征提取、建立模型、入侵检测以及反馈更新等步骤&#xff0c;及时检测网络和系统中可能遭受攻击的迹象并发出…

python安装talib库教程

【talib介绍】 Talib介绍 Talib&#xff0c;全称“Technical Analysis Library”&#xff0c;即技术分析库&#xff0c;是一个广泛应用于金融量化领域的Python库。该库由C语言编写&#xff0c;支持Python调用&#xff0c;为投资者、交易员和数据分析师提供了强大的技术分析工…

借力Jersey,铸就卓越RESTful API体验

目录 maven 创建 jersey 项目 运行 支持返回 json 数据对象 1. 引言 在当今数字化时代&#xff0c;API&#xff08;应用程序编程接口&#xff09;已成为连接不同软件系统和服务的桥梁。RESTful API以其简洁、轻量级和易于理解的特点&#xff0c;成为了API设计的首选标准。本…

达梦数据库的系统视图v$arch_file

达梦数据库的系统视图v$arch_file 在达梦数据库中&#xff0c;V$ARCH_FILE 是一个动态性能视图&#xff0c;用于显示当前数据库的归档日志文件信息。这个视图可以帮助数据库管理员监控和管理归档日志文件&#xff0c;确保数据库的备份和恢复过程顺利进行。 查询本地归档日志信…

golang 基础 泛型编程

&#xff08;一&#xff09; 示例1 package _caseimport "fmt"// 定义用户类型的结构体 type user struct {ID int64Name stringAge uint8 }// 定义地址类型的结构体 type address struct {ID intProvince stringCity string }// 集合转列表函数&#…

springboot框架的事务功能

事务回顾 概念 事务是一组操作集合&#xff0c;它是一个不可分割的工作单位&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败。 操作 开启事务~&#xff08;一组操作开始前&#xff0c;开启事务&#xff09;&#xff1a;start transaction / begin; 提交事务~&am…