游戏中有以下两种达到透明度效果:
1.透明度测试
只要一个片元的透明度不满足条件(通常小于某个阈值),那么就舍弃对应的片元。被舍弃的片元不会进行任何的处理,也不会对颜色缓冲产生任何影响。否则就会按照普通的不透明物体来处理,即进行深度测试,深度写入等等。虽然简单,但是很极端,要么完全透明,要么完全不透明。
2.透明度混合
可以得到真正的半透明效果,他会使当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是,透明混合需要关闭深度写入,这使得我们要非常小心物体的下渲染顺序。注意:透明度混合只关闭了深度写入,但没有关闭深度测试。这表示当使用透明度混合渲染一个片元时,还是会比较它的深度值与当前深度缓冲中的深度值,如果深度值距离摄像机更远,那么就不会在进行混合操作。比如一个不透明物体在透明物体前面,我们先渲染不透明物体,可以正常的挡住不透明物体。
说到透明度混合就需要知道:合并混合
合并,渲染过程是一个物体接着一个物体画到屏幕上,而每个像素的颜色信息被存储在一个名为颜色缓冲的地方。因此,我们执行这次渲染时,颜色缓冲中往往已经有了上次渲染之后的颜色结果,那么,我们使用这次渲染得到的颜色完全覆盖之前的结果还是进行其他处理,就是合并需要解决的。
对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会之间覆盖掉颜色缓冲区中的像素值,但对于半透明物体,就需要混合操作来让物体看起来是透明的。
透明度混合需要 不透明物体 关闭深度写入,开启深度测试。
什么是深度写入,什么是深度测试?
如下图所示,A物体在B的前面,假设A B 深度(Z)分别为 A: 0.3 B:0.6。
假设我们不关闭深度写入的情况:
我们先渲染 B,并写入深度 Z = 0.6,
然后在渲染A,A的深度 0.3 < 缓冲区中的 Z (0.6) 通过透明度测试,并写入深度 Z = 0.3
就会得到如下混的效果。
如果这时候在来个不透名物体C 深度为 0.5 ,在A和B的中间,这个时候,
C的深度 0.4 > 缓冲区中的 Z (0.3) 通不过透明度测试,所以不会进行渲染。
如果关闭不透明物体的 深度写入,深度缓冲区的值 Z = 0.6, A和C都会通过透明度测试,得到下面ABC混的结果。
从上面我们发现一个问题 ,渲染B-A-C,如果我们换了渲染顺序会怎么样 B-A-C,就会出现AC都被B盖住了,最终只有B的颜色,所以渲染顺序非常重要。unity给我们设定了以下几个级别的渲染顺序:文档
Unity ShaderLab 的 混合命令:Blend
只要有关键字Blend,就代表开启混合(除Blend off 以外)
命令结构:
参数
举个例子
Blend SrcFactor DstFactor
表示 开启混合,并设置混合因子。源颜色(该片元颜色)乘以 SrcFactor,目标颜色(已经存在于颜色缓冲区的颜色)会乘以DstFactor,然后把两者相加后在存入颜色缓冲区
混合操作
原文地址:ShaderLab 命令:BlendOp - Unity 手册
常见的混合操作类型
// 正常 透明度混合
Blend SrcAlpha OneMinusSrcAlpha
// 柔和相加
Blend OneMinusDstColor One
// 正片叠底
Blend DstColor Zero
// 两倍相乘
BlendOp min
BlendOne One
// 变亮
Blend OneMinusDstColor One
Blend One OneMinusSrcColor
// 线性减淡
Blend One One
透明度测试实现
Shader "Unlit/010"
{
Properties
{
// 漫反射颜色
_Diffuse ("Diffuse",Color) = (1,1,1,1)
// 高光反射颜色值
_Specular ("Specular",Color) = (1,1,1,1)
// 高光反射值
_Gloss ("_Gloss",range(1,100)) = 5
// 主纹理
_MaxTex ("_MaxTex",2d) = "white" {}
// 透明度阈值变量
_Cutoff("Alpha Cutoff",Range(0,1)) = 0.5
}
SubShader
{
// 渲染顺序设置为透明 忽略投影组件
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MaxTex;
float4 _MaxTex_ST;
float4 _Specular;
float4 _Diffuse;
float _Gloss;
float _Cutoff;
struct v2f
{
float4 vertex : POSITION;
float2 uv : TEXCOORD1;
float3 worldNormal : TEXCOORD0;
float3 viewDir : TEXCOORD2;
};
v2f vert (appdata_base v)
{
v2f o;
// 将对象空间中的点变换到齐次坐标中的摄像机裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
// 计算uv坐标
o.uv = TRANSFORM_TEX(v.texcoord,_MaxTex);
// o.uv = v.texcoord.xy * _MaxTex_ST.xy + _MaxTex_ST.zw;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.viewDir = normalize(WorldSpaceViewDir(o.vertex));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float4 texColor = tex2D(_MaxTex,i.uv);
if((texColor.a - _Cutoff)<0)
{
discard;
}
// 计算漫反射
float3 diffuse = texColor.rgb * _LightColor0.rgb * _Diffuse.rgb * (dot(_WorldSpaceLightPos0.xyz,i.worldNormal) * 0.5 + 0.5);
// 计算高光
float3 halfVector = normalize(normalize(_WorldSpaceLightPos0.xyz) + i.viewDir);
// 计算高光
float3 specular = texColor.rgb * _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(halfVector,i.worldNormal)),_Gloss);
return float4(diffuse,1);
}
ENDCG
}
}
}
shader中的漫反射、高光反射、纹理采样,不具体讲解了,有需要的可以点连接进去看,透明度测试非常简单,就是设定一个阈值 ,然后通过阈值对纹理透明度进行判断,通过的则显示,通不过的则透明。不存在半透明的情况
// 透明度阈值变量 _Cutoff("Alpha Cutoff",Range(0,1)) = 0.5// 阈值判断
if((texColor.a - _Cutoff)<0) { discard; }
还需要注意设置下渲染队列
// 渲染顺序设置为透明 忽略投影组件
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True"}
透明度混合Shader
Shader "Unlit/011"
{
Properties
{
// 漫反射颜色
_Diffuse ("Diffuse",Color) = (1,1,1,1)
// 高光反射颜色值
_Specular ("Specular",Color) = (1,1,1,1)
// 高光反射值
_Gloss ("_Gloss",range(1,100)) = 5
// 主纹理
_MaxTex ("_MaxTex",2d) = "white" {}
_AlphaScale("Alpha Scale",Range(0,1)) = 1
}
SubShader
{
// 渲染顺序设置为透明 忽略投影组件
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Zwrite Off // 关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha // 开启混合
Pass
{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MaxTex;
float4 _MaxTex_ST;
float4 _Specular;
float4 _Diffuse;
float _Gloss;
float _AlphaScale;
struct v2f
{
float4 vertex : POSITION;
float2 uv : TEXCOORD1;
float3 worldNormal : TEXCOORD0;
float3 viewDir : TEXCOORD2;
};
v2f vert (appdata_base v)
{
v2f o;
// 将对象空间中的点变换到齐次坐标中的摄像机裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
// 计算uv坐标
o.uv = TRANSFORM_TEX(v.texcoord,_MaxTex);
// o.uv = v.texcoord.xy * _MaxTex_ST.xy + _MaxTex_ST.zw;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.viewDir = normalize(WorldSpaceViewDir(o.vertex));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float4 texColor = tex2D(_MaxTex,i.uv);
// 计算漫反射
float3 diffuse = texColor.rgb * _LightColor0.rgb * _Diffuse.rgb * (dot(_WorldSpaceLightPos0.xyz,i.worldNormal) * 0.5 + 0.5);
// 计算高光
float3 halfVector = normalize(normalize(_WorldSpaceLightPos0.xyz) + i.viewDir);
// 计算高光
float3 specular = texColor.rgb * _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(halfVector,i.worldNormal)),_Gloss);
return float4(diffuse,texColor.a * _AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
要注意的几个地方:
// 渲染顺序设置为透明 忽略投影组件 渲染类型设置 Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} // 透明物体需要 关闭深度写入 Zwrite Off // 开启混合 Blend SrcAlpha OneMinusSrcAlpha
效果如下:
总结
代码上实际上没几句,主要还是需要理解上面 透明度测试,透明度混合,什么是深度写入,为什么要开启写入和关闭写入的原因。