获得深度和法线纹理,最后搞懂了

赶尽杀绝思路

  1.  获取Viewport里的右上角(z=Mathf.Abs(Camera.main.transform.position.z))
  2.  使用ViewportToWorldPoint
    将Vieport坐标转化为WordlPoint,游戏世界里的职位

 

比方设置好了地点的壁画机格局后,大家就能够在Shader中经过申明_CameraDepthTexture变量来访问它。这几个历程卓殊不难,但大家供给知道两行代码的骨子里,Unity为大家做了不可胜道办事。

代码达成

 

    [HideInInspector]public float leftBorder;
    [HideInInspector]public float rightBorder;
    [HideInInspector]public float topBorder;
    [HideInInspector]public float downBorder;
    private float width;
    private float height;

    void SetBasicValues(){
       

        //the up right corner
        Vector3 cornerPos=Camera.main.ViewportToWorldPoint(new Vector3(1f,1f,
                                                                       Mathf.Abs(-Camera.main.transform.position.z)));
        
        leftBorder=Camera.main.transform.position.x-(cornerPos.x-Camera.main.transform.position.x);
        rightBorder=cornerPos.x;
        topBorder=cornerPos.y;
        downBorder=Camera.main.transform.position.y-(cornerPos.y-Camera.main.transform.position.y);

        width=rightBorder-leftBorder;
        height=topBorder-downBorder;

    }

 

笔者们开始展览如下准备工作。

出现的题材

  • Unity3D中长度单位是米
  • 使用Screen.resolutions获取的荧屏音讯单位是像素

也正是说,就算取得了荧屏相关新闻及参数,也无能为力把音讯转换成可在editor中央银行使的音讯。当时想在做Protect
The Red 。

于是,就起首了本人的谷歌(Google)之旅。

 

在StackExchange和Unity Answers上翻了翻,最终搞懂了,大约思路是那样的。

 

图片 1
由地点的这几个姿势,大家得以推导出用d表示而得的Z(visw)的表明式:

收获–守住梅红

末段的结果是这么的,墙壁游戏运维后直接处于显示屏的四边。将小球控制在荧屏内。

图片 2     
图片 3

 

APK Download

Google
Play

图片 4

 

图片 5

雾效是游戏里时常使用的一种效用。Unity内置的雾效能够生出基于距离的线性或指数雾效。不过,要想在温馨编写的终端/片元着色器中落实那么些雾效,大家供给在Shader
中添加#pragma multi_compile_fog
指令,同时还须求动用相关的内置宏,例如UNITY_FOG_COORDS、UNITY_TRANSFER_FOG和UNITY_APPLY_FOG等。那种艺术的后天不足在于,大家不但须求为场景中拥有物体添加相关的渲染呆吗,而且能够落到实处的功能也卓殊有限。当大家须要对雾效实行局地天性化操作时,例如利用基于高度的雾效等,仅仅使用Unity内置的雾效就变得不再实用。

float d = SMAPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv);

下图显示了前面交付的Unity中透视投影对极端的更换进程,下图最左边的图展现了投影变换前,即观察空间下视锥体的结果以及对应的极端地点,中间的图展现了动用透视裁剪矩阵后的变换结果,即顶点着色器阶段输出的顶峰变换结果,最左边的图则是底层硬件实行了透视除法后获得的归一化的设施坐标。须求小心的是,那里的黑影进度是手无寸铁在Unity对坐标系的假如上的,相当于说,我们针对的是观测空间为右手坐标系,使用列矩阵在矩阵左边举办相乘,且变换成NDC后z分量范围将在[-1,1]时期的动静。而接近DirectX
这样的图样接口中,变换后z分量范围将在[0,1]之间。

首先,大家编辑FogWithDepthTexture.cs

图片 6

在取得NDC后,深度纹理中的像素值就足以很有益地一个钱打二16个结获得了,这一个深度值就对应了NDC中顶点坐标的z分量的值。由于NDC中z分量的范围在[-1,1],为了让这一个值能够存款和储蓄在一张图像中,大家需求运用上边包车型地铁公式对其实行映射:

