(一)透明效果是什么呢?
渲染中实现透明效果,通常会在渲染模型时控制它的透明通道。对于不透明的物体,不考虑渲染顺序也能得到正确的排序效果,这是由于强大的深度缓冲的存在(z-buffer)。实时渲染中,深度缓冲是用于解决可见性的问题。因此实现透明效果的时候需要关闭深度缓冲。
透明度测试:当片元的透明度不满足条件,对应的片元就会被舍弃。被舍弃的片元就不会做任何处理。因此透明度测试是不需要关闭深度写入的。他与不透明的物体最大的不同是会根据透明度来进行一些片元的舍弃。产生的效果要么看不到,完全透明,要么完全不透明。
透明度混合:这种方法才能得到真正的半透明效果。会使用当前片元的透明度作为混合因子,与已经在缓冲区的颜色进行混合,得到新的颜色。但是透明度混合需要关闭深度写入,但不关闭深度测试。因为当一个不透明的物体在透明的物体前的时候,仍然先渲染不透明的物体,使其遮挡住透明物体,对于透明度混合来说,深度缓冲是只读的。
关闭克深度测试后,渲染的顺序变得特别重要。对于A是半透明的物体,B是不透明的物体,应该先渲染B物体,再渲染A物体。
渲染引擎一般都会对物体进行排序,再渲染。常用的方法是:
(1)先渲染所有不透明的物体,并开启它们的深度测试和深度写入
(2)把半透明物体按它们距离摄像机的远近进行排序,然后按照从后往前的顺序渲染这些半透明的物体,并开启他们的深度测试,但关闭深度写入。
但是很多时候一个物体A的一部分被B挡住,另一部分又是A挡住了物体B,因此这个时候,会出现穿帮的情形,unity 是怎样解决这个问题的呢?
(二)Unity的渲染顺序,透明度测试和透明度混合
(1)Unity提前定义的5个渲染队列
Background 会在任何队列前被渲染 1000
Geometry 默认的渲染队列,大多数物体都适用这个渲染队列,不透明的物体使用这个队列 2000
AlphaTest 需要透明度测试的物体使用这个队列。2450
Transparent 这个队列中的物体会在Geometry 和 AlphaTest后被渲染,再从后往前的顺序渲染。任何使用了透明度混合(例如关闭了深度写入的Shader)的物体都应该使用该队列 3000
Overlay 该队列实现一些叠加的效果,任何在最后渲染的物体都应该使用该队列。
因此使用透明度测试实现透明效果,
SubShader{
Tags{ "Queue"="AlphaTest" }
Pass{ ... }
}
使用透明度混合实现透明效果,
SubShader{
Tags{ " Queue" = "Transparent" }
Pass{
ZWrite Off
... }
}
(2)透明度测试
透明度测试:只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元就会被舍弃,被舍弃的片元不会再进行任何处理,也不会对颜色缓冲区造成影响。否则就会按照普通的不透明物体进行处理。
clip函数进行透明度测试 void clip(float1-4 x);参数是裁剪的标量或者矢量,如果任何一个分量是负数,就会舍弃当前的输出颜色。
void clip(float4 x){
if(any(x)<0)
discard;
}
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
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"}
//透明测试用的是alphatest rendertype可以让unity把shader归入到提前定义的组,指名该shader使用了一个透明测试,IgnoreProjector设为true指名不受投影器的影响
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(UINTY_MATRIX_MVP,v.vertex);
o.worldNormal = UnityObjecttoWorldNormal(v.normal);
o.worldPos = mul(_Object2World,v.vertex).xyz;
0.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 = _LightColor.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient + diffuse ,1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"
}
(3)透明度混合
透明度混合:可以得到半透明的效果,使用当前的片元透明度当作混合因子,与已经存储在颜色缓冲区中的颜色进行混合,得到新的颜色。透明度混合要关闭深度写入,注意物体的渲染顺序
Blend Off 关闭混合
Blend SrcFactor DstFactor 开启混合,设置混合因子。源颜色(片元的颜色)*SrcFactor 目标颜色(已经存在于缓存的颜色)* DstFactor,两者相加放入颜色缓冲区
Blend SrcFactor DstFactor SrcFactorA DstFactorA 和上面的一样,只是使用不同的因子来混合透明通道
BlendOp BlendOperation 并非是把源颜色和目标颜色简单进行混合,而是使用BlendOperation进行其他操作
Shader "Unlit/AlphaTest"
{
Properties
{
_Color ("Main Tint",Color) = (1,1,1,1)
_MainTex ("Texture", 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//将颜色,该片元着色器产生的颜色的混合因子设置为SrcAlpha,把目标颜色,已经存在于颜色缓冲中的颜色的混合因子设置为OneMinusSrcAlpha,以得到合适的半透明效果
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct a2v{
float4 vertex : POSIOTION;
float3 normal :NORMAL;
fixed4 texcoord : TEXCOORD0;
}
struct v2f{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
}
v2f vert(a2v v){
v2f i;
i.pos = mul(UNITY_MARTEX_MVP,v.vertex);
i.worldPos = mul(_Object2World,v.vertex);
i.worldNormal = UnityObjectToWorldNormal(v.normal);
i.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return i;
}
fixed4 frag(v2f v) : SV_Target{
fixed3 worldnormal = normalize(v.worldNormal);
fixed3 worldlightdir = normalize(UnityWorldSpaceLightDir(v.worldpos));
fixed3 texcoord = tex2D(_MainTex,v,uv);
fixed3 albedo = texcoord.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHRMODEL_AMBIENT * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldnormal,worlflightdir));
return fixed4(ambient + diffuse , texcoord.a * _AlphaScale);//blend打开后,此时设置的透明通道才有意义
}
FallBack "Transparent/VertexLit"
ENDCG
}
}
}
此时,如果包含了非常复杂的遮挡关系,或是包含了复杂的非凸网格的时候,就会有各种各样的原因
(三)开启深度写入的半透明效果
(1)半透明的实现
使用两个Pass进行渲染,可以实现复杂的遮挡关系:第一个Pass开启深度写入,但不输出颜色,目的只是为了把该模型的深度值写入深度缓冲区中,;第二个Pass进行正常的透明度混合,由于上一个Pass意境得到了祝像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染。
渲染的Shader与透明度混合几乎完全相同,只是新加一个Pass
Pass{
ZWrite On;
ColorMask 0; //用于设置颜色通道的写掩码 ColorMask RGB | A | 0 ;0时意味着Pass不写入任何颜色通道,即不会输出任何颜色。
}
(2)ShaderLab 的混合命令
当片元着色器产生一个颜色的时候,可以选择与颜色缓存中的颜色进行混合,与两个参数有关:源颜色和目标颜色。源颜色是片元着色器产生的颜色S,目标颜色是从目标缓冲区得到的颜色D,对她们进行混合后的输出颜色为O。当谈到混合中的源颜色,目标颜色,输出颜色,都包含了RGBA四个通道的值,而非仅仅是RGB通道。
Unity中用Blend命令打开,OpenGL用 gleanable(GL_BLEND)。
Blend SrcFactor DstFactor SrcFactorA DstFactorA
Orgb = SrcFactor * Srgb + DstFactor * Drgb
Oa = SrcFactorA * Sa + DstFactorA * Da
ShaderLab 中的混合因子
One 因子为1
Zero 因子为0
SrcColor 因子为源颜色
SrcAlpha 因子味源颜色的透明度值(A通道)
DstColor 因子为目标颜色
DstAlpha 因子为目标颜色的透明度值(A通道)
OneMinusSrcColor 因子为1-源颜色
OneMinusSrcAlpha 因子为1-源颜色的透明度值
OneMinusDstColor 因子为1-目标颜色
OneMinusDstAlpha 因子为1-目标颜色透明度值
例如在混合后输出的为源颜色的透明度
Blend SrcAlpha OneMinusSrcAlpha ,One Zero
(3)常见的混合类型
Blend SrcAlpha OneMinusSrcAlpha 正常的透明度混合
Blend OneMinusDstColor One 柔和相加
Blend DstColor Zero 正片叠底
Blend DstColor SrcColor 两倍相乘
//变暗
BlendOp Min
Blend One One
//变亮
BlendOp Max
Blend One One
//滤色
Blend OneMinusDstColor One 等同于 Blend One OneMInusSrcColor
Blend One One //线性减淡
(四)双面渲染的透明效果
可以使用Cull来控制剔除哪个面的渲染图元
Cull Back | Front | Off //Back 背对着摄像机的图元不会被渲染(默认状态),Front正对着摄像机的不会被渲染,off关闭剔除功能,此时所有的图元都会渲染,图元数目会非常大。
(1)透明度测试的双面渲染
只需要关闭剔除即可 在Pass中增加 Cull Off
(2)透明度混合的双面渲染
此时需要两个Pass,透明度混合关闭了深度写入,因此渲染顺序很重要,需要首先渲染背面,然后渲染正面。
SubShader
{
Tags { "Queue"="Transparent" "IgnoreProjector" = "True" "RenderType"="Transparent" }
Pass
{
Tags {"LightMode" = "ForwardBase"}
Cull Front
........
}
Pass
{
Tags {"LightMode" = "ForwardBase"}
Cull Back
........
}