文章目录
什么是 CheckerBoard
你可以去翻译一下,就是棋盘格的意思
一般长下图的样子
(是否在很多 DCC 软件可以看到类似的图案,如:Photoshop 的透明底色,或是一些预览图片的软件都使用类似下面的 CheckerBoard 来表示透明部分)
来个最简单的 CheckerBoard
显示全屏UV
// jave.lin 2021/12/24
// T1 显示 屏幕 UV01 值
Shader "Test/T1_ShowScreenPos01"
{
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
void vert (
float4 positionOS : POSITION,
out float4 positionCS : SV_POSITION,
out float2 screenPos : TEXCOORD0
)
{
positionCS = UnityObjectToClipPos(positionOS);
float4 sp = ComputeScreenPos(positionCS);
screenPos = sp.xy / sp.w * _ScreenParams.xy; // jave.lin : screen width, height, 不是 0~1
}
fixed4 frag(float4 positionCS : SV_POSITION, float2 screenPos : TEXCOORD0) : SV_Target
{
return fixed4(screenPos / _ScreenParams.xy, 0, 1); // jave.lin : 现在调试效果,先显示 0~1
}
ENDCG
}
}
}
显示棋盘格
水平分段一下(但是黑色分割先只有1个像素)
// jave.lin 2021/12/24
// T2 显示棋盘格
Shader "Test/T2_ShowCheckerBox"
{
Properties
{
_CheckerBoardSize("Checker Board Size", Float) = 2
}
SubShader
{
Pass
{
...
fixed4 frag(float4 positionCS : SV_POSITION, float2 screenPos : TEXCOORD0) : SV_Target
{
return screenPos.x % _CheckerBoardSize; // 水平分段一下(但是黑色分割先只有1个像素) }
ENDCG
}
}
}
填充分阶的内容为 0 ~ 1 的渐变值
// jave.lin 2021/12/24
// T2 显示棋盘格
Shader "Test/T2_ShowCheckerBox"
{
Properties
{
_CheckerBoardSize("Checker Board Size", Float) = 2
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
half _CheckerBoardSize;
void vert (
float4 positionOS : POSITION,
out float4 positionCS : SV_POSITION,
out float2 screenPos : TEXCOORD0
)
{
positionCS = UnityObjectToClipPos(positionOS);
float4 sp = ComputeScreenPos(positionCS);
screenPos = sp.xy / sp.w * _ScreenParams.xy; // jave.lin : screen width, height, 不是 0~1
}
fixed4 frag(float4 positionCS : SV_POSITION, float2 screenPos : TEXCOORD0) : SV_Target
{
//return screenPos.x % _CheckerBoardSize; // 水平分段一下(但是黑色分割先只有1个像素)
return screenPos.x % _CheckerBoardSize / _CheckerBoardSize; // 填充分阶的内容为 0~1 的渐变值
}
ENDCG
}
}
}
将0 ~ 1 渐变值一分为二
将 0 ~ 1 的渐变值一分为二的思路可以使用:round(val)
函数,等价于 val > 0.5 ? 1 : 0
,二值化
将 screenPos.x
调整为 y
,可以输出纵向的色阶
return round(screenPos.y % _CheckerBoardSize / _CheckerBoardSize);
最终棋盘格:输出纵横向的相异为真的像素值
// jave.lin 2021/12/24
// T2 显示棋盘格
Shader "Test/T2_ShowCheckerBox"
{
Properties
{
_CheckerBoardSize("Checker Board Size", Float) = 2
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
half _CheckerBoardSize;
void vert (
float4 positionOS : POSITION,
out float4 positionCS : SV_POSITION,
out float2 screenPos : TEXCOORD0
)
{
positionCS = UnityObjectToClipPos(positionOS);
float4 sp = ComputeScreenPos(positionCS);
screenPos = sp.xy / sp.w * _ScreenParams.xy; // jave.lin : screen width, height, 不是 0~1
}
fixed4 frag(float4 positionCS : SV_POSITION, float2 screenPos : TEXCOORD0) : SV_Target
{
//return screenPos.x % _CheckerBoardSize; // 水平分段一下(但是黑色分割先只有1个像素)
//return screenPos.x % _CheckerBoardSize / _CheckerBoardSize; // 填充分阶的内容为 0~1 的渐变值
//return round(screenPos.x % _CheckerBoardSize / _CheckerBoardSize); // 将0~1 渐变值一分为二
//return round(screenPos.y % _CheckerBoardSize / _CheckerBoardSize); // 纵向的:将0~1 渐变值一分为二
screenPos = round(screenPos % _CheckerBoardSize / _CheckerBoardSize); // 纵横向一起计算
return screenPos.x != screenPos.y; // 相异为真输出1,但是,cg 函数没有运算吗,有点无语,直接用 |, &, !, ^ 都会编译不过
}
ENDCG
}
}
}
从上图分析我们可以知道,可以将 相异为真 的结果输出即可
优化
前面演示的的部分运算可以优化的:
screenPos = round(screenPos % _CheckerBoardSize / _CheckerBoardSize); // 纵横向一起计算
调整为:直接除法后,取小数部分,然后 二值化即可
screenPos = round(frac(screenPos / _CheckerBoardSize)); // 纵横向一起计算
f ( x , y ) = ( m o d ( x y ) ) f(x,y)= \pmod{(\frac{x}{y})} f(x,y)=(mod(yx))
其他 CheckerBoard 的样式
有了前面的 简单的 CheckerBoard 方式
下面我们搞一些其他的 pattern,都是经验公式(实验出来的)
同样的,使用 sin 都所有色阶抖动的函数(但是 0 ~ 1 色阶过度太平滑)
// jave.lin 2021/12/24
// T3 显示其他棋盘格
Shader "Test/T3_ShowOhterCheckerBox"
{
Properties
{
_CheckerBoardSize("Checker Board Size", Float) = 2
_CheckerBoardWrap("Checker Board Wrap", Float) = 2
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
half _CheckerBoardSize;
half _CheckerBoardWrap;
void vert (
float4 positionOS : POSITION,
out float4 positionCS : SV_POSITION,
out float2 screenPos : TEXCOORD0
)
{
positionCS = UnityObjectToClipPos(positionOS);
float4 sp = ComputeScreenPos(positionCS);
screenPos = sp.xy / sp.w * _ScreenParams.xy; // jave.lin : screen width, height, 不是 0~1
}
fixed4 frag(float4 positionCS : SV_POSITION, float2 screenPos : TEXCOORD0) : SV_Target
{
return sin(screenPos.x * _CheckerBoardSize) * _CheckerBoardWrap; // 同样的,使用 sin 都所有色阶抖动的函数(但是 0 ~ 1 色阶过度太平滑)
}
ENDCG
}
}
}
和之前一样,可以 round 一下二值化来分色阶
fixed4 frag(float4 positionCS : SV_POSITION, float2 screenPos : TEXCOORD0) : SV_Target
{
//return sin(screenPos.x * _CheckerBoardSize) * _CheckerBoardWrap; // 同样的,使用 sin 都所有色阶抖动的函数(但是 0 ~ 1 色阶过度太平滑)
return round(sin(screenPos.x * _CheckerBoardSize) * _CheckerBoardWrap); // 和之前一样,可以 round 一下二值化来分色阶
}
同样的相异为真输出 checkerboard
fixed4 frag(float4 positionCS : SV_POSITION, float2 screenPos : TEXCOORD0) : SV_Target
{
//return sin(screenPos.x * _CheckerBoardSize) * _CheckerBoardWrap; // 同样的,使用 sin 都所有色阶抖动的函数(但是 0 ~ 1 色阶过度太平滑)
//return round(sin(screenPos.x * _CheckerBoardSize) * _CheckerBoardWrap); // 和之前一样,可以 round 一下二值化来分色阶
screenPos = round(sin(screenPos * _CheckerBoardSize) * _CheckerBoardWrap); // 同样的相异为真输出 checkerboard
return screenPos.x != screenPos.y;
}
但是可以看到,这个棋盘格的效果和之前的不太一样
然后上面的 return screenPos.x != screenPos.y;
可以换成:return screenPos.x == screenPos.y;
,我向你应该知道什么结果(反向结果)
其他花样
T1
// s: _CheckerBoardSize: 0.04, _CheckerBoardWrap: 0~+infi, 0.86
return any(round(sin(screenPos * _CheckerBoardSize) * _CheckerBoardWrap));
T2
// s: _CheckerBoardSize: 0~+infi, _CheckerBoardWrap: 0~1
return all(round(sin(screenPos * _CheckerBoardSize) * _CheckerBoardWrap));
T3
// s: _CheckerBoardSize: 0.1, _CheckerBoardWrap: 0.86
screenPos = round(tan(screenPos * _CheckerBoardSize) * _CheckerBoardWrap);
return screenPos.x * screenPos.y;
T4
// s: _CheckerBoardSize: 0.04, _CheckerBoardWrap: 0~+infi, 0.86
screenPos = round(tan(screenPos * _CheckerBoardSize) * _CheckerBoardWrap);
return screenPos.x == screenPos.y;
etc.
// s: _CheckerBoardSize: 25, _CheckerBoardWrap: 25
sp = round(floor(sp % _CheckerBoardWrap) / _CheckerBoardSize);
//sp = round(floor(sp / _CheckerBoardWrap) / _CheckerBoardSize);
//sp = round(floor(sp * (1.0 / _CheckerBoardWrap)) * (1.0 / _CheckerBoardSize));
//sp = round(floor(sp * (1.0 / _CheckerBoardWrap)) * (1.0 / _CheckerBoardSize));
//sp = pow(sp, _CheckerBoardSize) * pow(sp, _CheckerBoardWrap);
//return all(sp);
//return any(sp);
//return sp.x != sp.y;
//return sp.x == sp.y;
//return round(sin(sp.x) * sin(sp.y)); // _CheckerBoardSize:0~2*_CheckerBoardWrap
//return (cos(sp.x) * cos(sp.y)); // _CheckerBoardSize:0~2*_CheckerBoardWrap
//return round(cos(sp.x) * cos(sp.y)); // _CheckerBoardSize:0~2*_CheckerBoardWrap
//return round(cos(sp.x) / cos(sp.y)); // _CheckerBoardSize:0~2*_CheckerBoardWrap
//return round(cos(sp.x) % cos(sp.y)); // _CheckerBoardSize:0~2*_CheckerBoardWrap
//return round(cos(sp.x) - cos(sp.y)); // _CheckerBoardSize:0~2*_CheckerBoardWrap
//return round(sin(sp.x) - cos(sp.y)); // _CheckerBoardSize:0~2*_CheckerBoardWrap
//return round(pow(sin(sp.x), cos(sp.y))); // _CheckerBoardSize:0~2*_CheckerBoardWrap
return round(tan(sp.x) * tan(sp.y)); // _CheckerBoardSize:0~2*_CheckerBoardWrap
//return round(tan(sp.x) % tan(sp.y)); // _CheckerBoardSize:2, *_CheckerBoardWrap: 100
上面的都是随便试出来的效果
最后 二值化,我们可以使用 step
+ 阈值 来替代 round
函数,效果可以自行测试
// jave.lin 2021/12/24
// T4 显示其他棋盘格(带 illum 阈值)
Shader "Test/T4_ShowOhterCheckerBoxWithIllumThreshold"
{
Properties
{
_CheckerBoardSize ("Checker Board Size", Float) = 0.1
_CheckerBoardWrap ("Checker Board Wrap", Float) = 1
_CheckerBoardIlluminationThreshold ("Checker Board Illumination Threshold", Range(-1, 1)) = 0.0
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
half _CheckerBoardSize;
half _CheckerBoardWrap;
half _CheckerBoardIlluminationThreshold;
//#define IllumThresholdClamp(v) round(v)
#define IllumThresholdClamp(v) step(_CheckerBoardIlluminationThreshold, v)
void vert (
float4 positionOS : POSITION,
out float4 positionCS : SV_POSITION,
out float2 screenPos : TEXCOORD0
) {
positionCS = UnityObjectToClipPos(positionOS);
float4 sp = ComputeScreenPos(positionCS);
screenPos = sp.xy / sp.w * _ScreenParams.xy; // jave.lin : screen width, height, 不是 0~1
}
fixed4 frag(float4 positionCS : SV_POSITION, float2 screenPos : TEXCOORD0) : SV_Target {
screenPos = IllumThresholdClamp(sin(screenPos * _CheckerBoardSize) * _CheckerBoardWrap);
return screenPos.x == screenPos.y;
}
ENDCG
}
}
}
这些东西能用在什么地方
如果我们将, checker board ==0 的黑色像素都 discard 掉
将 checker board 的 grid 弄得足够小,是不是就有点向一些游戏效果中的简单的 dither 作为透明,或是马赛克显示效果(如:原神 中,你如果把镜头往人物胯部看的话,就发现人物被 checker board pattern 的方式给 discard 掉部分像素,让想看的地方看得不太清晰,而不至于被和的得情况)
或是有些游戏会在:XRay 时显示 checker board 的像素,一般会显示 rim 效果的比较多
后续我会再写个,这些 checker board,不时通过程序化生成,而是 Texture 简单应用的场景,其实就时类似:马赛克分色阶 来溶解的效果
下面是偶然发现 unity 2019.4.30f1 中的某个 LOD 淡入淡出的 Dither Shader 处理:
再 UnityCG.cginc
文件下
#ifdef LOD_FADE_CROSSFADE
#define UNITY_APPLY_DITHER_CROSSFADE(vpos) UnityApplyDitherCrossFade(vpos)
sampler2D unity_DitherMask;
void UnityApplyDitherCrossFade(float2 vpos)
{
vpos /= 4; // the dither mask texture is 4x4
float mask = tex2D(unity_DitherMask, vpos).a;
float sgn = unity_LODFade.x > 0 ? 1.0f : -1.0f;
clip(unity_LODFade.x - mask * sgn);
}
#else
#define UNITY_APPLY_DITHER_CROSSFADE(vpos)
#endif
还是可以使用在 semitransparent,可以参考以下:cat like coding 博客中的:Semitransparent Shadows