2020 is not a smooth year. Temperature monitoring is required in public places, and thermal imaging technology is generally used to quickly measure the temperature of shopping malls with dense traffic. So today we’re going to talk about how to turn an ordinary picture into a thermal image.

If you are interested in shader-related technologies, you can also read the following articles:

Shader stylization: Mosaic

From this article you can learn:

  1. The basic principle of luminescence, how to apply different colors of light to pictures

  2. How to use Gaussian blur to solve ghosting problem

  3. What is colorRamp? Some advanced uses of texture2D, how to change the color style of an image using colorRamp

  4. How to combine the above techniques to achieve thermal imaging effect

This article will not dwell too much on the use of webglAPI. Focusing only on fragment shaders, here is the most basic shader:

precision mediump float; /* Variable declaration */ VARYING VEC2 uv; Uniform sampler2D U_texture0 UV coordinates passed in from the vertex shader; Uniform sampler2D u_texture1; // Global variable, texture 2 (ColorRamp we'll use later) uniform float u_r; // Global variable, uniform float u_g in ColorRamp; Uniform float U_B; // Uniform float U_b; Uniform float u_blurIntensity; // Uniform float u_blurIntensity; // Uniform float u_negativeAmount; Uniform float u_colorRampIntensity; // Uniform float u_colorRampIntensity; // Uniform float u_glowAmount; // uniform float u_glowAmount; Uniform vec3 u_glowColor; Void main () {vec4 color = texture2D (u_texture0, uv); gl_FragColor =color; }Copy the code

In the first part of the variable life we passed in a series of variables from the Web layer, which can be a bit confusing now, but don’t worry, we’ll talk about each one later. In the main function we got the texture information and rendered the image:

Glow effect

In GLSL, colors are represented by a four-dimensional vector:

,0,1.0 vec4 color = vec4 (0, 0.5, 1.0);Copy the code
  • The four components indicate that RGBA is a channel

  • If you want to take out such as RGB to form a three-dimensional vector, you can write it as color.rgb

  • The color range is between [0,1], corresponding to the common web color range [0,255].

The first effect we’re going to introduce is glow, which is simply making the image variable. The original idea was to make the color value bigger:

void main () { vec4 color = texture2D (u_texture0, uv); Color * = 2.0; gl_FragColor =color; }Copy the code

As we expected, the variable was:

But what if you wanted to make your image glow not just white, but any color? Naturally we want to multiply color by another color:

. vec4 color = texture2D (u_texture0, uv); Color. The RGB * = vec3 (1.0, 0.0, 0.5); // This is magenta....Copy the code

The image does turn red as we expected, but it turns dark due to vec3(1.0,0.0,0.5,); The green channel value of is 0. After vector multiplication, all the green channel information in the original picture is lost and becomes 0. (Readers may wish to verify whether the area blackened greatly is the original green area)

To do this we need to use the form of a sum rather than a product:

Color RGB + = vec3 (1.0, 0.0, 0.5) * color in RGB.Copy the code

Finally, we replaced the color and intensity with the variables we passed in, and the glow effect was achieved:

. vec4 color = texture2D (u_texture0, uv); vec4 emission = color; emission.rgb *= u_glowAmount * u_glowColor; color.rgb += emission.rgb; color.rgb *= color.a; gl_FragColor =color; .Copy the code

Fuzzy (Blur)

Next, we will talk about the second effect blur, the core idea is also very simple, if the color information of a pixel is mixed with the color information of the surrounding points, then the image will be blurred.

For this reason, we choose to talk about the information fusion of the four points above and below the pixel point:

. void main () { vec4 color = texture2D (u_texture0, uv); Float step = 0.005; Color += texture2D (u_texture0, uv+vec2(step,0.0)); Color += texture0 (u_texture0, uv+vec2(-step,0.0)); Color += texture2D (u_texture0, uv+vec2(0.0,step)); Color += texture2D (u_texture0, uv+vec2(0.0,-step)); // color = 0; // take the average.....Copy the code

In order to make the result better, we introduce the weight system, so that the original point can make the maximum contribution to the result. Let the weight be 4, and the weight of the point in the positive direction is 2, and the weight of the point in the 45 degree direction is 1:

Vec4 color = 4.0*texture2D (u_texture0, uv); Float step = 0.005; Color += 2.0*texture2D (u_texture0, uv+vec2(step,0.0)); Color += 2.0*texture2D (u_texture0, uv+vec2(-step,0.0)); Color += 2.0*texture2D (u_texture0, uv+vec2(0.0,step)); // color += 2.0*texture2D (u_texture0, uv+vec2(0.0,-step)); Color += 1.0*texture2D (u_texture0, uv+vec2(-step,-step)); Color += 1.0*texture2D (u_texture0, uv+vec2(step,-step)); Color += 1.0*texture2D (u_texture0, uv+vec2(-step,step)); Color += 1.0*texture2D (u_texture0, uv+vec2(step,step)); // color = (4.0+4.0*2.0+4.0*1.0); / / get the averageCopy the code

Just when we happily changed step to our passed variable u_blurIntensity, something tragic happened:

This is because although we add weights, the influence of points does not decrease with the size of the step. For example, the contribution of points directly above is 2 when the step is 0.001 and 0.1 (in fact, 0.001 carries more weight than 0.1 because it is closer to the original point). So when step increases, obviously there will be double shadow.

To this end, we refer to Gaussian blur and introduce an exponential function:

This function decreases as the x passed in increases, so we modify the code above:

  • Increase the value point from 3*3 to 8*8;

  • The decreasing exponential function is exp(-(x * x)/(2.0* BHQP * BHQP));

  • Control the range of the offset (float(iy-halfiterations)*0.00390625);

    Float Blur_Gauss (float BHQP, float x) {return exp (-(x * x)/(2.0 * BHQP * BHQP)); } vec4 Blur (vec2 uv, sampler2D source, float Intensity) { const int iterations = 16; Int halfIterations = iterations / 2; Float sigmaX = 0.1 + Intensity * 0.5; float sigmaY = sigmaX; Float total = 0.0; vec4 ret = vec4 (0., 0., 0., 0.); Float step = 0.00390625; For (int iy = 0; iy < iterations; ++iy) { float fy = Blur_Gauss (sigmaY, float (iy – halfIterations)); float offsety = float (iy – halfIterations) * step; for (int ix = 0; ix < iterations; ++ix) { float fx = Blur_Gauss (sigmaX, float (ix – halfIterations)); float offsetx = float (ix – halfIterations) * step; total += fx * fy; vec4 a = texture2D (source, uv + vec2 (offsetx, offsety)); a.rgb *= a.a; ret += a * fx * fy; } } return ret / total; }… color = Blur (uv, u_texture0, u_blurIntensity); .

Negative (negative)

This is probably the simplest effect in this article, which is to render the image as a negative, as follows:

color.rgb = abs(u_negativeAmount - color.rgb);
Copy the code

colorRamp

The colorRamp is a color card.

Before we go any further with colorRamp, let’s think about how to make the image black and white.

The most straightforward idea is to average the colors of R, G, and B:

. vec4 color = texture2D (u_texture0, uv); float p = color.r+color.g+color.b; Color RGB = vec3 (p / 3.0); .Copy the code

This can also be done with ColorRamp. We want to find a black to white color table and pass in the shader as u_texture1.

The x-coordinate of the leftmost point of the texture is 0, and the x-coordinate of the leftmost point of the texture is 1, so the code for the black and white effect can be written as:

. vec4 color = texture2D (u_texture0, uv); float p = color.r+color.g+color.b; Color.rgb = texture2D (u_texture1, vec2 (p/3.0,.0)).rgb; .Copy the code

The floating-point P here records the original image (u_texture0), and it sets the rules. The output colors are the average values of the R, G, and B channels.

R +color.g+color.b)/3.0 (color.r+color.g+color.b)/3.0 (color.r+color.g+color.b)/3.0

Of course, we are not limited to one rule, so we use u_R, u_G and U_B to control the weight of red, green and blue channels respectively:

. vec4 color = texture2D (u_texture0, uv); float p = dot(color.rgb,vec3(u_r,u_g,u_b)); R *u_r+color.g*u_g+color.b*u_b p += u_colorRampIntensity; Color. RGB = texture2D (u_texture1, vec2 (p,.0)).rgb; .Copy the code

Let’s take the second texture in the image above as an example and achieve the following effect:

In general we use the following colorRamp style:

Finally it is time to witness the miracle, we put the above effect together (glow =>colorRamp=> negative => blur) :

The final result

Of course, the shader can also be used on real people, such as cover art. The greater the blurIntensity, the more cartoon the style, so you can practice it yourself.

A meme that’s been used a lot lately

Let’s talk about some Cyberpunk shaders in the next video!

Several Shaders in 2077 style (I)