風格化shader:熱成像

image
已通過去的2020是一個不怎麼順遂的一年,出入公共場所都須要體溫監測,而人流量密集的商場,通常會採用熱成像技術來快速測量體溫。那麼今天咱們就來講說如何讓一張普通圖片變成具備熱成像的效果。javascript

若是你對shader相關的技術感興趣也能夠閱讀如下文章:前端

風格化shader:馬賽克java

本期代碼使用javascript編寫,涉及一些webgl,glsl相關知識。從本文中你能夠了解到:web

  1. 如何colorRamp實現gameboy效果
  2. 如何對圖片進行模糊處理
  3. 如何實現簡單的高亮效果
  4. 如何結合以上技術實現熱成像效果

colorRamp

這一小節咱們介紹colorRamp的原理,並僅僅使用colorRamp實現將圖片的GameBoy風格。canvas

最終的成品:segmentfault

什麼ColorRamp

簡單來講colorRamp就一個顏色條,他能夠是漸變的,也能夠就是固定的幾個顏色。相似ps填充顏色時使用的那個。函數

我也不知道這玩意叫啥

咱們可使用代碼生成這種圖片,但一般也會使用固定的圖片素材。這裏咱們以圖片素材爲例生成一個colorRamp:webgl

這樣就能夠在片斷着色器中使用這張圖片了:spa

color = texture2D(u_img, uv);

但須要注意的時如何按照這樣的代碼渲染colorRamp會獲得下面的結果3d

固定顏色的圖片,變成漸變的

這是由於咱們在調用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吧!

相關文章
相關標籤/搜索