Z缓冲区(Z-Buffer)算法
当要渲染一个片元时候,需要把它的深度值和已经存在于z缓冲器中的深度值进行比较(如果开启了深度测试),
如果该片元值较小,该片元的z值应该覆盖掉对应单元颜色缓冲区中的颜色值。并将该片元深度值写入z缓冲器中。
如果该片元深度值和z缓冲器中对应单元已有的值比较,值较大。表明有物体遮住了它,该片元不被渲染。
参考:https://www.cnblogs.com/cnblog-wuran/p/9830994.html
透明度操作-测试&混合
- 透明度测试 一个片元透明度不满足某个条件(通常是小于某个设置的阈值),对应的片元会被舍弃。不会对颜色缓冲区产生任何影响。否则就按照普通不透明片元来处理。
深度测试不需要关闭深度写入。
- 透明度混合 使用当前透明度作为混合因子,与已经存在颜色缓冲区中的颜色值进行混合,得到新的颜色。
深度混合需要关闭深度写入。不需要关闭深度测试。如果开启深度写入,靠近摄像机有个半透明物体,此物体深度写入深度缓冲区,再更远距离后边有个模型由于深度大于半透物体,被认为是被半透物体遮罩。不渲染,透过半透物体,看不到远距离物体。。。不合理。。。
因此,关闭深度写入才可能得到正确渲染结果。 但是关闭深度写入,需要设置正确的渲染顺序,才能得到正确的渲染结果。
UnityShader 的渲染顺序
为了解决渲染顺序,Unity为我们提供了渲染队列(render queue),我们可以用SubShader的Queue标签决定模型归于哪个渲染队列。索引号越小越早渲染。
名称 | 索引号 | 描述 |
Background | 1000 | 最先渲染,通常该队列用来渲染需要绘制在背景上的物体 |
Geometry | 2000 | 默认渲染队列,大多数物体都是用这个队列,不透明物体使用这个队列 |
AlphaTest | 2450 | 需要透明度测试的物体使用,Unity5之后把它从Geometry队列中单独分出来,这是因为在所有不透明物体渲染之后再渲染他们会更高效 |
Transparent | 3000 | 从后往前渲染,任何使用了透明度混合(关闭了深度写入的Shader)的物体,都应该使用该队列。 |
Overlay | 4000 | 实现叠加效果,需要在最后渲染的物体使用该队列 |
透明度测试(AlphaTest)
Shader "Chan/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
{
//Queue = 定义该SubShader的渲染队列 IgnoreProject = 不受投影影响 RenderType = 提前定义组???
Tags{"Queue" = "AlphaTest" "IgnoreProject" = "True" "RenderType" = "TransparentCutout"}
Pass
{
Tags{"LightMode" = "Forwardbase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float _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);
/*
if(texColor.a - _Cutoff) 为负
{
discard;//丢弃片元
}
*/
//同上 纹理A通道值小于_Cutoff,该片元被丢弃
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"
}
Clip()为负,该片元被丢弃。纹理A通道值小于_Cutoff,该片元被丢弃。否则按普通纹理,采样显示。
透明度混合(AlphaBlend)
透明度混合需要关闭深度写入。为了混合,需要Unity提供的混合模式的命令。设置了混合因子,就默认开启了混合模式。
语义 | 描述 |
Blend Off | 关闭混合 |
Blend On | 开启混合 |
Blend 混合因子A 混合因子B | 开启混合,并设置混合因子,具体混合操作看混合因子定义 |
BlendOp BlendOperation | 开启混合,使用BlendOperation已定义好的其他混合操作 |
关闭深度吸入的透明度混合效果 :
Shader "Chan/Chapter8_AlphaBlend" {
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
_AlphaScale("Alpha Scale",Range(0,1.0)) = 1.0
}
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;
float _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"
}
关闭深度写入后,模型自身有遮挡的情况就会出现右图情况。。。解决问题如下:
开启深度吸入的透明度混合效果 :
Shader "Chan/Chapter8_AlphaBlendZWrite" {
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_MainTex("Main Tex",2D) = "white"{}
_AlphaScale("Alpha Scale",Range(0,1.0)) = 1.0
}
SubShader
{
//透明度操作必有的三个标签
Tags{"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
//多了一个Pass
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;
float _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通道,1.开启深度写入,2.帧缓冲器中不写入颜色,只在深度缓冲器中写入深度值。
ColorMask 设置那些颜色通道可以写入帧缓存中。可以是RGBA四个通道中的任意组合,或者是0。例如:
ColorMask 0 不写入任何颜色
ColorMask RGA 写入颜色通道中的RGA通道。
开启深度写入效果如下:
透明度混合中的混合因子
混合操作时候:
- 有两个操作对象,源颜色S(片元着色器产生颜色)和目标颜色D(颜色缓冲区中读取的颜色),混合后得到输出颜色O(写入颜色缓冲区)。
- 混合时候,RGB通道和A通道时分开混合的,因此需要设置两个混合因子,如果只设置一个,A通道默认复用RGB通道混合因子
- 混合因子,默认是相加操作。使用其他操作,设置BlendOp BlendOperation
参数 | 描述 |
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-目标颜色的透明度值) |
透明度混合中的混合操作
操作 | 描述 |
Add | 将混合后的源颜色和目的颜色相加,默认的操作。 = SrcFactor x + DstFactor x = SrcFactorA x + DstFactorA x |
Sub | 用混合后的源颜色减去混合后的目的颜色。 = SrcFactor x - DstFactor x = SrcFactorA x - DstFactorA x |
RevSub | 混合后的目的颜色减去混合后的源颜色 = DstFactor x - SrcFactor x = DstFactorA x - SrcFactorA x |
Min | 使用源颜色和目的颜色中比较小的值,是逐分量比较的。 = |
Max | = |
其他逻辑操作 | 仅在DirectX 11.1中支持 |
常用混合类型
混合设置 | 混合效果 |
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 OneMinusDstColor | 滤色 |
Blend One One | 线性减淡 |
双面渲染的透明效果
透明物体,能看到物体内部及背面形状。因为渲染引擎默认剔除背对摄像机的图元。可通过Cull设置正反面剔除状态。
Cull Back/Front Off/On = 剔除 背面/正面 开启/关闭
双面透明度测试效果:
在AlphaTest Shader中添加 Cull Off不剔除背面即可。
双面透明度混合效果:
透明度混合关闭了深度写入,因此要控制物体正面背面渲染顺序。因此第一个Pass渲染背面,第二个Pass渲染正面。
相比AlphaBlend Shader中,添加一个跟原来一样的Pass。
然后第一个Pass 剔除正面-Cull Front 渲染背面,第二个Pass剔除背面-Cull Back渲染正面。