技术美术百人计划 | 《3.1深度测试与模板测试 》笔记

前言

GPU的渲染流水线:

其中的逐片元操作详细流程:

像素所有权测试→裁剪测试→透明度测试→模板测试→深度测试→透明度混合

  • Pixel Ownership Test:
    • 简单来说就是控制当前屏幕像素的使用权限
    • e.g.Unity中的Game和Scene窗口才会显示画面,其他窗口没有使用权限
  • Scissor Test(裁剪测试):
    • 在渲染窗口再自定义要渲染哪一部分
    • 和裁剪空间一起理解,也就是只渲染能看到的部分
    • e.g.只渲染Unity Game窗口的左下角小地图部分
  • AlphaTest(透明度测试):
    • 提前设置一个透明度预值
    • 只能实现不透明效果和全透明效果
    • e.g. 例如设置透明度a为0.5,如果片元大于这个值就通过测试,如果小于0.5就剔除掉
  • Stencil Test(模板测试)
  • Depth Test(深度测试)
  • Blending(透明度混合):
    • 可以实现半透明效果

完成接下来的其他一系列操作后,我们会将合格的片元/像素输出到帧缓冲区(FrameBuffer)和屏幕上。

逐片元操作是说是由管线/硬件自身规定好的,我们只能对里边的内容进行配置。

1. 模板测试 Stencil Test

1.1. 引例

模板测试的一个例子:其中0不显示,为黑色;1显示。

其他例子:

链接:https://www.patreon.com/posts/14832618

每个正方体面显示不同场景(每个面都是一个蒙版,用来来显示不同的场景)

1.2. 伪代码

  • 理解:
    • referenceValue:当前模板缓冲片元的参考值
    • stencilBufferValue:模板缓冲区里的值
    • comparisonFunction:做一个比较
  • 结果:
    • 如果通过,这个片元就进入下一个阶段
    • 未通过/抛弃,停止并且不会进入下一个阶段,也就是说不会进入颜色缓冲区
  • 总结:就是通过一定条件来判断这个片元/片元属性执行保留还是抛弃的操作

1.3. 定义

模板缓冲区-FrameBuffer

  • 模板缓冲区可以为屏幕上的每一个像素点保存一个无符号整数值(通常为8位int 0-255)。这个值的意义根据程序的具体应用而定。

模板测试

  • 渲染过程中,可以用这个值与预先设定好的参考值作(ReferenceValue)比较,根据结果来决定是否更新相应的像素点的颜色值。这个比较的过程就称为模板测试。
  • 执行顺序:透明度测试->模板测试->深度测试。如果模板测试通过,相应的像素点更新,否则不更新。

1.4. 代码实现

stencil
{
  Ref referenceValue//当前片元的参考值(0-255)
  ReadMask readMask//读掩码
  WriteMask writeMask//写掩码
  Comp comprisonFunction//比较操作函数
  Pass stencilOperation//测试通过之后进行操作
  Fail stencilOperation//测试未通过进行的操作
  ZFail stencilOperation//模板测试通过,深度测试未通过执行的操作
}

ComprisonFunction

Greater

相当于“>”操作,即仅当左边>右边,模板测试通过,渲染像素

GEqual

相当于“>=”操作,即仅当左边>=右边,模板测试通过,渲染像素

Less

相当于“<”操作,即仅当左边<右边,模板测试通过,渲染像素

LEqual

相当于“<=”操作,即仅当左边<=右边,模板测试通过,渲染像素

Equal

相当于“=”操作,即仅当左边=右边,模板测试通过,渲染像素

NotEqual

相当于“!=”操作,即仅当左边!=右边,模板测试通过,渲染像素

Always

不管公式两边为何值,模板测试总是通过,渲染像素

Never

不论公式两边为何值,模板测试总是失败 ,像素被抛弃

StencilOperation

Keep

保留当前缓冲中的内容,即stencilBufferValue不变。

Zero

