这里实现了四边形马赛克、六边形马赛克、三角形马赛克。
这里马赛克效果的代码也都是在片元着色程序.fsh中修改的,只需要处理纹理坐标即可。
- 四边形马赛克
.fsh
代码:
precision highp float;
varying vec2 textureCoords;
uniform sampler2D texture;
const vec2 mosaicSize = vec2(0.03, 0.03);
void main()
{
float mosaicX = floor(textureCoords.x/mosaicSize.x)*mosaicSize.x;
float mosaicY = floor(textureCoords.y/mosaicSize.y)*mosaicSize.y;
vec2 mosaicXY = vec2(mosaicX, mosaicY);
gl_FragColor = texture2D(texture, mosaicXY);
}
这里mosaicSize
代表马赛克大小,我们这里绘制的马赛克是正方形。
floor(x)
函数的意思是小于等于x
的最大整数。 使用该函数的目的是为了将纹理坐标以0.03
的大小为一个单位进行分割,也就是 :
[0.0, 0.03)
范围的坐标全部改为0.0 * 0.03 = 0.0
。
[0.03, 0.06)
范围的坐标全部改为1.0 * 0.03 = 0.03
。
[0.06, 0.09)
范围的坐标全部改为2.0 * 0.03 = 0.06
。
... ...
[0.96, 0.99)
范围的坐标全部改为32.0 * 0.03 = 0.96
。
[0.99, 1.0)
范围的坐标全部改为33.0 * 0.03 = 0.99
。
纹理坐标范围是[0.0, 1.0]
,所以每一行每一列能够绘制的马赛克的个数是1.0/0.03
个。
- 六边形马赛克
.fsh
代码:
precision highp float;
varying vec2 textureCoords;
uniform sampler2D texture;
const float mosaicSize = 0.03;
void main ()
{
float length = mosaicSize;
float TR = 0.866025;
float TB = 1.5;
float x = textureCoords.x;
float y = textureCoords.y;
int wx = int(x / (TB * length));
int wy = int(y / (TR * length));
vec2 v1, v2, vn;
if (wx/2 * 2 == wx) {//偶数行
if (wy/2 * 2 == wy) {//偶数列
//(0,0),(1,1)
v1 = vec2(length * TB * float(wx), length * TR * float(wy));
v2 = vec2(length * TB * float(wx + 1), length * TR * float(wy + 1));
} else {//奇数列
//(0,1),(1,0)
v1 = vec2(length * TB * float(wx), length * TR * float(wy + 1));
v2 = vec2(length * TB * float(wx + 1), length * TR * float(wy));
}
}else {//奇数行
if (wy/2 * 2 == wy) {//偶数列
//(0,1),(1,0)
v1 = vec2(length * TB * float(wx), length * TR * float(wy + 1));
v2 = vec2(length * TB * float(wx + 1), length * TR * float(wy));
} else {//奇数列
//(0,0),(1,1)
v1 = vec2(length * TB * float(wx), length * TR * float(wy));
v2 = vec2(length * TB * float(wx + 1), length * TR * float(wy + 1));
}
}
float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
if (s1 < s2) {
vn = v1;
} else {
vn = v2;
}
vec4 color = texture2D(texture, vn);
gl_FragColor = color;
}
需要注意,我们这里虽然是六边形马赛克,但是我们的区域划分并不是以六边形区域划分的。而是以四边形的方式划分的,将整个纹理坐标划分成若干个宽和高是特定比例的四边形。
我们这里要求的六边形马赛克是正六边形。下面来计算一下宽和高特定的比例多少,看图: 宽高比例计算图六边形的边为a
、宽w
、高h
,看图中的计算公式一目了然:w/h = 3/√3
。
我们设定六边形边长a = 0.03
,也就是代码中的mosaicSize
,根据上图中的计算公式可以得出:
w = 0.03 * (3/2) = 0.03 * 1.5 = 0.045
h = 0.03 * (√3 / 2) ≈ 0.03 * 0.866025 ≈ 0.02598
。
代码中的TR
就是上图公式中的 √3 / 2
,TB
就是上图公式中的 3/2
。
上图只画了四个四边形,他们中间已经可以构成一个六边形。整个纹理坐标中可以分割成很多这样的四边形。每一行可以分成 1.0/w = 1.0/0.045
个四边形, 每一列可以分成 1.0/h = 1.0/0.02598
个四边形。
了解了如何将纹理坐标分割成一个一个四边形的区域,下面我们需要清楚,如何分割这些四边形的区域,才能使绘制出来的马塞克为六边形。
首先我们需要找出四边形中对应的六边形的中心点,看图:
屏幕快照 2020-08-12 下午3.02.16.png
-
我们将这里的坐标,称为 四边形的坐标,理解:
X方向上 是以四边形的宽w
的大小为一个单位。
Y方向上 是以四边形的高h
的大小为一个单位。
所以代码中
int wx = int(x / (TB * length));
:代表 纹理坐标中X
的值对应在 四边形坐标 中X方向的值。
int wy = int(y / (TR * length));
:代表 纹理坐标中Y
的值对应在 四边形坐标 中Y方向的值。 -
图中的蓝点就是我们需要找的六边形的中心点,我们来找下规律:
屏幕快照 2020-08-12 下午3.24.57.png
00、02、04 四边形:中心点坐标在左上角和右下角。
11、13、15 四边形:中心点坐标也在在左上角和右下角。
01、03、05 四边形:中心点坐标在右上角和左下角。
10、12、14 四边形:中心点坐标也在右上角和左下角。
不难发现:
偶数行偶数列,奇数行奇数列:中心点的坐标全部在左上角和右下角。
奇数行偶数列,偶数行奇数列:中心点的坐标全部在右上角和左下角。
所以上面代码中对行数和列书的逻辑处理就是将 四边形坐标 划分成了四部分,取对应六边形的中心点。再将 四边形坐标值 转回 纹理坐标值,也就是代码中的vec2 v1, v2
。
接下来我们需要判断纹理的像素点距离v1, v2
的距离大小,代码:
float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
if (s1 < s2) {
vn = v1;
} else {
vn = v2;
}
如果距离v1
近就取v1
的值,距离v2
近就取v2
的值赋值给vn
。
逻辑并不复杂,到这已经结束了。核心思想就是取出六边形的中心点,拿纹理坐标跟两个中心点的距离做比较。
下面看下效果:
- 三角形形马赛克
.fsh
代码,在六边形代码的基础上添加如下代码:
vec4 mid = texture2D(texture, vn);
float a = atan((x - vn.x)/(y - vn.y));
vec2 area1 = vec2(vn.x, vn.y - length * TR / 2.0);
vec2 area2 = vec2(vn.x + length / 2.0, vn.y - length * TR / 2.0);
vec2 area3 = vec2(vn.x + length / 2.0, vn.y + length * TR / 2.0);
vec2 area4 = vec2(vn.x, vn.y + length * TR / 2.0);
vec2 area5 = vec2(vn.x - length / 2.0, vn.y + length * TR / 2.0);
vec2 area6 = vec2(vn.x - length / 2.0, vn.y - length * TR / 2.0);
if (a >= PI6 && a < PI6 * 3.0) {//30-90
vn = area1;
} else if (a >= PI6 * 3.0 && a < PI6 * 5.0) {
vn = area2;
} else if ((a >= PI6 * 5.0 && a <= PI6 * 6.0)|| (a<-PI6 * 5.0 && a>-PI6*6.0)) {
vn = area3;
} else if (a < -PI6 * 3.0 && a >= -PI6 * 5.0) {
vn = area4;
} else if(a <= -PI6 && a> -PI6 * 3.0) {
vn = area5;
} else if (a > -PI6 && a < PI6)
{
vn = area6;
}
我们在六边形的基础上再对区域进行划分,每个六边形包含六个三角形。
-
float a = atan((x - vn.x)/(y - vn.y));
:求出纹理坐标相对于中心点vn
的弧度值。 - 求出六个三角形的中心点坐标
area1、area2、area3、area4、area5、area6
这里的计算非常简单,结合 宽高比例计算图 很容易就明白了。 -
const float PI6 = 0.523599;
是30度的弧度值。
所以每个区域的弧度范围值是:
area1:[30°, 90°)
area2:[90°, 150°)
area3:[150°, 180°]、(-180°,-150°)
area4:[ -150°, -90°)
area5:( -90°, -30°]
area6:( -30°, 30°)
效果图:
三角形马赛克
网友评论