Unity Shader深度纹理、法线纹理的使用

前言:在进行屏幕后处理的时候,我们往往会应用很多图像处理的相关算法,例如高斯模糊,sobel边缘检测等等。但是这些图像算法都是基于图像的颜色值来计算的,而我们通过渲染管线得到屏幕图像时,不仅可以得到颜色缓冲,还可以得到深度缓冲以及法线信息等。更多的信息可以为我们提供更多的方法去处理和计算,例如,在边缘检测时,我们如果用sobel基于颜色信息计算得到边缘,那最终得到的边缘信息会受到物体纹理以及光照等外部因素的影响,为了解决这个问题,我们就可以基于深度纹理和法线纹理两个参考因素来检测,这两个因素是不受纹理和光照的影响的。

本章内容主要参考自《Unity Shader入门精要》和官方文档。

https://docs.unity3d.com/Manual/SL-DepthTextures.html

https://docs.unity3d.com/Manual/SL-CameraDepthTexture.html

一、unity中的深度纹理

在脚本中设置相机的depthTextureMode,然后就可以在shader中声明_CameraDepthTexture变量来访问深度纹理了;

其中DepthTextureMode是一个枚举,默认为None;

DepthTextureMode.Depth

深度纹理,视图空间的深度信息,大小和屏幕相同,深度纹理可作为全局着色器属性用于在着色器中采样,通过声明一个_CameraDepthTexture可以对相机的主深度纹理进行采样;

DepthTextureMode.DepthNormals

构建一个屏幕大小的32位纹理,其中视图空间法线被编码RG通道,深度被编码BA通道;可以借助UnityCG.cginc中的DecodeDepthNormal解码像素的深度和法线值,返回0-1内;

DepthTextureMode.MotionVectors

建立一个屏幕大小的RG16位纹理;

二、获取深度和法线纹理

深度纹理的获取在之前讲Unity阴影的时候就有讲述过,就是拿到深度缓冲区的内容,在深度纹理中,它的深度值范围是[0,1],而且通常是非线性分布的,也就是在存储到纹理前转换为了非线性。关于非线性分布的详细知识,可以参考:深度检测;在该篇文章的深度值精度一段中,详细讲述了为什么深度缓冲中的深度值也就是屏幕空间中的深度值是非线性分布的,必须了解这一点才可以理解接下来的一些公式的计算。

Unity如何获取深度纹理,它的原理:在延迟渲染中,深度纹理是可以从G-buffer中访问到的,而在前向渲染中,unity无法直接获取深度缓存,深度和法线纹理是通过一个单独的Pass渲染得到的。Unity会使用着色器替换技术选择那些渲染类型(RenderType)为Opaque的物体,如果满足条件就把它渲染到深度和法线纹理中,所以要想让物体能够出现在深度和法线纹理中,就必须在Shader中设置正确的RenderType标签。Unity选取需要的不透明物体,并使用它投射阴影时使用的pass来得到深度纹理中,如果shader中不包含这样一个pass,那么这个物体就不会出现在深度纹理中。而法线信息的获取,是由Unity在底层使用了一个单独的Pass把整个场景再次渲染一遍来完成。

具体的获取细节:直接在脚本中设置相机的depthTextureMode,然后就可以在shader中声明_CameraDepthTexture变量来访问深度纹理了。

camera.depthTextureMode |= DepthTextureMode.DepthNoramls;

 对深度纹理进行采样时,原理上和其它纹理都一样,但是要考虑到由于平台差异造成的问题,所以使用SAMPLE_DEPTH_TEXTURE宏对深度纹理进行采样,采样之后我们要对像素进行进行解码。

深度信息,

fixed4 center=SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv_depth);
float Depth = DecodeFloatRG(center.zw);
float3 Normal=DecodeViewNormalStereo(center);

这里解码得到的就是观察空间的深度和法线信息了。

Linear01Depth和LinearEyeDepth

两个和Depth解析相关的内置函数

  • Linear01Depth(i):given high precision value from depth texture i, returns corresponding linear depth in range between 0 and 1;给出该像素所对应的01范围内的视图空间下的线性深度值;
  • LinearEyeDepth:给出该像素对应的near,far范围内的视图空间下的线性深度值;

三、渲染深度纹理和法线纹理

将深度图和法线图渲染到屏幕上;

如下,在顶点着色器内,我们只做了顶点的平移变换(注意这是后处理,不需要使用mvc);

然后在片元中,通过对已经声明的_CameraDepthTexture和_CameraDepthNormalsTexture采样来完成着色;

Shader "Custom/DepthShader"
{
    Properties
    {
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _CameraDepthTexture;
            sampler2D _CameraDepthNormalsTexture;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = float4(v.vertex.xy * 2 - 1, 0.0, 1.0);
                o.uv = v.uv;
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // 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));
                return fixed4(normal * 0.5 + 0.5, 1.0);
            }
            ENDCG
        }
    }
}
 

然后我们需要在OnRenderImage中来处理,将着色效果使用一个材质来做一次Blit操作:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class CameraDepth : MonoBehaviour
{
    public Camera camera;
    public Material depthMat;
    void Start()
    {
        camera.depthTextureMode |= DepthTextureMode.Depth;
        camera.depthTextureMode |= DepthTextureMode.DepthNormals;
    }

    void OnRenderImage(RenderTexture source, RenderTexture dest){
        if (null != depthMat){
            Graphics.Blit(source, dest, depthMat);
        }else{
            Graphics.Blit(source, dest);
        }
    }
}

法线效果图:

四、运动模糊

如果使用模糊卷积核来进行运动模糊的话,由于我们无法得到运动的方向,所以无法得到合适的运动模糊。这里我们基于摄像机的移动来得到每个像素点的移动方向,原理是,我们通过深度纹理以及uv坐标来得到重建各个像素点的世界坐标,我们可以在脚本中存储相机在上一帧中的投影和裁剪矩阵,这样我们就可以通过重建的世界坐标得到上一帧中该像素点的位置,然后通过这两帧中像素的位置来得到该像素的移动方向,从而得到模糊的方向,然后进行运动模糊的计算。但是这种方案有一个明显缺陷,我们是根据相机来得到投影和裁剪矩阵的,如果相机没有发生移动,那么投影和裁剪矩阵也不会改变,也就是说只有相机移动的时候才有运动模糊效果,如果只有物体移动而相机不移动,是不会有运动模糊效果的。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值