Advertisement Jump to content
Sign in to follow this  
lipsryme

Nearest Neighbor resampling problem on CPU

This topic is 1838 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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);
}
Edited by lipsryme

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Edited by phil_t

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites


	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.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!