Stipple Transparency – 点阵像素剔除半透明
基于屏幕像素点阵以像素剔除的方式做半透明效果,比传统alpha blend方式更节约性能开销的半透明做法。
Stipple Transparency也叫做:Screen-door transparency,screen space stipple,stipple patterns,dissolve,fizzle,等等。中文可能叫做“点阵像素剔除半透明”吧。原理是将普通的非透明渲染对象,通过以网点方式剔除像素,获得近似半透明的效果。有点像漫画网点纸。
这个半透明实现方式已经是蛮有年头的老办法了。因为3D引擎中实现半透明效果一直是很麻烦的事,而且挺消耗性能的,因为一般半透明对象会把半透明的内容连同背景每一层都渲染一遍,可能会增加Draw Call,还牵扯到深度测试和排序问题。所以很早就有人想出了网点剔除的方式来实现半透明效果,因为这个方式性能开销很低。
早期的电脑屏幕像素低,这种半透明的显示效果其实很low。但是现在都是高清屏了,这种网点的方式反而变的又好看又实用。现在很多主机3A游戏还都在用,例如《马里奥-奥德赛》《神秘海域》。
具体做法就是在渲染的最后阶段,通过屏幕坐标计算出点阵的位置和透明度,透明度低于0的像素就剔除掉。
代码参考
Surface shader:
Shader "WalkingFat/StippleTransparency"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_Alpha ("Alpha", Range(0,1)) = 1.0
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 150
CGPROGRAM
#pragma surface surf Lambert noforwardadd
sampler2D _MainTex;
struct Input {
float2 uv_MainTex;
float4 screenPos;
};
half _Alpha;
void surf (Input IN, inout SurfaceOutput o) {
fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
o.Alpha = c.a;
// Screen-door transparency: Discard pixel if below threshold.
float4x4 thresholdMatrix =
{ 1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0,
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
};
float4x4 _RowAccess = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
float2 pos = IN.screenPos.xy / IN.screenPos.w;
pos *= _ScreenParams.xy; // pixel position
clip(_Alpha - thresholdMatrix[fmod(pos.x, 4)] * _RowAccess[fmod(pos.y, 4)]);
}
ENDCG
}
Fallback "Mobile/VertexLit"
}
Vertex and fragment shader:
Shader "WalkingFat/StippleTransparency"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_Alpha ("Alpha", Range(0,1)) = 1.0
}
SubShader
{
Tags
{
"RenderType"="Opaque"
"Queue" = "Geometry"
}
LOD 100
Pass
{
Tags
{
"LightMode" = "ForwardBase"
}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float4 screenPos : TEXCOORD1;
};
sampler2D _MainTex;
float4 _MainTex_ST;
half _Alpha;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
o.screenPos = ComputeScreenPos (o.pos);
return o;
}
float4 frag (v2f i) : COLOR {
fixed4 c = tex2D(_MainTex, i.uv);
// get pixel matrix and caculate screen position
float4x4 thresholdMatrix =
{ 1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0,
13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0,
4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0,
16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0
};
float4x4 _RowAccess = { 1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 };
float2 pos = i.screenPos.xy / i.screenPos.w;
pos *= _ScreenParams.xy; // pixel position
// Discard pixel if below threshold.
clip(_Alpha - thresholdMatrix[fmod(pos.x, 4)] * _RowAccess[fmod(pos.y, 4)]);
return c;
}
ENDCG
}
}
Fallback "Mobile/VertexLit"
}