为了让不同色彩的图片叠加后能够实现更多种色彩组合,从而渲染出各式各样的画面,PS 提供了各式各样规则的混合模式(这里就不具体一一介绍了,提供一个传送门,有兴趣的可自行了解:https://zhuanlan.zhihu.com/p/94081709)
2.如何实现混合模式?我们知道,我们在使用 OpenGL 进行图片效果开发时,将两张图片叠加,如果上层的图片是半透明的,如果我们想在不改变原图色彩的情况下透过上层图片看到底图,有两种实现方法,第一是使用opengl中为我们提供的混合模式的接口glBlendFunc(),第二是我们再片段着色器里使用 mix()函数,而这两种方法改变的都是图片的 alpha值(图片透明度), 可 ps 里的混合模式大多数是基于将两张图片的rgb 和 alpha做各种运算得出的,因此要在 OpenGL 中实现 PS的混合模式,更关键的是依赖图形相关算法,本文参照 Photoshop blend算法 ,介绍如何通过shader,在OpenGL中实现混合效果。
第一步,如何在OpenGL 中开启混合模式?
glEnable( GL_BLEND ); // 开启混合
glDisable( GL_BLEND ); // 关闭混合
第二步,如何设置混合模式?
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//对四通道统一进行混合 *** 作
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);//为RGB和alpha通道分别设置不同的混合选项
glBlendEquation(GLenum mode)//设置混合的运算符,与上两个函数搭配
glBlendFunc(GLenum sfactor, GLenum dfactor)
函数接受两个参数,来设置源和目标因子。OpenGL为我们定义了很多个选项,我们将在下面列出大部分最常用的选项。注意常数颜色向量C¯constant可以通过glBlendColor函数来另外设置。
GL_ZERO 因子等于0
GL_ONE 因子等于1
GL_SRC_COLOR 因子等于源颜色向量C¯source
GL_ONE_MINUS_SRC_COLOR 因子等于1−C¯source
GL_DST_COLOR 因子等于目标颜色向量C¯destination
GL_ONE_MINUS_DST_COLOR 因子等于1−C¯destination
GL_SRC_ALPHA 因子等于C¯source的alpha分量
GL_ONE_MINUS_SRC_ALPHA 因子等于1− C¯source的alpha分量
GL_DST_ALPHA 因子等于C¯destination的alpha分量
GL_ONE_MINUS_DST_ALPHA 因子等于1− C¯destination的alpha分量
GL_CONSTANT_COLOR 因子等于常数颜色向量C¯constant
GL_ONE_MINUS_CONSTANT_COLOR 因子等于1−C¯constant
GL_CONSTANT_ALPHA 因子等于C¯constant的alpha分量
GL_ONE_MINUS_CONSTANT_ALPHA
glBlendEquation(GLenum mode):
GL_FUNC_ADD:默认选项,将两个分量相加:C¯result=Src+Dst。
GL_FUNC_SUBTRACT:将两个分量相减: C¯result=Src−Dst。
GL_FUNC_REVERSE_SUBTRACT:将两个分量相减,但顺序相反:C¯result=Dst−Src。
通常我们都可以省略调用glBlendEquation,因为GL_FUNC_ADD对大部分的 *** 作来说都是我们希望的混合方程,但如果你真的想打破主流,其它的方程也可能符合你的要求。
第三步,如何用 OpenGL 实现 PS 中的混合模式?
这里先给出一张网上收集来的 PS 算法公式图:
理论上来说,我们只要在片段着色器里,根据如图所示的公式,将采样后的图片的 rgba 如法炮制进行变换是不是就可以如法炮制 ps 中各种花里胡哨的效果了呢?我一开始也是这么想的,但是坑就在这里!这种方式只能实现非透明图的效果混合,对于带透明度或者阴影的图片来说,会出现巨大的问题!(这里的坑被我踩爆了,研究了好久才知道没有想象的那么简单)
话不多说,先列出本次实现的共计九种混合模式算法,然后依次介绍实现:
1.正片叠底
2.叠加
3.线性减淡
4.线性加深
5.滤色
6.柔光
7.高光
8.线性光
9.差值
正片叠底:
理论上公式是 a * b 即可,本着control c+v的精神,我迅速实现了效果,代码如下
vec4 result = blendColor * baseColor
上机一看,好家伙,还真不赖(虽然有些黑边),虽然对比 PS 有一些差别,但是看起来问题不大,内心开始暗自窃喜,原来这么简单(手动狗头),效果如图(左图为开发效果,右边为 PS 上效果对比图):
然而,当我换了一张透明底图的贴纸之后,发现一切都变了… 周围的黑边开始越来越重
于是我立马当机立断,自欺欺人的立刻换了一张透明度更高的贴纸,并安慰自己肯定效果会更好的,结果直接傻掉,这什么玩意儿,这要是被产品看见了,不得被怼死…
此时,我还是自欺欺人的现将公式全部都CV 一遍,告诉自己或许只有正片叠底会出现这种情况,其他的或许不会,结果好家伙,每个都会出现该问题!
此刻,我终于发现事情不对劲,开始思考导致问题的原因,在使用了各种类型的贴纸图片后,我发现,带 alpha 度的图片的黑边问题会更严重,我开始意识到应该是 alpha 度混合出现了问题
想到这里,我先是熟练的打开了谷歌浏览器,熟练的输入了如何用 opengl 实现带透明度的 PS 混合算法,然而我得到的所有答案,都是 a * b!没办法,看来白piao 的方式是行不通了,只能自己动手了,
首先我们需要分析一下产生黑边的原因
1.图片出现黑边的程度与图片自身的透明度有关,由此以正片叠加为例,结合目前实现方式可以推断出产生黑边的原因,是由于公式中使用的是 a*b,当图片周围的为透明时,该透明处的 rgba 均为 0,导致相乘后结果值为 0,而我在外部混合调用的是
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);由于拿到的是结果图的 rgb 是 0,导致渲染上屏幕的这一块就是黑色,因此针对这一块的 alpha,需要进行改进,那么 alpha 该如何调整呢?调整的公式又是什么呢?
于是我开始不断推导公式,不断试错,开始观察 PS 上的具体效果变化,然后对正片叠底公式进行改进,得出如下结果:
vec4 result = blendColor * baseColor + blendColor * (1.0 - baseColor.a) + baseColor * (1.0 - blendColor.a);
其他的模式不多做赘述,直接 po 出代码:
//混合模式函数 总共9种
vec4 blendColor(int blendMode, vec4 baseColor, vec4 blendColor) {
if (blendMode <= 0 || blendMode > 9) { //判空 返回贴纸素材
return blendColor;
} else if (blendMode == 1) { // 1.正片叠底 Multiply
return blendColor * baseColor + blendColor * (1.0 - baseColor.a) + baseColor * (1.0 - blendColor.a);
} else if (blendMode == 2) { // 2.叠加 Overlay
float ra;
if (2.0 * baseColor.r < baseColor.a) {
ra = 2.0 * blendColor.r * baseColor.r + blendColor.r * (1.0 - baseColor.a) + baseColor.r * (1.0 - blendColor.a);
} else {
ra = blendColor.a * baseColor.a - 2.0 * (baseColor.a - baseColor.r) * (blendColor.a - blendColor.r) + blendColor.r * (1.0 - baseColor.a) + baseColor.r * (1.0 - blendColor.a);
}
float ga;
if (2.0 * baseColor.g < baseColor.a) {
ga = 2.0 * blendColor.g * baseColor.g + blendColor.g * (1.0 - baseColor.a) + baseColor.g * (1.0 - blendColor.a);
} else {
ga = blendColor.a * baseColor.a - 2.0 * (baseColor.a - baseColor.g) * (blendColor.a - blendColor.g) + blendColor.g * (1.0 - baseColor.a) + baseColor.g * (1.0 - blendColor.a);
}
float ba;
if (2.0 * baseColor.b < baseColor.a) {
ba = 2.0 * blendColor.b * baseColor.b + blendColor.b * (1.0 - baseColor.a) + baseColor.b * (1.0 - blendColor.a);
} else {
ba = blendColor.a * baseColor.a - 2.0 * (baseColor.a - baseColor.b) * (blendColor.a - blendColor.b) + blendColor.b * (1.0 - baseColor.a) + baseColor.b * (1.0 - blendColor.a);
}
return vec4(ra, ga, ba, 1.0);
} else if (blendMode == 3) { // 3. 线性减淡 Linear Dodage
return vec4(clamp((baseColor.rgb + baseColor.rgb * (1.0 - blendColor.a)+ blendColor.rgb * (1.0 - baseColor.a) + blendColor.rgb) , vec3(0.0), vec3(1.0)) , blendColor.a);
} else if (blendMode == 4) { // 4.线性加深 Linear Burn
return vec4(clamp(baseColor.rgb + blendColor.rgb - vec3(1.0) * blendColor.a, vec3(0.0), vec3(1.0)), baseColor);
} else if (blendMode == 5) { // 5.滤色 Screen
vec4 whiteColor = vec4(1.0);
return whiteColor - ((whiteColor - blendColor) * (whiteColor - baseColor));
} else if (blendMode == 6) { // 6.柔光 SoftLight
float alphaDivisor = baseColor.a + step(baseColor.a, 0.0);
return baseColor * (blendColor.a * (baseColor / alphaDivisor) + (2.0 * blendColor * (1.0 - (baseColor / alphaDivisor)))) + blendColor * (1.0 - baseColor.a) + baseColor * (1.0 - blendColor.a);
} else if (blendMode == 7) { // 7.亮光 Vivid Light
return vec4(clamp(baseColor.rgb + baseColor.rgb * (1.0 - blendColor.a)+ blendColor.rgb * (1.0 - baseColor.a) + baseColor.rgb * (2.0*blendColor.rgb - vec3(1.0))/ (2.0*(vec3(1.0)-blendColor.rgb)),vec3(0.),vec3(1.)),blendColor.a);
} else if (blendMode == 8) { // 8.线性光 Linear Light
return vec4(clamp(baseColor.rgb + baseColor.rgb * (1.0 - blendColor.a)+ blendColor.rgb * (1.0 - baseColor.a) + 2.0 * blendColor.rgb - vec3(1.0),vec3(0.0), vec3(1.0)),blendColor.a);
} else if (blendMode == 9) { // 9.差值 Different
return vec4(abs(clamp(blendColor.rgb + baseColor.rgb * (1.0 - blendColor.a)+ blendColor.rgb * (1.0 - baseColor.a) - baseColor.rgb,vec3(-1.),vec3(1.))),blendColor.a);
}
}
每种效果改进的方式因具体实现公式而有部分变化,主要是要注意两点:
1.当向量进行相加减的时候,需要使用 clamp()函数进行限制
2.在 rgb 的值大于 0.5(即比灰度图亮)的时候需要进行公式变化
3.根据 公式原理判断结果图的明暗度,进行最后混合使用原图还是混合图的 alpha
4.每个公式其实都是由 blendColor * (1.0 - baseColor.a) + baseColor * (1.0 - blendColor.a)变换而来
改进之后对比竞品效果如图:
PS:
醒图:
我的效果:
可以发现,我们开发的效果和 PS 上几乎是保持一致的,而醒图的效果由于 alpha 度没有处理好,导致曝光非常严重,严重影响效果
本文到这里就结束了,如果大家有兴趣,我再写一篇关于具体推导公式的文章,创作和分享不易,觉得有所收益的可以打赏一下
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)