Unity Shader入门精要 第8章 透明效果 读书笔记

第8章 透明效果

注意:图片的来源基本来自作者冯乐乐的GitHub,感谢作者分享

https://github.com/candycat1992/Unity_Shaders_Book

 

在实时渲染中实现透明效果:在渲染模型识控制模型的透明通道(Alpha Channel)

 

当开启透明混合后,当一个物体被渲染到屏幕上时,每个片元除了颜色值和深度值之外,还有透明度属性。透明度为1,完全不透明,透明度为0,完全透明不显示。

 

实现透明效果的两种方式:

1、透明度测试(Alpha Test):注意,该方法无法得到真正的半透明效果

2、透明度混合(Alpha Blending):真正的半透明效果

 

渲染顺序:

当场景中包含很多模型时,不透明(opaque)的物体,因为深度缓冲(depth buffer,z-buffer)的存在,即使不考虑渲染顺序也可以得到正确的排序效果。

 

在实时渲染中,深度缓冲用于解决可见性的问题,决定了哪个物体的哪些部分会被渲染在前面,而哪些部分会被其他物体遮挡。

深度缓冲的基本思想:在开启深度测试的前提下,会根据深度缓冲中的值判断该片元距离摄像机的距离,当渲染一个片元时,需要把深度值和已经存在于深度缓冲中的值进行比较,如果距离摄像机更远被其他物体挡住了,说明这个片元不应该被渲染在屏幕上,否则,在开启深度写入的前提下,这个片元会覆盖掉此时颜色缓冲中的像素值,并且将这个片元的深度值更新到深度缓冲中

使用了深度缓冲后,开发者不需要关心不透明物体的渲染顺序,例如A挡住B,即使先渲染A再渲染B也不会造成B遮盖掉A。因为深度测试的时候会判断B距离摄像机更远,也就不会写入到颜色缓冲中。

 

1、透明度测试(Alpha Test):“霸道极端”的机制。只要有一个片元的透明度不满足条件(小于某个阈值),那么对应的片元就会被舍弃。被舍弃的片元不会再进行任何处理,也不会对颜色缓冲产生任何影响,没有被舍弃的片元会按照普通的不透明物体的处理方式对片元进行深度测试、深度写入等处理方式。

透明度测试是不需要关闭深度写入的。

和其他不透明物体最大的不同是会根据透明度舍弃片元。

产生的效果极端,完全透明或者完全不透明。

 

2、透明度混合(Alpha Blending):

真正的半透明效果

使用当前片元的透明度作为混合因子,与已经存在颜色缓冲中的颜色值进行混合,得到新的颜色。

透明效果开启透明度混合的时候,不需要关闭深度测试,需要关闭 深度写入(ZWrite):如果不关闭深度写入,一个半透明表面背后的表面本来是应该可以透过半透明被看到,但是如果开启了深度写入后,深度测试的时候判断结果是该半透明表面距离摄像机更近,导致半透明表面后面的表面被剔除,也就无法透过半透明表面看到后面的物体了。但是因为关闭了深度写入(ZWrite),这也就非常无奈的破坏了深度缓冲的工作机制,这也就导致渲染顺序变得非常重要。

在关闭深度写入的情况下,就需要小心物体的渲染顺序

因为没有关闭深度测试,因此使用透明度混合渲染一个片元时,还是会比较片元的深度值与当前深度缓冲中的深度值,如果这个片元的深度值距离摄像机更远,那么就不会再进行混合操作,因此当一个不透明的物体出现在一个透明物体的前面的时候,假如先渲染了不透明物体,那么仍然可以正常地遮挡住透明物体,对于透明度混合来说,深度缓冲是只读的

 

渲染顺序:

透明度混合(Alpha Blending)需要关闭深度写入,需要小心处理透明物体的渲染顺序。

 

 

半透明物体A 和 不透明物体B:

Unity Shader入门精要 第8章 透明效果 读书笔记_第1张图片

 

不透明物体A 和 不透明物体B:

Unity Shader入门精要 第8章 透明效果 读书笔记_第2张图片

 

 

渲染引擎一般都会对物体进行排序,再渲染:

1、先渲染所有不透明的物体,并且开启这些不透明物体的深度测试和深度写入

2、再把半透明物体按照距离摄像机远近进行排序,越远的越先渲染,按照从后往前的顺序渲染半透明物体,并且开启半透明物体的深度测试,但是关闭半透明物体的深度写入

 

