存档

‘未分类’ 分类的存档

OpenGL(二十) 投影 的shader实现

2018年2月10日 没有评论

简单说来 投影 是将一个纹理贴到另一个模型或图片上的技术。在现实生活中,皮影戏就是根据这个原理产生的。

原理

参照皮影戏,我们需要一个投射器,一个采样图和一个成像模型。这样一想,跟OpenGL(十九)阴影 通过ShadowMap的shader实现中提到的投影模型是差不多的。不同之处在于,不需要采集深度图,当进行到第二步时,也不需要比较深度。而是直接取出采样图中texcoord对应的颜色进行混合。

实现

依照之前所述,在shader中我们需要像素上的点转换到投影视口中,然后依照texcoord从采样图中取色。

//vs
attribute vec3 pos;
attribute vec2 texcoord;
attribute vec3 normal;
uniform mat4 M;
uniform mat4 P;
uniform mat4 V;
uniform mat4 U_ProjectorMatrix;
varying vec2 V_Texcoord;
varying vec3 V_WorldPos;
varying vec4 V_ProjectCoord;
void main()
{
    V_Texcoord=texcoord;
    vec4 worldPos=M*vec4(pos,1.0);
    V_WorldPos=worldPos.xyz;
    V_ProjectCoord=U_ProjectorMatrix*worldPos;
    gl_Position=P*V*worldPos;
}
//fs
uniform sampler2D U_ProjectiveTexture;//projective texture
uniform sampler2D U_MainTexture;
varying vec2 V_Texcoord;
varying vec3 V_WorldPos;
varying vec4 V_ProjectCoord;
void main()
{
    if(V_ProjectCoord.z>0.0)
    {
        gl_FragColor=texture2D(U_MainTexture,V_Texcoord)*textureProj(U_ProjectiveTexture,V_ProjectCoord);
    }
    else
    {
        gl_FragColor=texture2D(U_MainTexture,V_Texcoord);
    }
}

有遮挡的投影

当成像物体不止一个时,投影通常要能体现出被投影物体的遮挡关系。这时就要用到深度图的比较了,根据z值来衡量是否需要投影采样图。具体可以结合之前的文章,比较简单就不实现了。

总结

本文介绍了如何制造投影。另外,在非平行光的情况下,可以制作出有形变的投影哦。

分类: 未分类 标签:

OpenGL(二十) 投影 的shader实现

2018年2月10日 没有评论

简单说来 投影 是将一个纹理贴到另一个模型或图片上的技术。在现实生活中,皮影戏就是根据这个原理产生的。

原理

参照皮影戏,我们需要一个投射器,一个采样图和一个成像模型。这样一想,跟OpenGL(十九)阴影 通过ShadowMap的shader实现中提到的投影模型是差不多的。不同之处在于,不需要采集深度图,当进行到第二步时,也不需要比较深度。而是直接取出采样图中texcoord对应的颜色进行混合。

实现

依照之前所述,在shader中我们需要像素上的点转换到投影视口中,然后依照texcoord从采样图中取色。

//vs
attribute vec3 pos;
attribute vec2 texcoord;
attribute vec3 normal;
uniform mat4 M;
uniform mat4 P;
uniform mat4 V;
uniform mat4 U_ProjectorMatrix;
varying vec2 V_Texcoord;
varying vec3 V_WorldPos;
varying vec4 V_ProjectCoord;
void main()
{
    V_Texcoord=texcoord;
    vec4 worldPos=M*vec4(pos,1.0);
    V_WorldPos=worldPos.xyz;
    V_ProjectCoord=U_ProjectorMatrix*worldPos;
    gl_Position=P*V*worldPos;
}
//fs
uniform sampler2D U_ProjectiveTexture;//projective texture
uniform sampler2D U_MainTexture;
varying vec2 V_Texcoord;
varying vec3 V_WorldPos;
varying vec4 V_ProjectCoord;
void main()
{
    if(V_ProjectCoord.z>0.0)
    {
        gl_FragColor=texture2D(U_MainTexture,V_Texcoord)*textureProj(U_ProjectiveTexture,V_ProjectCoord);
    }
    else
    {
        gl_FragColor=texture2D(U_MainTexture,V_Texcoord);
    }
}

有遮挡的投影

当成像物体不止一个时,投影通常要能体现出被投影物体的遮挡关系。这时就要用到深度图的比较了,根据z值来衡量是否需要投影采样图。具体可以结合之前的文章,比较简单就不实现了。

总结

本文介绍了如何制造投影。另外,在非平行光的情况下,可以制作出有形变的投影哦。

分类: 未分类 标签:

OpenGL(十九) 阴影 通过ShadowMap的shader实现

2018年2月10日 没有评论

阴影 可以使角色与地面的关系更加明确。本文主要介绍如何通过 ShadowMap 在OpenGL中实现 阴影 。

核心原理

Shadow Map的基本思想:通过LightView画一张DepthMap,然后Camera View渲场景的时候,把Pixel坐标变换到Light Space,比较Depth即可(Pixel的Depth大于Shadow Map的Depth即在阴影区)。也可以直接是ScreenSpaceDepthMapPost-processing

阴影 生成需要三个步骤:

  1. 在角色正上方照出一张深度图。
  2. 视线方向的模型绘制点,在上方摄像机视口的Z值与深度图相同位置的Z值进行比较,如果比深度图中的值小则为亮区,反之需要绘制阴影。
  3. 将颜色物体颜色与阴影绘制在一起。

实现

深度图其实就是一张灰度图片,可以简单的将摄像机放到物体正上方,绘制出常规效果之后,只提取R通道。另外通过pow可以加深depthValue。

//fs
varying vec2 V_Texcoord;
uniform sampler2D U_MainTexture;
void main()
{
    float depthValue=texture2D(U_MainTexture,V_Texcoord).r;
    gl_FragColor=vec4(vec3(pow(depthValue,2.0)),1.0);
}

有了shadowmap,接下来需要在比较阴影区域的深度值,可以通过

//vs
attribute vec3 pos;
attribute vec2 texcoord;
attribute vec3 normal;
uniform mat4 M;
uniform mat4 P;
uniform mat4 V;
uniform mat4 U_ProjectorMatrix;
varying vec2 V_Texcoord;
varying vec3 V_WorldPos;
varying vec4 V_ProjectCoord;
void main()
{
    V_Texcoord=texcoord;
    vec4 worldPos=M*vec4(pos,1.0);
    V_WorldPos=worldPos.xyz;
    V_ProjectCoord=U_ProjectorMatrix*worldPos;
    gl_Position=P*V*worldPos;
}
//fs
float CalculateShadow()
{
    vec3 fragPos=V_ProjectorSpaceFragPos.xyz/V_ProjectorSpaceFragPos.w;
    fragPos=fragPos*0.5+vec3(0.5); //to 0-1
    float depthInShadowMap=texture2D(U_ShadowMap,fragPos.xy).r;
    float currentDepth=fragPos.z;
    float shadow=(currentDepth-0.001)>depthInShadowMap?1.0:0.0;
    return shadow;
}
void main()
{
    //...
    vec4 color=ambientColor+(diffuseColor+specularColor)*vec4(vec3(1.0-CalculateShadow()),1.0);
}

总结

经过上述步骤可以将 阴影 绘制出来。如果希望阴影能降低锯齿,可以将深度图进行高斯模糊,或使用PCF(Percentage Closer Filtering)进行柔化。

OpenGL(十九) 阴影 通过ShadowMap的shader实现

分类: 未分类 标签:

OpenGL(十九) 阴影 通过ShadowMap的shader实现

2018年2月10日 没有评论

阴影 可以使角色与地面的关系更加明确。本文主要介绍如何通过 ShadowMap 在OpenGL中实现 阴影 。

核心原理

Shadow Map的基本思想:通过LightView画一张DepthMap,然后Camera View渲场景的时候,把Pixel坐标变换到Light Space,比较Depth即可(Pixel的Depth大于Shadow Map的Depth即在阴影区)。也可以直接是ScreenSpaceDepthMapPost-processing

阴影 生成需要三个步骤:

  1. 在角色正上方照出一张深度图。
  2. 视线方向的模型绘制点,在上方摄像机视口的Z值与深度图相同位置的Z值进行比较,如果比深度图中的值小则为亮区,反之需要绘制阴影。
  3. 将颜色物体颜色与阴影绘制在一起。

实现

深度图其实就是一张灰度图片,可以简单的将摄像机放到物体正上方,绘制出常规效果之后,只提取R通道。另外通过pow可以加深depthValue。

//fs
varying vec2 V_Texcoord;
uniform sampler2D U_MainTexture;
void main()
{
    float depthValue=texture2D(U_MainTexture,V_Texcoord).r;
    gl_FragColor=vec4(vec3(pow(depthValue,2.0)),1.0);
}

有了shadowmap,接下来需要在比较阴影区域的深度值,可以通过

//vs
attribute vec3 pos;
attribute vec2 texcoord;
attribute vec3 normal;
uniform mat4 M;
uniform mat4 P;
uniform mat4 V;
uniform mat4 U_ProjectorMatrix;
varying vec2 V_Texcoord;
varying vec3 V_WorldPos;
varying vec4 V_ProjectCoord;
void main()
{
    V_Texcoord=texcoord;
    vec4 worldPos=M*vec4(pos,1.0);
    V_WorldPos=worldPos.xyz;
    V_ProjectCoord=U_ProjectorMatrix*worldPos;
    gl_Position=P*V*worldPos;
}
//fs
float CalculateShadow()
{
    vec3 fragPos=V_ProjectorSpaceFragPos.xyz/V_ProjectorSpaceFragPos.w;
    fragPos=fragPos*0.5+vec3(0.5); //to 0-1
    float depthInShadowMap=texture2D(U_ShadowMap,fragPos.xy).r;
    float currentDepth=fragPos.z;
    float shadow=(currentDepth-0.001)>depthInShadowMap?1.0:0.0;
    return shadow;
}
void main()
{
    //...
    vec4 color=ambientColor+(diffuseColor+specularColor)*vec4(vec3(1.0-CalculateShadow()),1.0);
}

总结

经过上述步骤可以将 阴影 绘制出来。如果希望阴影能降低锯齿,可以将深度图进行高斯模糊,或使用PCF(Percentage Closer Filtering)进行柔化。

OpenGL(十九) 阴影 通过ShadowMap的shader实现

分类: 未分类 标签:

OpenGL(十八)Gamma校正 色域 与 HDR

2018年2月10日 没有评论

通常来说,在不同设备上看到的颜色是不同的。其中最常提及的概念是高动态光照渲染(High-Dynamic Range,简称 HDR )。它可以使图像在亮度的表现上更丰富。这篇文章讨论设备颜色和校正的相关概念。

眼前的黑不是黑

人眼对亮度的敏感程度不是线性的,因此我们我们更容易看到亮的区域。

OpenGL(十八)Gamma校正 色域 与 HDR
上图中左侧是线性渐变的图,右侧是我们实际看到的。在显示器上,输入线性信号就会显示出右图的效果。为了解决这个问题,在显示时会加入一个颜色校正,来保证图片看起来正确,这就是Gamma Correction,具体说来,这个算法是pow(color, 1/2.2)。所有的图片和视频都被编码到伽马空间中。可以这样理解,你所看到的一切都不是真的。

伽马校正

通过pow(color, 1/2.2)这个Gamma-encode操作,可以使灰度部分得到更多的色值,得到一张比较“亮”的图片并存储起来。这张图片虽然暗部细节都保留下来了,但是不能直接看这张“亮”的图片,那只是一个中间产物而已。我们需要显示器做一个pow(color, 2.2)的Gamma-decode操作把它压暗。

OpenGL(十八)Gamma校正 色域 与 HDR

从上图可以看出当gamma值为2.2时,曲线会上拱,输出值大于实际值,也就使得暗部获得更多的色彩。至于2.2是怎么来的,我只能说是经验数字,没什么科学依据。因此这个值也可以根据自己的实际体验进行调整,不过工业标准就是2.2哦。

虽然这个方案比较完美,但有了伽马校正之后,事情并没有变得简单。因为只有取到线性色值,光线计算才能在正确的线性空间(linear space)中进行,最终显示效果又需要应经过伽马校正。

sRGB

