您的位置:首页 > 移动开发 > Unity3D

Unity Shader学习笔记:透明

2017-08-25 16:57 190 查看
在Unity中,我们通常使用两种方法来实现透明效果:

1.透明度测试,这种方法其实无法得到真正的半透明效果

2.透明度混合

透明度测试:

它采用一种”霸道极端“的机制,只要一个片元的透明度不满足条件(通常是小于某个阀值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它,即进行深度测试,深度写入等。也就是说,透明度测试是不需要关闭深度写入的,它和其他不透明物体最大的不同是他会根据透明度舍弃一些片元,虽然简单,但是它产生的效果也很极端,要么透明,即看不到,要么完全不透明,就像不透明物体那样

透明度混合:

这种方法可以得到真正的半透明效果。他会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是透明度混合需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。需要注意的是,透明度混合只关闭了深度写入,但没有关闭深度测试。这意味着,当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果它的深度值距离摄像机更远,那么就不会进行混合操作。这一点决定了,当一个不透明物体出现在一个透明物体前面,而我们先渲染了不透明物体,它仍然可以正常的遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的。

注意:

(1)先渲染所有不透明物体,并开启它们的深度测试和深度写入

(2)把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入。

渲染队列:

可以使用SubShader的Queue标签来决定我们的模型将归于哪个渲染队列。Unity在内部使用一系列整数索引来表示每个渲染队列。且索引号越小表示越早被渲染。

Unity提前定义的5个渲染队列
名称               队列索引号          描述
Background       1000               这个渲染队列会在任何其他队列之前被渲染,我们通常使用该队列来渲染那些需要绘制在背景上的物体
Geometry         2000               默认的渲染队列,大多数物体都使用这个队列。不透明物体使用这个队列
AlphaTest        2450               需要透明度测试的物体使用这个队列。在Unity5中它从Geometry队列中被单独分出来,这是因为在所有不透明物体渲染之后再渲染它们
会更加高效。
Transparent      3000               这个队列中的物体会在所有Geometry和AlphaTest物体渲染后,再按从后往前的顺序进行渲染。任何使用了透明度混合(例如关闭了深度
写入的Shaser)的物体都应该使用该队列
Overlay          4000               该队列用于实现一些叠加效果。任何需要在最后渲染的物体都应该使用该队列。


透明度测试代码:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Own/Chapter8-Alpha Test"
{
Properties
{
_Color("Main Tint",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
//透明度测试时使用的判断条件
_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
}
SubShader
{
Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include"Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff;
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex,i.uv);
clip(texColor.a - _Cutoff);
//if((texColor.a - _Cutoff) < 0.0){
//      discard;
//}
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient + diffuse,1.0);
}
ENDCG
}
}
Fallback "Transparent/Cutout/VertexLit"
}


1.在Unity中透明度测试使用的渲染队列是AlphaTest队列

2.RenderType标签可以让Unity把这个shader归入到提前定义的组(这里就是TransparentCutout组)中,以指明该Shader是一个使用了透明度测试的Shader.RenderType标签通常被用于着色器替换功能

3.将IgnoreProjector设置为True,这意味着这个Shader不会受到投影器的影响。

4.void clip(float4 x); void clip(float3 x); void clip(float2 x); void clip(float1 x); void clip(float x);输入裁剪时使用的参数或矢量条件。如果给定参数的任何一个分量是负数,就会舍弃当前像素的输出颜色。它等同于下面的代码:

void clip(float4 x)
{
if(any(x < 0))
discard;
}


透明度混合

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

ShaderLab的Blend命令
语义                                  描述
Blend Off                           关闭混合
Blend SrcFactor DstFactor           开启混合,并设置混合因子。源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在与颜色缓存中的颜色)会乘以
DstFactor,然后把两者相加后的颜色存入颜色缓存中
Blend SrcFactor DstFactor,          和上面几乎一样,只是使用不同的因子来混合透明通道
SrcFactorA DstFactorA
BlendOp BlendOperation              并非是把源颜色和目标颜色简单相加后混合,而是使用BlendOperation对它们进行其他操作


ShaderLab中的混合因子
参数                  描述
One                 因子为1
Zero                因子为0
SrcColor            因子为源颜色值。当用于混合RGB的混合等式时,使用SrcColor的RGB分量作为混合因子;当用于混合A的混合等式时,使用SrcColor的A分量作为混合因子
SrcAlpha            因子为源颜色的透明度值(A通道)
DstColor            因子为目标颜色值。当用于混合RGB的混合等式时,使用DstColor的RGB分量作为混合因子;当用于混合A的混合等式时,使用DstColor的A分量作为混合因子
DstAlpha            因子为目标颜色的透明度值(A通道)
OneMinusSrcColor    因子为(1-源颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子,当用于混合A的混合等式时,使用结果的A分量作为混合因子
OneMinusSrcAlpha    因子为(1-源颜色的透明度值)
OneMinusDstColor    因子为(1-目标颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子,当用于混合A的混合等式时,使用结果的A分量作为混合因子
OneMinusDstAlpha    因子为(1-目标颜色的透明度值)


ShaderLab中的混合操作
操作              描述
Add             将混合后的源颜色和目标颜色相加。默认的混合操作。使用的混合等式是:
Orgb = SrcFactor x Srgb + DstFactor x Drgb
Oa = SrcFactorA x Sa + DstFactorA x Da
Sub             用混合后的源颜色减去混合后的目标颜色。使用的混合等式是:
Orgb = SrcFactor x Srgb - DstFactor x Drgb
Oa = SrcFactorA x Sa - DstFactorA x Da
RevSub          用混合后的目标颜色减去混合后的源颜色。使用的混合等式是:
Orgb = DstFactor x Drgb - SrcFactor x Srgb
Oa = DstFactorA x Da - SrcFactorA x Sa
Min             使用源颜色和目标颜色中较小的值,是逐分量比较的。使用的混合等式是:
Orgba = (min(Sr,Dr),min(Sg,Dg),min(Sb,Db),min(Sa,Da))
Max             使用源颜色和目标颜色中较大的值,是逐分量比较的。使用的混合等式是:
Orgba = (max(Sr,Dr),max(Sg,Dg),max(Sb,Db),max(Sa,Da))


常见的混合类型:没有设置混合操作,则默认是加法Add
//正常(Normal),即透明度混合
Blend SrcAlpha OneMinusSrcAlpha
//柔和相加(Soft Additive)
Blend OneMinusDstColr One
//正片叠第(Multiply),即相乘
Blend DstColor Zero
//两倍相乘(2x Multiply)
Blend DstColor SrcColor
//变暗(Darken)
BlendOp Min
Blend One One
//变亮(Lighten)
BlendOp Max
Blend One One
//滤色(Srceen)
Blend OneMinusDstColor One
//等同于
Blend One OneMinusSrcColor
//线性减淡(Linear Dodge)
Blend One One


透明度混合代码:

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Own/Chapter8-Alpha Blend"
{
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"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};

struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex,i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient + diffuse,texColor.a * _AlphaScale);
}
ENDCG
}
}
Fallback "Transparent/VertexLit"
}


1.ZWrite Off:关闭深度写入。

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

为了解决由于关闭深度写入而造成的错误排序的情况。一种解决方法是使用两个Pass来渲染模型:第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;第二个Pass进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明度渲染。但这种方法的缺点在于,多使用了一个Pass会对性能造成一定影响。

// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'

Shader "Own/Chapter8-Alpha Blend"
{
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{
ZWrite On
ColorMask 0
}
Pass
{
Tags{"LightMode" = "ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};

struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
};
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP,v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target{
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex,i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient + diffuse,texColor.a * _AlphaScale);
}
ENDCG
}
}
Fallback "Transparent/VertexLit"
}


1.这个新添加的Pass的目的仅仅是为了把模型的深度信息写入深度缓冲中,从而剔除模型中被自身遮挡的片元。因为,Pass的第一行开启了深度写入

2.ColorMask:用于设置颜色通道的写掩码。它的语义:

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

当ColorMask设为0时,意味着该Pass不写入任何颜色通道,即不会输出任何颜色。这正是我们需要的–该Pass只需写入深度缓存即可。

Cull指令

Unity中Cull指令的语法如下:

Cull Back | Front | Off

如果设置为Back,那么那些背对这摄像机的渲染图元就不会被渲染,这也是默认情况下的剔除状态,如果设置为Front,那么那些朝向摄像机的渲染图元就不会被渲染;如果设置为Off,就会关闭剔除功能,那么所有的渲染图元就会被渲染,但由于这时需要渲染的图元数目会成倍的增加,因此除非是用与特殊效果,例如:双面渲染的透明效果,通常情况下是不会关闭剔除功能的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  unity