在以前的文章中说起了 Shader 中的颜色计算,介绍了一些基本的颜色混合计算,然而在实际的 Shader 滤镜中,简单到加减乘除并不能很好地还原出咱们想要的效果,mix()
也只是其中一个选择。html
回顾一下,平时拿到设计师提供的设计稿,都能看到他们在 Photoshop 中应用了大量的图层混合模式,图层混合模式给设计师提供了丰富的图层混合效果,大大减小他们对颜色的操做,更天然地混合不一样图层。git
一样的,当咱们但愿经过 Shader 给图片增长不同的滤镜效果时,图层混合模式将很是适用。github
首先咱们先定义下什么是混合模式:markdown
混合模式是图像处理技术中的一个技术名词,不只用于普遍使用的 Photoshop 中,也应用于 After Effect、illustrator、 Dreamweaver、Fireworks 等软件。主要功效是能够用不一样的方法将对象颜色与底层对象的颜色混合。当您将一种混合模式应用于某一对象时,在此对象的图层或组下方的任何对象上均可看到混合模式的效果。 —— 百度百科ide
Adobe 也专门介绍了混合模式的相关知识:helpx.adobe.com/cn/photosho…函数
咱们简单罗列下不一样的混合模式:工具
相关的计算公式,也能够直接经过这个在线地址查看混合效果:jamieowen.github.io/glsl-blend/ (图片用的很差,不太好看出效果)oop
让人欣喜的是,咱们不用重复的去实现上面的逻辑了,这个 Github 库已经帮咱们实现了大部分的混合模式:github.com/jamieowen/g… Shader 均可以直接看到:post
什么状况下须要图层混合模式?下面举个抖音的例子,能够看到这个视频下面有一个叫作「霓虹」的特效:spa
实际应用到效果是这样的:
那咱们能够怎么来实现呢?首先实现出一个霓虹的效果来,简单来讲就是一个边缘羽化的圆形,以下所示:
// 封装了一个函数 vec3 drawLeaks(vec2 _uv, vec2 position, vec2 speed, vec2 size, vec3 resolution, vec3 color, float t, vec2 range) { vec2 leakst = _uv; vec2 newsize = normalize(size); newsize /= abs(newsize.x) + abs(newsize.y); leakst -= .5; // 坐标系居中 leakst.x *= resolution.x/resolution.y; // 等比例缩放 leakst.x -= position.x; // 位置调整x leakst.y -= position.y; // 位置调整y leakst.x -= speed.x * t * 10.; // 运动速率x leakst.y -= speed.y * t * 10.; // 运动速率y if (newsize.x < newsize.y) // 大小比例调整 leakst.y *= newsize.x / newsize.y; if (newsize.x > newsize.y) leakst.x *= newsize.y / newsize.x; float angle = atan(leakst.y, leakst.x); // 笛卡尔转极坐标 float radius = length(leakst); vec3 finalColor = vec3(smoothstep(range.x, range.y, radius))*color*(1.-t); // 预设size&上色 return finalColor; } void main() { vec3 leakColor = drawLeaks(myst, vec2(.0, .0), vec2(.0, .0), vec2(.0, .0), iResolution, vec3(166./255., 66./255., 65./255.)*1.5, 0., vec2(.3, 0.)); gl_FragColor = vec4(leakColor, 1.); } 复制代码
效果以下:
这个时候,这个效果能够理解为 PS 中的一个「图层」,咱们把它叫作混合层(Blend Layer),而后咱们须要增长一个基础层(Base Layer)用于混合,咱们先试试加法:
// 这里只展现主要代码 void main() { vec3 leakColor = drawLeaks(myst, vec2(.0, .0), vec2(.0, .0), vec2(.0, .0), iResolution, vec3(166./255., 66./255., 65./255.)*1.5, 0., vec2(.3, 0.)); vec3 texelColor = texture2D(texure, myst).rgb; gl_FragColor = vec4(leakColor + texelColor, 1.); } 复制代码
看样子还行啊,可是若是 Base Layer 是白色背景,则会出现一些问题:
因为使用了加法,符合加色系的规则,颜色的混合最终会往最亮的颜色靠拢,当任何颜色跟一个趋近于白色底相加,都会愈来愈亮,失去了原来的滤镜颜色。那加法不能同时适用于暗色底和白色底。乘法呢?若是要用乘法,首先必须把 Blend Layer 改为白色底(不然任何颜色和黑色底相乘都是黑色):
void main() { vec3 leakColor = drawLeaks(myst, vec2(.0, .0), vec2(.0, .0), vec2(.0, .0), iResolution, vec3((255.-255.)/255., (255.-95.)/255., (255.-32.)/255.), 0., vec2(.3, 0.)); gl_FragColor = vec4(leakColor, 1.); } 复制代码
而后使用乘法(虽然不太好看,但好歹是上了色):
再看看暗色底是什么表现:
简单来讲,经过乘法计算颜色其实是减色系的操做。颜色的混合只会愈来愈暗,滤镜并不能带来更鲜活的表现。因此不论是加法或是乘法,都没有办法将滤镜和底图很好的融合起来。因此这个时候,图层混合模式才这么重要。
当设计师拿到 Blend Layer 和 Base Layer,他们会坚决果断地给二者添加一个「滤色」的混合模式,为何他们会这么作,每每是源于对这个混合模式的了解和平常实践,做为工程师,达不到他们的第六感,咱们不妨看看「滤色」的定义:
查看每一个通道的颜色信息,并将混合色的互补色与基色进行正片叠底。结果色老是较亮的颜色。用黑色过滤时颜色保持不变。用白色过滤将产生白色。此效果相似于多个摄影幻灯片在彼此之上投影。
滤色的实现是这样的:
float blendScreen(float base, float blend) { return 1.0-((1.0-base)*(1.0-blend)); } 复制代码
简单来讲,滤色就是把两个图层中较暗的颜色去掉,取较亮的颜色。咱们不妨试试:
float blendScreen(float base, float blend) { return 1.0-((1.0-base)*(1.0-blend)); } vec3 blendScreen(vec3 base, vec3 blend) { return vec3(blendScreen(base.r,blend.r),blendScreen(base.g,blend.g),blendScreen(base.b,blend.b)); } void main() { vec3 leakColor = drawLeaks(myst, vec2(.0, .0), vec2(.0, .0), vec2(.0, .0), iResolution, vec3((255.-255.)/255., (255.-95.)/255., (255.-32.)/255.), 0., vec2(.3, 0.)); vec3 texelColor = texture2D(texture, myst).rgb; gl_FragColor = vec4(blendScreen(texelColor, leakColor), 1.); } 复制代码
这个效果是符合预期的。实际上咱们在抖音上获得的「霓虹」效果也是这样:浅色底效果较不明显,而深色底较明显。因此能够简单到猜想:抖音的这个特效一样是经过滤色的方式来添加霓虹效果。
上面经过一个简单的案例来讲明了混合模式的使用,实际上经过这些图层混合模式,咱们能够获得一批能直接上线的滤镜效果了。然而并不是全部状况都适合图层混合模式,好比在转场上,mix()
会更适合于两个图层之间的过渡融合。
图层混合模式更适合于那种 局部效果+背景纯色 须要应用在具体图像上的状况。好比上面是一个案例,另一个案例就是漏光(Light Leak):
或者镜头光晕(Lens flare):