Unity Shader - CheckerBoard(棋盘格) 等 Pattern 的测试

141 篇文章 35 订阅


什么是 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


Project

TestingCheckerBoardPattern_unity_2019.4.30f1

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Unity Shader是一种用于在Unity引擎中创建和控制图形渲染效果的编程语言。通过使用Unity Shader,开发人员可以自定义游戏中各种物体的外观和行为,从而实现更加逼真和出色的视觉效果。 而热图(Heatmap)是一种用于显示某个区域内物体热度分布的视觉化工具。在游戏开发中,热图通常用于统计和分析玩家在游戏中的行为和偏好,以便开发人员可以根据这些数据进行游戏优化和改进。 为了创建一个热图效果,我们可以使用Unity Shader来实现。首先,我们需要将游戏中各个物体按照玩家与其的互动情况和频率进行区分,不同的行为和频率可以对应不同的颜色或者纹理。接着,我们可以在Shader中根据这些信息来着色和渲染物体,以展示物体的热度分布。 在Shader中,我们可以通过为物体添加一张热图纹理,并使用该纹理来表示物体的热度值。热图纹理可以是一张灰度图,不同的灰度值对应不同的热度。然后,我们可以使用纹理坐标和采样操作来获取每个像素对应的热度值,并根据这些值来着色和渲染物体。 除了使用纹理来表示热度分布,我们还可以使用其他的技术和效果来增强热图的可视化效果。例如,我们可以使用颜色渐变和透明度来形成平滑的过渡效果,以更好地显示物体的热度变化。我们还可以添加动画效果,使热图效果更加生动和有趣。 总结而言,Unity Shader可以用于创建热图效果,通过着色和渲染来展示物体的热度分布。这样的热图可以帮助开发人员分析游戏中玩家的行为和偏好,从而优化和改进游戏的设计和内容。这些热图效果能够增强游戏的可视化效果,并提供有价值的数据供开发人员参考。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值