注意:深度缓冲中的值是像素级别的,每个像素有一个深度值。如果对单个物体级别进行排序,就会变成了物体A全部在物体B前面渲染,或者物体B全部在物体A前面渲染。如果出现 半透明物体A 和 半透明物体B 循环重叠的情况。那么就无法得到正确的结果。

如:3个循环重叠的半透明物体总是无法得到正确的排序顺序,无法得到半透明效果:

Unity Shader入门精要 第8章 透明效果 读书笔记_第3张图片

 

解决方法:将物体拆分成两个部分,再进行正确的排序。

但是这种拆分多个部分的方式,会出现另外一种情况:使用哪个深度对物体进行排序。

一个物体的网格结构占据了空间中的某一块区域,这个网格上的每一个点的深度值可能都是不一样的,选择哪个深度值作为整个物体的深度值和其他物体进行排序。

红色点分别标明了网格上距离摄像机最近的点、最远的点以及网格中点。这里无论是哪个深度值都会错误,排序结果总是A在B前面,但是因为实际上A有一部分被B遮挡,一旦选定判断方式后,某些情况半透明物体之间一定会出现错误的遮挡问题。

解决遮挡问题:

1、分割网格。让模型尽可能是凸面体,并且将复杂的模型拆分成可以独立排序的多个子模型等。

2、让透明通道更加柔和,穿插看起来不会更明显

3、使用开启深度写入的半透明效果近似模拟物体的半透明

Unity Shader入门精要 第8章 透明效果 读书笔记_第4张图片

 

Unity如何解决渲染顺序的问题:渲染队列(render queue):

SubShader 的 Queue 标签

Unity 在内部使用 整数索引 表示每个渲染队列,索引号越小就表示越早被渲染

5个渲染队列,在每个队列中间可以使用其他队列

Unity Shader入门精要 第8章 透明效果 读书笔记_第5张图片

 

SubShader {

      //透明度测试实现透明效果

      Tags { "Queue" = "AlphaTest" }

      Pass {

           //关闭深度写入

            ZWrite Off

            ...

      }

}

 

ZWrite Off 关闭深度写入,可以写在Pass中,如果可以写在SubShader中则表示这个SubShader下的所有的Pass都会关闭深度写入

 

 

 

透明度测试

只要一个片元的透明度不满足条件(小于某个阈值),那么对应的片元会被舍弃。被舍弃的片元不会再进行任何处理,也不会对颜色缓冲产生任何影响,否则就会按照普通的不透明物体的处理方式来处理它。

在处理透明度测试的时候实际上跟对待普通的不透明物体几乎是一样的,只是在片元着色器中增加了对透明度判断并且裁剪片元的代码。

片元着色器:clip裁剪函数 进行透明度测试。

clip:CG函数

 

void clip(float4 x)

{

      if(any(x<0))

            discard;

}

 

透明度测试无法实现真正的半透明效果

 

Unity Shader入门精要 第8章 透明效果 读书笔记_第6张图片

 

 

Properties {

      _Color ("Color Tint", Color) = (1, 1, 1, 1)

      _MainTex ("Main Tex", 2D) = "white" {}

      _Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5

}

 

_Cutoff:范围在 [0,1] 之间,在材质面板中控制透明度使用的阈值,纹理像素的透明度就是在 [0,1] 之间。决定调用 clip 进行透明度测试时使用的判断条件。

 

