第7章 透明效果
不透明和半透明物体渲染顺序、混合效果、透明测试、模板测试
- 按照正常思维,先绘制距离摄像机远的物体,然后再绘制离摄像机近的物体,绘制出来的结果并没有问题,但是如果从性能方面考虑,这个会产生
重叠绘制(OverDraw)
当重叠过多会造成巨大的性能开销,但是如果仅仅改变绘制的顺序并不能解决性能开销反而还会造成绘制出来物体显示的错误。所以引入了深度值(Depth)存储在屏幕空间顶点坐标的z中,这个深度值与物体距离屏幕远近距离成正比
深度测试
绘制时片段如果Depth比深度缓冲中的值大说明这个片段被其他物体遮挡,不应该被绘制出来,应被丢弃。深度写入
将Depth更新至深度缓存中
假设半透明物体距离摄像机近,不透明物体距离摄像机远
Unity中深度测试在顶点着色后进行,图像叠加是在最后一步Blending阶段完成,并且只有在Blending阶段才知道挡住自己的是否是半透明物体
- (“Queue” = “Transparent”)为半透明物体,在所有不透明物体渲染完成后绘制,但是这样并没有解决半透明物体遮挡住了不透明物体,因为
开启了深度测试,当不透明物体绘制时,发现深度缓冲器的值比自己大,半透明物体依旧会将不透明物体的绘制丢弃
- 那么有什么办法解决呢?
– 1. 关闭不透明物体的深度写入ZTest Off
,但是如果关闭了深度测试,如果前方还有其他不透明物体会直接覆盖×
– 2. 让不透明物体的片段不会被丢弃,深度测试总是通过的ZTest Always
,如果前方有不透明物体,根本不会被遮挡×
– 3. 关闭透明物体的深度写入ZWrite Off
, 这样会有一个问题:如果场内有多个半透明物体,并且还是按照先绘制距离近的半透明物体的话,那么就会导致远的半透明物体在近的半透明物体上面,所以Unity中半透明渲染队列是按照先绘制远的半透明物体,再绘制近的半透明物体
- 当所有Shader执行完成后,需要将新渲染的的图像和已经存在的图像合并这时候需要用到
混合指令(Blending),可以在SubShader和Pass中使用,SubShader中会影响到所有Pass
Blend Off
关闭混合处理,什么都不写就是这个Blend SrcFactor DstFactor
SrcFactor新渲染出来的混合系数,DstFactor目标图像的混合系数
C o l o r r g b a = S o u r c e r g b a ∗ S r c F a c t o r + D e s t i n a t i o n r g b a ∗ D s t F a c t o r Color_{rgba}=Source_{rgba}*SrcFactor+Destination_{rgba}*DstFactor Colorrgba=Sourcergba∗SrcFactor+Destinationrgba∗DstFactorBlend SrcFactor DstFactor SrcFactorA DstFactorA
和上面一样,但是对于alpha通道的混合系数用后面2个混合系数
C o l o r r g b = S o u r c e r g b ∗ S r c F a c t o r + D e s t i n a t i o n r g b ∗ D s t F a c t o r Color_{rgb}=Source_{rgb}*SrcFactor+Destination_{rgb}*DstFactor Colorrgb=Sourcergb∗SrcFactor+Destinationrgb∗DstFactor
C o l o r a = S o u r c e a ∗ S r c F a c t o r + D e s t i n a t i o n a ∗ D s t F a c t o r Color_{a}=Source_{a}*SrcFactor+Destination_{a}*DstFactor Colora=Sourcea∗SrcFactor+Destinationa∗DstFactorBlendOp Op
使用其他 *** 作进行混合,不再只是RGBA常用的混合指令、所有可以使用的混合系数、混合指令表
P_116
Shader "Unlit/BlendingTransparent"
{
Properties
{
_MainTex("MainTexture", 2D) = "White"{}
_MainColor("MainColor", Color) = (1,1,1,1)
}
SubShader
{
Tags{
"Queue" = "Transparent"
"RenderType" = "Transparent"
"IgnoreProjector" = "True"
}
Pass
{
Tags{"LightMode" = "ForwardBase"}
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha // 普通透明叠加
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float4 worldPos:TEXCOORD0;
float2 uv:TEXCOORD1;
float3 worldNormal:TEXCOORD2;
};
float4 _MainColor;
sampler2D _MainTex;
fixed4 _MainTex_ST;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
// 纹理坐标变换,没有缩放平移直接填v.uv
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
float3 normal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = normalize(normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float worldLight = UnityWorldSpaceLightDir(i.worldPos);
worldLight = normalize(worldLight);
fixed ndotl = dot(i.worldNormal, worldLight);
fixed4 c = tex2D(_MainTex, i.uv);
c.rgb *= _LightColor0 * _MainColor.rgb * saturate(ndotl);
c.rgb += unity_AmbientSky;
c.a *= _MainColor.a;
return c;
}
ENDCG
}
}
FallBack "Diffuse"
}
半透明物体双面渲染
- 单纯的加上Cull Off会导致反面叠加到正面上了,因为关闭了深度写入,没有深度信息导致正反面绘制顺序错误,
为了不让方面叠加在正面上,可以先绘制反面再去绘制正面,这样就需要2个Pass分别绘制反面和正面
代码都是一样的,只需要在反面Pass加上Cull Front,正面Pass加上Cull Back
- clip()可以丢弃数值小于0的像素
clip(c.a - _AlphaTest)
,效果如下,可以通过添加AlphaToMask On
来开启抗锯齿
除了透明混合、透明测试还可以通过模板测试达到透明效果
- 模板缓存中,每个像素都有8位整数值(0~255)的模板值,在执行DrawCall时,会对模板值进行比较,不符合要求的像素将会被丢弃,从而达到透明效果
- 模板测试流程,给物体A指定参考值m,让A直接通过模板测试,并将m写入模板缓存中,给物体B指定参考值n,将n和模板缓存中的m进行比较有2种情况
(通过或未通过模板测试),在模板测试结束之后,可以对模板缓存中的模板值进行 *** 作(替换或保留)
- 语法
Stencil
{
Ref referenceValue 上面说的参考值(0~255)
ReadMask readMask 读模板,例如11111111代表所有位都可以读取
WriteMask writeMask 写模板,当为0时,表示没有数值会被写入缓存,255表示所有位都允许写入
Comp comparisonFunction 参考值与缓存中的值进行比较函数,默认为always
Pass stencilOperation 如果模板测试和深度测试都通过,缓存中的模板值怎么处理,默认keep
Fail stencilOperation 如果模板测试未通过,缓存中的模板值怎么处理,默认keep
ZFail stencilOperation 如果模板测试通过,但是深度测试未通过,缓存中的模板值怎么处理,默认keep
}
比较的方法Comp:
渲染像素的参照值-符号-模板缓存中的值
Greater > 通过
GEqual >= 通过
Less < 通过
LEqual <= 通过
Equal = 通过
NotEqual != 通过
Always 总是通过
Never 总是不通过
模板 *** 作
Keep 继续保持模板缓存中的值
Zero 把0写入
Replace 替代
IncrSat 递增,如果等于255将停止
DecrSat 递减,如果为0将停止
Invert 将模板值按位取反
IncrWrap 递增,如果等于255变为0
DecrWrap 递减,如果为0变为255
下面是模板测试代码
Shader "Hidden/StencilTestA"
{
SubShader
{
Tags { "Queue"="Geometry-1" }
Pass
{
// 总是通过模板测试、A渲染的结果是模板缓存中全是1
Stencil
{
Ref 1
Comp Always
Pass Replace
}
// 物体A用于打洞,不需要任何渲染
ColorMask 0
// A不会将深度值写入深度缓存中
ZWrite Off
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
float4 vert (in float4 vertex:POSITION):SV_POSITION
{
float4 pos = UnityObjectToClipPos(vertex);
return pos;
}
void frag (out fixed4 color:SV_Target)
{
color = fixed4(0,0,0,0);
}
ENDCG
}
}
}
Shader "Hidden/StencilTestB"
{
Properties
{
_MainColor("MainColor", Color) = (1,1,1,1)
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
// A的是Geometry-1所以,先绘制A再绘制B
Tags { "Queue"="Geometry" }
Pass
{
//
Stencil
{
Ref 1
Comp NotEqual
Pass Keep
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float4 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
float2 uv : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _MainColor;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
float3 normal = UnityObjectToWorldNormal(v.normal);
o.worldNormal = normalize(normal);
o.uv = TRANSFORM_TEX(v.texcoord,_MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 worldLight = UnityWorldSpaceLightDir(i.worldPos.xyz);
worldLight = normalize(worldLight);
fixed ndotl = saturate(dot(i.worldNormal, worldLight));
fixed4 color = tex2D(_MainTex, i.uv);
color.rgb *= _MainColor * ndotl * _LightColor0.rgb;
color.rgb += unity_AmbientSky;
return color;
}
ENDCG
}
}
}
下面走进科学: A物体像素区域写入模板缓存为1,因为比较函数为NotEqual,所以重合部位B的像素被丢弃,没有重合的部位被渲染出来,从而达到B被A挖孔效果,如果将NotEqual换成Equal,可以发现B只有和A重合的像素被绘制出来
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)