XNA + HLSL高斯模糊产生偏移伪影

时间:2012-12-03 19:41:36

标签: c# hlsl gaussian resampling

所以我在XNA中构建了一个下采样算法(3.1。是的,我知道它已经过时了。是的,我有理由使用它)和HLSL。基本上它是如何工作的是通过对原始纹理应用高斯模糊,然后使用内置到XNA中的默认最近邻居重新缩放来调整它。我的想法是高斯模糊会给出一个颜色区域的平均值的近似值,因此它本质上是一种减少混叠的廉价方法。它效果很好,而且很快,但会产生一些有趣的人工制品 - 它似乎会略微拉伸图像。这通常不明显,但我正在下采样的一些事情是精灵表,并且当动画时,很明显精灵没有被放到正确的位置。我想知道一个不同的重采样器(也是内置于HLSL中的GPU的速度)可能是一个更好的选择,或者如果我可以修复这个错误。我会在这里发布我的代码,看看是否有人可以启发我。

首先是我的HLSL高斯效果文件:

#define RADIUS  7
#define KERNEL_SIZE (RADIUS * 2 + 1)

float weightX[KERNEL_SIZE];
float weightY[KERNEL_SIZE];
float2 offsetH[KERNEL_SIZE];
float2 offsetV[KERNEL_SIZE];

texture colorMapTexture;

sampler TextureSampler : register(s0);

void BlurH(inout float4 color : COLOR0, float2 texCoord : TEXCOORD0)
{
    float4 c = float4(0.0f, 0.0f, 0.0f, 0.0f);

    for (int i = 0; i < KERNEL_SIZE; ++i)
        c += tex2D(TextureSampler, texCoord + offsetH[i]) * weightX[i];

        color = c;
}

void BlurV(inout float4 color : COLOR0, float2 texCoord : TEXCOORD0)
{
    float4 c = float4(0.0f, 0.0f, 0.0f, 0.0f);

    for (int i = 0; i < KERNEL_SIZE; ++i)
        c += tex2D(TextureSampler, texCoord + offsetV[i]) * weightY[i];

        color = c;
}

technique GaussianBlur
{
    pass
    {
        PixelShader = compile ps_2_0 BlurH();
    }
    pass
    {
        PixelShader = compile ps_2_0 BlurV();
    }
}

我的初始化高斯效果的代码(请注意,gaussianBound设置为8,即在HLSL文件中找到的半径的1+):

 public static Effect GaussianBlur(float amount, float radx, float rady, Point scale)
        {
            Effect rtrn = gaussianblur.Clone(MainGame.graphicsManager.GraphicsDevice);

            if (radx >= gaussianBound)
            {
                radx = gaussianBound - 0.000001F;
            }
            if (rady >= gaussianBound)
            {
                rady = gaussianBound - 0.000001F;
            }
            //If blur is too great, image becomes transparent,
            //so cap how much blur can be used.
            //Reduces quality of very small images.

            Vector2[] offsetsHoriz, offsetsVert;
            float[] kernelx = new float[(int)(radx * 2 + 1)];
            float sigmax = radx / amount;
            float[] kernely = new float[(int)(rady * 2 + 1)];
            float sigmay = rady / amount;
            //Initialise kernels and sigmas (separately to allow for different scale factors in x and y)

            float twoSigmaSquarex = 2.0f * sigmax * sigmax;
            float sigmaRootx = (float)Math.Sqrt(twoSigmaSquarex * Math.PI);
            float twoSigmaSquarey = 2.0f * sigmay * sigmay;
            float sigmaRooty = (float)Math.Sqrt(twoSigmaSquarey * Math.PI);
            float totalx = 0.0f;
            float totaly = 0.0f;
            float distance = 0.0f;
            int index = 0;
            //Initialise gaussian constants, as well as totals for normalisation.

            offsetsHoriz = new Vector2[kernelx.Length];
            offsetsVert = new Vector2[kernely.Length];

            float xOffset = 1.0f / scale.X;
            float yOffset = 1.0f / scale.Y;
            //Set offsets for use in the HLSL shader.

            for (int i = -(int)radx; i <= radx; ++i)
            {
                distance = i * i;
                index = i + (int)radx;
                kernelx[index] = (float)Math.Exp(-distance / twoSigmaSquarex) / sigmaRootx;
                //Set x kernel values with gaussian function.
                totalx += kernelx[index];
                offsetsHoriz[index] = new Vector2(i * xOffset, 0.0f);
                //Set x offsets.
            }

            for (int i = -(int)rady; i <= rady; ++i)
            {
                distance = i * i;
                index = i + (int)rady;
                kernely[index] = (float)Math.Exp(-distance / twoSigmaSquarey) / sigmaRooty;
                //Set y kernel values with gaussian function.
                totaly += kernely[index];
                offsetsVert[index] = new Vector2(0.0f, i * yOffset);
                //Set y offsets.
            }

            for (int i = 0; i < kernelx.Length; ++i)
                kernelx[i] /= totalx;

            for (int i = 0; i < kernely.Length; ++i)
                kernely[i] /= totaly;

            //Normalise kernel values.

            rtrn.Parameters["weightX"].SetValue(kernelx);
            rtrn.Parameters["weightY"].SetValue(kernely);
            rtrn.Parameters["offsetH"].SetValue(offsetsHoriz);
            rtrn.Parameters["offsetV"].SetValue(offsetsVert);
            //Set HLSL values.

            return rtrn;
        }

除此之外,我的函数只是在效果的每次传递中绘制一个纹理,然后将结果绘制到不同比例的新纹理。这看起来非常好,但正如我所说的那样,产生这些东西并不是在正确的地方。这里的一些帮助将不胜感激。

Artefacts showing

2 个答案:

答案 0 :(得分:2)

嗯,我发现了一些东西:它与高斯模糊无关。问题是我正在缩小与最近邻居的距离,由于数据丢失而产生这些假象(例如,当某些东西需要基本上在像素5.5时,它只是将它放在像素5处,给出位置错误) 。感谢大家试图帮助解决这个问题,但看起来我只需要稍微重新考虑一下我的算法。

我通过添加额外约束来修复它,重新采样仅适用于整数重采样。其他任何内容都将重新采样到最近的可用整数样本,然后使用NN缩放其余部分。这几乎就是我以前工作的原因,但现在由于HLSL而变得更快了。我希望得到一个任意缩放算法,但它足以满足我的需求。它并不完美,因为我仍然会得到偏移误差(由于数据丢失而几乎不可能完全避免下采样),但现在它们显然不到一个像素,所以除非你正在寻找,否则不会引人注意它们。

答案 1 :(得分:0)

我有些疑惑。第二遍应使用第一遍'结果。 否则你可以将BlurH和BlurV组合在一起,结果将是相同的。 我没有找到任何使用第一次传递结果的代码或者将它从第一次传递传递到第二次传递。