关于SegmentFault:风格化shader热成像


曾经过来的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;
            }
        }

代码改进方向:

  • 将取值点由33减少到了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吧!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理