GameTech/URP 渲染实战
源码 / shaders/Ch04/C04_10_BayerDither.glsl
shaders/Ch04/C04_10_BayerDither.glsl
// 案例 4.10:Bayer 有序抖动 —— 用少量颜色表现连续渐变。(GLSL 版)
Shader "Cookbook/Ch04/BayerDither"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }

        Pass
        {
            Name "Unlit"
            Tags { "LightMode"="UniversalForward" }

            GLSLPROGRAM
            #include "Ch04/ProceduralCommon.glsl"

            uniform float _Mode, _Levels, _Resolution;
            uniform vec4  _Dark, _Light;

            #ifdef FRAGMENT
            // 要被抖动的“原图”:左→右的灰阶渐变 + 一个假光照的球
            float SourceImage(vec2 uv)
            {
                float v = uv.x;
                vec2 p = (uv - vec2(0.5, 0.55)) / 0.32;
                float r2 = dot(p, p);
                if (r2 < 1.0)
                {
                    vec3 n = vec3(p.x, p.y, sqrt(1.0 - r2));
                    v = saturate(dot(n, normalize(vec3(-0.5, 0.6, 0.65)))) * 0.95 + 0.03;
                }
                return v;
            }

            void main()
            {
                // 把面片划成 _Resolution×_Resolution 个“虚拟像素”:每个虚拟像素取一个值、用一个阈值。
                // 这样抖动图案贴在面片上、大小可控;想贴在屏幕上就改用 gl_FragCoord.xy(单位:屏幕像素)
                vec2 cellPx = floor(uv * _Resolution);
                float v = SourceImage((cellPx + 0.5) / _Resolution);

                uvec2 pixel = uvec2(cellPx);
                float threshold = 0.5;                                       // 模式 0:所有像素同一个阈值
                if (_Mode > 0.5) threshold = BayerThreshold(pixel, uint(_Mode));  // 1→2×2, 2→4×4, 3→8×8
                // 量化到 _Levels 级:先放大到 [0, L-1],加上阈值后向下取整
                float L = _Levels - 1.0;
                float q = floor(v * L + threshold) / L;
                fragColor = vec4(mix(_Dark.rgb, _Light.rgb, saturate(q)), 1.0);
            }
            #endif
            ENDGLSL
        }
    }
}