snprintf实现_高斯模糊的GLSL实现

本文是OpenGL 4.0 Shading Language Cookbook的学习笔记。

主要介绍使用GLSL来实现高斯模糊。

模糊过滤可以有效减少图像中的噪点数量。前面提到,在边缘检测之前使用模糊过滤可以减少图像的高频变化。模糊过滤的基本原理是对附近像素进行加权和来混合当前像素颜色。通常使用的权重随距离减小(二维屏幕空间距离),距离当前像素较远的像素贡献较小。

高斯模糊使用二维高斯函数来计算附近像素的权重:

的平方项是高斯函数的方差,它决定了高斯曲面的宽度。高斯函数在(0,0)处取得最大值,对应需要模糊处理的像素,随着x或y坐标的增加,高斯函数值减小。下图显示了
平方为4.0时的二维高斯函数的变化趋势。

450d101555e12ffb4bf00b31d5ccb6ba.png

下图显示了高斯模糊前后的画面,左边还没有进行高斯模糊,右边是进行高斯模糊后的图像:

7534465b9f17afd547ed9cb83a0b4dc3.png

使用高斯模糊,对于每一个像素,我们需要计算所有像素的加权和。这一算法存在下面这些问题:

  • 算法复杂度是
    ,对于实时渲染来说太慢了。
  • 为了避免改变图像整体亮度,所有权重相加的和必须为1。

由于我们是在离散的坐标上使用高斯函数采样,我们的权重和也不等于1。

我们可以通过限制采样的像素数目,然后对高斯函数的值进行规范化来解决上面的两个问题。在这里,我们使用9x9大小的高斯模糊过滤器,在这种情况下,对于每个像素,我们只需要采样81个像素就可以完成模糊操作。

这个方法需要为每个像素在片段着色器中访问纹理元素81次。一张800x600大小的图像,需要访问纹理元素800x600x81=38,880,000次。看起来访问次数非常多?我们可以通过进行两遍高斯模糊来减小访问次数。

二维高斯函数实际上是两个一维高斯函数的乘积:

一维高斯函数的公式如下:

假设

为坐标(i,j)处的像素的颜色,则我们需要计算的加权和的公式如下:

我们对上面的公式进行改写得到:

上面公式表明,我们可以使用两遍处理来实现高斯模糊。在第一遍处理时,我们计算上面公式中与j相关的和,将结果存储在临时的纹理中。在第二遍处理时,我们使用临时纹理计算与i相关的和。

现在,还有一个重要的问题要处理。之前说过,我们的权值和必须等于1,所以,我们改写我们的等式为下面的形式来规范化权值:

等式中的k是原始高斯函数的权重和:

让我们把重点转移到代码上。

我们在这里通过三遍处理和两张纹理实现高斯模糊。第一遍处理时,我们渲染整个场景到纹理中。在第二遍处理时,我们使用上一遍处理得到的纹理计算垂直和。最后一遍处理,我们进行水平和的计算,然后将结果输出到默认的帧缓冲中。

创建两个帧缓冲对象,以及它们绑定的纹理。由于我们在第一遍时需要绘制三维场景,第一个帧缓冲对象还需要绑定一个深度缓冲。第二遍和第三遍处理,我们只需要在片段着色器中绘制二维屏幕,不需要深度缓冲。

我们将使用子程序来绑定每一遍处理使用的着色器功能。我们的OpenGL程序需要对下面的Uniform变量进行设置:

  • Width:以像素为单位的屏幕宽度。
  • Height:以像素为单位的屏幕高度。
  • Weight[]:规范化后的高斯权重数组。
  • Texture0:设置为对应0号纹理单元。
  • PixOffset[]:需要模糊处理的像素的位置偏移。

我们采用下面的步骤实现高斯模糊:

1. 使用我们之前介绍的边缘检测技术使用的顶点着色器。

2. 使用下面的代码作为片段着色器:

#version 400
in vec3 Position; // Vertex position
in vec3 Normal; // Vertex normal
in vec2 TexCoord; // Texture coordinate
uniform sampler2D Texture0;
uniform int Width; // Width of the screen in pixels
uniform int Height; // Height of the screen in pixels
subroutine vec4 RenderPassType();
subroutine uniform RenderPassType RenderPass;
// Other uniform variables for
// the Phong reflection model can
// be placed here
layout( location = 0 ) out vec4 FragColor;
uniform float PixOffset[5]=float[](0.0,1.0,2.0,3.0,
				4.0);
