01UV坐标操控
04像素化
像素艺术一般指一种风格,一般其中会有一个基础大小色块,色块中为一种同一颜色。而整张图案则是由这些基础色块构成。这实际跟计算机渲染图案一直。一般图片都有一个大小,这个大小实际就是横纵的像素个数。只是一般这些像素非常多,让整个图案看起来是连续的。
而像素化,则是通过shader来让一个原始清晰的图案,看上去转化成由大的像素块构成。例如教程最终效果如下:

根据目标效果,也可以明确得出这里的渲染目标:将相邻像素构成的矩形块,使用中心像素来渲染。
所以这里用到的主要函数就是floor。floor函数在离散数学中很常见,也很重要。表示取底,向下取到最近的整数。例如$f_{floor}(1.5)=1,f_{floor}(2)=2$。其离散书选中一般用$\lfloor x \rfloor$来表示。类似的还用取顶函数ceil,函数标记一般用$\lceil x \rceil$。
重新考虑回shader中的UV坐标。其一般用$(0,0)\to (1,1)$从左上角到右下角表示一整张贴图的采样映射。当想控制采样映射效果的时候,修改这个采样映射就可以。现在假设每一个UV精确值都对应一个采样的目标像素(一般应该是根据面片屏幕空间占据位置,结合顶点UV值取算处这个屏幕像素具体的UV值来进行采样)。那么这里做像素画,实际上相当于将UV方块区域$(0,0)\to (x,x)$都映射到一个具体的UV值上面。
而这一步可以通过如下方式来获得
$$\lfloor UV*\frac{1}{x} \rfloor*x$$其中$UV*1/x$相当于放大$1/x$倍。这样$(0,0)\to (x,x)$区域就被映射到$(0,0)\to(1,1)$区域。这个区域经过取底函数后,都会映射到$(0,0)$值。构成了区块映射到一个值的效果。而因为我们放大了$1/x$倍,最后还要缩放回去,使得整个区间任然保持在UV可采样的$(0,0)\to (1,1)$区间内即可。
对应代码如下:
uniform float scale = 128.0;
void fragment() {
// 这里的scale即前面公式中的 1/x 只要一个缩放倍数即可
vec2 pixel_uv = floor(UV*scale)/scale;
COLOR = texture(TEXTURE,pixel_uv);
}最后呈现图案如下:

可以看到有明显的区块划分,有很多明显的色块区域。
但同时对比可以发现,似乎整个图形有点往右下角堆积。不在中心堆成,左上角空一些,右下角挤一些。实际上这也可以由前面公式得出。
因为使用floor函数通过缩放实际将$(0,0)\to (x,x)$全部映射到了$(0,0)$,这表明实际映射到了色块的左上角,而非中心。修正这个方式也很简单,最直接思路。最后的映射结果都添加$(x/2,x/2)$即可。即如下代码
uniform float scale = 128.0;
void fragment() {
// 即添加0.5/scale即可
vec2 pixel_uv = (floor(UV*scale)+0.5)/scale;
COLOR = texture(TEXTURE,pixel_uv);
}这也可以这样理解。取floor后,区域色块被映射到一个(1,1)的矩形内,这时候只要偏移(0.5,0.5)便是中心位置,然后缩放回去即可。最后即可得到开始的像素画图案。
- 表述细节
这里还有个小的优化细节。我们前面使用缩放时候,直接使用一个float数值来缩放,这个值的语义可能不是很明确。而且这个像素化效果实际取决于原始贴图大小。
举例来说,加入原始图片大小为$128\times 128$像素。那么缩放倍数32,实际表示每个色块大小为4像素。也可以理解缩放倍数为横纵像素个数。
另一种表述方式描述我们需要的像素色块大小,例如我需要每个色块大小原始图片中的4个像素。这种描述使用TEXTURE_PIXEL_SIZE转换即可。代码如下:
uniform int pexel_size : hint_range(0.0,32.0);
void fragment() {
// 算出原始贴图像素大小 然后根据色块像素大小 计算出缩放倍率
vec2 texture_size_px = 1.0/TEXTURE_PIXEL_SIZE;
vec2 gird_size = texture_size_px/float(pexel_size);
vec2 pixel_uv = (floor(UV*gird_size)+0.5)/gird_size;
COLOR = texture(TEXTURE,pixel_uv);
}另一方面,在后面的例子中,也有很多像素画思想。其思路基本都是一致的,但是所采用的采样描述空间可能不太一样。只要按照相同思路来操作即可。