unity shader学习---透明度测试,透明度混合

unity实现透明效果

  • 效果图
  • 理论
  • Alpha Test
  • Alpha Blend
  • 改进
    • 开启深度写入的半透明效果
    • 双面渲染的透明效果
  • 代码部分

效果图

Alpha Test
unity shader学习---透明度测试,透明度混合_第1张图片
Alpha Test With Both Side:Cull Off关闭剔除
unity shader学习---透明度测试,透明度混合_第2张图片
Alpha Blend Scale = 0.5
unity shader学习---透明度测试,透明度混合_第3张图片
AlphaBlendZWrite:新加一个Pass,Zwrite On, 按照像素级别的深度排序结果进行透明渲染
unity shader学习---透明度测试,透明度混合_第4张图片

Alpha Blend With Both Side:两个Pass,Cull Front Cull Back
unity shader学习---透明度测试,透明度混合_第5张图片

理论

  • 渲染不透明和半透明物体

景中既有不透明物体,又有半透明物体时,渲染引擎一般都会先对物体进行排序,再渲染
(1) 先渲染所有不透明物体,开启它们的深度测试和深度写入,渲染顺序为从前往后(Front-to-Back)
(2) 再渲染半透明物体,开启它们的深度测试,但关闭深度写入,按它们距离摄像机的远近进行排序,渲染顺序为从后往前(Back-to-Front)

【渲染不透明物体】
不考虑渲染顺序也能得到正确的排序效果,利用深度缓冲(z-buffer)解决可见性问题。
当渲染每个片元时, 需要把它的深度值和已经存在于深度缓冲中的值进行比较(如果开启了深度测试),如果它的值距离摄像机更远,那么说明这个片元不应该被渲染到屏幕上(有物体挡住了它);否则,这个片元应该覆盖掉此时颜色缓冲中的像素值,并把它的深度值更新到深度缓冲中(如果开启了深度写入)

为什么从前往后渲染?
减少深度颜色缓冲器的写入操作,提升性能,避免overdraw

【渲染透明物体】
通过控制透明通道 (Alpha Channel) 实现透明效果

1、透明度测试:
不需要关闭深度测试和深度写入
产生的效果很极端,要么完全透明,即看不到,要么完全不透明,就像不透明物体那样。只要片元的透明度不满足条件(通常是小千某个阙值),那么就会被舍弃。否则,就会按照普通的不透明物体的处理方式来处理它,即进行深度测试、深度写入等。

2、透明度混合:
开启深度测试,关闭深度写入(深度缓冲是只读的)
得到真正的半透明效果,使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。

为什么要关闭深度写入?
如果不关闭深度写入,半透物体后面的表面将会被剔除,就无法透过半透明表面看到后面的物体了

为什么要非常小心物体的渲染顺序?
为了渲染的正确性
unity shader学习---透明度测试,透明度混合_第6张图片

如何排序?
尽可能让模型是凸面体,并且考虑将复杂的模型拆分成可以独立排序的多个子模型等

  • unity Shader的渲染顺序

unity shader学习---透明度测试,透明度混合_第7张图片
透明度测试
unity shader学习---透明度测试,透明度混合_第8张图片
透明度混合
unity shader学习---透明度测试,透明度混合_第9张图片

Alpha Test

【效果】
unity shader学习---透明度测试,透明度混合_第10张图片
unity shader学习---透明度测试,透明度混合_第11张图片
默认剔除背面,在Pass里设置Cull Off关闭剔除
unity shader学习---透明度测试,透明度混合_第12张图片
得到的透明效果在边缘处往往参差不齐有锯齿,这是因为在边界处纹理的透明度的变化精度问题
unity shader学习---透明度测试,透明度混合_第13张图片
【代码解析】
跟渲染普通的不透明物体几乎是一样的
FS:增加了对透明度判断并裁剪片元

