[UnityShader学习日志]4.第八章实验记录
提要
- 透明是一种常见的效果,在实时渲染中若要实现透明效果,就需要在渲染模型时控制其渲染通道(Alpha),RGBA中的A指的就是透明度,也因此,当一个物体被渲染到屏幕上时,每个片元除了记录有颜色值和深度值外,还有透明度,当透明度为1,即完全不透明,为0则完全透明
- 本节将采用两种方法实现透明效果:透明度测试和透明度混合
实验1-1:透明度测试
- 透明度测试相对简单,但效果并不太好,尤其是对于半透明的物体,其原理就是对于一个片元,如果它的透明度低于某个值,那么就舍弃该片元(完全透明),否则就按照不透明的物体来处理。
Shader "Unlit/AlphaTest"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Main Tint",COLOR) = (1,1,1,1)
_Cutoff ("Alpha Cutoff",Range(0,1)) = 0.5
}
SubShader
{
Tags{"Queue" = "AlphaTest" "IgnoreProjector" = "True" "RenderType" = "TransparentCutout"}
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
fixed _Cutoff;
sampler2D _MainTex;
float4 _MainTex_ST;
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_TARGET0
{
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 = _LightColor0.rgb * albedo * max(0,dot(worldNormal,worldLightDir));
return fixed4(ambient + diffuse,1.0);
}
ENDCG
}
}
}
-
整体是比较简单的,对于透明度测试,只需要裁剪不满足的片元即可
-
可以看到,随着我们设定的阈值不同,材质上透明的方块数也不同,但每个方块也确实只有两种极端状态:完全透明和完全不透明
实验2-1:透明度混合
基础混合
- 透明度混合可以实现真正的半透明效果,它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,从而得到新的颜色,透明度混合需要关闭深度写入,这是为了防止透明物体后面的不透明物体没有被渲染到,同时这也使得我们需要非常小心物体的渲染顺序
- 混合是一个逐片元的操作,是不可编程的,但是高度可配置的,也就是说我们可以设置一些参数来配置混合(比如混合因子):
Blend SrcFactor DstFactor
Blend SrcFactor DstFactor,SrcFactorA,DstFactorA
- 在混合过程中,实际上是将两个颜色的RGB通道和A通道进行混合操作,这个操作Blend默认为加(也可以自定义),两个通道需要两个等式,每个等式需要两个混合因子,因此第一行的命令是让两个通道的混合因子都是相同的值,计算的操作实际上就是得到颜色的对应通道值等于SrcFactor * 源颜色(该片元颜色)的通道值加上DstFactor * 目标颜色(已经存在于颜色缓冲中的颜色)的通道值。
- 在接下来的实验中,我们将源颜色的混合因子设置成SrcAlpha(表示源颜色的透明度值),目标颜色的混合因子设置为OneMinusSrcAlpha(1-)
Shader "Unlit/AlphaBlend"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Main Tint",COLOR) = (1,1,1,1)
_AlphaScale ("Alpha Scale",Range(0,1)) = 1
}
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;
fixed _AlphaScale;
sampler2D _MainTex;
float4 _MainTex_ST;
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_TARGET0
{
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半透明效果
-
关闭深度写入,排好渲染顺序,然而很多模型都并非这么简单,模型的位置以及自身的各种因素都会导致普通的顺序不太适用,比如:
-
由于关闭了深度写入,这下就完全乱成一锅,折叠起来了,这个时候我们还需要一个Pass,它只负责深度写入,也就是先将模型的深度值写入到深度缓冲中,得到正确的深度信息,然后后一个Pass再处理刚刚的过程,这样缺点就是会对性能造成影响,以及模型内部间不会有任何真正的半透明效果
-
只需要在上一个代码的基础上再加一块即可
实验3-1:双面渲染的透明效果
- 现实生活中的物体若是,其实还可以看到它的内部结构,但刚刚实现的透明效果,我们无法看到正方体内部以及背面的形状,这是因为在渲染时渲染引擎会默认剔除物体背面(相对摄像机方向)的渲染图元,如果想要得到双面渲染的效果,我们需要使用Cull指令
Cull Back | Front | Off
- Cull代表剔除,可以指定剔除那一面的渲染图元,也可以off关闭掉剔除,双面渲染则需要关闭
- 对于透明度混合,由于我们关闭了深度写入,就需要小心控制渲染顺序得到正确的深度关系,于是我们需要分两个pass,先渲染背面后渲染正面
Shader "Unlit/AlphaBlend"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Color ("Main Tint",COLOR) = (1,1,1,1)
_AlphaScale ("Alpha Scale",Range(0,1)) = 1
}
SubShader
{
Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" }
Pass
{
Cull Front
Tags{"LightMode" = "ForwardBase"}
ZWrite off//关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
fixed _AlphaScale;
sampler2D _MainTex;
float4 _MainTex_ST;
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_TARGET0
{
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
{
Cull Back
Tags{"LightMode" = "ForwardBase"}
ZWrite off//关闭深度写入
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
fixed _AlphaScale;
sampler2D _MainTex;
float4 _MainTex_ST;
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_TARGET0
{
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
}
}
}