为了规范显示的颜色。微软联合HP、三菱、爱普生等厂商联合开发的sRGB(standard Red GreenBlue)通用色彩标准,绝大多数的数码图像采集设备厂商都已经全线支持sRGB标准,如:数码相机、数码摄像机、扫描仪、显示器等都能看到sRGB的选项。而且几乎所有的打印、投影等成像设备也都支持了sRGB标准。有了这套标准,从理论上保证了各种影音设备上经过校正的颜色显示效果相同。它的校正方式如下图所示:

OpenGL(十八)Gamma校正 色域 与 HDR

其中蓝线为经过Gamma-encode的色值,红线为sRGB的值。

Unity中的Gamma

对于PC平台上的程序可以直接设置颜色空间

OpenGL(十八)Gamma校正 色域 与 HDR

但移动平台并没有,所以意义并不大。而对于默认的Gamma模式,渲染时shader中读到的颜色和贴图已经应用了gamma-encode。所以它会比实际更亮一些。这也使得模型和场景有种褪色的感觉。对比图如下:

OpenGL(十八)Gamma校正 色域 与 HDR

解决它的办法,是在shader中进行Gamma校正:

//input
float3 diffuseCol = pow(tex2D( diffTex, texCoord ), 2.2 );  
//output
fragColor.rgb = pow(fragColor.rgb, 1.0/2.2);
return fragColor;

但这也不是万全之策。当混合时,需要用到framebuffer,这时取到的已经是encode的色值。那么两种颜色空间的值(线性和非线性),就会在一起混合。虽然这不是正确的做法,但也只能将就了。

另外也可以通过PostEffect来统一处理颜色校正。这能避免在每个shader中都加入Gamma校正。但其实它是在错误的计算结果上进行修正,并没从根本上还原色彩,而且还产生了性能消耗,因此效果也需要衡量。

由于手机硬件只支持Gamma格式的资源,所以需要使用Gamma工作流。在这种模式下,图和颜色都是Gamma-encode之后的,而引擎将其作为线性输入来处理。为了保证一个合理的输出结果,编辑器在输出结果时也不加Gamma-encode。(错上加错,能是对?我也是醉了,可能是因为图形学第一定律吧)

另一方面,在Unity中还可以直接使用Linear space图元,只要勾选Texture面板中的Bypass sRGB Sample即可。

OpenGL(十八)Gamma校正 色域 与 HDR

这样设置后,在线性模式中可以防止其被重复解码。在Gamma模式中,没找到相关记载,我觉得要不就在输出时会对其进行一次encode,要不压根就没什么用。

对于LightMap都是在线性空间下计算,以Gamma color存储的,可以不用担心移动端上色值的问题,记得选中TextureType为Lightmap即可。

最后你要是问Android、IOS支持不支持线性空间?官方表示:

On Android, linear rendering requires at least OpenGL ES 3.0 graphics API and Android 4.3.
On iOS, linear rendering requires the Metal graphics API.

所以基本GG。而且Android机器屏幕也不全是sRGB的,比如华为Mate9就是NTSC。而且有些是1600w色,有些是1670w色,有些厂商偷偷摸摸在后面改个颜色偏向。我觉得只能通过动态调整PostEffect中的Gamma来达到相同的显示效果。

你说的白是什么白

归根到底,出现伽马校正的原因是存储颜色时灰阶色值不够,24位色图片每个通道只有2^8个色阶,总共只能显示2^24种颜色。如果能很精细的映射灰度,就不需要伽马校正。拿白色来说,如果必须用8位色值,白色只能保存为(255,255,255)。但如果用16位浮点数,白色就可以保存为(255.0,255.0,255.0)。这时就可以精确的映射灰度。另一方面,由于有了足够的色值,也可以表现出更亮的白色,具体说就是允许rgb中的值超过1,这就是高动态光照渲染。

HDR

这项技术的核心就在于使用了float16存储颜色数据。在OpenGL中使用

glTexImage2D(GL_TEXTURE_2D, 0, dataType, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT, NULL);

即可增加图片的存储精度。

它的优点在于:

  • 强光部分颜色不会损失
  • bloom和glow效果支持更好
  • Reduction of banding in low frequency lighting areas

劣势在于:

  • 浮点数渲染会更慢,并且需要更多显存。
  • 不支持硬件抗锯齿。
  • 不是所有硬件都支持。

Unity中的配置

通过在Camera上勾选可以开启:

OpenGL(十八)Gamma校正 色域 与 HDR

总结

总的说来,如果感觉褪色严重,就需要进行Gamma校正。在制作流程中,如果希望规避,就改对应的shader,在这个过程中注意性能消耗与视觉效果的性价比。如果性能允许,就打开HDR。如果要使用bloom或glow,也要打开哦,反正都要费性能了,就不差这点显存啦。

参考文献:

  • https://docs.unity3d.com/Manual/LinearLighting.html
  • https://docs.unity3d.com/Manual/HDR.html
  • https://www.zhihu.com/question/27467127/answer/111973548
  • https://www.zhihu.com/question/20602284
  • http://blog.csdn.net/candycat1992/article/details/46228771

分类: 未分类 标签:

OpenGL(十八)Gamma校正 色域 与 HDR

2018年2月10日 没有评论

通常来说,在不同设备上看到的颜色是不同的。其中最常提及的概念是高动态光照渲染(High-Dynamic Range,简称 HDR )。它可以使图像在亮度的表现上更丰富。这篇文章讨论设备颜色和校正的相关概念。

眼前的黑不是黑

人眼对亮度的敏感程度不是线性的,因此我们我们更容易看到亮的区域。

OpenGL(十八)Gamma校正 色域 与 HDR
上图中左侧是线性渐变的图,右侧是我们实际看到的。在显示器上,输入线性信号就会显示出右图的效果。为了解决这个问题,在显示时会加入一个颜色校正,来保证图片看起来正确,这就是Gamma Correction,具体说来,这个算法是pow(color, 1/2.2)。所有的图片和视频都被编码到伽马空间中。可以这样理解,你所看到的一切都不是真的。

伽马校正

通过pow(color, 1/2.2)这个Gamma-encode操作,可以使灰度部分得到更多的色值,得到一张比较“亮”的图片并存储起来。这张图片虽然暗部细节都保留下来了,但是不能直接看这张“亮”的图片,那只是一个中间产物而已。我们需要显示器做一个pow(color, 2.2)的Gamma-decode操作把它压暗。