将0写入缓冲,即stencilBufferValue值变为0。

Replace

将参考值写入缓冲,即将referenceValue赋值给stencilBufferValue。

IncrSat

stencilBufferValue加1,如果stencilBufferValue超过255了,那么保留为255,即不大于255。

DecrSat

stencilBufferValue减1,如果stencilBufferValue超过为0,那么保留为0,即不小于0。

Invert

将当前模板缓冲值(stencilBufferValue)按位取反

IncrWrap

当前缓冲的值加1,如果缓冲值超过255了,那么变成0,(然后继续自增)。

DecrWrap

当前缓冲的值减1,如果缓冲值已经为0,那么变成255,(然后继续自减) 。

1.5. 案例

1.5.1. 案例一

透视卡片所用Shader:

//模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1

Shader "Custom/StencilMask"
{
    Properties
    {
        _ID("Mask ID",int)=1
    }
    SubShader
    {
        //覆盖了所有的像素,在所有物体的后面被渲染,队列设置为 "Geometry+1 "
        Tags{"RenderType"="Opaque""Queue"="Geometry+1"}

        //颜色遮罩,0就是什么都不输出
        //可以改为RGBA(没有遮罩)、输出单通道R、G、B
        ColorMask 0
        ZWrite off 

        //模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1
        Stencil
        {
            Ref[_ID]//当前片元的参考值(0-255)
            Comp Always//比较操作函数
            Pass replace//测试通过之后将参考值写入缓冲
        }

        Pass 
        {
            //CGINCLUDE代码块会被插入到每一个Pass中,
            //因此我们可以把Pass中的公共的字段,结构和方法等声明在CGINCLUDE代码块中。
            CGINCLUDE

            struct a2v 
            {
                float4 vertex:POSITION;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);

                return o;
            }

            fixed4 frag(v2f i):SV_Target 
            {
                return fixed4(1,1,1,1);
            }

            ENDCG

        }

    }
    FallBack "Diffuse"
}

卡片中内容所用Shader:

//模板测试:以ID作为参考值,仅当左边=右边,模板测试通过

Shader "Custom/LitStencilMask"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Ramp("Toon Ramp(RGB)",2D)="white"{}//漫反射色调的渐变纹理
        _ID("Mask ID",Int)=1
    }
    SubShader
    {
        //在StencilMask.shader之后被渲染
        Tags{"RenderType"="Opaque""Queue"="Geometry+2"}
        LOD 200 

        //模板测试:以ID作为参考值,仅当左边=右边,模板测试通过
        Stencil 
        {
            Ref [_ID]
            Comp equal//仅当左边=右边,模板测试通过,渲染像素
        }

        CGPROGRAM
        #pragma surface surf ToonRamp
        #pragma Lighting ToonRamp exclude_path:forward 

        fixed4 _Color;
        sampler2D _MainTex;
        sampler2D _Ramp;
        int _ID;

        //表面函数的输入结构体
        struct Input 
        {
            float2 uv:TEXCOORD0;
        };

        //表面函数
        //表面函数定义了表面属性,一个对象的表面属性定义了它的反射率、光滑度、透明度等值。
        void surf(Input IN,inout SurfaceOutput o)
        {
            half4 c=tex2D(_MainTex,IN.uv)*_Color;
            o.Albedo=c.rgb;
            o.Alpha=c.a;
        }

        //卡通渲染漫反射光照函数
        //光照函数会使用表面函数中设置的各种表面属性,来应用某些光照模型,进而模拟物体表面的光照效果。
        inline half4 LightingToonRamp(SurfaceOutput s,half3 lightDir,half atten)
        {
            #ifdef USING_DIRECTIONAL_LIGHT
                lightDir=normalize(lightDir);
            #endif

            half d=dot(s.Normal,lightDir)*0.5+0.5;//从向量映射到颜色uv
            half3 rampColor=tex2D(_Ramp,float2(d,d)).rgb;//漫反射采样颜色

            half4 c;
            c.rgb=s.Albedo*_LightColor0.rgb*rampColor*atten*2;//albedo反照率,atten光衰
            c.a=0;

            return c;
        }

        ENDCG

    }
    FallBack "Diffuse"
} 

