2

所以我在 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,即 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;
        }

除此之外,我的函数只是简单地在每次通过效果时绘制到纹理,然后将结果绘制到不同比例的新纹理。这看起来真的很好,但正如我所说,会产生这些不正确的东西的人工制品。一些帮助在这里将不胜感激。

文物展示

4

2 回答 2

2

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

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

于 2012-12-04T18:06:45.407 回答
0

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

于 2013-02-28T12:15:18.113 回答