静态分屏
静态分屏是指,每一个屏的图像都完全一样。
分屏滤镜实现的原理:无非是在片段着色器中,修改纹理坐标和纹理的对应关系。
例如,1 :2 分屏的片段着色器如下
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
float y;
if (uv.y >= 0.0 && uv.y <= 0.5) {
y = uv.y + 0.25;
} else {
y = uv.y - 0.25;
}
gl_FragColor = texture2D(Texture, vec2(uv.x, y));
}
同理,我们可以通过计算去实现 1:3 分屏,2:2 分屏,3:2 分屏等等分屏效果。
但是,这样的做法,在分屏之后,每个屏内纹理的对应关系都不一样,因此,我们需要大量的区域判断逻辑,而且对于每一种分屏,我们都需要单独去实现一个片段着色器,拓展性也比较差。
那么,是否有比较优雅的方案去实现分屏的效果?
GLSL 运算
1、向量与向量的加减乘除(两个向量需要保证维数相同)
下面以乘法为例,其他类似。
vec2 a, b, c;
c = a * b;
等价于
c.x = a.x * b.x;
c.y = a.y * b.y;
2、向量与标量的加减乘除
下面以加法为例,其他类似。
vec2 a, b;
float c;
b = a + c;
等价于
b.x = a.x + c;
b.y = a.y + c;
3、向量与向量的 mod 运算(两个向量需要保证维数相同)
vec2 a, b, c;
c = mod(a, b);
等价于
c.x = mod(a.x, b.x);
c.y = mod(a.y, b.y);
4、向量与标量的 mod 运算
vec2 a, b;
float c;
b = mod(a, c);
等价于
b.x = mod(a.x, c);
b.y = mod(a.y, c);
片段着色器编写
precision highp float;
uniform sampler2D inputImageTexture;
varying vec2 textureCoordinate;
uniform float horizontal; // (1)
uniform float vertical;
void main (void) {
float horizontalCount = max(horizontal, 1.0); // (2)
float verticalCount = max(vertical, 1.0);
float ratio = verticalCount / horizontalCount; // (3)
vec2 originSize = vec2(1.0, 1.0);
vec2 newSize = originSize;
if (ratio > 1.0) {
newSize.y = 1.0 / ratio;
} else {
newSize.x = ratio;
}
vec2 offset = (originSize - newSize) / 2.0; // (4)
vec2 position = offset + mod(textureCoordinate * min(horizontalCount, verticalCount), newSize); // (5)
gl_FragColor = texture2D(inputImageTexture, position); // (6)
}
(1) 我们最终暴露的接口,通过 uniform 变量的形式,从着色器外部传入横向分屏数 horizontal 和纵向分屏数 vertical 。
(2) 开始运算前,做了最小分屏数的限制,避免小于 1.0 的分屏数出现。
(3) 从这一行开始,是为了计算分屏之后,每一屏的新尺寸。比如分成 2 : 2,则 newSize 仍然是 (1.0, 1.0),因为每一屏都能显示完整的图像;而分成 3 : 2(横向 3 屏,纵向 2 屏),则 newSize 将会是 (2.0 / 3.0, 1.0),因为每一屏的纵向能显示完整的图像,而横向只能显示 2 / 3 的图像。
(4) 计算新的图像在原始图像中的偏移量。因为我们的图像要居中裁剪,所以要计算出裁剪后的偏移。比如 (2.0 / 3.0, 1.0) 的图像,对应的 offset 是 (1.0 / 6.0, 0.0) 。
(5) 这一行是这个着色器的精华所在,可能不太好理解。我们将原始的纹理坐标,乘上 horizontalCount 和 verticalCount 的较小者,然后对新的尺寸进行求模运算。这样,当原始纹理坐标在 0 ~ 1 的范围内增长时,可以让新的纹理坐标在 newSize 的范围内循环多次。另外,计算的结果加上 offset,可以让新的纹理坐标偏移到居中的位置。

(6) 通过新的计算出来的纹理坐标,从纹理中读出相应的颜色值输出。
文中片段着色器代码获取