关于SegmentFault:风格化shader热成像

3次阅读

共计 4714 个字符,预计需要花费 12 分钟才能阅读完成。


曾经过来的 2020 是一个不怎么顺遂的一年,出入公共场所都须要体温监测,而人流量密集的商场,个别会采纳热成像技术来疾速测量体温。那么明天咱们就来说说如何让一张一般图片变成具备热成像的成果。

如果你对 shader 相干的技术感兴趣也能够浏览以下文章:

风格化 shader:马赛克

本期代码应用 javascript 编写,波及一些 webgl,glsl 相干常识。从本文中你能够理解到:

  1. 如何 colorRamp 实现 gameboy 成果
  2. 如何对图片进行含糊解决
  3. 如何实现简略的高亮成果
  4. 如何联合以上技术实现热成像成果

colorRamp

这一大节咱们介绍 colorRamp 的原理,并仅仅应用 colorRamp 实现将图片的 GameBoy 格调。

最终的成品:

什么 ColorRamp

简略来说 colorRamp 就一个色彩条,他能够是突变的,也能够就是固定的几个色彩。相似 ps 填充色彩时应用的那个。

咱们能够应用代码生成这种图片,但通常也会应用固定的图片素材。这里咱们以图片素材为例生成一个 colorRamp:

这样就能够在片段着色器中应用这张图片了:

color = texture2D(u_img, uv);

但须要留神的时如何依照这样的代码渲染 colorRamp 会失去上面的后果

固定色彩的图片,变成突变的

这是因为咱们在调用 gl.texParameteri 函数是第三个参数传入 gl.LINEAR,如果传入 gl.NEAREST,则不会进行插值。

var val ;
if(condition){val = gl.LINEAR;}else{val = gl.NEAREST}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, val); 
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, val);

colorRamp 只是咱们的配色计划,不会间接将它绘制在 canvas 上。有了配色计划后咱们还须要通知 shader 什么地位应用何种配色。例如在场景一个类 Minecraft 的游戏中咱们须要一些 ” 土块 ”.

这时咱们的 shader 能够是这样的

blender 中图形化的 shader

它能够依据 z 轴的方向来决定是涂上绿色还是涂上土色。更进一步如果咱们将 colorRamp 作为参数,在不扭转 shader 的逻辑的状况下,就能够做出多种材质,实现 shader 的复用。

对于更简单的几何体咱们还能够联合法线,mix 函数制作。这里就不深刻开展。

Gameboy 格调

要如何实现上述的 gameBoy 格调 shader?代码如下

uniform float u_colorRampLuminosity;
uniform sampler2D u_img0;
uniform sampler2D u_img1;
float saturate(float x){return clamp(x,0.,1.); 
}
void main() {vec4 color = texture2D(u_img0, uv);
      float luminance = 0.;
      luminance = dot(vec3(.3,.6,.1) , color.rgb);
      luminance = saturate(luminance + u_colorRampLuminosity);
      color.rgb = texture2D(u_img1, vec2(luminance, .0)).rgb;
      gl_FragColor = color;
}

这里咱们咱们向 shader 中传入三个变量别离是咱们的原图纹理 u_img0,colorRamp 纹理 u_img1,以及一个控制参数 u_colorRampLuminosity。

每一个像素都有其对应的 color,每一个 color 依据肯定的规定失去一个介于[0,1)之间的值 luminance。最初咱们在通知 shader,这个像素点须要用 colorRamp 中的哪一个色彩上色。因为 colorRamp 是一个一维散布的色条所以 y 重量为 0。

dot(vec3(.3,.6,.1), color.rgb)这一步骤,其实就是计算 rgb 三个通道的奉献,对应的重量大该通道的奉献就大。

RGB 取值对后果影响

而控制参数 u_colorRampLuminosity,则能够了解为偏移量,就是在目前的根底上向 colorRamp 的左侧 / 右侧做偏移:

不同 u_colorRampLuminosity 对后果的影响

Blur

在这篇文章中咱们已经说到过一种实现含糊的卷积核这是上帝的杰作:《前端图形学从入门到放弃》2.5 画皮:纹理贴图。其原理就是对每一个像素取该像素和其四周像素点色彩的平均值来实现。

