目前处理透明效果的常见方式有透明测试(适用于完全透明/不透明,非0即1)和透明混合(适用于颜色的混合叠加)
透明测试
和深度测试的思路类似,剔除某些小于透明阈值的像素,实现某些位置完全透明,我们设定一个alpha阈值,当偏于不满足条件(通常是小于阈值)就直接舍弃当作全透明,满足条件的片元就直接当作不透明物体进行处理。所以注意AlphaTest并没有实现真正的半透明效果。
注意渲染流程先做深度测试,再做透明度测试,最后才是Blending混合渲染。在渲染队列中,透明度测试在非透明物体渲染之后,半透明物体之前,这样更高效。
一般透明测试的Shader包含以下指令:
SubShader
{
//设置渲染标签
Tags
{
"Queue" = "AlphaTest" ////透明测试的渲染队列
"RenderType" = "TransparentCutout"
"IgnoreProjector" = "True" //透明物体不接受投影仪的投射
}
Pass
{
CGPROGRAM
clip(textureColor.a - alphaCutoffValue);
ENDCG
}
}
实例Shader(就与Lambert光照模型)
Shader "Custom/AlphaTest Transparent"
{
Properties
{
_MainTex("MainTex",2D) = "white"{}
_AlphaTest("Alpha Test",Range(0, 1)) = 0
}
SubShader
{
//设置渲染标签
Tags
{
"Queue" = "AlphaTest"
"RenderType" = "TrannsparentCutout"
"IgnoreProjector" = "True"
}
//渲染背面
Pass
{
Tags{"LightMode" = "ForwardBase"}
//关闭几何体剔除
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 worldPos : TEXCOORD0;
float2 texcoord : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaTest;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = normalize(worldNormal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//灯光法线 等同于_WorldSpaceLightPos0
float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz);
worldLight = normalize(worldLight);
//按照公式计算漫反射
fixed ndotl = dot(i.worldNormal,worldLight);
fixed4 color = tex2D(_MainTex, i.texcoord);
//开启AlphaTest
clip(color.a - _AlphaTest);//丢弃小于阈值的像素
color.rgb *= _LightColor0 * saturate(ndotl);
color.rgb += unity_AmbientSky;
return color;
}
ENDCG
}
}
}
效果:

开启透明测试:

混合透明效果
将新渲染出来的图像和已存在的进行合并。
常用的混合模式有:
- Blend Off 关闭混合处理。
- Blend SrcFactor DstFactor 开启混合处理,自定义混合模式,将目标图像(新渲染出的图像)和源图像(已存在的图像)按照混合系数进行rgba四通道的加权混合。
- Blend SrcFactor DstFactor,SrcFactorA DstFactorA 与上条类似,只不过分别计算rgb通道和alpha通道的混合。
- BlendOp Op:使用逻辑操作指令指定加权后的图像像素值具体如何混合,比如Add,Sub等(默认不写为Add)。
- BlendOp OpColor,OpAlpha: 使用逻辑操作,分别计算rgb通道和alpha通道。
注意,先用BlendOp声明逻辑操作类型,在用Blend声明混合系数。
常用混合逻辑操作:

混合系数:

常用混合指令(操作为Add)

混合透明的使用办法
一般混合透明Shader包含以下指令:
SubShader
{
Tags
{
"Queue" = "Transparent" // 透明物体的单独渲染队列,使得透明物体在不透明物体之后被渲染
"RenderType" = "Transparent" //透明物体的渲染类型,使得透明物体在不透明物体之后被渲染
"IgnoreProjector" = "True" //透明物体不接受投影仪的投射
}
Pass
{
ZwriteOff //关闭透明物体深度写入
Blend SrcAlpha OneMinusSrcAlpha //透明叠加
}
}
混合透明实例
对Lambert光照模型的代码进行修改。
Shader "Custom/Blending Transparent"
{
Properties
{
_MainTex("MainTex",2D) = "white"{}
_MainColor("MainColor",Color) = (1, 1, 1, 1)
}
SubShader
{
//设置渲染标签
Tags
{
"Queue" = "Transparent" // 透明物体的单独渲染队列,使得透明物体在不透明物体之后被渲染
"RenderType" = "Transparent" //透明物体的渲染类型,使得透明物体在不透明物体之后被渲染
"IgnoreProjector" = "True" //透明物体不接受投影仪的投射
}
Pass
{
Tags{"LightMode" = "ForwardBase"}
//设置渲染状态
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 worldPos : TEXCOORD0;
float2 texcoord : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _MainColor;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = normalize(worldNormal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//灯光法线 等同于_WorldSpaceLightPos0
float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz);
worldLight = normalize(worldLight);
//按照公式计算漫反射
fixed ndotl = dot(i.worldNormal,worldLight);
fixed4 color;
color.rgb = _LightColor0 * tex2D(_MainTex, i.texcoord) * _MainColor.rgb * saturate(ndotl);
color.rgb += unity_AmbientSky;
//通过_MainColor属性的a分量控制透明度
color.a = _MainColor.a;
return color;
}
ENDCG
}
}
FallBack "Diffuse"
}
效果

半透明物体的双面渲染
上节实现的透明混合效果实际上是没有背面(物体的内表面,背朝摄像机)的,这是因为渲染状态默认是Cull Back,将背面进行了剔除。

可以使用Cull Off关闭背面剔除,但由于关闭了深度写入,深度缓存中没有物体的深度信息,导致背面和正面绘制顺序错误,出现了背面的图像叠加到正面的现象(背面的图片反而更亮,经过了一次透明叠加,而正面反而经过了两次)。

为此,需要分两个Pass,先渲染背面再渲染正面。
Pass
{
Tags{"LightMode" = "ForwardBase"}
//开启正面剔除
Cull front
//设置渲染状态
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 worldPos : TEXCOORD0;
float2 texcoord : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _MainColor;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = normalize(worldNormal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//灯光法线 等同于_WorldSpaceLightPos0
float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz);
worldLight = normalize(worldLight);
//按照公式计算漫反射
fixed ndotl = dot(i.worldNormal,worldLight);
fixed4 color;
color.rgb = _LightColor0 * tex2D(_MainTex, i.texcoord) * _MainColor.rgb * saturate(ndotl);
color.rgb += unity_AmbientSky;
//通过_MainColor属性的a分量控制透明度
color.a = _MainColor.a;
return color;
}
ENDCG
}
//渲染正面
Pass
{
Tags{"LightMode" = "ForwardBase"}
//开启背面剔除
Cull Back
//设置渲染状态
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float4 worldPos : TEXCOORD0;
float2 texcoord : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _MainColor;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = normalize(worldNormal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//灯光法线 等同于_WorldSpaceLightPos0
float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz);
worldLight = normalize(worldLight);
//按照公式计算漫反射
fixed ndotl = dot(i.worldNormal,worldLight);
fixed4 color;
color.rgb = _LightColor0 * tex2D(_MainTex, i.texcoord) * _MainColor.rgb * saturate(ndotl);
color.rgb += unity_AmbientSky;
//通过_MainColor属性的a分量控制透明度
color.a = _MainColor.a;
return color;
}
正常的双面渲染:

半透明物体的交叉重叠问题也可以用类似的想法解决,用两个Pass,第一个Pass写入深度信息不做渲染,第二个Pass再做渲染
本文详细介绍了Unity 3D中处理透明效果的两种常见方法:透明测试和透明混合。透明测试通过设置alpha阈值剔除部分像素,实现完全透明或不透明的效果,而透明混合则通过混合新图像和已存在图像来创建半透明效果。文章还探讨了渲染流程、混合模式、渲染队列以及如何处理双面透明和交叉重叠问题。同时,提供了具体的Shader代码示例,帮助读者理解并应用这些技术。
1134

被折叠的 条评论
为什么被折叠?



