一、快速高斯模糊
1.1 背景
高斯模糊在wiki上定义为一种图像模糊滤波器,使用正态分布计算每个像素输出颜色。正态分布函数和图像如下所示:
\[G(u,v)=\frac{1}{2\pi\delta^2}e^{(-u^2+v^2)/(2\delta^2)} \] 由图可以发现,当x在\(-3\delta\)到\(3\delta\)的时候,函数曲线已经很平滑了,实际运用中大概只采样半径为\(3\delta\)内的像素计算。模糊的过程如下:
a. 输入当前像素的\(u v\)坐标
b. 遍历半径为\(3\delta\)内的像素,代入高斯函数中计算得到权重\(w_i\)
c. 将当前遍历的像素颜色乘以权重:\(w_i*(r,g,b)\)
d. 将所有颜色求和,并除以权重的和$\frac{\Sigma{w_i*(r,g,b)}}{\Sigma{w_i}} $
1.2 简化过程
高斯模糊算法因为线性可分,可以在二维图像上对两个独立的一维空间分别计算。对于矩形阴影这样的利用高斯模糊实现的效果,可以取捷径将高斯函数写成如下分段函数形式:
// =============================================================================
// approximation to the gaussian integral [x, infty)
// =============================================================================
static finline float gi(float x)
{
const float i6 = 1.f / 6.0;
const float i4 = 1.f / 4.0;
const float i3 = 1.f / 3.0;
if (x > 1.5) return 0.0;
if (x < -1.5) return 1.0;
float x2 = x * x;
float x3 = x2 * x;
if (x > 0.5) return .5625 - ( x3 * i6 - 3 * x2 * i4 + 1.125 * x);
if (x > -0.5) return 0.5 - (0.75 * x - x3 * i3); // else
return 0.4375 + (-x3 * i6 - 3 * x2 * i4 - 1.125 * x);
}
也就是说对于如下图所示的单色边缘交界面,我们可以通过这个函数快速计算得到中间过渡带函数值,其效果和利用高斯函数多次采样纹理的一致。
1.3 shader实现
uniform sample2D baseTexture;
in vec2 texCoord;
out vec4 fragColor;
// approximation to the gaussian integral [x, infty)
float gi(float x) {
float i6 = 1.0 / 6.0;
float i4 = 1.0 / 4.0;
float i3 = 1.0 / 3.0;
if (x > 1.5) return 0.0;
if (x < -1.5) return 1.0;
float x2 = x * x;
float x3 = x2 * x;
if (x > 0.5) return .5625 - ( x3 * i6 - 3. * x2 * i4 + 1.125 * x);
if (x > -0.5) return 0.5 - (0.75 * x - x3 * i3);
return 0.4375 + (-x3 * i6 - 3. * x2 * i4 - 1.125 * x);
}
// create a line shadow mask
float lineShadow(vec2 border, float pos , float sigma) {
float pos1 = ((border.x - pos) / sigma) * 1.5;
float pos2 = ((pos - border.y) / sigma) * 1.5;
return 1.0 - abs(gi(pos1) - gi(pos2));
}
void main()
{
vec4 texCol1=texture(baseTexture,texCoord);
vec4 texCol2=texture(baseTexture,((texCoord-0.5)*2.0*1.5+1.0)*0.5);
float lineV=lineShaow(vec2(0.18,0.82),texCoord.x,0.05);
float lineH=lineShaow(vec2(0.2,0.8),texCoord.y,0.24);
float dist=dot(texCoord,vec2(1.0,1.0));
vec3 edgeCol=mix(vec3(0.8,0.9,0.8),vec3(0.0),lineV*lineH*smoothstep(1.5,0.5,dist));
edgeCol=mix(edgeCol,texCol2.rgb,texCol2.a);
fragColor=vec4(edgeCol,texCol1.a*mix(lineV*lineH,texCol2.a,texCol2.a));
}
二、色调映射(Tone Mapping)
2.1 背景
色调映射就是让亮的场景变暗,暗的场景变亮,并且保存尽可能多的细节。如下图所示,右上角的图像曝光大,亮度高而右下角图像曝光少,亮度低,通过色调映射算法可以将两者亮度范围综合到一起,显示如左侧图像一样,细节看得更清楚。
2.2 Reinhard tone mapping
经验公式如下:
float3 ReinhardToneMapping(float3 color, float adapted_lum)
{
const float MIDDLE_GREY = 1;
color *= MIDDLE_GREY / adapted_lum;
return color / (1.0f + color);
}
解释为输入一个较小亮度0.1时,输出为0.091,当输入一个较大的亮度10时,输出为0.91。也就是归一化到了0到1区间,但是原本的亮度都变暗了。
2.3 Filmic tone mapping
拟合的公式如下:
float3 F(float3 x)
{
const float A = 0.22f;
const float B = 0.30f;
const float C = 0.10f;
const float D = 0.20f;
const float E = 0.01f;
const float F = 0.30f;
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;
}
float3 Uncharted2ToneMapping(float3 color, float adapted_lum)
{
const float WHITE = 11.2f;
return F(1.6f * adapted_lum * color) / F(WHITE);
}
大部分游戏中用的方法,优点是相比较Reinhard有更大的对比度,颜色更鲜艳一些,缺点是运算量比较大。
三、伽马校正(gamma correction)
3.1 背景
早期的CRT显示器输入0.5的值,输出显示的并不是0.5,而是约等于0.218,进行了伽马系数2.2的幂次运算,这称为伽马校正。为了得到正确的输出,需要先乘以1/2.2的补偿运算。伽马校正除了解决早期CRT显示器的非线性输出问题,同时可以帮我们改善输出的图像质量,因为人眼对较暗的亮度值比较敏感,因此现在仍然保留这一过程。
假如我们要存储0.24和0.243这两个亮度值,如果不进行伽马校正则有\(0.24*255=61 0.243*255=61\),输出结果一样;而使用伽马校正后有
伽马校正增大了较暗数值的表示精度,而减小了较亮数值的表示精度, 人眼又恰好对较暗数值比较敏感,对较亮数值不太敏感,于是从视觉角度讲,输出的图像质量就被伽马校正”改善”了
标签:return,渲染,float,0.5,实用,x2,原理,1.0,const From: https://www.cnblogs.com/wangxydela/p/18559436