SubShader {

      Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}

      Pass {

            Tags { "LightMode"="ForwardBase" }

 

一般 透明度测试 的 Shader 都需要在 SubShader 中设置 三个标签:

1、Queue 标签:Unity中透明度测试使用的渲染队列:AlphaTest 的队列

2、RenderType 标签:Unity把这个Shader归入到提前定义的组:TransparentCutout,表明这个 Shader 是一个使用了透明度测试的 Shader。通常被用于着色器替换功能。

3、IgnoreProjector 标签:true 表示这个 Shader 不会受到投影器(Projectors)的影响。

 

和属性类型相匹配的变量:

fixed4 _Color;

sampler2D _MainTex;

float4 _MainTex_ST;

//_Cutoff的范围在 [0,1],使用 fixed 精度存储即可

fixed _Cutoff;

 

顶点着色器:

1、计算出世界空间的法线方向

2、计算出世界空间的顶点位置

3、计算出世界空间的变换后的纹理坐标

再将这些数据传递给片元着色器

v2f vert(a2v v) {

      v2f o;

      o.pos = UnityObjectToClipPos(v.vertex);

      o.worldNormal = UnityObjectToWorldNormal(v.normal);

      o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

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

      return o;

}

 

片元着色器:

clip:裁剪函数。透明度测试

fixed4 frag(v2f i) : SV_Target {

      fixed3 worldNormal = normalize(i.worldNormal);

      fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

      // 纹理采样

      fixed4 texColor = tex2D(_MainTex, i.uv);

                

      // Alpha test

      clip (texColor.a - _Cutoff);

      // Equal to

      // clip:判断参数 (texColor.a - _Cutoff) 为负数会舍弃片元输出

      // 即:texColor.a 小于材质参数 _Cutoff 会产生完全透明的效果

      //              if ((texColor.a - _Cutoff) < 0.0) {

      // discard 显式剔除片元

      //                  discard;

      //              }

 

      // 反射率

      fixed3 albedo = texColor.rgb * _Color.rgb;

      // 环境光照(rgb)

      fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

      // 漫反射光照(rgb)

      fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

      //fixed4:rgba

      return fixed4(ambient + diffuse, 1.0);

}

 

保证使用透明度测试的物体可以正确地向其他物体投射阴影:

FallBack "Transparent/Cutout/VertexLit"

 

 

透明度混合

更加柔滑的透明效果

可以得到真正的半透明效果

使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。

但是透明度混合需要关闭深度写入,因此需要非常小心物体的渲染顺序。

 

Unity 提供的混合模式的命令:Blend命令

想要实现半透明的效果,就需要把当前自身的颜色和已经存在于颜色缓冲中的颜色进行混合,混合时使用的函数是由该指令决定。

 

模型透明效果的必要条件:(即 在Pass中使用 Blend命令)

1、打开混合模式(开启混合)

2、设置混合因子

 

Unity Shader入门精要 第8章 透明效果 读书笔记_第7张图片

Unity Shader入门精要 第8章 透明效果 读书笔记_第8张图片

透明度混合的缺点:

由于 Pass通道中 关闭深度写入,会带来各种问题:

当模型本身有复杂的遮挡关系 或者 模型包含了复杂的非凸网格 的时候,会有各种各样因为排序而产生的错误的透明效果。

Unity Shader入门精要 第8章 透明效果 读书笔记_第9张图片

解决由于关闭深度写入而造成的错误排序的情况:

开启深度写入的半透明效果

Unity Shader入门精要 第8章 透明效果 读书笔记_第10张图片

使用两个 Pass 渲染模型:

第一个Pass 开启深度写入,但不输出颜色,目的仅仅是把模型的深度值写入深度缓冲中

第二个Pass 进行正常的透明度混合,由于上一个Pass 已经得到了逐像素的正确的深度信息,所以第二个Pass 就可以按照像素级别的深度排序结果进行透明渲染

 

缺点:多使用一个Pass对性能造成影响

 

Shader "Unity Shaders Book/Chapter 8/Alpha Blending With ZWrite" {

      Properties {

            _Color ("Color Tint", Color) = (1, 1, 1, 1)

           _MainTex ("Main Tex", 2D) = "white" {}

            _AlphaScale ("Alpha Scale", Range(0, 1)) = 1

      }

      SubShader {

            Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}

            // Extra pass that renders to depth buffer only

            // 新添加的Pass的目的是为了把模型的深度信息写入到深度缓冲中,从而剔除模型中被自身遮挡的片元

            Pass {

                  // 开启深度写入

                  ZWrite On

                  // ColorMask用于设置颜色通道的写掩码(write mask)

                  // ColorMask为0 表示这个Pass 不写入任何颜色通道,即不会输出任何颜色,这里这个Pass只需要写入深度缓冲就可以,因此需要设置为0

                  ColorMask 0

            }

        

            Pass {

                  Tags { "LightMode"="ForwardBase" }

            

                  ZWrite Off

                  Blend SrcAlpha OneMinusSrcAlpha

            

                  CGPROGRAM

                  ... ...

 

ColorMask 语义:

ColorMask RGB | A | 0 | 其他任何R、G、B、A的组合

 

 

ShaderLab的混合命令:

混合是如何实现的:片元着色器产生一个颜色的时候,可以选择与颜色缓冲中的颜色进行混合。

 

混合输出颜色O 和 两个操作数有关:

1、源颜色S(source color)-由片元着色器产生的颜色值RGBA

2、目标颜色D(destination color)-从颜色缓冲中读取到的颜色值RGBA

源颜色 混合Blend 目标颜色,混合输出颜色O的RGBA,重新写入到颜色缓冲中

 

在Unity中使用Blend(Blend Off命令除外)命令的时候,除了会设置混合状态之外,也会同时开启混合。如果不借助Unity引擎的情况下直接使用图形API,则需要手动开启:OpenGL 需要使用 glEnable(GL_BLEND) 开启混合

 

混合是一个逐片元操作,并且不可编程,但是可以配置(设置混合时使用的运算操作、混合因子等方式影响混合)。设置混合状态实际上设置的就是混合等式中的操作和因子。

 

混合等式:源颜色S 和 目标颜色D 使用等式计算,得到输出颜色O。

默认情况下:混合等式使用的是加操作。如果需要使用其他操作可以设置混合因子。

 

混合使用到的两个混合等式:(一共用到了4个混合因子)

等式1:混合RGB通道

      两个因子:

            因子1、用于和源颜色相乘

            因子2、用于和目标颜色相乘

等式2:混合A通道

      两个因子:

           因子1、用于和源颜色相乘

            因子2、用于和目标颜色相乘

 

混合操作:

混合操作命令:BlendOp BlendOperation 命令

 

Unity Shader入门精要 第8章 透明效果 读书笔记_第11张图片

 

 

常见混合类型:

混合操作和混合因子命令的组合,得到类似Photoshop混合模式中的混合效果:

 

Unity Shader入门精要 第8章 透明效果 读书笔记_第12张图片

 

双面渲染的透明效果:

默认情况下,渲染引擎会剔除物体背面(相对于摄像机方向)的渲染图元,只渲染物体的正面

因此在透明度测试和透明度混合,半透明的正方形内部及其背面的形状都无法看到。

因此需要得到双面渲染的效果,需要使用 Cull 指令控制需要剔除的哪个面的渲染图元

 

Cull Back | Front | Off

Back:背对摄像机的渲染图元不会被渲染(默认的剔除状态)

Front:正对摄像机的渲染图元不会被渲染

Off:关闭剔除功能,所有的渲染图元被渲染(注意:这时需要渲染的图元数目会“成倍增加”,除非特殊效果否则不要关闭剔除功能)

 

双面渲染的 透明度测试:

在 Pass 中添加 Cull Off

Pass{

      Tags { "LightMode" = "ForwardBase" }

      // 关闭剔除功能

      Cull Off

 

 

没有关闭深度写入,可以利用深度缓冲 按照 逐像素的粒度 进行深度排序,从而保证渲染的正确性

Unity Shader入门精要 第8章 透明效果 读书笔记_第13张图片

 

双面渲染的 透明度混合:

因为透明度混合需要关闭深度写入。

关闭深度写入后需要小心控制渲染顺序从而得到正确的深度关系。

因此渲染顺序非常重要,需要保证图元从后往前渲染。

如果 直接使用 Cull Off 关闭剔除功能,就无法保证同一个物体的正面和背面图元的渲染顺序,就有可能得到错误的半透明效果

Unity会顺序执行SubShader中的各个Pass,利用Pass的顺序,保证背面总是在正面被渲染之前渲染,从而保证正确的深度渲染关系。双面渲染工作分成两个Pass:

1、只渲染背面

2、只渲染正面

 

Shader "ShaderName"{

      Properties {

            _Color("Main Tint",Color) = (1,1,1,1)

            _MainTex("Main Tex", 2D) = "white"{}

            _AlphaScale("Alpha Scale",Range(0,1)) = 1

      }

 

      SubShader{

            Tags {

                  "Queue" = "Transparent"

                  "IgnoreProjector"="True"

                  "RenderType"="Transparent"

            }

            Pass{

                  Tags{ "LightMode"="ForwardBase" }

                  //按照顺序:第一个Pass 只渲染背面 裁剪正面

                  Cull Front

                  ... ...

            }

            Pass {

                  Tags{ "LightMode"="ForwardBase" }

                  //按照顺序:第二个Pass 只渲染正面 裁剪背面

                  Cull Back

                  ... ...

            }

      }

      Fallback "Transparent/VertexLit"

}

Unity Shader入门精要 第8章 透明效果 读书笔记_第14张图片

 

 

你可能感兴趣的:(读书笔记,-,Unity,Shader,入门精要)