源码 / shaders/Ch26/Pixelate.glsl
shaders/Ch26/Pixelate.glsl
// 案例 26.7:像素化。把 UV 吸附到“大像素”中心,用点采样取色;再在 sRGB 空间做色阶量化 + Bayer 抖动。 Shader "Cookbook/Ch26/Pixelate" { SubShader { Tags { "RenderType"="Opaque" "RenderPipeline"="UniversalPipeline" } ZTest Always ZWrite Off Cull Off Pass { Name "Pixelate" GLSLPROGRAM uniform vec4 _PixelateParams; // x 像素尺寸, y 色阶数, z 抖动强度 VARYING vec2 texcoord; #ifdef VERTEX // Blit.hlsl 的 Vert:用 gl_VertexID 生成盖住全屏的大三角形(_BlitTexture、_BlitTexture_TexelSize 等在 Core.glsl 里声明) void main() { vec2 p = vec2((gl_VertexID << 1) & 2, gl_VertexID & 2); texcoord = p * _BlitScaleBias.xy + _BlitScaleBias.zw; gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0); } #endif #ifdef FRAGMENT // 4×4 Bayer 矩阵,返回 [0,1) 的阈值 float Bayer4(uvec2 p) { const float m[16] = float[16](0.0, 8.0, 2.0, 10.0, 12.0, 4.0, 14.0, 6.0, 3.0, 11.0, 1.0, 9.0, 15.0, 7.0, 13.0, 5.0); return m[(p.y & 3u) * 4u + (p.x & 3u)] / 16.0; } vec4 Frag() { vec2 screen = _ScreenParams.xy; float size = _PixelateParams.x; // 1) 当前像素属于哪个“大像素”,取它中心的颜色(点采样,避免边缘被双线性混色) vec2 block = floor(texcoord * screen / size); vec2 uv = (block + 0.5) * size / screen; // GLSL 不能按调用指定 sampler_PointClamp:用 texelFetch 按整数纹素坐标取 ivec2 texSize = textureSize(_BlitTexture, 0); vec3 c = texelFetch(_BlitTexture, clamp(ivec2(uv * vec2(texSize)), ivec2(0), texSize - 1), 0).rgb; // 2) 色阶量化:在 sRGB 空间均匀分级(线性空间分级会让暗部台阶过粗) float levels = _PixelateParams.y; if (levels > 0.5) { vec3 s = LinearToSRGB(saturate(c)); float threshold = (Bayer4(uvec2(block)) - 0.5) * _PixelateParams.z; // 以大像素为单位抖动 s = floor(s * (levels - 1.0) + 0.5 + threshold) / (levels - 1.0); c = SRGBToLinear(saturate(s)); } return vec4(c, 1.0); } void main() { fragColor = Frag(); } #endif ENDGLSL } } }