目录标题
片元的信息
对于渲染片元
,其所携带的信息包括颜色值
(用于与颜色缓冲区中的颜色进行合并----覆盖和混合),深度值
(用于与深度缓冲区中的深度值进行比较,通过测试后,在开启深度写入的情况下,更新深度缓冲区中的值),透明度
(用于透明度测试或者用作混合因子进行颜色混合(透明度混合))!
不透明物体间无需考虑渲染顺序!----深度缓冲
当场景中包含很多模型时,对于不透明 (opaque) 物体,由于深度缓冲depth buffer (z-buffer) 的存在,不必考虑它们的渲染顺序也能得到正确的渲染效果!即距离摄像机近的不透明物体总会把远的不透明物体遮挡。
例如A挡住B, 即便我们先渲B再渲染A也不用担心B会遮盖掉 A, 因为在进行深度测试时会发现A的深度值大于B,判断出B距离摄像机更远,B的颜色值也就不会写入到颜色缓冲区中 。
在实时渲染中 ,深度缓冲解决片元的可见性问题的基本思想是:
当渲染某个不透明物体的片元
时,在开启了深度测试
的前提下,需要把它的深度值和已经存储在深度缓冲中的深度值进行比较,如果渲染片元的深度值大于深度缓冲中已存储的深度值,则将渲染片元的颜色值覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲区中(如果开启了深度写入
)!反之如果渲染片元的深度值小于深度缓冲中已存储的深度值,就意味着这个片元没有通过测试,是不可见的,不应该被渲染到屏幕上(即有东西挡住了它,可能是遮挡的物体,也有可能是其他)。
(半)透明效果的实现
但如果想要实现透明效果,事情就不那么简单了。
在Unity 中,我们通常使用两种方法来实现透明效果:
- 使用透明度测试(Alpha Test):这种方法其实无法得到真正的半透明效果,要么完全透明,要么完全不透明;
即在片元进行深度测试,深度写入之前就进行透明度测试!它采用一种“霸道极端”的机制,只要某个片元的透明度不满足条件(通常是小于某个阙值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响。 - 使用透明度混合 (Alpha Blending):这种方法可以得到真正的半透明效果;
用透明度混合时,需要关闭了深度写入 (ZWrite),但并没有关闭深度测试!此时就需要格外注意渲染顺序----距摄像机由远到近依次将半透明物体进行渲染!
当渲染一个半透明物体的片元
时,先进行深度测试,比较渲染片元的深度值与当前深度缓冲中的深度值,未通过测试片元则直接被丢弃,不做任何处理;通过测试的片元认为是可见的,才有资格进行透明度混合,更新颜色缓冲区中的颜色,但不会进行深度写入。这就决定了当一个不透明物体出现在一个透明物体的前面,而我们先渲染了不透明物体,它仍然可以正常地遮挡住透明物体。也就是说,对于透明度混合来说,深度缓冲是只读的。
什么是透明度混合?使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。
对于透明度混合实现半透明效果为什么需要关闭深度写入功能?
如果不关闭深度写入,即在深度测试后进行深度写入和透明度混合, 一个半透明物体表面的后表面本来是可以透过它的前表面被我们看到见,在先渲染前表面时,由于深度测试时判断该半透明物体的后表面距离摄像机更远, 导致后面的表面将会被剔除, 我们也就无法透过半透明表面看到后面的物体了。 即使在满足半透明物体间正确的渲染顺序的情况下,先渲染半透明物体的后表面,再渲染前表面,得到的效果也并非正确的效果!
渲染引擎整体的渲染顺序:
渲染引擎一般都会先对物体进行排序,再渲染。常用的方法是。
(1) 先渲染所有不透明物体而无需考虑渲染顺序。
渲染不透明物体时无需也不能关闭深度写入!
(2)理想情况下,对于所有的半透明物体按照距摄像机由远及近的顺序依次渲染。
Unity通过渲染队列来解决渲染顺序
通过SubShader的Queue标签来决定模型将归于哪个渲染队列。Unity在内部使用一系列整数索引来表示每个渲染队列,且索引号越小表示越早被渲染!
将模型划分给相应的渲染队列,Unity根据已规定好的渲染队列顺序去对各个渲染队列进行顺序渲染,而渲染队列中的模型则根据渲染队列本身的规定顺序进行渲染,这里主要强调Transparent渲染队列本身的顺序----将半透明的物体按照距摄像机由远及近的顺序依次渲染。
例如:
对于复杂的半透明物体
即半透明物体表面的正面和背面相互覆盖(遮挡)的情况,如何渲染?
代码示例
通过透明度测试实现透明效果
三个基本的标签:
Queue标签属性值设置为 AlphaTest ,表明将该Shader(即使用该shader的模型归为AlphaTest渲染队列中)
RenderType 标签可以让 Unity 把这个 Shader 归入到提前定义的组(这里就是 TransparentCutout 组)中,以指明该 Shader是一个使用了透明度测试的 Shader
RenderType 标签通常被用于着色器替换功能。
IgnoreProjector 设置为 True, 这意味着这个 Shader 不会受到投影器 (Projectors) 的影响。通常,
使用了透明度测试的 Shader 应该在 SubShader 中设置这三个标签。
最后 LightMode 标签是 Pass标签中的一种,它用于定义该 Pass通道在Unity 的光照流水线中的角色。只有定义了正确的 LightMode,我们才能正确得到一些 Unity 的内置光照变量,例如_LightColor0。
Shader "Unlit/016"
{
Properties
{
_MainTex("MainTex",2D) = "White"{}
_Diffuse("Diffuse",Color) = (1,1,1,1)
_Cutoff("Alpha Cutoff",Range(0,1))=0.5
}
SubShader
{
Tags { "RenderType" = "TransparentCutout" "Queue"="AlphaTest" "IngnoreProjector"="True"}
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _Diffuse;
sampler2D _MainTex;
float4 _MainTex_ST;
float _Cutoff;
//用作顶点着色输出的输出结构体至少包含绑定了SV_POSITION语义的变量
struct v2f {
float4 vertex:SV_POSITION;
fixed3 worldnormal : TEXCOORD0;
float3 worldvertex:TEXCOORD1;
float2 uv:TEXCOORD2;
};
//通过片元着色器实现的漫反射+Blinn高光反射光照模型+添加纹理贴图
v2f vert(appdata_base v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldnormal = UnityObjectToWorldNormal(v.normal);
o.worldvertex = mul(unity_ObjectToWorld, v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag(v2f i) : SV_TARGET
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//纹理采样
fixed4 colortest = tex2D(_MainTex,i.uv);
if ((colortest.a - _Cutoff) < 0) {
discard;
}
fixed3 worldlightdirection = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * colortest.rgb * (dot(i.worldnormal, worldlightdirection) * 0.5 + 0.5);
return fixed4(ambient + diffuse,1);
}
ENDCG
}
}
}