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

标签 c# hlsl gaussian resampling

所以我在 XNA(3.1。是的,我知道它已经过时了。是的,我有使用它的理由)和 HLSL 中构建了一个下采样算法。本质上它的工作原理是对原始纹理应用高斯模糊,然后使用 XNA 内置的默认最近邻缩放来调整其大小。我的想法是,高斯模糊会给出颜色区域的平均值的近似值,因此这本质上是减少锯齿的一种廉价方法。它工作得很好,速度也很快,但产生了一些有趣的人工制品——它似乎稍微拉伸(stretch)了图像。这通常并不明显,但我要下采样的一些东西是 Sprite 表,并且在动画时,很明显 Sprite 没有被放置到正确的位置。我想知道不同的重采样器(也内置于 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,即 1+ HLSL 文件中找到的半径):

 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

最佳答案

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

我通过添加一个额外的约束来修复它,即重采样仅适用于整数重采样。其他任何内容都会重新采样到最接近的可用整数样本,然后使用 NN 缩放其余部分。这与我之前的工作几乎一模一样,但由于 HLSL,现在速度快了很多。我希望获得任意缩放算法,但它足以满足我的需求。它并不完美,因为我仍然遇到偏移错误(由于数据丢失,在下采样时几乎不可能完全避免),但它们现在显然小于一个像素,因此除非您正在寻找,否则它们并不明显他们。

关于c# - XNA + HLSL 高斯模糊产生偏移伪影,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13690448/

相关文章:

c# - .NET 标识 : Generate token without explicit username/password

c# - 以及对十六进制字符串的操作

c++ - 使用头文件定义 mt19937 和 normal_distribution

c# - 二维瓷砖照明

javascript - 具有/正态曲线子数组长度的 1D -> 2D 数组

python - 高斯拟合无法使用 Python

javascript - 使用ajax和json保存数据

c# - 在 C# 中使用参数启动新线程

math - 在 Direct3D 中使用像素着色器绘制旋转球体

unity-game-engine - Unity 计算着色器同步