vec4 Blur(vec2 uv, sampler2D source, float Intensity)
{
  float step = 0.00390625 * Intensity;
  vec4 result = vec4 (0, 0, 0, 0);
  vec2 texCoord = vec2(0, 0);
  texCoord = uv + vec2(-step, -step);
  result += texture2D(source, texCoord);
  texCoord = uv + vec2(-step, 0);
  result += 2.0 * texture2D(source, texCoord);
  texCoord = uv + vec2(-step, step);
  result += texture2D(source, texCoord);
  texCoord = uv + vec2(0, -step);
  result += 2.0 * texture2D(source, texCoord);
  texCoord = uv;
  result += 4.0 * texture2D(source, texCoord);
  texCoord = uv + vec2(0, step);
  result += 2.0 * texture2D(source, texCoord);
  texCoord = uv + vec2(step, -step);
  result += texture2D(source, texCoord);
  texCoord = uv + vec2(step, 0);
  result += 2.0* texture2D(source, texCoord);
  texCoord = uv + vec2(step, -step);
  result += texture2D(source, texCoord);
  result = result * 0.0625;
  return result;
}

下面的代码实现了一个繁难的含糊函数,对图片 (sampler2D source) 含糊解决。用来均匀的是上下左右以及斜向 45 度的 8 个点。其中原始点的权重最大为 4,正上下左右的权重为 2,残余点为 1。再通过第三个参数 Intensity 管制取点的间隔。Intensity 越大选取的点离原始坐标越远。

最终成果

但这种办法有一个毛病,就是当 Intensity 取值过大时,不可避免的会产生重影。这是因为尽管咱们为每个点加上了权重,但变动仍旧是线性,而咱们须要的含糊成果因该是随着间隔的增大影响递加的。

Intensity 等于 14 时,重影相当严重

为此咱们须要改进代码

float BlurHD_G(float bhqp, float x)
{return exp(-(x * x) / (2.0 * bhqp * bhqp));
}
vec4 BlurHD(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.);
        for (int iy = 0; iy < iterations; ++iy)
        {float fy = BlurHD_G(sigmaY, float(iy - halfIterations));
            float offsety = float(iy - halfIterations) * 0.00390625;
            for (int ix = 0; ix < iterations; ++ix)
            {float fx = BlurHD_G(sigmaX, float(ix - halfIterations));
                float offsetx = float(ix - halfIterations) * 0.00390625;
                        total += fx * fy;
                        vec4 a = texture2D(source, uv + vec2(offsetx, offsety));
                        a.rgb *=a.a;
                ret += a * fx * fy;
            }
        }

代码改进方向:

  • 将取值点由 3 3 减少到了 1616;
  • 采纳递加的指数函数:exp(-(x x)/(2.0 bhqp * bhqp));
  • 管制了偏移的范畴(float(iy – halfIterations)*0.00390625);

Intensity 等于 14 时,含糊很重大但未重影

e^(-xx/a) [红色 a =4, 杨红 a =2, 蓝色 a =1]*

Glow

最初咱们来说下发光,这个 shader 实现很简略:

vec4 emission = color;
float low = .5;
vec3 glowColor = vec3(1.,1.,1.);
emission.rgb *=  glow * glowColor;
color.rgb += emission.rgb;

这里最终的色彩等于根底色(color)加上发光(emission),emission 是强度 glow 与色彩 glowColor 的乘积。

右边原始图片,左边发光强度 0.5,发光色红色

当然也能够扭转发光色:

热成像

看到这里读者必定都焦急了,说好的热成像?别急上面就是见证奇观的时刻:

void main() {vec4 color = vec4(1.,1.,1.,1.);
      // 含糊
      color = BlurHD(fragColor, u_image0, u_blurIntensity);
      // 负片
      color.rgb = 1.0 - color.rgb;
      // colorRamp
      float luminance = 0.;
      luminance = dot(vec3(u_r,u_g,u_b) , color.rgb);
      luminance = saturate(luminance + u_colorRampLuminosity);
      color.rgb = texture2D(u_image1, vec2(luminance, .0)).rgb;
      // 发光
      vec4 emission = color;
      float glow = 0.5;
      vec3 glowColor = vec3(1.,1.,.1);
      emission.rgb *=  glow * glowColor;
      color.rgb += emission.rgb;
      color.rgb *= color.a;
      gl_FragColor = color;
}

这里有一个成果 color.rgb =1.0- color.rgb; 因为太过简略,下面没有独自拿出来说,相当于做了一个负片的成果。shader 间断应用了含糊 => 负片 => colorRamp => 发光

这里 u_r,u_g,u_b 是外界传入的值,例如 r =1,g,b = 0.0;就是仅由红通道来决定 colorRamp,有点相似 blender 中的 seperateRGB 的意思。

float saturate(float x){return clamp(x,0.,1.); 
}

最终成果

当然这个 shader 也可用在真人身上,比方封面图。其中 Intensity 越大格调越卡通,大家能够把他们抽离进去当初参数从外界传入。本人领会一下。

最近很罕用的一张表情包 **

下期视频咱们来聊聊一些赛博朋克格调的 shader 吧!

正文完
 0