Properties{
		_Color("Color Tint", Color) = (1, 1, 1, 1) // Diffuse
		_MainTex("Main Tex", 2D) = "white" {}
		_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5 // cutoff
}
SubShader{
	Tags {"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"} 
	Pass {
			Tags { "LightMode" = "ForwardBase" } // 定义了正确的 LigbtMode,才能正确得到一些 Unity的内置光照变量,例如_LigbtColorO

透明度测试的subshader的固定三个标签
“Queue” = “AlphaTest” : 透明度测试使用的渲染队列
“IgnoreProjector” = “True”:这个 Shader 不会受到投影器(Projectors)的影响
“RenderType” = “TransparentCutout”: 该 Shader是个使用了透明度测试的Shader

fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _Cutoff; //  范围在[O,1], 使用 fixed 来存储

和Properties 语义块中声明的属性建立联系

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;
};

VS略,同纹理映射
FS

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 = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

clip CG 中的一个函数

if ((texColor.a - _Cutoff) < 0.0) {
  discard;
}

FallBack "Transparent/Cutout/VertexLit"

和之前使用的 Diffuse Specular 不同,能够保证SubShader 无法在当前显卡上工作时有合适的代替 Shader, 还可以保证使用透明度测试的物体可以正确地向其他物体投射阴影

Alpha Blend

alpha scale = 0.3
unity shader学习---透明度测试,透明度混合_第14张图片
alpha scale = 0.8
unity shader学习---透明度测试,透明度混合_第15张图片
【代码解析】
FS:把当前自身的颜色和已经存在于颜色缓冲中的颜色值进行混合

Properties{
		_Color("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex("Main Tex", 2D) = "white" {}
		_AlphaScale("Alpha Scale", Range(0, 1)) = 1 // 控制透明度
}
SubShader{
	Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}

使用了透明度混合的 Shader都应该在 SubShader 中设置这3个标签,透明度测试用的是TransparentCutout,透明度混合用的是Transparent

Pass {
	Tags { "LightMode" = "ForwardBase" } // 正确提供各个光照变量
	ZWrite Off // 深度只读
	Blend SrcAlpha OneMinusSrcAlpha // 

开启并设置了该Pass的混合模式(重要!!)
将源颜色(该片元着色器产生的颜色)的混合因子设为SrcAlpha, 把目标颜色(已经存在于颜色缓冲中的颜色)的混合因子设为OneMinusSrcAlpha
在这里插入图片描述
当设置混合状态时,我们实际上设置的就是混合等式中的操作和因子unity shader学习---透明度测试,透明度混合_第16张图片
unity shader学习---透明度测试,透明度混合_第17张图片
混合操作:
unity shader学习---透明度测试,透明度混合_第18张图片
unity shader学习---透明度测试,透明度混合_第19张图片

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;
};

VS略
FS:

fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb; // albedo
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale); 

先采样纹理贴图,然后设置该FS返回值中的透明通道 (= 纹理像素的透明通道* AlphaScale )

改进

开启深度写入的半透明效果

由于关闭深度写入,无法对模型进行像素级别的深度,当模型本身有复杂的遮挡关系或是包含了复杂的非凸网格的时候,就会有各种各样因为排序错误而产生的错误的透明效果
一种解决方法是分割网格
一种解决方法是使用两个Pass来渲染模型,第一个 Pass 开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;第二个Pass 进行正常的透明度混合,但是对性能有一定影响

// 新加一个Pass
Pass{
	Zwrite On
	ColorMask 0 // 
}

ColorMask 用于设置颜色通道的写掩码 write mask,0代表 Pass 不写入任何颜色通道,即不会输出任何颜色

效果:
unity shader学习---透明度测试,透明度混合_第20张图片
unity shader学习---透明度测试,透明度混合_第21张图片

双面渲染的透明效果

默认情况下渲染引擎剔除了物体背面(相对于摄像机的方向)的渲染图元,而只渲染了物体的正面
Cull Back I Front I Off
如果设置为 Off,就会关闭剔除功能,那么所有的渲染图元都会被渲染,计算量成倍增加,因此除非是用于特殊效果,例如这里的双面渲染的透明效果,通常是不会关闭剔除功能的

  • 透明度测试的双面渲染
    Cull Off
  • 透明度混合的双面渲染
    分成两个 Pass——第一个 Pass 渲染背面(Cull Front),第二个 Pass只渲染正面(Cull Back),由于 Unity 会顺序执行 SubShader 中的各个 Pass, 从而可以保证先渲染背面,后渲染正面,渲染关系正确
    对比:
    unity shader学习---透明度测试,透明度混合_第22张图片
    unity shader学习---透明度测试,透明度混合_第23张图片

代码部分

【alpha test】

Shader "Custom/AlpbaTest"
{
	Properties{
			_Color("Color Tint", Color) = (1, 1, 1, 1) // Diffuse
			_MainTex("Main Tex", 2D) = "white" {}
			_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5 // cutoff
	}
	SubShader{
		// 透明度测试的subshader的固定三个标签
		// AlphaTest: 透明度测试使用的渲染队列
		// TransparentCutout:该 Shader是个使用了透明度测试的Shader
		// "IgnoreProjector" = "True":这个 Shader 不会受到投影器(Projectors)的影响
		Tags {"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"} 
		
		Pass {
			Tags { "LightMode" = "ForwardBase" } // 定义了正确的 LigbtMode,才能正确得到一些 Unity的内置光照变量,例如_LigbtColorO

			Cull Off // 关闭剔除,看到背面

			CGPROGRAM

			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"

			fixed4 _Color;
			sampler2D _MainTex;
			float4 _MainTex_ST;
			fixed _Cutoff; //  范围在[O I], 使用 fixed 来存储

			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);

				// Alpha test
				clip(texColor.a - _Cutoff);
				// Equal to 
//				if ((texColor.a - _Cutoff) < 0.0) {
//					discard;
//				}

				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"
}

【Alpha Blend】

// Q1
// 当模型本身有复杂的遮挡关系或是包含了复杂的非凸网格的时候,就会有各种各样因为排序错误而产生的错误的透明效果。
// 新加一个 Pass,开启深度写入,但不输出颜色; 第二个Pass 进行正常的透明度混合
// 就可以按照像素级别的深度排序结果进行透明渲染,但是对性能有一定的影响

// Q2 双面渲染
// 第一个 Pass 渲染背面,第二个 Pass只渲染正面
// 保证正确的深度渲染关系


Shader "Custom/AlphaBlend"
{
	Properties{
			_Color("Color Tint", Color) = (1, 1, 1, 1)
			_MainTex("Main Tex", 2D) = "white" {}
			_AlphaScale("Alpha Scale", Range(0, 1)) = 1 // 控制透明度
	}
	SubShader{
		// 通常,使用了透明度混合的 Shader都应该在 SubShader 中设置这3个标签,透明度测试用的是TransparentCutout
		Tags {"Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent"}
		

		// 新加一个Pass,按照像素级别的深度排序结果进行透明渲染,但是对性能有一定的影响
		Pass{
			Zwrite On
			ColorMask 0 // ColorMask 用于设置颜色通道的写掩码 write mask,0代表 Pass 不写入任何颜色通道,即不会输出任何颜色
		}
		
		Pass {
			Tags { "LightMode" = "ForwardBase" } // 正确提供各个光照变量
			// Cull Off
			ZWrite Off // 深度只读
			Blend SrcAlpha OneMinusSrcAlpha // 开启并设置了该Pass的混合模式[重要],将源颜色(该片元着色器产生的颜色)的混合因子设为SrcAlpha, 把目标颜色(已经存在于颜色缓冲中的颜色)的混合因子设为OneMinusSrcAlpha
			// BlendOp Min

			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 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; // albedo

				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;

				fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));

				return fixed4(ambient + diffuse, texColor.a * _AlphaScale); // 纹理像素的透明通道 * _AlphaScale
			}

			ENDCG
		}
	}
	FallBack "Transparent/VertexLit"
}

【Alpha Blend With Both Side】

Shader "Custom/Alpha Blend With Both Side" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_MainTex ("Main Tex", 2D) = "white" {}
		_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
	}
	SubShader {
		Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			// First pass renders only back faces 
			Cull Front
			
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			
			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 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
		}
		
		Pass {
			Tags { "LightMode"="ForwardBase" }
			
			// Second pass renders only front faces 
			Cull Back
			
			ZWrite Off
			Blend SrcAlpha OneMinusSrcAlpha
			
			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 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"
}

你可能感兴趣的:(图形学,unity,shader,alpha,test,alpha,blend)