透明
在实时渲染中要实现透明效果,通常在渲染模型时控制它的透明通道(Alpha Channel)。开启透明混合时,当一个物体被渲染到屏幕后时,每个片元除了颜色值和深度值之外,还有另一个属性透明度。透明度为1,完全不透明,为0,完全透明,像素完全不会显示。在unity中实现方法:1.透明度测试(Alpha Test),但是无法实现真正的半透明效果;2.透明度混合(Alpha Blending);
渲染顺序问题:在渲染不透明(opaque)物体时,不考虑渲染顺序也能正确的排序效果。由于强大的深度缓冲(depth buffer----------> z-buffer)的存在。深度缓冲用于解决可见性(visibility)的问题,决定那个物体的那个部分会被渲染到前面,那些部分会被其他物体遮挡,基本思想:根据深度缓存中的值来判断该片元距离摄像机的距离,当渲染一个片元时,需要把它的深度值和已经存在的深度缓冲值进行比较(如果开启了深度测试),它的值距离摄像机更远,则说明该片元不该被渲染 到屏幕,否则这个片元应该覆盖掉此时的颜色缓存中的像素值。并把它的深度值更新到深度缓冲中(如果开启的深度写入---ZWrite)。
透明测试和透明混合基本原理:
-
透明测试:只要一个片元不满足条件(小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元不会再进行任何处理,也不会对颜色缓冲产生影响;否则就按照不透明物体的处理方式处理它,深度测试、深度写入等。也就是说,透明度测试不需要关闭深度写入,它和其他不透明物体最大的不同就是根据深度值来舍弃一些片元。----造成要么透明,要么不透明的效果。
-
透明混合:使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。但是透明度混合需要关闭深度写入,所以要控制渲染的顺序,关闭了深度写入(如果不关闭,一个半透明表面背后的表面本来是可以透过它被看到,但是由于深度测试时判断结果是该半透明表面距离摄像机更近,导致后面的表面将会被剔除。渲染顺序就变得及其重要),没关闭深度测试,意味着当使用透明度混合渲染一个片元,还是会比较它的深度值与当前深度缓冲的深度值,如果它的深度值距离摄像机更远,就不再进行混合操作,这一点决定了当一个不透明物体出现在一个透明物体前,先渲染了不透明物体,它仍然可以正常地遮挡住透明物体,所以对于透明混合来说,深度缓冲是只读的。-----能够得到半透明效果。
由于透明混合对深度缓冲是只读的,先渲染透明,后渲染的物体会覆盖颜色缓冲中的颜色值。
(渲染引擎一般都会先对物体进行排序,再渲染)常用的两种方法
- 先渲染所有不透明物体,并开启它们的深度测试和深度写入。
- 把半透明物体按它们距离摄像机的远近进行排序(多个物体循环重叠无法排序,如果通过网格点来排序,也会出现问题,通常解决办法--分割网格,尽可能让模型是凸表面,减少排序错误;),然后按照从后往前的顺序渲染这些半透明物体,并开启深度测试,但关闭深度写入。
untiy shader渲染队列
渲染队列(render queue)解决渲染顺序问题,在subshade的Queue标签来决定我们的模型将归于那个渲染队列。在unitu内部使用一系列整数索引表示每个渲染队列,且索引越小越早渲染。
透明度测试
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
Shader "Unlit/6"
{
Properties{
_Color("_Color",Color)=(1,1,1,1)
_MainTex("_MainTex",2D)="white"{}
_CutOff("Alpha Test CoutOff",Range(0,1))=0.5
}
SubShader{
//alphaTest测试 Opaque Transparent Geometry Overlay
//alphaTest 只要有一个片元不满足条件就会被舍弃
//clip 函数----> 裁剪时使用的标量和矢量条件----》如果给定参数的任何一个分量是负数,就会舍弃当前像素的输出颜色
// IgnoreProjector 透明物体不受projector(投影器)的影响 ---不接受阴影
// RenderType 通常用于着色器替换功能 Opaque TransparentCutout
Tags{ "Queue"="AlphaTest" "IgnoreProjector"="True"
"RenderType"="TransparentCutout"}
Pass{
//LightMode 定义该pass在光照模型中的角色,
Tags{"LightMode"="ForwardBase"}
//Cull Off |Front|Back 默认时back 剔除物体背面(相对于摄像机) front 剔除前面 Off关闭剔除----》渲染图元数目会成倍增加。
//透明度测试关闭---》背面也会渲染---》可以看到内部结构
Cull Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _CutOff;
struct a2v{
float4 vertex:POSITION;
float4 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert(a2v a){
v2f v;
v.pos=UnityObjectToClipPos(a.vertex);
v.worldNormal=UnityObjectToWorldNormal(a.normal);
v.worldPos=mul(unity_ObjectToWorld,a.vertex).xyz;
//v.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
v.uv=a.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//根据顶点和纹理得到世界法线、坐标、uv再传递给片元着色器。
return v;
}
fixed4 frag(v2f v):SV_Target{
fixed3 worldLight=normalize(UnityWorldSpaceLightDir(v.worldPos));
fixed3 worldNormal=normalize(v.worldNormal);
fixed4 texColor=tex2D(_MainTex,v.uv);
//alpah test //为负数 舍弃该片元的输出 最终的结果 要么完全透明或不透明
clip(texColor.a - _CutOff);
/*if(texColor.a - _CutOff<0){
discard;//discard 剔除命令
}*/
fixed3 albedo=texColor.rgb * _Color.rgb;
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse=_LightColor0.rgb * albedo * saturate(dot(worldNormal,worldLight));
return fixed4(ambient+diffuse,1.0);
}
ENDCG
}
}
//如无法在当前显卡上工作时,可以有合适的shader来代替shader,还可以保证透明度测试的物体可以正确地向其他物体投射阴影。
FallBack "Transparent/Cutout/VertexLit"
// SubShader{
// Tags{"Queue"="Transparent"}
// Pass{
// ZWrite Off //关闭深度写入
// }
// }
}
透明度混合
Shader "Unlit/7"
{
Properties{
_Color("_COlor",Color)=(1,1,1,1)
_MainTex("_MainTex",2D)="white"{}
_AlphaScale("_AlphaScale",Range(0,2))=0.5
}
SubShader{
//表明使用了透明混合的shader
Tags{"Queue"="Transparent" "IgnoreProjector"="true" "RenderType"="Transparent"}
//透明度混合,使用当前片元透明度作为透明因子,与已经存储在颜色缓冲中的颜色值进行混合得到新的颜色。需要关闭深度写入
//Blend 混合模式 1.Off关闭
//2.SrcFactor DstFactor 开启混合并设置混合因子,源颜色(该片元产生的颜色)乘以SrcFactor,而且目标颜色(已经存在于颜色缓冲的颜色)会乘以DstFactor,最后相加再写入颜色缓冲。
// 3. SrcFactor DstFactor,SrcFactorA DstFactorA 与2相同,只是使用不同的因子来混合
// 4. BlendOp BlendOperation 并非是把源颜色和目标颜色简单相加后混合,而是使用 BlendOperation进行其他操作。
//最终的颜色 = SrcAlpha x SrcColor + (1-SrcAlpha)x DstColor
Pass{
Tags{"LightMode"="ForwardBase"}
ZWrite Off
//BlendOp 默认是Add
//正常混合
Blend SrcAlpha OneMinusSrcAlpha
//一个pass渲染前面 一个pass渲染后面,就得到了双面渲染的效果---》可以看到内部结构
Cull Front //渲染前面
// 柔和相加
//Blend OneMinusDstColor One
//正片叠底
//Blend DstColor Zero
//两倍相乘
//Blend DstColor SrcColor
// 变暗
//BlendOp Min
//Blend One One // 使用min或max,会忽略混合因子
// 线性减淡
//Blend One One
//变亮B
//BlendOp Max
//Blend One One
// 滤色
//Blend OneMinusDstColor One
// Or
//Blend One OneMinusSrcColor
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 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert(a2v a){
v2f v;
v.pos = UnityObjectToClipPos(a.vertex);
v.worldPos=mul(unity_ObjectToWorld,a.vertex).xyz;
v.worldNormal=UnityObjectToWorldNormal(a.vertex).xyz;
v.uv=TRANSFORM_TEX(a.texcoord,_MainTex);
return v;
}
fixed4 frag(v2f v):SV_Target{
fixed3 worldNormal=normalize(v.worldNormal);
fixed3 worldLight=normalize(UnityWorldSpaceLightDir(v.worldPos));
fixed4 texColor=tex2D(_MainTex,v.uv);
fixed3 albedo=texColor.rbg * _Color.rgb;
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse=_LightColor0.rgb * albedo * saturate(dot(worldNormal,worldLight));
return fixed4(ambient+diffuse,texColor.a*_AlphaScale);
}
ENDCG
}
Pass{
Tags{"LightMode"="ForwardBase"}
ZWrite Off
//BlendOp 默认是Add
//正常混合
Blend SrcAlpha OneMinusSrcAlpha
Cull Back //渲染后面
// 柔和相加
//Blend OneMinusDstColor One
//正片叠底
//Blend DstColor Zero
//两倍相乘
//Blend DstColor SrcColor
// 变暗
//BlendOp Min
//Blend One One // 使用min或max,会忽略混合因子
// 线性减淡
//Blend One One
//变亮B
//BlendOp Max
//Blend One One
// 滤色
//Blend OneMinusDstColor One
// Or
//Blend One OneMinusSrcColor
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 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert(a2v a){
v2f v;
v.pos = UnityObjectToClipPos(a.vertex);
v.worldPos=mul(unity_ObjectToWorld,a.vertex).xyz;
v.worldNormal=UnityObjectToWorldNormal(a.vertex).xyz;
v.uv=TRANSFORM_TEX(a.texcoord,_MainTex);
return v;
}
fixed4 frag(v2f v):SV_Target{
fixed3 worldNormal=normalize(v.worldNormal);
fixed3 worldLight=normalize(UnityWorldSpaceLightDir(v.worldPos));
fixed4 texColor=tex2D(_MainTex,v.uv);
fixed3 albedo=texColor.rbg * _Color.rgb;
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse=_LightColor0.rgb * albedo * saturate(dot(worldNormal,worldLight));
return fixed4(ambient+diffuse,texColor.a*_AlphaScale);
}
ENDCG
}
}
FallBack "Transparent/VertexLit"
}
开启深度写入的半透明
两个pass,一个pass仅仅将模型的深度值写入深度缓冲,这样可以得到正确的深度值,第二个pass进行正常的透明度混合。
Shader "Unlit/8"
{
Properties{
_Color("_COlor",Color)=(1,1,1,1)
_MainTex("_MainTex",2D)="white"{}
_AlphaScale("_AlphaScale",Range(0,2))=0.5
}
SubShader
{
Tags{"Queue"="Transparent" "IgnoreProjector"="true" "RenderType"="Transparent"}
//关闭深度写入而造成的错误排序,一种解决方式--使用两个pass来渲染模型,第一个pass开启深度写入。但不输出颜色,仅仅把深度值写入深度缓冲中。第二个pass正常混合。
//第一个pass就得到了正确的深度值。后续的就可以按照像素级别深度排序进行透明渲染。缺点在于会对性能造成一定的影响。
Pass{
ZWrite On
// ColorMask 用于设置颜色通道的写掩码(write mask) ColorMask RGB |A|0 其他任何R、G、B、A的组合
//为0时,意味这该pass不写入任何颜色通道,既不会输出任何颜色。
ColorMask 0
}
Pass{
Tags{"LightMode"="ForwardBase"}
ZWrite Off
//想要使用混合,必须首先开启它
// SrcFactor DstFactor,SrcFactorA DstFactorA 与2相同,只是使用不同的因子来混合
//rgb=SrcFactor * S+DstFactor*D
//a=SrcAlpha * Sa +DstFactorA *Da
Blend SrcAlpha OneMinusSrcAlpha,One Zero
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 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert(a2v a){
v2f v;
v.pos = UnityObjectToClipPos(a.vertex);
v.worldPos=mul(unity_ObjectToWorld,a.vertex).xyz;
v.worldNormal=UnityObjectToWorldNormal(a.vertex).xyz;
v.uv=TRANSFORM_TEX(a.texcoord,_MainTex);
return v;
}
fixed4 frag(v2f v):SV_Target{
fixed3 worldNormal=normalize(v.worldNormal);
fixed3 worldLight=normalize(UnityWorldSpaceLightDir(v.worldPos));
fixed4 texColor=tex2D(_MainTex,v.uv);
fixed3 albedo=texColor.rbg * _Color.rgb;
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse=_LightColor0.rgb * albedo * saturate(dot(worldNormal,worldLight));
return fixed4(ambient+diffuse,texColor.a*_AlphaScale);
}
ENDCG
}
}
FallBack "Diffuse"
}
双面渲染的透明
使用Cull Back|off|Front 命令进行控制剔除。默认剔除Back。透明度测试,直接关闭掉剔除 cull Off ,当关闭时,图元数据会成倍增加; 透明混合则一个pass 是由Cull Front渲染前面,一个pass使用 Cull Back渲染后面。