GameTech/URP 渲染实战
源码 / Ch26/Pixelate.shader
Ch26/Pixelate.shader
// 案例 26.7:像素化。把 UV 吸附到“大像素”中心,用点采样取色;再在 sRGB 空间做色阶量化 + Bayer 抖动。
Shader "Cookbook/Ch26/Pixelate"
{
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" }
        ZTest Always ZWrite Off Cull Off
        Pass
        {
            Name "Pixelate"
            HLSLPROGRAM
            #pragma vertex Vert
            #pragma fragment Frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"

            float4 _PixelateParams;   // x 像素尺寸, y 色阶数, z 抖动强度

            // 4×4 Bayer 矩阵,返回 [0,1) 的阈值
            float Bayer4(uint2 p)
            {
                static const float m[16] = { 0, 8, 2, 10, 12, 4, 14, 6, 3, 11, 1, 9, 15, 7, 13, 5 };
                return m[(p.y & 3) * 4 + (p.x & 3)] / 16.0;
            }

            half4 Frag(Varyings input) : SV_Target
            {
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
                float2 screen = _ScreenParams.xy;
                float size = _PixelateParams.x;

                // 1) 当前像素属于哪个“大像素”,取它中心的颜色(点采样,避免边缘被双线性混色)
                float2 block = floor(input.texcoord * screen / size);
                float2 uv = (block + 0.5) * size / screen;
                half3 c = SAMPLE_TEXTURE2D_X(_BlitTexture, sampler_PointClamp, uv).rgb;

                // 2) 色阶量化:在 sRGB 空间均匀分级(线性空间分级会让暗部台阶过粗)
                float levels = _PixelateParams.y;
                if (levels > 0.5)
                {
                    half3 s = LinearToSRGB(saturate(c));
                    float threshold = (Bayer4((uint2)block) - 0.5) * _PixelateParams.z;   // 以大像素为单位抖动
                    s = floor(s * (levels - 1.0) + 0.5 + threshold) / (levels - 1.0);
                    c = SRGBToLinear(saturate(s));
                }
                return half4(c, 1.0);
            }
            ENDHLSL
        }
    }
}