OpenGL(十八)Gamma校正 色域 与 HDR

从上图可以看出当gamma值为2.2时,曲线会上拱,输出值大于实际值,也就使得暗部获得更多的色彩。至于2.2是怎么来的,我只能说是经验数字,没什么科学依据。因此这个值也可以根据自己的实际体验进行调整,不过工业标准就是2.2哦。

虽然这个方案比较完美,但有了伽马校正之后,事情并没有变得简单。因为只有取到线性色值,光线计算才能在正确的线性空间(linear space)中进行,最终显示效果又需要应经过伽马校正。

sRGB

为了规范显示的颜色。微软联合HP、三菱、爱普生等厂商联合开发的sRGB(standard Red GreenBlue)通用色彩标准,绝大多数的数码图像采集设备厂商都已经全线支持sRGB标准,如:数码相机、数码摄像机、扫描仪、显示器等都能看到sRGB的选项。而且几乎所有的打印、投影等成像设备也都支持了sRGB标准。有了这套标准,从理论上保证了各种影音设备上经过校正的颜色显示效果相同。它的校正方式如下图所示:

OpenGL(十八)Gamma校正 色域 与 HDR

其中蓝线为经过Gamma-encode的色值,红线为sRGB的值。

Unity中的Gamma

对于PC平台上的程序可以直接设置颜色空间

OpenGL(十八)Gamma校正 色域 与 HDR

但移动平台并没有,所以意义并不大。而对于默认的Gamma模式,渲染时shader中读到的颜色和贴图已经应用了gamma-encode。所以它会比实际更亮一些。这也使得模型和场景有种褪色的感觉。对比图如下:

OpenGL(十八)Gamma校正 色域 与 HDR

解决它的办法,是在shader中进行Gamma校正:

//input
float3 diffuseCol = pow(tex2D( diffTex, texCoord ), 2.2 );  
//output
fragColor.rgb = pow(fragColor.rgb, 1.0/2.2);
return fragColor;

但这也不是万全之策。当混合时,需要用到framebuffer,这时取到的已经是encode的色值。那么两种颜色空间的值(线性和非线性),就会在一起混合。虽然这不是正确的做法,但也只能将就了。

另外也可以通过PostEffect来统一处理颜色校正。这能避免在每个shader中都加入Gamma校正。但其实它是在错误的计算结果上进行修正,并没从根本上还原色彩,而且还产生了性能消耗,因此效果也需要衡量。

由于手机硬件只支持Gamma格式的资源,所以需要使用Gamma工作流。在这种模式下,图和颜色都是Gamma-encode之后的,而引擎将其作为线性输入来处理。为了保证一个合理的输出结果,编辑器在输出结果时也不加Gamma-encode。(错上加错,能是对?我也是醉了,可能是因为图形学第一定律吧)

另一方面,在Unity中还可以直接使用Linear space图元,只要勾选Texture面板中的Bypass sRGB Sample即可。

OpenGL(十八)Gamma校正 色域 与 HDR

这样设置后,在线性模式中可以防止其被重复解码。在Gamma模式中,没找到相关记载,我觉得要不就在输出时会对其进行一次encode,要不压根就没什么用。

对于LightMap都是在线性空间下计算,以Gamma color存储的,可以不用担心移动端上色值的问题,记得选中TextureType为Lightmap即可。

最后你要是问Android、IOS支持不支持线性空间?官方表示:

On Android, linear rendering requires at least OpenGL ES 3.0 graphics API and Android 4.3.
On iOS, linear rendering requires the Metal graphics API.

所以基本GG。而且Android机器屏幕也不全是sRGB的,比如华为Mate9就是NTSC。而且有些是1600w色,有些是1670w色,有些厂商偷偷摸摸在后面改个颜色偏向。我觉得只能通过动态调整PostEffect中的Gamma来达到相同的显示效果。

你说的白是什么白

归根到底,出现伽马校正的原因是存储颜色时灰阶色值不够,24位色图片每个通道只有2^8个色阶,总共只能显示2^24种颜色。如果能很精细的映射灰度,就不需要伽马校正。拿白色来说,如果必须用8位色值,白色只能保存为(255,255,255)。但如果用16位浮点数,白色就可以保存为(255.0,255.0,255.0)。这时就可以精确的映射灰度。另一方面,由于有了足够的色值,也可以表现出更亮的白色,具体说就是允许rgb中的值超过1,这就是高动态光照渲染。

HDR

这项技术的核心就在于使用了float16存储颜色数据。在OpenGL中使用

glTexImage2D(GL_TEXTURE_2D, 0, dataType, width, height, 0, GL_RGBA, GL_UNSIGNED_SHORT, NULL);

即可增加图片的存储精度。

它的优点在于:

  • 强光部分颜色不会损失
  • bloom和glow效果支持更好
  • Reduction of banding in low frequency lighting areas

劣势在于:

  • 浮点数渲染会更慢,并且需要更多显存。
  • 不支持硬件抗锯齿。
  • 不是所有硬件都支持。

Unity中的配置

通过在Camera上勾选可以开启:

OpenGL(十八)Gamma校正 色域 与 HDR

总结

总的说来,如果感觉褪色严重,就需要进行Gamma校正。在制作流程中,如果希望规避,就改对应的shader,在这个过程中注意性能消耗与视觉效果的性价比。如果性能允许,就打开HDR。如果要使用bloom或glow,也要打开哦,反正都要费性能了,就不差这点显存啦。

参考文献:

  • https://docs.unity3d.com/Manual/LinearLighting.html
  • https://docs.unity3d.com/Manual/HDR.html
  • https://www.zhihu.com/question/27467127/answer/111973548
  • https://www.zhihu.com/question/20602284
  • http://blog.csdn.net/candycat1992/article/details/46228771

分类: 未分类 标签:

OpenGL(十七)Photoshop blend算法 与 图层混合模式

2018年2月10日 没有评论

使用混合模式可以制作丰富多彩的效果。而OpenGL中可以轻松开启这种模式,但更关键的是图形算法。本文参照 Photoshop blend算法 ,介绍如何通过shader,在OpenGL中实现混合效果。

OpenGL中开启混合

在OpenGL中可以开启混合模式:

glEnable( GL_BLEND );   // 启用混合
glDisable( GL_BLEND );  // 禁用关闭混合

统一说法,参照Photoshop,下方图层、 baseColor 为原色。上方图层、 blendColor 为混合色。下面介绍 Photoshop blend算法 的详细实现。

公式汇总

先上一张公式汇总图,图片来自网络,出于效率和效果的平衡,有些公式跟下文提到的有些出入。

OpenGL(十七)Photoshop blend算法 与 图层混合模式

正常混合

正常 Opacity

使上方图层完全遮住下方图层。

gl_FragColor = baseColor * (1.0-blendColor.a) + blendColor * blendColor.a;

使结果更暗

整个变暗块都是通过混合色的叠加,使结果更暗。常用的是变亮变暗,程度正常,正片叠底及滤色,较为柔和。

变暗 Darken

两个图层中较暗的颜色将作为混合的颜色保留,比混合色亮的像素将被替换,而比混合色暗像素保持不变。

gl_FragColor = min(baseColor,blendColor);

正片叠底 Multiply

整体效果显示由上方图层和下方图层的像素值中较暗的像素合成的图像效果,任意颜色与黑色重叠时将产生黑色,任意颜色和白色重叠时颜色则保持不变。

gl_FragColor = baseColor*blendColor;

颜色加深 Color Burn

选择该项将降低上方图层中除黑色外的其他区域的对比度,使图像的对比度下降,产生下方图层透过上方图层的投影效果。

gl_FragColor = vec4(1.0) - (vec4(1.0)-baseColor)/blendColor);

线性加深 Linear Burn

上方图层将根据下方图层的灰度与图像融合,此模式对白色无效。

gl_FragColor = baseColor+blendColor-vec4(1.0);

使结果更亮

变亮 Lighten

使上方图层的暗调区域变为透明,通过下方的较亮区域使图像更亮。

gl_FragColor = max(baseColor,blendColor);

滤色 Screen

该项与“正片叠底”的效果相反,在整体效果上显示由上方图层和下方图层的像素值中较亮的像素合成的效果,得到的图像是一种漂白图像中颜色的效果。

gl_FragColor = vec4(1.0) - ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));

颜色减淡 Color Dodge

和“颜色加深”效果相反,“颜色减淡”是由上方图层根据下方图层灰阶程序提升亮度,然后再与下方图层融合,此模式通常可以用来创建光源中心点极亮的效果。

gl_FragColor = baseColor/(vec4(1.0)-blendColor);

线性减淡 Linear Dodage

根据每一个颜色通道的颜色信息,加亮所有通道的基色,并通过降低其他颜色的亮度来反映混合颜色,此模式对黑色无效。

gl_FragColor = baseColor+blendColor;

溶合颜色

叠加柔光这块是使50%灰度上的颜色更亮,50%灰度下的颜色更暗,主要用于增加对比度的,常用的是叠加和柔光,柔光更柔和。

叠加 Overlay

此项的图像最终效果最终取决于下方图层,上方图层的高光区域和暗调将不变,只是混合了中间调。

vec4 lumCoeff=vec4(0.2125,0.7154,0.0721,1.0);
float luminance = dot(baseColor.rgb,lumCoeff.rgb);
if(luminance < 0.45)
{
    gl_FragColor = 2.0 *baseColor * blendColor;
}
else if(luminance >0.55)
{
    gl_FragColor = vec4(1.0)-2.0* ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));
}
else
{
    vec4 colorT1 =  2.0 *baseColor * blendColor;
    vec4 colorT2 =  vec4(1.0)-2.0* ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));
    gl_FragColor =  mix(baseColor,blendColor,(luminance-0.45)*10);
}

柔光 Soft Light

使颜色变亮或变暗让图像具有非常柔和的效果,亮于中性灰底的区域将更亮,暗于中性灰底的区域将更暗。

gl_FragColor = 2.0 * baseColor * blendColor + baseColor*baseColor -2.0*baseColor*baseColor*blendColor;

强光 Hard Light

此项和“柔光”的效果类似,但其程序远远大于“柔光”效果,适用于图像增加强光照射效果。

vec4 lumCoeff=vec4(0.2125,0.7154,0.0721,1.0);
float luminance = dot(blendColor.rgb,lumCoeff.rgb);
if(luminance < 0.45)
{
    gl_FragColor = 2.0 *baseColor * blendColor;
}
else if(luminance >0.55)
{
    gl_FragColor = vec4(1.0)-2.0* ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));
}
else
{
    vec4 colorT1 =  2.0 *baseColor * blendColor;
    vec4 colorT2 =  vec4(1.0)-2.0* ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));
    gl_FragColor =  mix(baseColor,blendColor,(luminance-0.45)*10);
}

亮光 Vivid Light

根据融合颜色的灰度减少比对度,可以使图像更亮或更暗。

gl_FragColor = baseColor + baseColor * (2*blendColor - vec4(1.0)) / (2*(vec4(1.0)-blendColor));

线性光 Linear Light

根据混合颜色的灰度,来减少或增加图像亮度,使图像更亮。

 gl_FragColor = baseColor + 2 * blendColor - vec4(1.0);

点光 Pin Light

如果混合色比50%灰度色亮,则将替换混合色暗的像素,而不改变混合色亮的像素;反之如果混合色比50%灰度色暗,则将替换混合色亮的像素,而不改变混合色暗的像素。

gl_FragColor = min(baseColor,2*blendColor - vec4(1.0));

色差颜色

差值 Difference

上方图层的亮区将下方图层的颜色进行反相,暗区则将颜色正常显示出来,效果与原图像是完全相反的颜色。

gl_FragColor = vec4(abs(blendColor-baseColor).rgb,1.0);

排除 Exclusion

创建一种与“差值”模式类似但对比度更低的效果。3lian.com,与白色混合将反转基色值,与黑色混合则不发生变化。

gl_FragColor =vec4((baseColor + blendColor).rgb - (2.0*baseColor*blendColor).rgb,1.0);

减去 Subtract

gl_FragColor = vec4(baseColor.rgb-blendColor.rgb,1.0);

划分 Divide

gl_FragColor = baseColor/blendColor;

单图处理

平滑

与高斯模糊相同,算子都为1

float kernel[9];
kernel[6]=1;kernel[7]=1;kernel[8]=1;
kernel[3]=1;kernel[4]=1;kernel[5]=1;
kernel[0]=1;kernel[1]=1;kernel[2]=1;
int index=0;
for(int y=0;y<coreSize;y++)
{
    for(int x=0;x<coreSize;x++)
    {
        vec4 currentColor=texture2D(U_MainTexture,V_Texcoord+vec2((-1+x)*texelOffset,(-1+y)*texelOffset));
        color+=currentColor*kernel[index++];
    }
}
color/=9.0;
gl_FragColor=color;

锐化

同上面的平滑效果,其算子为

kernel[6]=0;kernel[7]=-1;kernel[8]=0;
kernel[3]=-1;kernel[4]=4;kernel[5]=-1;
kernel[0]=0;kernel[1]=-1;kernel[2]=0;
//如果不明显
gl_FragColor = baseColor + color;

边缘检测

同上面的平滑效果,其算子为

kernel[6]=0;kernel[7]=1;kernel[8]=0;
kernel[3]=1;kernel[4]=-4;kernel[5]=1;
kernel[0]=0;kernel[1]=1;kernel[2]=0;
//如果不明显
gl_FragColor = baseColor + color;

总结

本文介绍了 Photoshop blend算法 的OpenGL实现方式。主要是编写各种Shader。在实际使用的过程中,不一定严格按照公式来编写,只要效果看着正确就可以使用,有时候没准还会获得出乎意料的效果呢。

分类: 未分类 标签:

OpenGL(十七)Photoshop blend算法 与 图层混合模式

2018年2月10日 没有评论

使用混合模式可以制作丰富多彩的效果。而OpenGL中可以轻松开启这种模式,但更关键的是图形算法。本文参照 Photoshop blend算法 ,介绍如何通过shader,在OpenGL中实现混合效果。

OpenGL中开启混合

在OpenGL中可以开启混合模式:

glEnable( GL_BLEND );   // 启用混合
glDisable( GL_BLEND );  // 禁用关闭混合

统一说法,参照Photoshop,下方图层、 baseColor 为原色。上方图层、 blendColor 为混合色。下面介绍 Photoshop blend算法 的详细实现。

公式汇总

先上一张公式汇总图,图片来自网络,出于效率和效果的平衡,有些公式跟下文提到的有些出入。

OpenGL(十七)Photoshop blend算法 与 图层混合模式

正常混合

正常 Opacity

使上方图层完全遮住下方图层。

gl_FragColor = baseColor * (1.0-blendColor.a) + blendColor * blendColor.a;

使结果更暗

整个变暗块都是通过混合色的叠加,使结果更暗。常用的是变亮变暗,程度正常,正片叠底及滤色,较为柔和。

变暗 Darken

两个图层中较暗的颜色将作为混合的颜色保留,比混合色亮的像素将被替换,而比混合色暗像素保持不变。

gl_FragColor = min(baseColor,blendColor);

正片叠底 Multiply

整体效果显示由上方图层和下方图层的像素值中较暗的像素合成的图像效果,任意颜色与黑色重叠时将产生黑色,任意颜色和白色重叠时颜色则保持不变。

gl_FragColor = baseColor*blendColor;

颜色加深 Color Burn

选择该项将降低上方图层中除黑色外的其他区域的对比度,使图像的对比度下降,产生下方图层透过上方图层的投影效果。

gl_FragColor = vec4(1.0) - (vec4(1.0)-baseColor)/blendColor);

线性加深 Linear Burn

上方图层将根据下方图层的灰度与图像融合,此模式对白色无效。

gl_FragColor = baseColor+blendColor-vec4(1.0);

使结果更亮

变亮 Lighten

使上方图层的暗调区域变为透明,通过下方的较亮区域使图像更亮。

gl_FragColor = max(baseColor,blendColor);

滤色 Screen

该项与“正片叠底”的效果相反,在整体效果上显示由上方图层和下方图层的像素值中较亮的像素合成的效果,得到的图像是一种漂白图像中颜色的效果。

gl_FragColor = vec4(1.0) - ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));

颜色减淡 Color Dodge

和“颜色加深”效果相反,“颜色减淡”是由上方图层根据下方图层灰阶程序提升亮度,然后再与下方图层融合,此模式通常可以用来创建光源中心点极亮的效果。

gl_FragColor = baseColor/(vec4(1.0)-blendColor);

线性减淡 Linear Dodage

根据每一个颜色通道的颜色信息,加亮所有通道的基色,并通过降低其他颜色的亮度来反映混合颜色,此模式对黑色无效。

gl_FragColor = baseColor+blendColor;

溶合颜色

叠加柔光这块是使50%灰度上的颜色更亮,50%灰度下的颜色更暗,主要用于增加对比度的,常用的是叠加和柔光,柔光更柔和。

叠加 Overlay

此项的图像最终效果最终取决于下方图层,上方图层的高光区域和暗调将不变,只是混合了中间调。

vec4 lumCoeff=vec4(0.2125,0.7154,0.0721,1.0);
float luminance = dot(baseColor.rgb,lumCoeff.rgb);
if(luminance < 0.45)
{
    gl_FragColor = 2.0 *baseColor * blendColor;
}
else if(luminance >0.55)
{
    gl_FragColor = vec4(1.0)-2.0* ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));
}
else
{
    vec4 colorT1 =  2.0 *baseColor * blendColor;
    vec4 colorT2 =  vec4(1.0)-2.0* ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));
    gl_FragColor =  mix(baseColor,blendColor,(luminance-0.45)*10);
}

柔光 Soft Light

使颜色变亮或变暗让图像具有非常柔和的效果,亮于中性灰底的区域将更亮,暗于中性灰底的区域将更暗。

gl_FragColor = 2.0 * baseColor * blendColor + baseColor*baseColor -2.0*baseColor*baseColor*blendColor;

强光 Hard Light

此项和“柔光”的效果类似,但其程序远远大于“柔光”效果,适用于图像增加强光照射效果。

vec4 lumCoeff=vec4(0.2125,0.7154,0.0721,1.0);
float luminance = dot(blendColor.rgb,lumCoeff.rgb);
if(luminance < 0.45)
{
    gl_FragColor = 2.0 *baseColor * blendColor;
}
else if(luminance >0.55)
{
    gl_FragColor = vec4(1.0)-2.0* ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));
}
else
{
    vec4 colorT1 =  2.0 *baseColor * blendColor;
    vec4 colorT2 =  vec4(1.0)-2.0* ((vec4(1.0)-baseColor)*(vec4(1.0)-blendColor));
    gl_FragColor =  mix(baseColor,blendColor,(luminance-0.45)*10);
}

亮光 Vivid Light

根据融合颜色的灰度减少比对度,可以使图像更亮或更暗。

gl_FragColor = baseColor + baseColor * (2*blendColor - vec4(1.0)) / (2*(vec4(1.0)-blendColor));

线性光 Linear Light

根据混合颜色的灰度,来减少或增加图像亮度,使图像更亮。

 gl_FragColor = baseColor + 2 * blendColor - vec4(1.0);

点光 Pin Light

如果混合色比50%灰度色亮,则将替换混合色暗的像素,而不改变混合色亮的像素;反之如果混合色比50%灰度色暗,则将替换混合色亮的像素,而不改变混合色暗的像素。

gl_FragColor = min(baseColor,2*blendColor - vec4(1.0));

色差颜色

差值 Difference

上方图层的亮区将下方图层的颜色进行反相,暗区则将颜色正常显示出来,效果与原图像是完全相反的颜色。

gl_FragColor = vec4(abs(blendColor-baseColor).rgb,1.0);

排除 Exclusion

创建一种与“差值”模式类似但对比度更低的效果。3lian.com,与白色混合将反转基色值,与黑色混合则不发生变化。

gl_FragColor =vec4((baseColor + blendColor).rgb - (2.0*baseColor*blendColor).rgb,1.0);

减去 Subtract

gl_FragColor = vec4(baseColor.rgb-blendColor.rgb,1.0);

划分 Divide

gl_FragColor = baseColor/blendColor;

单图处理

平滑

与高斯模糊相同,算子都为1

float kernel[9];
kernel[6]=1;kernel[7]=1;kernel[8]=1;
kernel[3]=1;kernel[4]=1;kernel[5]=1;
kernel[0]=1;kernel[1]=1;kernel[2]=1;
int index=0;
for(int y=0;y<coreSize;y++)
{
    for(int x=0;x<coreSize;x++)
    {
        vec4 currentColor=texture2D(U_MainTexture,V_Texcoord+vec2((-1+x)*texelOffset,(-1+y)*texelOffset));
        color+=currentColor*kernel[index++];
    }
}
color/=9.0;
gl_FragColor=color;

锐化

同上面的平滑效果,其算子为

kernel[6]=0;kernel[7]=-1;kernel[8]=0;
kernel[3]=-1;kernel[4]=4;kernel[5]=-1;
kernel[0]=0;kernel[1]=-1;kernel[2]=0;
//如果不明显
gl_FragColor = baseColor + color;

边缘检测

同上面的平滑效果,其算子为

kernel[6]=0;kernel[7]=1;kernel[8]=0;
kernel[3]=1;kernel[4]=-4;kernel[5]=1;
kernel[0]=0;kernel[1]=1;kernel[2]=0;
//如果不明显
gl_FragColor = baseColor + color;

总结

本文介绍了 Photoshop blend算法 的OpenGL实现方式。主要是编写各种Shader。在实际使用的过程中,不一定严格按照公式来编写,只要效果看着正确就可以使用,有时候没准还会获得出乎意料的效果呢。

分类: 未分类 标签:

OpenGL(十六)通过 卷积 实现: 边缘混合 、 Blur 和 高斯模糊

2018年2月5日 没有评论

卷积

卷积 (Convolution)是两个变量在某范围内相乘后求和的结果。卷积计算通常用来处理边缘的颜色或整体的混色。作为采样之后的处理,可以供很多功能使用。因此在图像效果处理时,它还是应用比较广泛的。

区域缩暗

通过最简单的像素采样,可以将图片中整体的锐利度降低,通过在一个3×3的区域取颜色,并取出区域中的最小色值,可以实现整体变暗,并且边界会像内缩一段距离。

void main()
{
    vec4 minValue = vec4(1.0);
    int coreSize=3;
    int halfSize=coreSize/2;
    float texelOffset = 1/100.0;
    for(int y=0;y<coreSize;y++)
    {
        for(int x = 0;x<coreSize;x++)
        {
            vec4 currentAlpha = texture2D(U_MainTexture,V_Texcoord+vec2((-halfSize+x)*texelOffset,(-halfSize+y)*texelOffset));
            minValue = min(minValue,currentAlpha);
        }
    }
    gl_FragColor=minValue;
}

区域加亮

与区域缩暗相反,取域中最亮的值就可以加亮整体效果,另外边界也会加大。

void main()
{
    vec4 maxValue = vec4(0.0);
    int coreSize=10;
    int halfSize=coreSize/2;
    float texelOffset = 1/100.0;
    for(int y=0;y<coreSize;y++)
    {
        for(int x = 0;x<coreSize;x++)
        {
            vec4 currentAlpha = texture2D(U_MainTexture,V_Texcoord+vec2((-halfSize+x)*texelOffset,(-halfSize+y)*texelOffset));
            maxValue = max(maxValue,currentAlpha);
        }
    }
    gl_FragColor=maxValue;
}

高斯模糊

当加在算子中加入权重,就是高斯模糊的效果,比如:

1,2,1
2,4,2
1,2,1

然后在求和之后除以算子的总和16。

void main()
{
    vec4 color = vec4(0.0);
    int coreSize=3;
    int halfSize=coreSize/2;
    float texelOffset = 1/100.0;
    float kernel[9];
    kernel[6] = 1; kernel[7] = 2; kernel[8] = 1;
    kernel[3] = 2; kernel[4] = 4; kernel[5] = 2;
    kernel[0] = 1; kernel[1] = 2; kernel[2] = 1;
    int index = 0;
    for(int y=0;y<coreSize;y++)
    {
        for(int x = 0;x<coreSize;x++)
        {
            vec4 currentColor = texture2D(U_MainTexture,V_Texcoord+vec2((-1+x)*texelOffset,(-1+y)*texelOffset));
            color += currentColor*kernel[index];
            index++;
        }
    }
    color/=16.0;
    gl_FragColor=color;
}

这样就按原理实现了高斯模糊,但实际过程中并不会这样算。因为遍历的成本太高了。通常拆成两个一维向量,这样时间复杂度就由NxNxWxH下降为2xNxWxH(W为图像的宽,H为图像的高)。另外这些权重又是对称的,因此对于一个5×5的高斯核,只需要记录三个值就可以计算 卷积 了。有时我们也会将水平和竖直的模糊拆分到两个Pass中方便进行效果调整。如果为了节省Drawcall,就会在同一个元片中混合,但也应水平和竖直分别处理,因为它们的像素偏移可能会不同。

void main()
{
    float weight[3] = {0.4026,0.2442,0.0545};
    float texelOffset = 1/100.0;
     //only show vertical here...
    float uv[5];
    uv[0]=V_Texcoord;
    uv[1]=uv + vec2(0.0, texelOffset*1.0 );
    uv[2]=uv - vec2(0.0, texelOffset*1.0 );
    uv[3]=uv + vec2(0.0, texelOffset*2.0 );
    uv[4]=uv - vec2(0.0, texelOffset*2.0 );
    fixed3 sum = tex2D(U_MainTexture,uv[0]).rgb *weight[0];
    for(int i = 1;i<3;++i)
    {
        sum += tex2D(U_MainTexture,uv[2i-1]).rgb * weight[i];
        sum += tex2D(U_MainTexture,uv[2i]).rgb * weight[i];
    }
    gl_FragColor=sum;
}

上面的代码只是垂直方向的 卷积 计算,相同的办法可以算出水平的效果,最后如果需要混合到一起即可。

总结

本文介绍了如何利用RTT和 卷积 算法来制作屏幕效果。

OpenGL(十六)通过 卷积 实现: 边缘混合 、 Blur 和 高斯模糊

分类: 未分类 标签:

OpenGL(十六)通过 卷积 实现: 边缘混合 、 Blur 和 高斯模糊

2018年2月5日 没有评论

卷积

卷积 (Convolution)是两个变量在某范围内相乘后求和的结果。卷积计算通常用来处理边缘的颜色或整体的混色。作为采样之后的处理,可以供很多功能使用。因此在图像效果处理时,它还是应用比较广泛的。

区域缩暗

通过最简单的像素采样,可以将图片中整体的锐利度降低,通过在一个3×3的区域取颜色,并取出区域中的最小色值,可以实现整体变暗,并且边界会像内缩一段距离。

void main()
{
    vec4 minValue = vec4(1.0);
    int coreSize=3;
    int halfSize=coreSize/2;
    float texelOffset = 1/100.0;
    for(int y=0;y<coreSize;y++)
    {
        for(int x = 0;x<coreSize;x++)
        {
            vec4 currentAlpha = texture2D(U_MainTexture,V_Texcoord+vec2((-halfSize+x)*texelOffset,(-halfSize+y)*texelOffset));
            minValue = min(minValue,currentAlpha);
        }
    }
    gl_FragColor=minValue;
}

区域加亮

与区域缩暗相反,取域中最亮的值就可以加亮整体效果,另外边界也会加大。

void main()
{
    vec4 maxValue = vec4(0.0);
    int coreSize=10;
    int halfSize=coreSize/2;
    float texelOffset = 1/100.0;
    for(int y=0;y<coreSize;y++)
    {
        for(int x = 0;x<coreSize;x++)
        {
            vec4 currentAlpha = texture2D(U_MainTexture,V_Texcoord+vec2((-halfSize+x)*texelOffset,(-halfSize+y)*texelOffset));
            maxValue = max(maxValue,currentAlpha);
        }
    }
    gl_FragColor=maxValue;
}

高斯模糊

当加在算子中加入权重,就是高斯模糊的效果,比如:

1,2,1
2,4,2
1,2,1

然后在求和之后除以算子的总和16。

void main()
{
    vec4 color = vec4(0.0);
    int coreSize=3;
    int halfSize=coreSize/2;
    float texelOffset = 1/100.0;
    float kernel[9];
    kernel[6] = 1; kernel[7] = 2; kernel[8] = 1;
    kernel[3] = 2; kernel[4] = 4; kernel[5] = 2;
    kernel[0] = 1; kernel[1] = 2; kernel[2] = 1;
    int index = 0;
    for(int y=0;y<coreSize;y++)
    {
        for(int x = 0;x<coreSize;x++)
        {
            vec4 currentColor = texture2D(U_MainTexture,V_Texcoord+vec2((-1+x)*texelOffset,(-1+y)*texelOffset));
            color += currentColor*kernel[index];
            index++;
        }
    }
    color/=16.0;
    gl_FragColor=color;
}

这样就按原理实现了高斯模糊,但实际过程中并不会这样算。因为遍历的成本太高了。通常拆成两个一维向量,这样时间复杂度就由NxNxWxH下降为2xNxWxH(W为图像的宽,H为图像的高)。另外这些权重又是对称的,因此对于一个5×5的高斯核,只需要记录三个值就可以计算 卷积 了。有时我们也会将水平和竖直的模糊拆分到两个Pass中方便进行效果调整。如果为了节省Drawcall,就会在同一个元片中混合,但也应水平和竖直分别处理,因为它们的像素偏移可能会不同。

void main()
{
    float weight[3] = {0.4026,0.2442,0.0545};
    float texelOffset = 1/100.0;
     //only show vertical here...
    float uv[5];
    uv[0]=V_Texcoord;
    uv[1]=uv + vec2(0.0, texelOffset*1.0 );
    uv[2]=uv - vec2(0.0, texelOffset*1.0 );
    uv[3]=uv + vec2(0.0, texelOffset*2.0 );
    uv[4]=uv - vec2(0.0, texelOffset*2.0 );
    fixed3 sum = tex2D(U_MainTexture,uv[0]).rgb *weight[0];
    for(int i = 1;i<3;++i)
    {
        sum += tex2D(U_MainTexture,uv[2i-1]).rgb * weight[i];
        sum += tex2D(U_MainTexture,uv[2i]).rgb * weight[i];
    }
    gl_FragColor=sum;
}

上面的代码只是垂直方向的 卷积 计算,相同的办法可以算出水平的效果,最后如果需要混合到一起即可。

总结

本文介绍了如何利用RTT和 卷积 算法来制作屏幕效果。

OpenGL(十六)通过 卷积 实现: 边缘混合 、 Blur 和 高斯模糊

分类: 未分类 标签: