GLSL is boring and boring, but it can write a lot of interesting special effects ah, such as the various 80,000 filters in various video apps. This article introduces a few simple filter effects, or effects. I am not going to introduce how GLSL code is used in iOS. If you don’t understand it, you can read my previous article

Split screen filter

Suppose our requirement is divided into two screens, with the middle part (0.25-0.75) as the content.

The vertex shader code looks like this:

attribute vec4 position;
attribute vec2 textureCoord;
varying lowp vec2 varyingTextureCoord;

void main() { varyingTextureCoord = textureCoord; // Pass texture coordinates to the fragment shader gl_Position = position; // Assign vertex coordinates}Copy the code

There’s nothing more to this code than simply assigning the vertex coordinates to the built-in gl_Position variable.

The fragment shader code is as follows:

precision highp float;
uniform sampler2D colorMap;
varying lowp vec2 varyingTextureCoord;

void main() {
    vec2 uv = varyingTextureCoord;
    if(uv.y < 0.5) {
        uv.y = uv.y + 0.25;
    } else{uv.y = uv. y-0.25; } gl_FragColor = texture2D(colorMap, uv); }Copy the code

We know that the built-in function texture2D is used to extract texes based on texture coordinates (i.e., extract the color corresponding to the corresponding texture coordinates), so we just need to divide the texture into two halves and extract the middle elements (0.25 to 0.75). As shown in the figure below, we simply need to fill the dashed line into the upper and lower parts of the texture.



Therefore, when extracting text elements, only add 0.25 to the ordinate of texture coordinates of the upper part of the texture (0-0.5), and similarly subtract 0.25 from the ordinate of texture coordinates of the lower part of the texture when extracting text elements.

The specific effects are as follows: the left picture is the original picture, and the right picture is the upper and lower split screen effect.

 

Gray filter

The general image display depends on three color channels (red, green and blue). A grayscale filter requires only one brightness information. Therefore, it is only necessary to transform red, green and blue into grayscale values. We know that when red, green, and blue have the same value, it’s a gray. The only difference is the gray value. It is easy to know this, for example, we can take the average of red, green and blue, or even all green and so on can achieve the effect of grayscale filter. However, in order to obtain the best visual effect, we refer to the weight value in GPUImage — 21.25% for red, 71.54% for green and 7.21% for blue. The specific code is as follows:

Vertex shaders:

attribute vec4 position;
attribute vec2 textureCoord;
varying lowp vec2 varyingTextureCoord;

void main() { varyingTextureCoord = textureCoord; // Pass texture coordinates to the fragment shader gl_Position = position; // Assign vertex coordinates}Copy the code

There’s nothing to be said here the same as above.

Slice shader:

precision highp float;
uniform sampler2D Texture;
varying vec2 varyingTextureCoord;
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);
void main (void) {
    
    vec4 mask = texture2D(Texture, varyingTextureCoord);
    floatluminance = dot(mask.rgb, W); Gl_FragColor = vec4 (vec3 (luminance), 1.0); }Copy the code

This code first extracted the elements of the original texture, and then used the elements dot product (red, green and blue multiplied by 0.2125, 0.7154, 0.0721 and then added) weight vector as the gray value. Finally, use this gray value to generate a new color that is copied to the built-in variable gl_FragColor. The final display is as follows:

                   

In fact, other weights should also be possible, the effect may not be so good you can adjust to try. The three weights should add up to 1.

Emboss filter

An emboss effect is one in which the foreground of an image is forward to bulge out the background. Its basic principle is: according to the difference of pixels and the pixels around certain relief image pixel values, differences between larger pixel edge points (general image pixel difference is bigger) pixel value is bigger, in grayscale expression is lighter, edge, forming relief, then add a grayscale offset value, as the overall impression of pictures. Relief is divided into eight relief and harmonic relief:

  • Octonal relief: The most basic relief effect in which a new direction is determined by the interpolation of the pixel values with the values of the surrounding eight pixels.
  • Harmonic emboss: a new pixel is determined by combining the difference between the pixel and the four directions of upper left, upper right, lower left and lower right.

Compare to the stereoscopic effect of eight relief is more obvious, so here we refer to the principle of eight relief. Here’s the code. Vertex shader is the same as above and I’m not going to write it here, I’m going to look at pixel shaders.

precision highp float; The uniform sampler2D Texture; varying vec2 varyingTextureCoord; Const highp vec3 W = vec3(0.2125, 0.7154, 0.0721); // const vec2 TexSize = vec2(500.0, 500.0); // Const vec4 bkColor = vec4(0.5, 0.5, 0.5, 1.0); // Background color voidmain() { vec2 tex = varyingTextureCoord; Vec2 upLeftUV = vec2(tex.x-1.0/ texsize.x, tex.y-1.0/ texsize.y); Vec4 curColor = texture2D(Texture, varyingTextureCoord); Vec4 upLeftColor = texture2D(Texture, upLeftUV); Vec4 delColor = curcolor-upleftcolor; // Differentiate with the upper left pixelfloatluminance = dot(delColor.rgb, W); Gl_FragColor = VEC4 (VEC3 (luminance), 0.0) + bkColor; // Add a grayscale offset as the background color}Copy the code

The difference here is the difference from the upper left, because the texture is flipped by default and the upper left becomes the lower right when the texture is flipped later. The specific effect is shown below:

                         

Spiral filter

Vortex is mainly in a radius range, the current sampling point rotation of a certain Angle, after the rotation of the current point color will be replaced by the color of the rotated point, so the whole radius will have the effect of rotation. If the rotation Angle decreases with the distance from the current point to the center of the circle, the entire image will appear as a vortex effect. The parabolic decline factor is set here :(1.0-(r/Radius)*(r/Radius)), where r is the distance from the current sampling point to the center of the circle, Radius.

The specific code is as follows. Here we go straight to pixel shaders, vertex shaders are the same as above.

precision highp float;
const float PI = 3.14159265;
uniform sampler2D Texture;
const floatUD = 80.0; // Represents the rotation Angle constfloatUR = 0.5; varying vec2 varyingTextureCoord; voidmain() { ivec2 ires = ivec2(512, 512); // Assume the texture is 512 * 512 pixelsfloat Res = float(ires.y); Vec2st = varyingTextureCoord; // Texture coordinates (map coordinates)floatRadius = Res * uR; // radius vec2xy = Res * st; Vec2 dxy = xy-vec2 (Res/2., Res/2.); // The vector from the sampling point to the center of the circlefloatr = length(dxy); // The distance from the sampling point to the center of the circlefloatAttenValue = (1.0 - / Radius (r) * / Radius (r)); // Parabolic decline factorfloatBeta = atan(dxy.y, dxy.x) + radians(uD) * 2.0 * attenValue; // The rotation Angleif(r <= Radius) {xy = Res/2.0 + r * vec2(cos(beta), sin(beta)); } st = xy/Res; // Convert the actual Texture position to Texture coordinates (0 to 1) vec3 irGB = texture2D(Texture, st).rgb; Gl_FragColor = vec4(irGB, 1.0); }Copy the code

Note that we assume the texture size is 512 by 512, or we can assume any other size as long as it’s a square, the effect is basically the same. See the following figure for specific logic:


In the figure above, point A is the texture coordinate point (0,0), point B is the center of the circle (0.5,0.5), point B is on the circle, AC is the vector xy,AB is the vector vec2(Res/2., Res/2.), so ac-ab = BC. So the Angle in the diagram is atan(dxy.y, dxy.x). Therefore, when r is on the circle, r= radius, the attenValue decreasing factor is 0, the Angle remains unchanged, and the pixel at point C will not change. When R moves into the circle, the attenValue decreasing factor begins to increase, and point C begins to rotate and offset. When r turns to 0, the offset Angle becomes the maximum. Float beta = atan(dxy.y, dxy.x) + radians(uD) * 2.0 * attenValue float beta = atan(dxy.y, dxy.x) Dxy.x) to see if it’s the original).

Therefore, when r< radius, we need to calculate the texture coordinates of the rotated pixel and use the built-in function texture2D to extract the new text element. R * vec2(cos(beta), sin(beta)) is dxy.x and dxy.y, plus Res/2.0, are converted to texture coordinates at point A (0,0). Of course, we need to divide by Res to transform the mapping coordinates from 0 to 1.

The filter effect is as follows:

                   

Mosaic

The Mosaic effect is to fill a sizable area of the picture with the color of the same point. Think of it as massively reducing the resolution of the image and hiding some of the details of the image.

Rectangular Mosaic

Rectangular Mosaic is very simple, is to divide the texture into a rectangle, and then calculate the color of each rectangle. The specific code is as follows, also directly look at the pixel shader:

precision highp float; varying vec2 varyingTextureCoord; uniform sampler2D Texture; Const vec2 TexSize = vec2(400.0, 400.0); Const vec2 mosaicSize = VEC2 (8.0, 8.0); voidmain() { vec2 intXY = vec2(varyingTextureCoord.x*TexSize.x, varyingTextureCoord.y*TexSize.y); Vec2 XYMosaic = vec2(floor(intxy.x/mosaicsie.x)* mosaicsie.x, floor(intXY.y/mosaicSize.y)*mosaicSize.y); Vec2 UVMosaic = vec2(XYMosaic. X /TexSize. X, XYMosaic. Y /TexSize. UVMosaic vec4 color = texture2D(Texture, UVMosaic); Gl_FragColor = color; }Copy the code

This code is relatively simple, you can directly read the comment line, run as follows:

                  

Hexagonal Mosaic

Hexagon Mosaic is to divide the texture into a hexagon, and then take the corresponding text elements of the same hexagon. The diagram below:

Ideas are as follows: take each hexagonal center dotted out a matrix, draw a lot of long and wide ratio is 3: the square root of 3 rectangular array, assumes that the top left of the screen points to (0, 0), then any point we can find it on the screen corresponds to the rectangle, know in which the rectangle you can find the nearest the center of hexagon. The diagram below:

The specific code is as follows:

precision highp float;
uniform sampler2D Texture;
varying vec2 varyingTextureCoord;
const floatMosaicSize = 0.02; void main (void) {floatlength = mosaicSize; // Set the edge length of the hexagon Mosaic to lengthfloatTR = SQRT (3.0) / 2.0;float x = varyingTextureCoord.x;
    floaty = varyingTextureCoord.y; Int wx = int(x / 1.5 / length); int wy = int(y / TR / length); Vec2 v1, v2, vn; vec2 v1, v2, vn;if(wx/2 * 2 == wx) {// Even linesif(wy/2 * 2 == wy) {// Even column v1 = vec2(length * 1.5 *)float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        } else{// the odd column v1 = vec2(length * 1.5 *)float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy)); }}else{/ / odd linesif(wy/2 * 2 == wy) {// Even column v1 = vec2(length * 1.5 *)float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        } else{// the odd column v1 = vec2(length * 1.5 *)float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1)); }}floatS1 = SQRT (pow (v1. X - x 2.0) + pow (2.0) v1. - y, y);floatS2 = SQRT (pow (v2. X x 2.0) + pow (2.0) v2. - y, y);if (s1 < s2) {
        vn = v1;
    } else {
        vn = v2;
    }
    vec4 color = texture2D(Texture, vn);
    gl_FragColor = color;
    
}
Copy the code

This code, it’s a little bit complicated, so let’s go through it. First, we link each hexagon center point, as shown in the figure above, the whole texture is divided into rectangles, and then create a new coordinate system, with the origin and scale of coordinates as shown in the figure above.

Now look at the length ratio of the rectangle. As shown in the figure above, the length represented by the yellow arrow is 2 units. Since the Angle marked in the figure is 30 degrees, the width of the rectangle is √3 and the length is 1+2 = 3. So let the Mosaic be changed to length, then the width of the rectangle is length* √3/2, and the length of the rectangle is 1.5*length.

Assume that the texture coordinates of any point on the texture are (x,y), and (wx,wy) represent the corresponding matrix coordinates of the point, that is, the point is in the rectangular region to which row Wx and column WY belong.

After determining which rectangular region the point is in, there are two cases:



In the first case, when the points are in even rows and even columns, or in odd rows and odd columns, in this case, only the upper left and lower right points are the center of the hexagon. The texture coordinates of the upper left point are: v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy)); V2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));

The second way: represents the case when the point is in the odd row, even column, or even row, odd column, in which case, only the lower left and upper right point is the center of the hexagon. V1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1)); The texture coordinates of the upper right point are :v2 = v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy)). The diagram below:



In the end, we just need to calculate the distance between the point and the center of the two hexagons corresponding to the rectangular region in these two cases, and then compare the closer distance is the point we want to take the text element.

The code runs as follows:

                       

Triangular Mosaic

Triangle Mosaic is based on the basis of hexagon, the specific principle is as follows, the hexagon is divided into a triangle can be left.



The specific idea is to first calculate the texture coordinates of the central points of six triangles, and then determine which triangle the points are in, and finally take the corresponding central points of the triangle text.

The code is as follows:

precision highp float;
uniform sampler2D Texture;
varying vec2 varyingTextureCoord;

floatMosaicSize = 0.05; void main (void){ constfloatTR = SQRT (3.0) / 2.0; constfloatPI6 = 0.523599; //30 degrees, 1/6 PIfloat x = varyingTextureCoord.x;
    float y = varyingTextureCoord.y;
    
    int wx = int(x/(1.5 * mosaicSize));
    int wy = int(y/(TR * mosaicSize));
    
    vec2 v1, v2, vn;
    
    if (wx / 2 * 2 == wx) {
        if(wy/2 * 2 == wy) {v1 = vec2(mosaicSize * 1.5 *)float(wx), mosaicSize * TR * float(wy));
            v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy + 1));
        } else{v1 = vec2(mosaicSize * 1.5 *float(wx), mosaicSize * TR * float(wy + 1));
            v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy)); }}else {
        if(wy/2 * 2 == wy) {v1 = vec2(mosaicSize * 1.5 *)float(wx), mosaicSize * TR * float(wy + 1));
            v2 = vec2(mosaicSize * 1.5 * float(wx+1), mosaicSize * TR * float(wy));
        } else{v1 = vec2(mosaicSize * 1.5 *float(wx), mosaicSize * TR * float(wy));
            v2 = vec2(mosaicSize * 1.5 * float(wx + 1), mosaicSize * TR * float(wy+1)); }}floatS1 = SQRT (pow (v1. X - x 2.0) + pow (2.0) v1. - y, y);floatS2 = SQRT (pow (v2. X x 2.0) + pow (2.0) v2. - y, y);if (s1 < s2) {
        vn = v1;
    } else {
        vn = v2;
    }
    
    vec4 mid = texture2D(Texture, vn);
    floata = atan((y - vn.y),(x - vn.x)); Vec2 area1 = vec2(vn.x, vn.y - mosaicSize * TR / 2.0); vec2 area1 = vec2(vn.x, vn.y - mosaicSize * TR / 2.0); Vec2 area2 = vec2(vn.x + mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0); Vec2 area3 = vec2(vn.x + mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0); vec2 area3 = vec2(vn.x + mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0); Vec2 area4 = vec2(vn.x, vn.y + mosaicSize * TR / 2.0); vec2 area4 = vec2(vn.x, vn.y + mosaicSize * TR / 2.0); Vec2 area5 = vec2(vn.x - mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0); Vec2 area6 = vec2(vn. x-mosaicsize / 2.0, vn. y-mosaicsize * TR / 2.0);if(a >= PI6 * 2.0&&a < PI6 * 4.0) {vn = area1; }else if(a >= 0.0&&a < PI6 * 2.0) {vn = area2; }else if(a>= -pi6 * 2.0&&a < 0.0) {vn = area3; }else if(a > = 4.0 - PI6 * && a < - PI6 * 2.0) {.vn = area4; }else if(a >= -pi6 * 6.0&& a < -pi6 * 4.0) {vn = area5; }else if(a >= PI6 * 4.0 && a < PI6 * 6.0) {vn = area6; } vec4 color = texture2D(Texture, vn); gl_FragColor = color; }Copy the code