实现效果:

案例的效果:

  • Unity的模板缓冲区的默认值是0,默认所有物体在应用shader之前都是0。
  • 渲染蒙版(mask),ID给了1,并且Pass的设置为replace,也就是说mask所在的模板缓冲区的值变成了1。因此mask外的ID值是0,mask的值是1。
  • 渲染物体:场景中的物体ID给了1,只有ID相等的时候才渲染,所以只渲染Mask内所看到的部分。

1.5.2. 案例二

  1. 透视Quad所用Shader:

//模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1

Shader "Custom/StencilWindow"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _ID("Mask ID",int)=1

        [Enum(UnityEngine.Rendering.CompareFunction)] _SComp("Stencil Comp",Float)=8//8种比较操作函数
        [Enum(UnityEngine.Rendering.StencilOp)] _SOp("Stencil Op",Float)=2
    }

    SubShader
    {
        //覆盖了所有的像素,在所有物体的后面被渲染,队列设置为 "Geometry+1 "
        Tags{"RenderType"="Opaque""Queue"="Geometry+1"}

        //颜色遮罩,0就是什么都不输出
        //可以改为RGBA(没有遮罩)、输出单通道R、G、B
        ColorMask 0
        ZWrite off 

        //模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1
        Stencil
        {
            Ref[_ID]//当前片元的参考值(0-255)为_ID
            Comp[_SComp]//比较操作函数
            Pass[_SOp]//Mask测试通过之后将参考值_SOp写入缓冲
        }

        Pass 
        {
            //CGINCLUDE代码块会被插入到每一个Pass中,
            //因此我们可以把Pass中的公共的字段,结构和方法等声明在CGINCLUDE代码块中。
            CGINCLUDE

            struct a2v 
            {
                float4 vertex:POSITION;
            };

            struct v2f
            {
                float4 pos:SV_POSITION;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);

                return o;
            }

            fixed4 frag(v2f i):SV_Target 
            {
                return fixed4(1,1,1,1);
            }

            ENDCG

        }

    }
    FallBack "Diffuse"
}

  1. 物体所用Shader:

//模板测试:以ID作为参考值,仅当左边=右边,模板测试通过

Shader "Custom/BumpDiffuseStencil"
{
  Properties 
  {
    _Color ("Main Color", Color) = (1,1,1,1)
      _MainTex ("Base (RGB)", 2D) = "white" {}
    _BumpMap ("Normalmap", 2D) = "bump" {}
    _ID("Mask ID",int)=1

      [Enum(UnityEngine.Rendering.CompareFunction)] _SComp("Stencil Comp",Float)=3
      [Enum(UnityEngine.Rendering.StencilOp)] _SOp("Stencil Op",Float)=0
    }
  SubShader 
  {
    Tags { "RenderType"="Opaque""Queue"="Geometry+2" }
    LOD 300


      //模板测试:以ID作为参考值,且总是通过测试,测试通过就替换为1
      Stencil
    {
      Ref[_ID]//当前片元的参考值(0-255)为_ID
        Comp[_SComp]//比较操作函数
        Pass[_SOp]//Mask测试通过之后将参考值_SOp写入缓冲
      }		

    CGPROGRAM
      #pragma surface surf Lambert
      #pragma target 3.0

      sampler2D _MainTex;
    sampler2D _BumpMap;
    fixed4 _Color;
    int _ID;
    //Float _SComp;
    //Float _SOp;

    struct Input 
    {
      float2 uv_MainTex;
      float2 uv_BumpMap;
    };

    void surf (Input IN, inout SurfaceOutput o) 
    {
      fixed4 tex = tex2D(_MainTex, IN.uv_MainTex);
      o.Albedo = tex.rgb * _Color.rgb;
      o.Alpha = tex.a * _Color.a;
      o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
    }

    ENDCG
    } 

  FallBack "Legacy Shaders/Diffuse"
  }