uniform float Weight[5];
vec3 phongModel( vec3 pos, vec3 norm )
{
	// The code for the Phong reflection model
	// goes here
}
subroutine (RenderPassType)
vec4 pass1()
{
	return vec4(phongModel( Position,Normal ),1.0);
}
subroutine( RenderPassType )
vec4 pass2()
{
	float dy = 1.0 / float(Height);
	vec4 sum = texture(Texture0, TexCoord) *
				Weight[0];
	for( int i = 1; i < 5; i++ )
	{
		sum += texture( Texture0,
				TexCoord +vec2(0.0,
				PixOffset[i]) * dy )
				* Weight[i];
		sum += texture( Texture0,
				TexCoord - vec2(0.0,
				PixOffset[i]) * dy )
				* Weight[i];
	}
	return sum;
}

subroutine( RenderPassType )
vec4 pass3()
{
	float dx = 1.0 / float(Width);
	vec4 sum = texture(Texture0, TexCoord) *
			Weight[0];
	for( int i = 1; i < 5; i++ )
	{
		sum += texture( Texture0,
			TexCoord + vec2(PixOffset[i],
			0.0) * dx )
			* Weight[i];
		sum += texture( Texture0,
			TexCoord - vec2(PixOffset[i],
			0.0) * dx )
			* Weight[i];
	}
	return sum;
}
void main()
{
	// This will call either pass1(),
	// pass2(), or pass3()
	FragColor = RenderPass();
}

3. 在OpenGL程序中,我们使用PixOffset进行坐标偏移计算高斯权重,然后将结果存储在Weight数组中。代码如下:

char uniName[20];
float weights[5], sum, sigma2 = 4.0f;
// Compute and sum the weights
weights[0] = gauss(0,sigma2); // The 1-D Gaussian
// function
sum = weights[0];
for( int i = 1; i < 5; i++ ) {
	weights[i] = gauss(i, sigma2);
	sum += 2 * weights[i];
}
// Normalize the weights and set the uniform
for( int i = 0; i < 5; i++ ) {
	snprintf(uniName, 20, "Weight[%d]", i);
	prog.setUniform(uniName, weights[i] / sum);
}

对于第一遍处理,我们采取以下步骤:

1. 选择进行渲染的帧缓冲,启用深度测试,清除颜色/深度缓冲区。

2. 选择执行pass1子程序。

3. 绘制场景。

对于第二遍处理,我们采取以下步骤:

1. 选择中转使用的帧缓冲,关闭深度测试,清除颜色缓冲区。

2. 选择执行pass2子程序。

3. 设置视图,投影和模型矩阵为单位矩阵。

4. 绑定第一遍处理生成的纹理到0号纹理单元。

5. 绘制中间纹理。

对于第三遍处理,我们采取以下步骤:

1. 解除绑定中转帧缓冲(设置为默认的帧缓冲),清除颜色缓冲区。

2. 选择执行pass3子程序。

3. 绑定第二遍处理生成的纹理到0号纹理单元。

4. 绘制纹理到屏幕。

原理

步骤3中使用的gauss函数的第一个参数是x坐标,第二个参数是

的平方。gauss函数关于坐标0进行偏移,我们只需要使用偏移量作参数即可。在这里,我们只使用正整数来进行偏移,我们需要对非0数值的结果乘以2(对应整数和负数偏移)。

第一遍处理(pass1子程序),我们使用Phong反射模型将场景渲染到纹理中。

第二遍处理进行垂直权重和高斯模糊操作,并将结果存储在另一张纹理中。我们使用PixOffset数组来进行垂直方向的坐标偏移求出权重和。(dy项是一个纹理元素在纹理坐标下的高度)我们同时累加两个方向(距离要模糊的像素垂直方向正反两边四个像素内的像素)的权重。

第三遍处理和第二遍处理类似。我们计算水平方向的权重和。这一遍处理后,我们将结果直接输出到默认的帧缓冲。

优化

我们还可以继续优化将访问纹理的次数减少一半。如果我们好好利用纹理访问时自动进行的线性插值(纹理的缩放模式为GL_LINEAR),我们就可以一次获得两个纹理元素的信息!Daniel Rákos的博客详细描述了这一技术(http://rastergrid.com/blog/2010/09/efficientgaussian-blur-with-linear-sampling/)。

我们可以通过增加Weight和PixOffset数组的大小来增大模糊效果。还可以使用不同的

的平方值来改变高斯函数的趋势。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值