Nearest Neighbor resampling problem on CPU

Started by
4 comments, last by Krypt0n 10 years, 3 months ago

I'm trying to downsample an image (for mipmap generation purposes) but I'm having some problems.

The problem is that each miplevel looks like it's being sucked towards the bottom right corner of the image.

I've got a quick video on how it looks when switching through the miplevels:

Does anyone have an idea what I'm doing wrong ?

My mipmap creation code looks like this:


void CreateMipMaps()
{
	// Create 8 mipmap levels
	for (unsigned short i = 0; i < 8; ++i)
	{
		width[i + 1] = static_cast<unsigned int>(std::ceil(width[i] / 2.0f));
		height[i + 1] = static_cast<unsigned int>(std::ceil(height[i] / 2.0f));
		this->BuildMipMap(this->pixels[i], this->pixels[i+1],
                                  width[i], height[i], width[i + 1], height[i + 1]);
	}
}

void BuildMipMap(std::vector<Rgb> &source, std::vector<Rgb> &dest,
		 const unsigned int srcWidth, const unsigned int srcHeight,
		 const unsigned int destWidth, const unsigned int destHeight)
{
	dest.resize(destWidth * destHeight);
				
	double x_ratio = srcWidth / (double)destWidth;
	double y_ratio = srcHeight / (double)destHeight;
	double px, py;
	for (unsigned int y = 0; y < destHeight; ++y)
	{
		for (unsigned int x = 0; x < destWidth; ++x)
		{
			px = std::floor(x * x_ratio);
			py = std::floor(y * y_ratio);

			dest[x + y * destWidth] = 
                                     source[(unsigned int)(px + py * srcWidth)];
		}
	}
}

And this is my texture (point) sampler function (sry for the bad format):


static Vector4_SSE SampleTex2DPoint(Texture2D* tex, const Vector4_SSE &uv,
                                    const unsigned int miplevel = 0)
{
        const unsigned int texWidth = tex->width[miplevel];
	const unsigned int texHeight = tex->height[miplevel];

	const unsigned int sampleX = std::min<unsigned int>(static_cast<unsigned int>(uv.X() * texWidth),
                                                                                texWidth - 1); // floor of x
	const unsigned int sampleY = std::min<unsigned int>(static_cast<unsigned int>(uv.Y() * texHeight),
                                                                                texHeight - 1); // floor of y

return Vector4_SSE(tex->pixels[miplevel][sampleX + sampleY * texWidth].r,
		   tex->pixels[miplevel][sampleX + sampleY * texWidth].g,
                   tex->pixels[miplevel][sampleX + sampleY * texWidth].b);
}
Advertisement

Perhaps what you ought to be doing for a nearest neighbour mipmap function is always sample from the top layer.

Each pixel in a mip level is going to choose one of 4 pixels from the mip above, and by doing it iteratively you're introducing a systematic position shift. If you always sample from the top mip you'll avoid this problem. Another solution might be alternate which of the 4 pixels from the mip above that you choose from depending on the mip level so the shift doesn't accumulate.

You misunderstand I'm not doing any mipmap interpolation (yet). In the video I'm just showing how the mip levels look (by iterating through them via button press) since there seems to be something wrong with the nearest neighbor filter I'm using to generate them.

Why are you using a nearest neighbor filter to generate your mips? Generally each mip pixel should contain a weighted average of the 4 pixels (in the more detailed mip) that correspond to that pixel.

Also, if you're using nearest neighbour, and only sampling from the next most detailed mip, I think no matter how you do it you're gonna see a gradual "shift". The information as to where the pixel "came from" is gone.

You misunderstand I'm not doing any mipmap interpolation (yet). In the video I'm just showing how the mip levels look (by iterating through them via button press) since there seems to be something wrong with the nearest neighbor filter I'm using to generate them.

You're sampling the nearest pixel from a 2x2 quad. There is no "middle" pixel.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”




	const unsigned int sampleX = std::min<unsigned int>(static_cast<unsigned int>(uv.X() * texWidth),
                                                                                texWidth - 1); // floor of x
	const unsigned int sampleY = std::min<unsigned int>(static_cast<unsigned int>(uv.Y() * texHeight),
                                                                                texHeight - 1); // floor of y

assume your texture mipmap level is 2x2 texel

you are addressing

- texel [0,0] by UVs [0.f,0.f] to [0.999999f,0.999999f],

- texel [1,0] by UVs [1.f,0.f] to [1.f,0.999999f],

- texel [0,1] by UVs [0.f,1.f] to [1.f,0.999999f],

- texel [1,1] by UVs [1.f,1.f],

you might want to round instead of flooring.

This topic is closed to new replies.

Advertisement