您的位置:首页 > 移动开发 > Unity3D

Unity_Shader学习(六)基础透明Shader

2019-01-28 15:11 309 查看

今天学习了简单的透明Shader的实现,真的是比想象中简单很多,但是有很多需要注意的点,特别是渲染顺序!

 

1. 渲染顺序

首先我们要知道,对于透明物体,他只开启了深度测试而没有开启深度写入

我们来考虑这样一种情况,物体A是透明的,物体B是不透明的。物体A在物体B前面,刚好遮住了B。

(1)如果我们先渲染物体B,B将会写入颜色缓冲与深度缓冲中。然后渲染物体A,我们发现物体A是透明物体,它将会进行深度测试,发现A比B离摄像机更近,因此我们将A的信息与颜色缓冲中的信息(即B的信息)进行混合,得到了正确的结果

(2)如果我们先渲染物体A,A发现颜色缓冲与深度缓冲是空的,于是它直接写入颜色缓冲。然后渲染B,B发现深度缓冲是空的,于是它不去考虑颜色缓冲有没有信息,直接用自己的信息覆盖了原来的颜色缓冲中的信息。这样视觉显示上B就在A前面了,得到错误的结果

据此,我们确定不透明物体必须先于透明物体渲染

 

我们来考虑另一种情况,物体A和B都是透明的,物体A在物体B前面,遮住了物体B。

(1)如果先渲染物体B,物体B的信息会写入颜色缓冲。然后渲染物体A,A会与颜色缓冲中的信息进行混合,得到相应的效果

(2)如果先渲染物体A,然后渲染物体B,我们就得到了刚好相反的结果...

据此,我们确定透明物体必须按由远至近的顺序渲染

 

2.透明度测试

在UnityShader中有以下几个渲染队列:

Background:这是最先渲染的队列,通常用于渲染背景信息

Geometry:一般不透明物体都在这个队列中

AlphaTest:需要透明度测试的物体使用这个队列

Transparent:进行透明度混合的物体使用这个队列

Overlay:该队列用于实现一些叠加效果

 

接下来我们来实现透明度测试,透明度测试是指,只要一个片元的透明度不满足条件,就舍弃这个片元。也就是说一个片元要么全部保留,要么全部舍弃,我们这样就得到完全透明与不透明两种极端的片元类型。

[code]Shader "Custom/MyAlphaTest" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("Albedo (RGB)", 2D) = "white" {}
_CutOff("CutOff",Range(0,1.0)) = 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;
sampler2D _MainTex;
float4 _MainTex_ST;
float _CutOff;

struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD0;
};

struct v2f{
float4 pos:SV_POSITION;
float2 uv:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float3 worldPos:TEXCOORD2;
};

v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);

o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldNormal = normalize(mul(v.normal, unity_WorldToObject).xyz);

o.uv = _MainTex_ST.xy * v.texcoord.xy + _MainTex_ST.zw;

return o;
}

fixed4 frag(v2f i):SV_TARGET{
fixed3 worldNormal = i.worldNormal;
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

fixed4 texColor = tex2D(_MainTex,i.uv);

clip(texColor.a - _CutOff);

fixed3 albedo = _Color.rgb * texColor.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(worldLightDir,worldNormal));

return fixed4(ambient + diffuse,1.0);
}

ENDCG
}
}
FallBack "Diffuse"
}

在这个Shader中,我们用_CutOff来表示透明度条件的阈值,在SubShader的标签中,我们标明队列为AlphaTest,设置IngnoreProjector为true来忽略投影,用RenderType将Shader归入组TransparentCutout中。在frag函数中,我们使用了clip函数,这个函数为:

[code]void clip(float4 x){

if(any(x<0))
discard;

}

效果如下:

 

 

2.透明度混合

透明度混合比透明度测试复杂,为了进行混合,我们需要使用Blend命令:

Blend Off:关闭混合

Blend SrcFactor Dstfactor:开启混合并设置混合因子

Blend SrcFactor Dstfactor A B:和上面基本相同,只是使用了不同的因子A与B

Blend BlendOperation:使用特定函数

 

最终颜色=src因子 * 该物体颜色 + dst因子 * 颜色缓冲区颜色

 

[code]Shader "Custom/AlphaBlend" {
Properties {
_Color ("Color", Color) = (1,1,1,1)
_MainTex ("MainTex", 2D) = "white" {}
_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;
sampler2D _MainTex;
float4 _MainTex_ST;
float _AlphaScale;

struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 texcoord:TEXCOORD;
};

struct v2f{
float4 pos:SV_POSITION;
float3 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv:TEXCOORD2;
};

v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldNormal = normalize(mul(v.normal, unity_WorldToObject).xyz);
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;

return o;
}

fixed4 frag(v2f i):SV_TARGET{
float3 worldNormal = i.worldNormal;
float3 LightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT * albedo;
fixed3 diffuse = albedo * _LightColor0.rgb * saturate(dot(worldNormal, LightDir));
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}

ENDCG
}
}
FallBack "Diffuse"
}

_AlphaScale用于设置透明度,Queue设置为Transparent,RenderType设置为Transparent。同时关闭深度写入ZWrite=Off,然后使用Blend命令设置混合为最终颜色为SrcAlpha * 该物体颜色 + (1-SrcAlpha) * 缓冲区颜色。效果如下:

 

3.开启深度写入的透明度混合

对于某些形状诡异扭来扭曲的物体....如果我们直接采用上面的透明度混合方法,就会出现这种情况:

这是由于我们关闭了深度写入造成的,那么怎么在实现透明的同时避免这个问题呢?

我们可以采用两个Pass,第一个Pass将模型深度值写入深度缓冲中,第二个Pass进行透明度混合。

第一个Pass实现如下:

[code]                Pass{
ZWrite On
ColorMask 0
}

第二个Pass采用透明度混合Pass即可。这样得到正确的结果:

但是这种处理方式同样有缺点,即它将无法实现下面的透明物体自身的效果。

 

4.透明物体自身的问题

如果我们仔细观察上述Shader效果,我们会发现虽然可以透过透明物体看到其他物体,但是却不能透过透明物体的一个面看到自身的另一个面,这是由于默认的剔除设置:Cull Back。这个设置会自动剔除背面的信息,从而导致上述问题。我们现在关闭剔除,即在Pass块中加上命令Cull Off。得到如下效果:

可以看出透明度测试得到了正确的结果,但透明度混合由于关闭了深度写入,不能确保图元从后往前渲染,得到了不能清楚辨识的视觉效果。所以对于透明度混合,我们采用两个Pass进行处理,第一个Pass只渲染背面Cull Front,第二个Pass只渲染正面Cull Back,这样确保了渲染顺序得到了可以清楚辨识正面背面的视觉效果:

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: