已通过去的2020是一个不怎么顺遂的一年,出入公共场所都须要体温监测,而人流量密集的商场,通常会采用热成像技术来快速测量体温。那么今天咱们就来讲说如何让一张普通图片变成具备热成像的效果。javascript
若是你对shader相关的技术感兴趣也能够阅读如下文章:前端
风格化shader:马赛克java
本期代码使用javascript编写,涉及一些webgl,glsl相关知识。从本文中你能够了解到:web
这一小节咱们介绍colorRamp的原理,并仅仅使用colorRamp实现将图片的GameBoy风格。canvas
最终的成品:segmentfault
简单来讲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风格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对结果的影响
在这篇文章中咱们曾经说到过一种实现模糊的卷积核这是上帝的杰做:《前端图形学从入门到放弃》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; } }
代码改良方向:
Intensity等于14时,模糊很严重但未重影
e^(-xx/a) [红色a=4,杨红a=2,蓝色a=1]*
最后咱们来讲下发光,这个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吧!