一、基本概念
- 在Unity中通常使用两种方法来实现透明效果
- 透明度测试(无法达到真正的半透明效果)
- 透明度混合(关闭了深度写入)
透明度测试
- 基本原理:设置一个阈值,只要片元的透明度小于阈值,就会被舍弃(不会进行任何处理,也不会对颜色缓冲造成影响);否则,就会按照普通的不透明物体来处理(进行深度测试、深度写入等)
- 效果极端,要么透明,要么完全不透明
透明度混合
- 基本原理:使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色
- 需要关闭深度写入,但没有关闭深度测试——深度缓冲是只读的
- 为什么关闭深度写入:如果开启,半透明物体在不透明物体前面时,半透明物体会挡住不透明物体,实现不了半透明效果
- 可以实现半透明效果
二、渲染顺序
- 对于半透明物体和不透明物体,因为关闭了深度写入,所以应该在不透明物体渲染完后再渲染半透明物体
- 对于两个半透明物体,应该先渲染远处的,再渲染近处的(不准确)
1.渲染引擎的方法
- 先渲染所有不透明物体,并开启他们的深度测试和深度写入
- 把半透明物体按他们的距离摄像机的远近进行排序,再按照从后往前的顺序渲染,开启深度测试,关闭深度写入
2.Unity的渲染顺序
- 使用SubShader的 Queue 标签来决定我们的模型将归于哪个渲染队列,使用整数索引来表示每个渲染队列,号小的越早被渲染
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d8344316e79c479596dc2eb2f78f4595.png)
- 如果想要通过透明度测试来实现效果,代码:
SubShader{
Tags{ "Queue" = "AlphaTest"}
Pass{...}
}
SubShader{
Tags{ "Queue" = "Transparent"}
Pass{
ZWrite Off
...}
}
三、透明度测试
- 基本原理:设置一个阈值,只要片元的透明度小于阈值,就会被舍弃(不会进行任何处理,也不会对颜色缓冲造成影响);否则,就会按照普通的不透明物体来处理(进行深度测试、深度写入等)
- 通常会在片元着色器中使用 clip函数 进行透明度测试
- 如果给定参数的任何一个分量是负数,则会舍弃当前像素的输出颜色
Shader "Custom/Chapter8-AlphaTest"
{
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" = "TransparentCutoff"}
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 = 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;
}
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);
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"
}
- _Cutoff (“Alpha Cutoff”, Range(0,1)) = 0.5 用于决定我们调用clip进行透明度测试时使用的判决条件
- clip(texColor.a - _Cutoff); 做透明度测试,判断texColor.a - _Cutoff是否为负数,负数的纹理颜色为全透明
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/0444586187af494a8ba6316919150d11.png)
四、透明度混合
- 基本原理:使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色
- 需要使用Unity提供的混合命令 Blend
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/f60f7ff4c9984b8bbadf74ebcd0d2118.png)
- 本节中使用的是Blend SrcFactor DstFactor来进行混合,将源颜色混合因子SrcFactor设为SrcAlpha,目标颜色混合因子设为OneMinusSrcAlpha
- 即混合后的新颜色为
-
D
s
t
C
o
l
o
r
n
e
w
DstColor_{new}
DstColornew = SrcAlpha × SrcColor + (1-SrcAlpha) ×
D
s
t
C
o
l
o
r
o
l
d
DstColor_{old}
DstColorold
- ZWrite Off 关闭深度写入
Shader "Custom/Chapter8-AlphaBlend"
{
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 = 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;
}
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"
}
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/61059278199b40139edd9627d1a78201.png)
五、开启深度写入的半透明效果
- 对于复杂网格的半透明处理,避免错误排序
- 方法:使用 两个Pass 来渲染模型
- 第一个开启深度写入,但不输出颜色,仅仅为了把该模型的深度值写入深度缓存中
- 第二个Pass 进行正常的透明度混合,根据第一个Pass,可以进行像素级别的深度排序
- 缺点是 多个Pass会造成性能的影响
Shader "Custom/Chapter8-AlphaBlend"
{
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 = 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;
}
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"
}
Pass
{
ZWrite On
ColorMask 0
}
- ZWrite On 打开深度写入,ColorMask 0 意味着Pass不写入任何颜色通道,不会输出任何颜色
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/e0b6db69090d47c39ef469b842fc0a8e.png)
六、ShaderLab的混合命令
- 混合与两个操作数有关:源颜色(SourceColor)和目标颜色(DestinationColor)
- 源颜色指片元着色器产生的颜色 S
- 目标颜色指从颜色缓冲中读取到的颜色值 D
- 混合后的颜色 O
- 包含了 RGBA四个颜色通道
1.混合等式和参数
- 将S和D进行混合的等式——混合等式,需要两个,一个用于混合RGB,一个用于混合A
- 设置混合状态时,即设置等式中的 “操作” 和 “因子”
- 一共有两个等式(分别用于混合RGB和A)
- 一个等式里有两个因子(一个与S相乘,一个与D相乘)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/ac62b6a0c9da4539bf4fa2b6b3530885.png)
- 第一个命令中只有两个因子——RGB和A通道用相同的两个因子
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a1c66815a2374e049fb7cdcd6a43ae43.png)
混合因子可以是哪些值呢?
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/0e1507fb45bc4b7c88af64041ac4cea1.png)
2.混合操作
- 可以使用 BlendOp BlendOperation命令
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/1178179d05b549bc899431b61d8331b2.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/36146305d6c6463db572464d5da7d5fc.png)
- 当使用Min和Max操作时,混合因子不起作用的
3.常见的混合类型(效果)
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/c190faefabee4df78857a1b7de61827e.png)
七、双面渲染的透明效果
- 观察到其内部结构
- 可以使用Cull指令来控制需要剔除哪个面的渲染图元
- Back:背对相机的渲染图元不会被渲染
- Front:朝向相机的渲染图元不会被渲染
- Off:关闭剔除功能
Cull Back | Front | Off
1.透明度测试的双面渲染
- 在Pass中使用 Cull Off 来关闭剔除功能即可
- 只在 AlphaTest.shader 里Pass中加一句 Cull Off
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d3b1c60b61734510ae8548bae8fbd512.png)
2.透明度混合的双面渲染
- 因为关闭了深度写入,如果直接加 Cull Off 的话会造成前后面混乱
- 所以,写了两个Pass (与AlphaBlend.shader一样)
- 只在Pass中,第一个Pass只渲染背面 ,添加了Cull Front;第二个只渲染正面 添加了Cull Back
- SubShader是会按照顺序执行Pass
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/453b5ab3eb2547cfa3a00a2226d42470.png)