里头,i.srcPos
是在终极着色器中通过调用ComputeScreenPos(o.pos)获得的荧屏坐标。上述这么些宏,可以在Unity内置的HLSLSupport.cginc文件中找到。

大家在那边贯彻的移动模糊适用于静止场景、摄像机赶快移动的状态,那是因为大家在测算时只考虑了摄像机的位移。要是把那边的代码应用到1个实体快捷移动而录像机静止的场景,会意识不会产生任何活动模糊效果。

我们再落到实处Shader部分:

图片 7

接下去,大家兑现Shader部分:

float4 worldPos = _WorldSpaceCameraPos + linearDepth * interpolateRay;

事先大家曾介绍怎样运用Sobel算子对荧屏图像进行边缘检查和测试,完结描边的法力。不过,这种直白运用颜色消息进行边缘检查和测试的法子会产生众多大家不期望得到的边缘线,如下图所示。

2)创设贰个带有3面墙的房间,放置三个立方和三个圆球

图片 8

图片 9

笔者们事先已知,当我们采纳透视投影的剪裁矩阵P(clip)对意见空间下的四个终极举行转移后,裁剪空间下顶点的z和w分量为:

camera.depthTextureMode = DepthTextureMode.DepthNormals;

为了方便总结,大家能够先总结四个向量——toTop

toRight,它们是起源位于近裁剪平面大旨、分别针对摄像机正上方和正右方的趋势。它们的计算公式如下:

图片 10
能够看出,物体的纹理、阴影等职位也被描上黑边,而这频繁不是大家期待寓指标。大家将学习怎么在深度和法线纹理上实行边缘检查和测试,那一个图像不会受纹理和光照的震慑,而单单保留了单钱渲染物体的模型音信,通过如此的法子检查和测试出来的边缘特别可信。大家能够博得近似下图中的结果。

一经我们必要得到深度+法线纹理,可以直接选用tex2D函数对_CameraDepthNormalsTexture
进行采集样品,获得里面储存的吃水和法线音讯。Unity提供了声援函数来为我们队那个采集样品结果开始展览解码,从而赢得深度值和法线方向。这几个函数是DecodeDepthNormal,它在UnityCG.cginc里被定义:

个中,i.uv
是贰个float2项目标变量,对应了脚下像素的纹路坐标。类似的宏还有SAMPLE_DEPTH_TEXTURE_PROJ
和 SAMPLE_DEPTH_TEXTURE_LOD。SAMPLE_DEPTH_TEXTURE_PROJ
宏同样接受多少个参数——深度纹理和三个float3或float4类型的纹理坐标,它的里边使用了tex2Dproj那样的函数举办投影纹理采集样品,纹理坐标的前多少个轻重首先会除以最后三个份量,再进行纹理采集样品。若是提供了第七个轻重,还会开始展览1回相比,
日常用于阴影的落到实处中。SAMPLE_DEPTH_TEXTURE_PROJ
的第一个参数平时是由顶点着色器输出插值而得的显示屏坐标,例如:

inline void DecodeDepthNormal(float4 enc, out float depth,out float3 normal){
    depth = DecodeFloatRG(enc.zw);
    normal = DecodeViewNormalStereo(enc);
}

有幸的是,Unity提供了三个帮助函数来为大家进行上述的一个钱打二14个结进度——LinearEyeDepth
和 Linear01Depth。LinearEyeDepth
负责把深度纹理的采集样品结果转换成意见空间下的吃水值,也
正是我们地点得到的Z(visw)。而 Linear01Depth
则会回到3个限制在[0,
1]的线性深度值,也便是大家地点获得的Z(01),那多少个函数内部接纳了安置的_ZBufferParams变量来获取远近裁剪平面包车型地铁相距。

大概输出法线方向:

小心,上边求得的伍个向量不仅包蕴了可行性消息,它们的模对应了六个点到壁画机的空间距离。由于大家获取的线性深度值并非是点到摄像机的欧式距离,而是在z方向上的相距,因而,我们无法直接使用深度值和伍个角的单位方向的乘积来计量它们到摄影机的偏移量,如下图所示。

