《Unity Shader入门精要》第八章笔记 | 透明度
渲染顺序
名称 | 队列索引号 | 描 述 |
---|---|---|
Background | 1000 | 这个渲染队列会在任何其他队列之前被渲染,我们通常使用该队列来渲染那些需要绘制在背景上的物体 |
Geometry | 2000 | 默认的渲染队列,大多数物体都使用这个队列。不透明物体使用这个队列 |
AlphaTest | 2450 | 需要透明度测试的物体使用这个队列。在Unity 5中它从Geometry队列中被单独分出来,这是因为在所有不透明物体渲染之后再渲染它们会更加高效 |
Transparent | 3000 | 这个队列中的物体会在所有Geometry和AlphaTest物体渲染后,再按从后往前的顺序进行渲染。任何使用了透明度混合(例如关闭了深度写入的Shader)的物体都应该使用该队列, srpite模式是该队列 |
Overlay | 4000 | 该队列用于实现一些叠加效果。任何需要在最后渲染的物体都应该使用该队列 |
透明度测试
透明度测试: 只要一个片元的透明度不满足条件(通常是小于某个阈值),那么它对应的片元就会被舍弃。被舍弃的片元将不会再进行任何处理,也不会对颜色缓冲产生任何影响;否则,就会按照普通的不透明物体的处理方式来处理它
- 函数 :void clip(float4 x); void clip(float3 x); void clip(float2 x); void clip(float1 x); void clip(float x);
- 参数 :裁剪时使用的标量或矢量条件。
- 描述 :如果给定参数的任何一个分量是负数,就会舍弃当前像素的输出颜色。
代码
Shader "Unity Shaders Book/Chapter 8/Alpha Test" {
Properties {
_Color ("Main 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;
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 = mul(UNITY_MATRIX_MVP, v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(_Object2World, 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"
}
透明度混合
- 这种方法可以得到真正的半透明效果。
- 它会使用当前片元的透明度作为混合因子,与已经存储在颜色缓冲中的颜色值进行混合,得到新的颜色。
- 透明度混合需要关闭深度写入,这使得我们要非常小心物体的渲染顺序。
ShaderLab的Blend命令
语 义 | 描 述 |
---|---|
Blend Off | 关闭混合 |
Blend SrcFactor DstFactor | 开启混合,并设置混合因子。源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,然后把两者相加后再存入颜色缓冲中 |
Blend SrcFactor DstFactor, SrcFactorA DstFactorA | 和上面几乎一样,只是使用不同的因子来混合透明通道 |
BlendOp BlendOperation | 并非是把源颜色和目标颜色简单相加后混合,而是使用BlendOperation对它们进行其他操作 |
与透明度测试不同的是,我们还需要在Pass中为透明度混合进行合适的混合状态设置
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
代码
Shader "Unity Shaders Book/Chapter 8/Alpha Test" {
Properties {
_Color ("Main 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;
};
v2f vert(a2v v) {
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(_Object2World, 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"
}
只有使用Blend命令打开混合后,我们在这里设置透明通道才有意义
开启深度写入的半透明效果
- 第一个Pass开启深度写入,但不输出颜色,它的目的仅仅是为了把该模型的深度值写入深度缓冲中;
- 第二个Pass进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染 使用这种方法,我们仍然可以实现模型与它后面的背景混合的效果,但模型内部之间不会有任何真正的半透明效果。
ShaderLab的混合命令
混合等式和参数
ShaderLab中设置混合因子的命令
命 令 | 描 述 |
---|---|
Blend SrcFactor DstFactor | 开启混合,并设置混合因子。源颜色(该片元产生的颜色)会乘以SrcFactor,而目标颜色(已经存在于颜色缓存的颜色)会乘以DstFactor,然后把两者相加后再存入颜色缓冲中 |
Blend SrcFactor DstFactor, SrcFactorA DstFactorA | 和上面几乎一样,只是使用不同的因子来混合透明通道 |
ShaderLab中的混合因子
参 数 | 描 述 |
---|---|
One | 因子为1 |
Zero | 因子为0 |
SrcColor | 因子为源颜色值。当用于混合RGB的混合等式时,使用SrcColor的RGB分量作为混合因子;当用于混合A的混合等式时,使用SrcColor的A分量作为混合因子 |
SrcAlpha | 因子为源颜色的透明度值(A通道) |
DstColor | 因子为目标颜色值。当用于混合RGB通道的混合等式时,使用DstColor的RGB分量作为混合因子;当用于混合A通道的混合等式时,使用DstColor的A分量作为混合因子。 |
DstAlpha | 因子为目标颜色的透明度值(A通道) |
OneMinusSrcColor | 因子为(1-源颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子 |
OneMinusSrcAlpha | 因子为(1-源颜色的透明度值) |
OneMinusDstColor | 因子为(1-目标颜色)。当用于混合RGB的混合等式时,使用结果的RGB分量作为混合因子;当用于混合A的混合等式时,使用结果的A分量作为混合因子 |
OneMinusDstAlpha | 因子为(1-目标颜色的透明度值) |
如果我们想要在混合后,输出颜色的透明度值就是源颜色的透明度,可以使用下面的命令
Blend SrcAlpha OneMinusSrcAlpha, One Zero
混合操作
操 作 | 描 述 |
---|---|
Add | 将混合后的源颜色和目标颜色相加。默认的混合操作。使用的混合等式是:{{O}{rgb}}=SrcFactor\times {{S}{rgb}}+DstFactor\times {{D}{rgb}} {{O}{a}}=SrcFactorA\times {{S}{a}}+DstFactorA\times {{D}{a}} |
Sub | 用混合后的源颜色减去混合后的目标颜色。使用的混合等式是:{{O}{rgb}}=SrcFactor\times {{S}{rgb}}-DstFactor\times {{D}{rgb}} {{O}{a}}=SrcFactorA\times {{S}{a}}-DstFactorA\times {{D}{a}} |
RevSub | 用混合后的目标颜色减去混合后的源颜色。使用的混合等式是:{{O}{rgb}}=DstFactor\times {{D}{rgb}}-SrcFactor\times {{S}{rgb}} {{O}{a}}=DstFactorA\times {{D}{a}}-SrcFactorA\times {{S}{a}} |
Min | 使用源颜色和目标颜色中较小的值,是逐分量比较的。使用的混合等式是:{{O}{rgba}}=(\min ({{S}{r}},{{D}{r}}),\min ({{S}{g}},{{D}{g}}),\min ({{S}{b}},D{}{b}),\min ({{S}{a}},{{D}_{a}})) |
Max | 使用源颜色和目标颜色中较大的值,是逐分量比较的。使用的混合等式是:{{O}{rgba}}=(\max ({{S}{r}},{{D}{r}}),\max ({{S}{g}},{{D}{g}}),\max ({{S}{b}},D{}{b}),\max ({{S}{a}},{{D}_{a}})) |
其他逻辑操作 | 仅在DirectX 11.1中支持 |
常见的混合类型
// 正常(Normal),即透明度混合
Blend SrcAlpha OneMinusSrcAlpha
// 柔和相加(Soft Additive)
Blend OneMinusDstColor One
// 正片叠底(Multiply),即相乘
Blend DstColor Zero
// 两倍相乘(2x Multiply)
Blend DstColor SrcColor
// 变暗(Darken)
BlendOp Min
Blend One One
// 变亮(Lighten)
BlendOp Max
Blend One One
// 滤色(Screen)
Blend OneMinusDstColor One
// 等同于
Blend One OneMinusSrcColor
// 线性减淡(Linear Dodge)
Blend One One
双面渲染的透明效果
可以使用Cull 指令来控制需要剔除哪个面的渲染图元。在Unity中,Cull指令的语法如下
Cull Back | Front | Off
透明度测试的双面渲染
和透明度测试的代码一样,只是多了一行代码, 如下
Pass {
Tags { "LightMode"="ForwardBase" }
// Turn off culling
Cull Off
我们可以透过正方体的镂空区域看到内部的渲染结果。
透明度混合的双面渲染
透明度混合需要关闭深度写入。复制原Pass的代码,得到另一个Pass。
- 第一个Pass只渲染背面 Cull Front
- 第二个Pass只渲染正面 Cull Back
扩展阅读
ZTest (copy)
比较新旧z值的大小,就是Ztest;之后更新摄像机每一个像素的z值,就是Zwrite。 Ztest影响的是当前物体的显示;Zwrite影响的是之后渲染物体的显示。
Zwrite的概念相对简单,无非就是根据条件,对一个变量进行反复地赋值。比较有意思的Ztest。 在三个盒子的例子里,我一直都在强调“默认”两个字。那么默认是什么呢,就是Zwrite On + Ztest On。Zwrite就两种情况(On或者Off)。
而对于Ztest来说,条件就要丰富得多得多。Ztest的条件总共有如下几种: Less (当物体的这个像素的Z值小于当前摄像机在这个像素上的Z值,则通过Ztest)
LEqual(条件变为小于等于)
Greater(条件变为大于)
GEqual(条件变为大于等于)
Equal(条件变为相等)
NotEqual(条件变为不相等)
Always(Ztest永远通过)
Never(Ztest永远不通过)
Off(等同于 ZTest Always)
On(等同于ZTest LEqual)
Stencil (copy)
我们假定Stencil也有一个值叫Ref值。那么Stencil的用法也实在是看着眼熟:获取Ref值->测试(比较)Ref值->写入新的Ref值(如果通过测试)。
说到底这俩玩意儿的区别,就是在第一步,获取当前物体在这个像素上的这个变量。
Z值是根据像素到摄像机的距离算出来的,不会因为你的个人意愿而改变;S值是你可以随便填的(是的随便填,想写几就写几,范围0-255)。
这样一来Stencil可以帮助你突破Ztest所带来的限制,用更灵(jian)活(dan)便(cu)捷(bao)的方式来控制渲染效果。
Ref就是写入这个像素的Ref值,正如我之前提到的想写几就写几完全看心情(所以我一直都认为叫Stencil Buffer模板缓冲实在是有点唬人的感觉。改成“看哪个数字顺眼就用哪个数字比大小”更贴切一些)。
Com是进行Test的条件,当你看到一大堆Less\LEqual\Greater\GEqual\Equal\NotEqual\Always\Never这样的字眼儿,是不是感到非常的眼熟?这一步比较的过程和Ztest完全一样。
Pass和Zwrite简直就是一个妈生出的俩个孩儿。区别就是这个小哥比他兄弟花样儿多点。Zwrite无非就是写入或者不写入(On or Off)。Pass甚至还可以控制如何写入(虽然大多数情况下可能用不到)。
引用
ztest: https://zhuanlan.zhihu.com/p/28557283
--完--
- 原文作者: 留白
- 原文链接: https://zfunnily.github.io/2022/05/shaderalpha/
- 更新时间:2024-04-16 01:01:05
- 本文声明:转载请标记原文作者及链接