自学内容网 自学内容网

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

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

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_TOP
     if (_MainTex_TexelSize.y < 0)
         o.uv_depth.y = 1-o.uv_depth.y;
     #endif
     return 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);
    //除以w
    previousPos /= 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_TOP
    if(_MainTex_TexelSize.y < 0)
        o.uv_depth.y = 1-o.uv_depth.y;
    #endif

    int 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_TOP
    if(_MainTex_TexelSize.y < 0)
        index = 3- index;
    #endif

    o.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_TOP
if (_MainTex_TexelSize.y < 0)
uv.y = 1 - uv.y;
#endif

o.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 here
half2 diffNormal = abs(centerNormal - sampleNormal) * _Sensitivity.x;
int isSameNormal = (diffNormal.x + diffNormal.y) < 0.1;
// difference in depth
float diffDepth = abs(centerDepth - sampleDepth) * _Sensitivity.y;
// scale the required threshold by the distance
int isSameDepth = diffDepth < 0.1 * centerDepth;

// return:
// 1 - if normals and depth are similar enough
// 0 - otherwise
return 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
    在这里插入图片描述

原文地址:https://blog.csdn.net/weixin_70073176/article/details/140523281

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!