图片 11

使用帧调节和测试器查看到的吃水纹理是非线性空间的深浅值,而深度+法线纹理都以由Unity编码后的结果。有时,显示出线性空间下的深度消息或解码后的法线方向会越来越使得。此时,我们得以活动在片元着色器中输出转换或解码后的深浅和法线值,如下图所示。

Shader "Unity Shaders Book/Chapter 13/Edge Detection Normals And Depth" {
    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 = mul(UNITY_MATRIX_MVP, 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;
        }

        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;
        }

        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);
        }

        ENDCG

        Pass { 
            ZTest Always Cull Off ZWrite Off

            CGPROGRAM      

            #pragma vertex vert  
            #pragma fragment fragRobertsCrossDepthAndNormal

            ENDCG  
        }
    } 
    FallBack Off
}

取得深度和法线纹理

在Unity5中,大家仍是能够在摄像机的Camera组件上看到眼下摄像机是或不是需求渲染深度或深度+法线纹理。当在Shader中访问到深度纹理_CameraDepthTexture
后,大家就足以行使当前像素的纹路坐标对它进行采样。绝当先三分之二情景下,大家一贯利用tex2D函数采集样品即可,但在少数平台上,大家供给有个别出奇处理。Unity为我们提供了一个联合的宏SAMPLE_DEPTH_TEXTURE,用来拍卖这些由于平台差别导致的题材。而笔者辈只须要在Shader中选取SAMPLE_DEPTH_TEXTURE宏对纵深纹理举办采集样品,例如:

1)新建贰个景观,去掉天空盒子。

在前头,大家上学了怎么着通过混合多张显示器图像来模拟运动模糊的效能。不过,别的一种接纳越发广泛的技能则是采用速度映射图。速度映射图中蕴藏了各个像素的进程,然后选用那些速度来支配模糊的取向和尺寸。速度缓冲的生成有多样情势,一种方法是把场景中颇具物体的快慢渲染到一张纹理中。但以此格局的弱项在于需求修改场景中全数物体的Shader代码,使其添加计算速度的代码并出口到八个渲染纹理中。

public class MotionBlurWithDepthTexture : PostEffectsBase {

    public Shader motionBlurShader;
    private Material motionBlurMaterial = null;

    public Material material {  
        get {
            motionBlurMaterial = CheckShaderAndCreateMaterial(motionBlurShader, motionBlurMaterial);
            return motionBlurMaterial;
        }  
    }

    private Camera myCamera;
    public Camera camera {
        get {
            if (myCamera == null) {
                myCamera = GetComponent<Camera>();
            }
            return myCamera;
        }
    }
    //定义运动模糊时模糊图像使用的大小
    [Range(0.0f, 1.0f)]
    public float blurSize = 0.5f;

    //保存上一帧摄像机的视角*投影矩阵
    private Matrix4x4 previousViewProjectionMatrix;

    void OnEnable() {
        camera.depthTextureMode |= DepthTextureMode.Depth;

        previousViewProjectionMatrix = camera.projectionMatrix * camera.worldToCameraMatrix;
    }

    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);
        }
    }
}

图片 12

图片 13

1)新建场景,去掉天空盒子

图片 14

图片 15

3)在摄像机上新建叁个剧本MotionBlurWithDepthTexture.cs

随着,大家修改Shader

图片 16图片 17

出口代码格外不难,大家能够动用类似上边包车型客车代码来输出线性深度值:

当通过纹理采集样品获得深度值后,那个深度值往往是非线性的,那种非线性来自于透视投影使用的剪裁矩阵。可是,在我们的持筹握算进程中家常便饭是亟需线性的吃水值,也正是说,我们必要把影子后的纵深值变换来线性空间下,例如视角空间下的吃水值。那么,大家相应什么开始展览那个转换呢?实际上,大家只要求倒推顶点变换的进度即可。上边大家以透视投影为例,推导如何由深度纹理中的深度音讯计算获得视角空间下的吃水值。

为了接纳深度纹理模拟运动模糊,大家开展如下准备干活:

在简练的雾效完成中,大家须要总括贰个雾效周到f,作为混合原始颜色和雾的颜料的交集周到:

大局雾效

2)营造贰个富含3面墙的房间,放置三个立方和三个圆球。

图片 18

public class EdgeDetectNormalsAndDepth : PostEffectsBase {

    public Shader edgeDetectShader;
    private Material edgeDetectMaterial = null;
    public Material material {  
        get {
            edgeDetectMaterial = CheckShaderAndCreateMaterial(edgeDetectShader, edgeDetectMaterial);
            return edgeDetectMaterial;
        }  
    }

    [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;

    void OnEnable() {
        GetComponent<Camera>().depthTextureMode |= DepthTextureMode.DepthNormals;
    }

    //[ImageEffectOpaque] 让透明物体不被描边
    [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);
        }
    }
}

它的取值范围正是视锥体深度范围,即[Near,Far]。若是大家想要得到范围在[0,
1]以内的吃水值,只需求把上边获得的结果除以Far即可。那样,0就表示该点与摄像飞机地点于同一职位,1意味该点位于视锥体的远裁剪平面上。结果如下:

interpolatedRay
来源于对近裁剪平面包车型客车四个角的某些特定向量的插值,那5个向量包涵了它们到水墨画机的趋势和距离消息,大家得以选择录像机的近裁剪平面距离、FOV、横纵比计算而得。下图突显了计算时行使的一部分声援向量。

因而可得,我们必要的TL距离录像机的欧式距离dist:

图片 19

2)搭建二个测试活动模糊的现象,创设了二个带有3面墙的艺术,并放置了多少个立方。

荧屏后甩卖的规律是应用一定的材料去渲染2个刚好填充整个荧屏的四边形面片。这一个四边形面片的5个顶峰就对应了近裁剪平面包车型客车五个角。由此,大家得以把地点的盘算结果传递给顶点着色器,顶点着色器依照近年来的岗位选用它所对应的向量,然后再将其出口,经插值后传递给片元着色器获得interpolatedRay,大家就可以动用在此之前提到的公式重建该像素在世界空中下的地点了。

fixed3 normal = DecodeViewNormalStereo(tex2D(_CameraDepthNormalsTexture, i.uv).xy);
return fixed4(normal * 0.5 + 0.5, 1.0);
Shader "Unity Shaders Book/Chapter 13/Fog With Depth Texture" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        _FogDensity ("Fog Density", Float) = 1.0
        _FogColor ("Fog Color", Color) = (1, 1, 1, 1)
        _FogStart ("Fog Start", Float) = 0.0
        _FogEnd ("Fog End", Float) = 1.0
    }
    SubShader {
        CGINCLUDE

        #include "UnityCG.cginc"

        float4x4 _FrustumCornersRay;

        sampler2D _MainTex;
        half4 _MainTex_TexelSize;
        sampler2D _CameraDepthTexture;
        half _FogDensity;
        fixed4 _FogColor;
        float _FogStart;
        float _FogEnd;

        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 = mul(UNITY_MATRIX_MVP, 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;
        }

        fixed4 frag(v2f i) : SV_Target {
            float linearDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth));
            float3 worldPos = _WorldSpaceCameraPos + linearDepth * i.interpolatedRay.xyz;

            float fogDensity = (_FogEnd - worldPos.y) / (_FogEnd - _FogStart); 
            fogDensity = saturate(fogDensity * _FogDensity);

            fixed4 finalColor = tex2D(_MainTex, i.uv);
            finalColor.rgb = lerp(finalColor.rgb, _FogColor.rgb, fogDensity);

            return finalColor;
        }

        ENDCG

        Pass {
            ZTest Always Cull Off ZWrite Off

            CGPROGRAM  

            #pragma vertex vert  
            #pragma fragment frag  

            ENDCG  
        }
    } 
    FallBack Off
}

以此雾效全面f 有为数不少乘除办法。在Unity
内置的雾效实现中,帮助二种雾的计量办法——线性、指数以及指数的平方。当给定距离z后,f的总括公式分别如下:

Linear:

Exponential
Squared:

