所以我在 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;
}
除此之外,我的函数只是将效果的每次传递绘制到纹理,然后将结果绘制到不同比例的新纹理。这看起来真的很不错,但正如我所说,会产生这些不完全在正确位置的人工制品。在此提供一些帮助将不胜感激。
最佳答案
嗯,我发现了一些东西:它与高斯模糊无关。问题是我正在使用最近邻进行缩小,由于数据丢失,它会产生这些伪影(例如,当某些东西需要基本上位于像素 5.5 时,它只是将其放在像素 5 处,从而产生位置错误) 。感谢每个人试图帮助解决这个问题,但看起来我只需要稍微重新考虑我的算法。
我通过添加一个额外的约束来修复它,即重采样仅适用于整数重采样。其他任何内容都会重新采样到最接近的可用整数样本,然后使用 NN 缩放其余部分。这与我之前的工作几乎一模一样,但由于 HLSL,现在速度快了很多。我希望获得任意缩放算法,但它足以满足我的需求。它并不完美,因为我仍然遇到偏移错误(由于数据丢失,在下采样时几乎不可能完全避免),但它们现在显然小于一个像素,因此除非您正在寻找,否则它们并不明显他们。
关于c# - XNA + HLSL 高斯模糊产生偏移伪影,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13690448/