标签(空格分隔): shader
Unity中使用2种方法实现透明效果:一是透明度测试,二是透明度混合。
透明度测试:不需要关闭深度写入,透明度不达标就去掉,所以物体要么不透明要么完全透明。
透明度混合:可以得到真正的半透明效果。原理是将透明度与颜色缓冲的颜色混合得到新的颜色。但是这需要关闭深度写入。只有关闭深度写入才能吧保证半透明物体不影响不透明物体或半透明后面半透明物体的渲染
关闭深度写入后就需要注意物体的渲染顺序,因为关闭之前物体的渲染是由深度z-buffer决定,必须渲染半透明物体后的非透明后物体(这里的前后指的是相对虚拟摄像头的距离),再渲染透明物体,否则未关闭的深度测试会直接覆盖透明度混合后的颜色缓冲,导致失去了该有的半透明效果。
可是即便设置好了渲染顺序,有时候依旧会出现问题,比如两根缠绕的绳子,根本难以分清2个物体的前后,无法制定合理的渲染顺序。
要想解决这种问题就需要分割网格。
为解决渲染顺序,unity提供了渲染队列。Queue标签决定模型使用哪个队列
名称 | 队列索引 | 描述 |
---|---|---|
Background | 1000 | 最早渲染的队列,用于渲染背景 |
Geometry | 2000 | 默认渲染队列,不透明物体使用这个 |
AlphaTest | 2450 | 透明度测试 |
Transparent | 3000 | 使用透明度混合或关闭深度写入的shader |
Overlay | 4000 | 使用一些叠加效果,最后渲染的物体都使用该队列 |
使用实例
如果要使用透明度测试,需要这般加入以下tag
SubShader {
Tags {"Queue"="AlphaTest"
同理,透明度混合需要这般使用以下tag
SubShader {
Tags {"Queue"="Transparent"
unity片元着色器会使用clip函数进行透明度测试
void clip(float4 x)
{
if(any(x<0))
discard;
}
配置
配置中tags相当关键,Queue标签之前提过,暂且不提RenderType是用以制定渲染的队列,”IgnoreProjector”=”True”是保证shader不受投影器的影响
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_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;
sampler2D _MainTex;
float4 _MainTex_ST;
//_Cutoff范围只有[0,1],所有使用fix精度
fixed _Cutoff;
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);
}
Fallback
使用内置的Transparent/Cutout/VertexLit的作为fallback保证shader无法在当前显卡工作时有可替换的shader。
FallBack "Transparent/Cutout/VertexLit"
同样,对应透明度混合,unity提供了内置命令Blend
语义 | 描述 |
---|---|
Blend Off | 关闭混合 |
Blend SrcFactor DstFactor | 开启混合 |
代码实现
首先是配置
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" }
//关闭深度写入
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;
};
片元着色器
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));
//在这里设置透明度通道,必须使用Blend命令,设置透明度通道才有意义
return fixed4(ambient + diffuse, texColor.a * _AlphaScale);
}
开启深度写入也可以实现半透明效果,不过需要使用2个pass,会造成一定的性能损耗
Pass {
ZWrite On
ColorMask 0
}
Pass {
之前的透明度混合代码
这第一个pass是为把模型深度信息输入深度缓存,同时设置ColorMask为0让该pass不用输出任何掩饰
这实现是取消了剔除功能,渲染了所有物体,会消耗较多的性能
Cull Off//关闭剔除功能
透明度混合的双面渲染
由于开启了双面渲染,为保证渲染顺序,我们需要重新设置代码
Pass {
// First pass renders only back faces
Cull Front
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
。。。。
}
Pass {
Tags { "LightMode"="ForwardBase" }
// Second pass renders only front faces
Cull Back
。。。。
使用2个pass利用cull指令制定渲染顺序,从而剔除不同朝向的渲染图元
这只是我个人的学习笔记和个人总结,如有不当之处,望指正。