在Unity中,深度纹理能够直接来自于真正的纵深缓存,也得以是由1个独立的Pass渲染而得,那取决使用的渲染路径和硬件。平时来讲,当使用延缓渲染路径时,深度纹理理所当然能够访问到,因为延迟渲染会把这几个新闻渲染到G-buffer
中。而当不能直接得到深度缓存时,深度和法线纹理是因而3个单身的Pass渲染而得的。具体贯彻是,Unity会使用着色器替换技术选用这几个渲染类型为Opaque的实体,判断它们采纳的渲染队列是不是低于2500,即便知足条件,就把它渲染到深度和法线纹理中。由此,要想让实体能够产出在深度和法线纹理中,就亟须在Shader中安装科学的RenderType
标签。

在查阅深度纹理时,大家收获的画面恐怕差不离是全黑或全白的。这时我们得以把摄像机的远裁剪平面包车型客车离开(Unity暗许为1000)调小,使视锥体的限制恰恰覆盖场景的所在区域。那是因为,由于投影变换时须求覆盖从近裁剪平面到远裁剪平面包车型地铁有所深度区域,当远裁剪平面包车型客车离开过大时,会导致离录制机较近的偏离被映射到丰硕小的深度值,假诺场景是2个查封的区域,那么那就会造成画面看起来大致是全黑的。相反,尽管场景是3个怒放区域,且物体离录制机的偏离较远,就会造成画面差不离是全白的。

纵深纹理实际上正是一张渲染纹理,只然则它里面储存的像素值不是颜色值而是二个高精度的纵深值。由于被储存在一张纹理中,深度纹理里的深度值范围是[0,1],而且日常是非线性分布的。那么,这个深度值是从何地获得的啊?总体来说,这几个深度值来自于极端变换后取得的归一化的装备坐标(Normalized
Device Coordinates,
NDC)。2个模子要想最终被绘制在荧屏上,必要把它的巅峰从模型空间更换成齐次裁剪坐标系下,那是通过在终端着色器中乘以MVP变换矩阵获得的。在转移的结尾一步,我们必要使用一个投影矩阵来转换顶点,当大家利用的是看破投影类型的壁画机时,那几个投影矩阵正是非线性的。

camera.depthTextureMode |= DepthTextureMode.Depth;
camera.depthTextureMode |= DepthTextureMode.DepthNormals;

同理,即使想要获取深度+法线纹理,大家只须求在代码中那样设置:

Exponential:

笔者们利用罗Bert算子来展开边缘检查和测试。它选择的卷积核如下图所示。

为了在Unity中落到实处基于显示屏后处理的雾效,大家必要开始展览如下准备工作。

在Unity中,获取深度纹理是非凡简单的,我们只须求报告Unity“把深度纹理给本人!”然后再在Shader中央直机关接待上访问特定的纹理属性即可。那一个与Unity交换的进度是通过在剧本中装置摄像机的depthTextureMode来成功的,例如大家可以透过上面包车型客车代码来获取深度纹理:

下图显示了再使用正交录像机时投影变换的进程。同样变换后会获得贰个限制为[-1,1]的立方体。正交易投资影使用的变换矩阵是线性的。

图片 20

《GPU
Gems》在第一7章中介绍了一种生成速度映射图的办法。那种方法应用深度纹理在片元着色器中为每种像素总计其在世界空中下的岗位,那是透过动用当前的见地*投影矩阵的逆矩阵对NDC下的终点坐标实行转换获得的。当获得世界空中中的顶点坐标后,大家总括前一帧和如今帧的职位差,生成该像素的快慢。那种办法的有点是足以在三个显示屏后甩卖步骤中成功全数职能的固步自封,但缺点是急需在片元着色器中展开四回矩阵乘法的操作,对品质有所影响。

想要在深度值转换到到水墨画机的欧式距离也一点也不细略,大家以TL点为例,根据相似三角形原理,TL所在的射线上,像素的深度值和它到水墨画机的实在距离的比等于近裁剪平面包车型大巴偏离和TL向量的模的比,即