每个Quad和物体对应的ID不一样

实现效果:

1.6. 总结

  • 使用模板缓冲区最重要的两个值:当前模板缓冲值(stencilBufferValue)和模板参考值(referenceValue)
  • 模板测试主要就是对这两个值使用特定的比较操作:Never,Always,Less ,LEqual,Greater,Equal等等
  • 模板测试之后要对模板缓冲区的值(stencilBufferValue)进行更新操作,更新操作包括:Keep,Zero,Replace,IncrSat,DecrSat,Invert等等
  • 模板测试之后可以根据结果对模板缓冲区做不同的更新操作,比如模板测试成功操作Pass,模板测试失败操作Fail,深度测试失败操作ZFail,还有正对正面和背面精确更新操作PassBack,PassFront,FailBack等等

其他可实现效果:

参考资料:

2. 深度测试

判断场景中物体之间的遮挡关系

例如以下由深度测试所实现的效果:

2.1. 伪代码

和模板测试的异同:

都要比较值,但是深度测试要额外写入颜色

2.2. 一些定义

2.2.1. 深度测试ZTest、深度缓冲Z-Buffer和深度写入

深度测试ZTest

所谓深度测试ZTest,就是针对当前对象在屏幕上(更准确的说是frame buffer)对应的像素点,将对象自身的深度值与当前该像素点缓存的深度值进行比较,如果通过了,本对象在该像素点才会将颜色写入颜色缓冲区,否则不会写入颜色缓冲。

ZTest状态

描述

Greater

深度大于当前缓存则通过

LEqual

深度小于等于当前缓存则通过

Less

深度小于当前缓存则通过

GEqual

深度大于等于当前缓存则通过

Equal

深度等于当前缓存则通过

NotEqual

深度不等于当前缓存则通过

Always(Off)

深度不论如何都通过

Never

深度不论如何都不通过

深度缓冲z-buffer

深度缓冲z-buffer就像颜色缓冲(储存所有的片段颜色:视觉输出)一样,在每个片段中储存了信息,存储的是当前的深度信息,对于每个像素存储一个深度值。并且通常和颜色缓冲有着一样的宽度和高度。深度缓冲是由窗口系统自动创建的,它会以16、24或32位float的形式储存它的深度值。在大部分的系统中,深度缓冲的精度都是24位的。

深度写入ZWrite

深度写入ZWrite包括两种状态:ZWrite On与ZWrite Off,开启和关闭

  • ZWrite On:当我们开启深度写入的时候,物体被渲染时针对物体在屏幕(更准确地说是frame buffer)上每个像素的深度都写入到深度缓冲区
  • ZWrite Off:物体的深度就不会写入深度缓冲区。但是,物体是否会写入深度,除了ZWrite这个状态之外,更重要的是需要深度测试通过,也就是ZTest通过,如果ZTest都没通过,那么也就不会写入深度了

深度测试ZTest分为通过和不通过两种情况,ZWrite分为开启和关闭两种情况的话,一共就是四种情况:

默认情况下

  • Z Write:On
  • Z Test:LEqual
  • Z Buffer:无穷大

2.2.2. 渲染队列Queue

Unity内置的几种渲染队列,按照渲染顺序从先到后排序,队列数越小,越先渲染。

Unity中设置渲染队列语法:

Tags { “Queue” = “渲染队列名”}
  • Unity中不透明物体的渲染顺序:从前往后
  • Unity中透明物体的渲染顺序:从后往前(类似画家算法,会造成OverDraw)

2.2.3. Early-Z技术

其中的逐片元操作详细流程:

在传统的渲染管线中,ZTest是在逐片元操作的Blending阶段进行的。其中的一部分像素不会通过ZTest,它们仍然在进行深度测试之前还需要执行前面的一系列操作,这会造成性能的浪费,而Over Draw也是这么产生的。

