在做AR云渲染服务中,需要对华为AR Engine获取得到的深度图进行处理,AR Engine获取得到的深度图就是直接利用tof获取得到的深度图,都没有进行优化过,这里要吐槽一下AR Engine,人家友商arcore获取得到深度图就非常的平滑。
一种简单的方式是取了图像周围所有像素,然后求平均值。虽然它确实给我们一个容易模糊,但它并没有给出最好的结果。高斯模糊是基于高斯曲线,高斯曲线通常被描述为钟形曲线,在靠近中心处给出高值,随着距离逐渐磨损。高斯曲线可以用不同的数学形式表示,但通常具有以下形状:
由于高斯曲线靠近其中心的面积较大,因此使用其值作为权重来模糊图像会得到更自然的结果,因为附近的样本具有更高的优先性。例如,如果我们对一个32x32的框在片段周围取样,那么我们使用距离片段越大的递进权值越小;这将产生一个更好和更现实的模糊,称为高斯模糊。
为了实现高斯模糊滤波器,我们需要一个二维权重框,我们可以从二维高斯曲线方程得到。然而,这种方法的问题在于它很快会变得非常沉重的性能。以32乘32的模糊内核为例,这将需要我们对每个片段的纹理进行总共1024次的采样!
幸运的是,高斯方程有一个非常整洁的性质,它允许我们把二维方程分成两个较小的一维方程:一个描述水平权重,另一个描述垂直权重。然后,我们首先使用场景纹理上的水平权重来做水平模糊,然后在生成的纹理上做垂直模糊。由于这个属性的结果是完全相同的,但这次节省了我们令人难以置信的数量的性能,因为我们现在只需要做32 + 32个样本相比1024!这被称为两遍高斯模糊。
这确实意味着我们需要模糊图像至少两次,这里使用framebuffer对象效果最好。特别针对两遍高斯模糊,我们将实现乒乓帧缓冲。这是一对帧缓冲器,在给定次数的时间内,我们渲染和交换另一个帧缓冲器的颜色缓冲器到当前帧缓冲器的颜色缓冲器,并具有交替着色效果。我们基本上连续地切换帧缓冲区渲染和纹理绘制。这允许我们首先将场景的纹理模糊在第一帧缓冲器,然后将第一帧缓冲器的颜色缓冲器模糊到第二帧缓冲器,然后将第二帧缓冲器的颜色缓冲器模糊到第一帧缓冲器,等等。
在深入研究帧缓冲器之前,让我们先讨论一下高斯模糊的片段着色器:
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D image;
uniform bool horizontal;
uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
void main()
{
vec2 tex_offset = 1.0 / textureSize(image, 0); // gets size of single texel
vec3 result = texture(image, TexCoords).rgb * weight[0]; // current fragment's contribution
if(horizontal)
{
for(int i = 1; i < 5; ++i)
{
result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weight[i];
}
}
else
{
for(int i = 1; i < 5; ++i)
{
result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weight[i];
result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weight[i];
}
}
FragColor = vec4(result, 1.0);
}
这里我们选取一个相对较小的高斯权重样本,我们每个样本用来给当前片段周围的水平或垂直样本分配特定的权重。您可以看到,我们根据设置水平均匀值的任何值,将模糊滤镜分成水平和垂直部分。我们根据纹理大小的1.0除以纹理大小(从纹理大小的vec2)得出的texel的确切大小来计算偏移距离。
为了使图像模糊化,我们创建两个基本的帧缓冲区,每个帧缓冲区仅具有颜色缓冲区纹理:
unsigned int pingpongFBO[2];
unsigned int pingpongBuffer[2];
glGenFramebuffers(2, pingpongFBO);
glGenTextures(2, pingpongBuffer);
for (unsigned int i = 0; i < 2; i++)
{
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[i]);
glBindTexture(GL_TEXTURE_2D, pingpongBuffer[i]);
glTexImage2D(
GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL
);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glFramebufferTexture2D(
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, pingpongBuffer[i], 0
);
}
将纹理填充到乒乓帧缓存中,然后对图像进行10次模糊(水平5次,垂直5次):
bool horizontal = true, first_iteration = true;
int amount = 10;
shaderBlur.use();
for (unsigned int i = 0; i < amount; i++)
{
glBindFramebuffer(GL_FRAMEBUFFER, pingpongFBO[horizontal]);
shaderBlur.setInt("horizontal", horizontal);
glBindTexture(
GL_TEXTURE_2D, first_iteration ? colorBuffers[1] : pingpongBuffers[!horizontal]
);
RenderQuad();
horizontal = !horizontal;
if (first_iteration)
first_iteration = false;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
每次迭代,我们将根据是水平还是垂直模糊来绑定两个帧缓冲中的一个,并将另一个帧缓冲的颜色缓冲作为要模糊的纹理来绑定。在第一次迭代中,我们特别地绑定了我们想要模糊的纹理(亮度纹理),否则两个颜色缓冲器最终会空。通过重复这个过程10次,亮度图像以重复5次的完整高斯模糊结束。这个结构允许我们尽可能频繁地模糊任何图像;高斯模糊迭代越多,模糊越强。