在那之中,d对应了深度纹理中的像素值,Z(ndc)对应了NDC坐标中的z分量的值。

转发自
冯乐乐的 《Unity Shader 入门精要》

尽管在Unity里获取深度和法线纹理的代码格外简单,不过大家有必不可少在那前面率先掌握它们背后的完毕原理。

笔者们选拔一种基于荧屏后处理的大局雾效的贯彻。使用那种格局,大家不须要更改场景内渲染的实体所利用的Shader
代码,而仅仅注重3回显示屏后甩卖的步骤即可。那种方法的自由度高,我们能够一本万利地模仿各样雾效,例如均匀的雾效、基于距离的线性/指数雾效、基于高度的雾效等。大家得以赢得近似下图中的结果。
图片 21

public class FogWithDepthTexture : PostEffectsBase {

    public Shader fogShader;
    private Material fogMaterial = null;

    public Material material {  
        get {
            fogMaterial = CheckShaderAndCreateMaterial(fogShader, fogMaterial);
            return fogMaterial;
        }  
    }

    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;
        }
    }

    //控制雾的浓度
    [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;

    void OnEnable() {
        camera.depthTextureMode |= DepthTextureMode.Depth;
    }

    void OnRenderImage (RenderTexture src, RenderTexture dest) {
        if (material != null) {
            Matrix4x4 frustumCorners = Matrix4x4.identity;
            //先计算近裁剪平面的四个角对应的向量
            float fov = camera.fieldOfView;
            float near = camera.nearClipPlane;
            float aspect = camera.aspect;

            float halfHeight = near * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
            Vector3 toRight = cameraTransform.right * halfHeight * aspect;
            Vector3 toTop = cameraTransform.up * halfHeight;

            Vector3 topLeft = cameraTransform.forward * near + toTop - toRight;
            float scale = topLeft.magnitude / near;

            topLeft.Normalize();
            topLeft *= scale;

            Vector3 topRight = cameraTransform.forward * near + toRight + toTop;
            topRight.Normalize();
            topRight *= scale;

            Vector3 bottomLeft = cameraTransform.forward * near - toTop - toRight;
            bottomLeft.Normalize();
            bottomLeft *= scale;

            Vector3 bottomRight = cameraTransform.forward * near + toRight - toTop;
            bottomRight.Normalize();
            bottomRight *= scale;
            //将4个向量存储在矩阵类型的frustumCorners 中
            frustumCorners.SetRow(0, bottomLeft);
            frustumCorners.SetRow(1, bottomRight);
            frustumCorners.SetRow(2, topRight);
            frustumCorners.SetRow(3, topLeft);

            material.SetMatrix("_FrustumCornersRay", frustumCorners);

            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);
        }
    }
}

再谈边缘检查和测试

然后在Shader中通过评释_CameraDepthNormalsTexture变量来做客它。

出于在Unity使用的理念空间中,摄像机正向对应的z值均为负值,因而为了得到深度值的正数表示,大家必要对地点的结果取反,最终获得的结果如下:

图片 22

DecodeDepth诺玛l
的第③个参数是对纵深+法线纹理的采集样品结果,那么些采集样品结果是Unity对纵深和法线消息编码后的结果,它的xy分量存储的是理念空间下的法线新闻,而深度音讯被编码进了zw分量。通过调用DecodeDepthNormal
函数对采集样品结果解码后,大家就足以获得解码后的深度值和法线。这些深度值是限量在[0,
1]的线性深度值(那与单身的吃水纹理中存款和储蓄的深浅值不一致),而获得的法线则是观点空间下的法线方向。同样,大家也能够由此调用DecodeFloat凯雷德G
和 DecodeViewNormalStereo来解码深度+法线纹理中的深度和法线新闻。

图片 23
其中,Far 和 Near
分别是远近裁剪平面包车型大巴距离。然后,大家透过齐次除法就足以拿走NDC下的z分量:

咱俩精通,坐标系中的二个巅峰坐标能够经过它相对于另贰个极端坐标的偏移量来求得。重建像素的世界坐标也是基于那样的思想。我们只需求明白录像机在世界空中下的地方,以及世界空中下该像素相对于摄像机的偏移量,把它们相加就能够博得该像素的世界坐标。整个经过能够运用下边包车型大巴代码来表示:

float d = SMAPLE_DEPTH_TEXTURE_PROJ(_CameraDepthTexture, UNITY_PROJ_COORD(i.srcPos));

图片 24
后面大家通晓,深度纹理中的深度值是
通过下边包车型地铁公式由NDC总括而得的:

图片 25

4)新建多少个Shader
Chapter13-MotionBlurWithDepthTexture

在Unity中,我们得以选用让3个水墨画机生成一张深度纹理或是一张深度+法线纹理。当渲染前者,即只需求一张单独的吃水纹理时,Unity会直接获取深度缓存或是按在此以前讲到的着色器替换技术,选拔须要的不透明物体,并行使它映射阴影时利用的Pass(即LightMode棉被服装置为ShadowCaster的Pass)来取得深度纹理。假诺Shader
中不包涵那样一个Pass,那么这么些物体就不会冒出在深度纹理中(当然,它也无法向其它实体投射阴影)。深度纹理的精度平日是2四位或13人,那有赖于使用的吃水缓存的精度。若是采纳生成一张深度+法线纹理,Unity会创立一张和显示器分辨率相同、精度为叁15人的(纹理),其中观看空间下的法线音讯会被编码进纹理的Odyssey和G通道,而深度新闻会被编码进B和A通道。法线信息的取得在延迟渲染中是能够格外简单就取得的,Unity只必要联合深度和法线缓存即可。而在前向渲染中,暗许情状下是不会成立法线缓存的,因而Unity底层使用了叁个独自的Pass把全副场景再一次渲染1次来完毕。这么些Pass被含有在Unity内置的2个Unity
Shader
中,大家得以在置放的builtin_shaders-xxx/DefaultResources/Camera-DepthNormalTexture.shader文件中找到那几个用于渲染深度和法线音信的Pass。

其中,_WorldSpaceCameraPos
是录像机在世界空中下的职位,那能够由Unity
的松手变量直接待上访问得到。而linearDepth * interpolatedRay
则可以测算获得该像素相对于录像机的偏移量,linearDepth
是由深度纹理获得的线性深度值,interpolatedRay
是由顶点着色器输出并插值后拿走的射线,它不光含有了该像素到摄像机的可行性,也带有了离开新闻。

大家先修改EdgeDetectNormalsAndDepth.cs
脚本

鉴于多少个点相互对称,由此别的3个向量的模和TL相等,即大家得以采取同3个因子和单位向量相乘,获得它们对应的向量值:

图片 26

里面,Near
是近裁剪平面的偏离,FOV是竖直方向的见识范围,camera.up、camera.right分别对应了录像机的正上方和正右方。当得到这多少个协理向量后,大家就能够测算四个角相对于录制机的势头了。我们以左上角为例,它的总括公式如下:

作者们运用类似线性雾的总括方法,总结基于中度的雾效。具体方法是,当给定一些在世界空中下的中度y后,f的计算公式为:

3)在摄像机上新建三个本子FogWithDepthTexture.cs。

大家还足以组成那几个情势,让3个雕塑机同时发生一张深度和纵深+法线纹理:

罗Berts
算子的本来面目便是总结左上角和右下角的插值,乘上右上角和左下角的差值,作为评估边缘的依照。在底下的落实中,大家也会按那样的措施,取对角方向的吃水或法线值,比较它们之间的差值,假设跨越有个别阈值,就觉得它们之间存在一条边。

float3 afterFog = f*fogColor + (1 - f) * origColor;