因此现代GPU中运用了Early-Z的技术,大致的渲染管线可以描述成:

  • 应用阶段(CPU)->几何阶段(顶点着色器)->early-z(提前深度测试)->光栅化阶段(片元着色器)->各种测试(深度测试,透明度测试,模板测试等)->颜色缓冲区(buffer)

如果Early-Z深度测试失败,就不必进行fragment阶段的计算了,因此在性能上会有很大的提升。

但是最终的ZTest仍然需要进行,以保证最终的遮挡关系结果正确。

  • 前面的一次主要是Z-Cull,为了裁剪以达到优化的目的
  • 后一次主要是Z-Check,为了检查,如下图:

2.2.4. 深度值

在深度缓冲区中包含了深度值,它介于0.01.0之间。而在视图空间中, z 值可以是投影的近平面和远平面之间的任何值。因此我们需要一些方法将这些视图空间的 z 值转换到 [0,1] 的范围内,方法之一就是线性转换 :

下图给出 z 值和其相应的深度值的关系:

然而,在实践中是几乎从来不使用这样的线性转换,而是使用了非线性的映射方法:

正确的投影特性的非线性深度方程是和1/z成正比、和z成反比的 。这样在z很小的时候是高精度(深度值变化快), z 很大的时候是低精度(深度值变化小)。

由于非线性函数是和 1/z 成正比,例如1.0 到 2.0 之间的 z 值,深度值将变为 0 到 0.5之间, 这样在z非常小的时候给了我们很高的精度。而 当z值越大,深度值变化越小,这正是我们想要的。

2.2.5. 深度冲突 Z-fighting

如果两个平面或三角形距离非常近且相互平行,那么深度缓冲区不具有足够的精度以至于无法得到哪一个更加靠前。这会导致两个平面不断切换顺序导致显示出问题。这种情况被称为深度冲突(Z-fighting)

防止深度冲突

  • 让物体之间不要离得太近,以至于他们的三角形重叠。
  • 尽可能把近平面设置得远一些,这样可以提高精度。然而把近平面移动的太远会导致近处的物体被裁剪掉。所以不断调整测试近平面的值,为场景找出最好的近平面的距离。
  • 放弃一些性能来得到更高的深度值的精度。大多数的深度缓冲区都是24位。但现在显卡支持32位深度值,这让深度缓冲区的精度提高了一大节。

2.3. 案例

2.3.1. 案例一

Shader "Custom/ZTest"
{
    Properties
    {
        [HideInInspector]_MainTex("Texture",2D)="white"{}
        _Color("Color",Color)=(1,1,1,1)
        [Enum(off,0,On,1)]_ZWriteMode("ZWrite Mode",Float)=1
        [Enum(UnityEngine.Rendering.CompareFunction)]_ZComp("ZTest Comp",Float)=4
    }
    SubShader
    {
        Pass 
        {
            Tags{"RenderType"="Opaque""Queue"="Geometry"}
            ZWrite[_ZWriteMode]
            ZTest[_ZComp]

            Cull Off 
            CGPROGRAM
             
            #pragma vertex vert 
            #pragma fragment frag 
            #pragma multi_compile_fog 

            #include "UnityCG.cginc"

            sampler2D _MainTex;
            fixed4 _Color;
            float4 _MainTex_ST;

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

            struct v2f 
            {
                float4 pos:SV_POSITION;
                float2 texcoord:TEXCOORD0;
                UNITY_FOG_COORDS(1)
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos=UnityObjectToClipPos(v.vertex);
                o.texcoord=TRANSFORM_TEX(v.uv,_MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);

                return o;
            }

            fixed4 frag(v2f i):SV_Target 
            {
                fixed4 col=_Color;
                UNITY_APPLY_FOG(i.fogCoord,col);
                return col;
            }


            ENDCG
        }
    }
    FallBack "Diffuse"
}