As you can see, the first part of this code is basically the same as the hexagon. The second part is the logic to calculate the texture coordinates of the center points of the six triangles and determine which triangle region the points are in.

One thing to note here is the use of a built-in function, atan, which is an arctangent function that computes angles. Its arguments have two modes: atan(y, x) returns radians [-pi, PI]; Atan (y/x): returns radians [-pi /2, PI/2]. We need to use the function model of [-pi, PI] here.

We changed the size of the Mosaic from 0.03 to 0.05 to make the effect more obvious, you can adjust it according to your preference.

                         

Circular Mosaic

Because the circle does not seem to be able to cover the entire texture, so the effect is not so good, here I only provide an idea and shader code, interested can leave a message. The idea is as follows: Split the texture into squares; Then make an inner circle within each square; Finally, judge whether the point in each square is in the garden. If it is in the garden, take the circular element; if it is not in the garden, keep the original picture unchanged. The code is as follows:

precision highp float; uniform sampler2D inputImageTexture; varying vec2 varyingTextureCoord; Const vec2 TexSize = vec2(400.0, 400.0); Const vec2 mosaicSize = vec2(16.0, 16.0); void main (void) { vec2 intXY = vec2(varyingTextureCoord.x*TexSize.x, varyingTextureCoord.y*TexSize.y); vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x,floor(intXY.y/mosaicSize.y)*mosaicSize.y) + 0.5 * mosaicSize; vec2 delXY = XYMosaic - intXY;float delL = length(delXY);
    vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x,XYMosaic.y/TexSize.y);

    vec4 _finalColor;
    if(delL< 0.5*mosaicSize.x)
       _finalColor = texture2D(inputImageTexture,UVMosaic);
    else
       _finalColor = texture2D(inputImageTexture,varyingTextureCoord);

    gl_FragColor = _finalColor;
}
Copy the code

The operation effect is shown as follows:

                 

It seems too long. I’ll stop here and continue in the next one.