依据荧屏后甩卖的全局雾效的最首假如,依据深度纹理来重建各样像素在世界空中下的职位。即便事先大家在模仿运动模糊时早已落实了这么些须求,即营造出当下像素的NDC坐标,再通过当前录制机的眼光*投影矩阵的逆矩阵来获取世界空中下的像素坐标,不过,那样的完结须要在片元着色器中开始展览矩阵乘法的操作,而这一般会影响游戏品质。我们上学一个飞快从深度纹理中重建世界坐标的法子。那种方法首先对图像空间下的视锥体射线(从录制机出发,指向图像上的某点的射线)举行插值,那条射线存款和储蓄了该像素在世界空中下到摄像机的方向新闻。然后,我们把该射线和线性化后的见地空间下的纵深值相乘,再拉长录制机的世界地方,就足以获得该像素在世界空中下的岗位。当我们得到世界坐标后,就足以轻松地选拔各样公式来效仿全局雾效了。

再谈运动模糊

图片 27

洋洋时候,大家期望能够查看生成的纵深和法线纹理,以便对Shader实行调节。Unity
5
提供了一个有益的办法来查看摄像机生成的深浅和法线纹理,那么些主意正是运用帧调节和测试器。下图展现了帧调节和测试器查看到的纵深纹理和深度+法线纹理。

图片 28

1)新建二个场馆,去掉天空盒子。

4)新建三个Shader
 Chapter13-FogWithDepthTexture。

大家先编制MotionBlurWithDepthTexture.cs
脚本

内需注意的是,那里的兑现是基于录像机的投影类型是看破投影的前提下的。假诺需求在正交易投资影的情状下重建世界坐标,须要使用差别的公式。

图片 29

float depth = SMAPLE_DEPTH_TEXTURE(_CameraDepthTexture,i.uv);
float linearDepth = Linear01Depth(depth);
return fixed4(linearDepth,linearDepth,linearDepth,1.0);

大家完成的描边效果是依照整个显示器空间拓展的,也正是说,场景内拥有物体都会被添加描边效果。但有时,我们希望只对一定的实体实行描边,例如当玩家渲染场景中的有个别物体后,我们想要在该物体周围添加一层描边效果。那时,大家需求使用Unity提供的Graphics.DrawMesh
或 Graphics.DrawMeshNow
函数把须要描边的物体再一次渲染一回(在有着不透明物体渲染完毕后),然后再采纳本节提到的边缘检查和测试算法计算深度或法线纹理中各样像素的梯度值,判断它们是不是低于有个别阈值,假如是,就再Shader
中选拔clip函数将该像素剔除掉,从而呈现原来的实体颜色。

camera.depthTextureMode = DepthTextureMode.Depth;

3)摄像机上添加艾德geDetectNormalsAndDepth.cs
脚本

同理,别的二个角的一个钱打二十五个结也是类似的:

图片 30

4)新建三个Shader
Chapter13-艾德geDetect诺玛lAndDepth。

图片 31

Shader "Unity Shaders Book/Chapter 13/Motion Blur With Depth Texture" {
    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;

        struct v2f {
            float4 pos : SV_POSITION;
            half2 uv : TEXCOORD0;
            half2 uv_depth : TEXCOORD1;
        };

        v2f vert(appdata_img v) {
            v2f o;
            o.pos = mul(UNITY_MATRIX_MVP, 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 {
            // Get the depth buffer value at this pixel.
            float d = SAMPLE_DEPTH_TEXTURE(_CameraDepthTexture, i.uv_depth);
            // H is the viewport position at this pixel in the range -1 to 1.
            float4 H = float4(i.uv.x * 2 - 1, i.uv.y * 2 - 1, d * 2 - 1, 1);
            // Transform by the view-projection inverse.
            float4 D = mul(_CurrentViewProjectionInverseMatrix, H);
            // Divide by w to get the world position. 
            float4 worldPos = D / D.w;

            // Current viewport position 
            float4 currentPos = H;
            // Use the world position, and transform by the previous view-projection matrix.  
            float4 previousPos = mul(_PreviousViewProjectionMatrix, worldPos);
            // Convert to nonhomogeneous points [-1,1] by dividing by w.
            previousPos /= previousPos.w;

            // Use this frame's position and last frame's to compute the pixel velocity.
            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);
        }

        ENDCG

        Pass {      
            ZTest Always Cull Off ZWrite Off

            CGPROGRAM  

            #pragma vertex vert  
            #pragma fragment frag  

            ENDCG  
        }
    } 
    FallBack Off
}