渲染顺序

对于不透明物体,且Render Queue相等,那么从前往后渲染

ZTest为小于等于,那么深度值越小(越靠近摄像机)则通过测试

  • 梳理渲染过程:
    • 没渲染时,此时Unity的深度缓冲区默认值为无穷大
    • 渲染绿色正方体:相对于默认深度缓冲区的无穷大,肯定是小于等于,所以测试通过
    • 渲染蓝色正方体
      • 蓝色正方体进行深度测试,深度测试同样是LessEqual,并且蓝色的深度值比绿色正方体的大。
      • 结果就是:两个正方体重叠部分是大于深度缓冲区的,也就是测试不通过,所以重叠部分没有写入绿色,还是蓝色的。没有重叠部分,深度当然比无穷大小,所以写入, 渲染出来了绿色正方体未重叠的部分。
    • 红色同理。


关闭绿色正方体的深度写入,即使测试通过,但是并没有写入深度。也就是说,渲染完蓝色正方体时,深度缓冲区的值还是无穷大。而蓝色正方体的深度值小于无穷大,所以先渲染。



将红色也改为总是通过,但因为是从前往后渲染,所以应该如下


蓝色正方体只在大于绿色正方体部分的才通过测试

2.3.2. 案例二

Shader "Custom/XRay"
{
    Properties
    {
        _OutlineColor ("Outline Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
    }
    SubShader
    {
        CGINCLUDE
        #include "UnityCG.cginc"

        fixed4 _OutlineColor;
        sampler2D _MainTex;
        float4 _MainTex_ST;

        struct a2v 
        {
            float4 vertex:POSITION;
            float4 texcoord:TEXCOORD;
            float3 normal: NORMAL;
            
        };

        struct v2f 
        {
            float4 pos:SV_POSITION;
            float3 normal:TEXCOORD0;
            float3 viewDir:TEXCOORD1;
            fixed4 color:COLOR;
        };

        //1. 绘制外轮廓
        v2f vertXRay(a2v v)
        {
            v2f o;
            o.pos=UnityObjectToClipPos(v.vertex);
            o.normal=v.normal;
            o.viewDir=ObjSpaceViewDir(v.vertex);

            float3 normal=normalize(v.normal);
            float3 viewDir=normalize(o.viewDir);
            float rim=1-dot(normal,viewDir);//外轮廓

            o.color=_OutlineColor*rim;
            return o;
        }

        fixed4 fragXRay(v2f i):SV_Target 
        {
            return i.color;
        }

        ///
        struct v2f2
        {
            float4 pos:SV_POSITION;
            float2 uv:TEXCOORD0;
        };

        //2. 正常绘制
        v2f2 vertNormal(a2v v)
        {
            v2f2 o;
            o.pos=UnityObjectToClipPos(v.vertex);
            o.uv=TRANSFORM_TEX(v.texcoord,_MainTex);

            return o;
        }

        fixed4 fragNormal(v2f2 i):SV_Target 
        {
            return tex2D(_MainTex,i.uv);
        }

        ENDCG

        /
        //绘制外轮廓
        Pass 
        {
            Tags{"RenderType"="Transparent" "Queue"="Transparent"}
            Blend SrcAlpha One 
            ZTest Greater//核心:当处于其他物体后面时绘制外轮廓
            Zwrite Off 
            Cull Back
            
            CGPROGRAM
            #pragma vertex vertXRay
            #pragma fragment fragXRay

            ENDCG
        }

        //正常绘制
        Pass 
        {
            Tags{"RenderType"="Opaque"}
            ZTest LEqual//正常遮挡关系
            Zwrite On 

            CGPROGRAM
            #pragma vertex vertNormal
            #pragma fragment fragNormal

            ENDCG                
        }
    }
    FallBack "Diffuse"
}

2.3.3. 案例三

Shader "Unlit/ParticleZTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        //对默认Unlit Shader做以下修改
        Tags { "RenderType"="Opaque""Queue"="Transparent" }//渲染队列改为透明物体的渲染队列
        LOD 100
        ZWrite Off//无遮挡,对于透明物体,是有相互叠加关系的,所以关掉写入
        ZTest LEqual //如果透明物体前有不透明物体,此时透明物体看不到;如果透明物体后面有不透明物体,此时透明物体可以看到。
        Blend One One//加法混合,叠加效果的显示

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog

            #include "UnityCG.cginc"

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

            struct v2f
            {
                float2 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
                fixed4 col = tex2D(_MainTex, i.uv);
                // apply fog
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

----->

另一个弹簧粒子动画代码,不知道为什么没有效果

Shader "Custom/ParticleAnimation"
{
    Properties
    {        
        _MainTex ("Texture", 2D) = "white" {}
        _TimeOffset("Noise Offset",Range(0,100))=0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque""Queue"="Transparent" }
        LOD 100
        Blend One One
        ZWrite Off//无遮挡

        Pass 
        {
            CGPROGRAM

            #pragma vertex vert 
            #pragma fragment frag 
            #include "UnityCG.cginc"

            //实现模糊效果
            #pragma multi_compile_fog   

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _TimeOffset;

            struct a2v 
            {
                float4 vertex:POSITION;
                fixed4 color:COLOR;
                float3 uv:TEXCOORD0;
            };

            struct v2f 
            {
                float4 pos:SV_POSITION;
                float3 texcoord:TEXCOORD0;
                UNITY_FOG_COORDS(1)
                fixed4 color:COLOR;
            };

            v2f vert(a2v v)
            {
                v2f o;

                //动画定义
                float sineFrequency=5.0;
                float sineAmplitude=4.0;
                float time=_Time.y+_TimeOffset;
                float sineOffset=sin(time*sineFrequency)*sineAmplitude;
                float agePercent=v.uv.z;
                float3 vertexOffset=float3(0,sineOffset*agePercent,0);

                v.vertex.xyz+=vertexOffset;                
                o.pos=UnityObjectToClipPos(v.vertex);

                o.color=v.color;
                o.texcoord.xy=TRANSFORM_TEX(v.uv,_MainTex);
                o.texcoord.z=v.uv.z;

                UNITY_TRANSFER_FOG(o,o.pos);
                return o;            
            }

            fixed4 frag(v2f i):SV_Target 
            {
                fixed4 col=tex2D(_MainTex,i.texcoord);//纹理颜色
                col*=i.color;//和粒子颜色相乘
                float particleAgePercent=i.texcoord.z;
                fixed4 colorRed=fixed4(1,0,0,1);
                col=lerp(col,colorRed*col.a,particleAgePercent);//从纹理颜色插值为红色
                UNITY_APPLY_FOG(i.fogCoord,col);//应用模糊效果

                return col;
            }

            ENDCG
        }        
    }    
}

2.4 总结

  • 最重要的两个值:当前深度缓冲区的值(ZBufferValue)和深度参考值(ReferenceValue)。通过比较操作还实现理想的渲染效果
  • Unity中的渲染顺序:先渲染不透明物体(从前到后),再渲染透明物体(从后往前)
  • Unity中的默认条件:
    • ZWrite:On
    • Ztest:LessEqual
    • 渲染队列:Geometry(2000)
  • 通过对ZWrite和ZTest的相互组合配置来控制半透明物体的渲染(关闭深度写入,快开启深度测试,透明度混合)
  • 引入Early-Z之后深度测试相关的内容(Z-Cull、Z-Check)
  • 深度缓冲区中存储的深度值为[0,1]的非线性值

2.5. 深度测试的扩展用法

  • 基于深度的着色,如:湖水的效果
  • 阴影贴图(shadowma),比较摄像机空间和灯光空间的深度值得到阴影范围
  • 透明物体、粒子渲染
  • 透视X-Ray效果
  • 切边效果

参考资料:

3. 作业

待补充...

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值