概述
之前写过一篇去色shader,大致就是使用一种类似区间划分的方法来将原来图片所使用的颜色离散化,来达到一些特殊的视觉效果。
使用之前那个shader,将颜色数量减少到两种的时候,效果如下,已经完全看不出原图是什么东西了。
我们今天使用一种特殊的dither抖动 shader,来实现只用两种颜色但依然能保证一定的图片细节的效果。
先看看效果,下图只使用了纯黑或者纯白两种效果,虽然依旧很模糊,但效果比上面那张图要好的太多了。
可能有人不相信这张图只使用了两种颜色的(我一开始也没有相信),我们这里截取局部来放大,就可以清楚的看到,这张图只有纯黑或纯白两种颜色。另外有学过计算机图形学的应该都看出这是一种什么算法了。
简单概述一下,在计算机图形学中,有一种只用黑白两种颜色来表示更多种颜色的方法。如下图所示,使用一个2x2像素的方格,通过对里面四个点填充黑点的数量来做变化,就可以得到五种不同的灰度,将每个像素都用这种2x2像素的方格替换,就可以得到5种颜色变化,虽然实际上还是只有黑白两色,但是只要不放大看,就可以得到有5种颜色变化的错觉。
用2x2方格可以做出5种变化,那么想要更多的灰度级别,就需要提升方格的大小,4x4的方格可以得到17种灰度,8x8的方格可以得到惊人的65种灰度。
但是使用一个2x2的方格来代表原来的一个像素的话,整张图片就需要更大的分辨率。假设原图为1024x1024长宽的图片,则一共有1048576个像素。如果使用2x2的方格来代表原来的一个像素,虽然可以得到5种灰度,但是分辨率需要提升为2048x2048。使用更大的方格同理,虽然可以得到更多的颜色级别,但是会需要更大的分辨率才能显示下原图。
有一种不用提升分辨率就能在原图上使用dither算法的方法,也就是我们今天要讲的方法。假设使用上面所说的8x8的块,此方法不用把每个像素变大64倍,而是直接在原图上进行这种划分,将原图划分为若干8x8的块,对原图的每个像素,和相应块中的阈值做比较,超过后则打上白点否则打上黑点。
先获得像素对应的屏幕坐标
float2 screenPos = IN.screenPos.xy / IN.screenPos.w * _ScreenParams.xy;
获得像素对应的亮度
int brightness = Luminance(c) * 256;
8x8的方格支持65种亮度,因此先要把256色降低到64色(即0~64,一共65种颜色)
brightness = brightness >> 2;
对坐标求余,然后传入比较函数,来得到该点是映射到一个黑点还是白点。
gray = Dither8x8Bayer(screenPos.x % 8, screenPos.y % 8, brightness);
关键的比较方法如下,传入屏幕坐标和对应像素亮度,通过对1~64之间的阈值做比较,就可以把这个像素转换成一个白点或黑点。
float Dither8x8Bayer(int x, int y, float brightness)
{
const float dither[64] = {
1, 49, 13, 61, 4, 52, 16, 64,
33, 17, 45, 29, 36, 20, 48, 32,
9, 57, 5, 53, 12, 60, 8, 56,
41, 25, 37, 21, 44, 28, 40, 24,
3, 51, 15, 63, 2, 50, 14, 62,
35, 19, 47, 31, 34, 18, 46, 30,
11, 59, 7