[4.0.0] Orthographic Projection Sampling Issues Topic is solved

Problems building or running the engine, queries about how to use features etc.
User avatar
haloman30
Halfling
Posts: 43
Joined: Mon Aug 29, 2022 2:53 pm
x 4

[4.0.0] Orthographic Projection Sampling Issues

Post by haloman30 »

Ogre Version: 4.0.0 (commit 8b7b3d17310c0243c264706cfb9f66424fbaf877)
Operating System: Windows 7 x64
Render System: Direct3D11

Hello!

So, this seems to be a pretty fundamental issue I'm running into - part of what I'm aiming for my project to do is support 2D rendering, with particular consideration made for pixel art assets. Thus far, I've been using an orthographic camera for 2D UI, which has worked fine - but it seems that using a similar setup for more traditional 2D assets isn't working as well.

After digging around a bit, it seems to be some sort of sampling issue - the screenshots below use 4 quads, each sampling a 16x16 section of a 32x32 texture, as a very primitive tilemap implementation (hence only 4 tiles). The issue is that the texture doesn't get rendered correctly onscreen, with a sort of distortion - which seems to be due to how the texture is being sampled. Despite attempting to correct for half-pixel alignments and such (going off of info I found online), I've been unable to get this to work properly and also continue doing so with differing zoom levels (which is done by dividing the size of the viewport based on zoom level, only using whole integers so no 1.5x zoom or anything).

Further, I've also been poking around with bgfx, as there's been a few particularly troublesome issues I've run into and I've potentially considered building my own graphics engine on top of it to work around them - but I'm trying to avoid this if at all possible, seems more likely that I'd be better off using something developed by more knowledgeable folk. However, when getting a basic setup that replicates the setup I had in OGRE (more minimally of course), the issue didn't occur. No zooming, or zooming in at different steps, no issue.

I did also try this under OpenGL (in OGRE) and got the same result, as some info I was finding online suggested this could be a D3D11 quirk.

Any pointers or things to try on my end? I've tried various approaches, including adjusting things by a half-pixel (by translating the projection matrix itself, just moving the camera position, adjusting vertex positions in the tilemap shader), and I've tried changing how the texture gets sampled (using normal UVs or passing direct pixel coords in the UVs and using equivalents to texelFetch) - and given that a basic stripped-down renderer just works without any special consideration, I'm unsure what else to try. I did also try to use bgfx's function for creating the orthographic projection matrix, but not knowing much about the deeper stuff at that point (and the info I found online going over my head a bit), I was unable to get a usable result out of it.

Any information or help is greatly appreciated! I've got a couple images below:

Expected result:
Image

Actual result:
Image

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5534
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1394

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by dark_sylinc »

  1. Is mipmapping disabled? This sounds like a mipmapping issue.
  2. Why is expected and actual result in different resolutions and not aligned?
User avatar
haloman30
Halfling
Posts: 43
Joined: Mon Aug 29, 2022 2:53 pm
x 4

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by haloman30 »

dark_sylinc wrote: Wed Nov 05, 2025 8:34 pm
  1. Is mipmapping disabled? This sounds like a mipmapping issue.
  2. Why is expected and actual result in different resolutions and not aligned?

Yes, mipmapping is off. As for the screenshots, they're small screencaptures (using Gyazo) of the full screenshots in order to make the issue easier to see.

Might also add, that (when setup is such that gives the 'actual result' output), rendering the same object with a perspective camera, things look fine there (and I've added a third screenshot demonstrating as such).

To get the "expected result" output, I was going off of advice which suggested to use a half-pixel offset in the shader - which did actually correct the problem when using no zoom level in orthographic camera, but resulted in the outer half pixel of the texture being cut off (which is most apparent in 3D view, and I've attached a screenshot showing this as well).

I've attached the original screenshots below, if you believe they can be helpful:

Expected result:
Image

Expected result in 3D, no issue:
Image

Actual result:
Image

Actual result in 3D, note the outer half-pixel being cut off:
Image

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5534
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1394

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by dark_sylinc »

I've been thinking about it, the only thing I can think of is that your half offset is poorly implemented.

Let's review how it should be done: Your texture is 32x32.

To get pixel-perfect sampling, the UVs (not the position) should be offset by 0.5 (and have point filtering, no mipmapping). You want to address [0.5 / 32; 31.5 / 32]

You are using 4 quads.

The UVs for the quads should go:

  1. From [0.5 / 32; 0.5 / 32] to [15.5 / 32; 15.5 / 32]
  2. From [15.5 / 32; 0.5 / 32] to [31.5 / 32; 31.5 / 32]
  3. From [0.5 / 32; 15.5 / 32] to [15.5 / 32; 31.5 / 32]
  4. From [15.5 / 32; 15.5 / 32] to [31.5 / 32; 31.5 / 32]

If you make the UVs go from [0; 1] and then simply add 0.5 / 32, it will be off because it will go from 0.015625 (correct) to 1.015625 (incorrect), because it should go from 0.015625 to 0.984375.

Cheers

User avatar
haloman30
Halfling
Posts: 43
Joined: Mon Aug 29, 2022 2:53 pm
x 4

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by haloman30 »

dark_sylinc wrote: Thu Nov 06, 2025 4:25 pm

I've been thinking about it, the only thing I can think of is that your half offset is poorly implemented.

Let's review how it should be done: Your texture is 32x32.

To get pixel-perfect sampling, the UVs (not the position) should be offset by 0.5 (and have point filtering, no mipmapping). You want to address [0.5 / 32; 31.5 / 32]

You are using 4 quads.

The UVs for the quads should go:

  1. From [0.5 / 32; 0.5 / 32] to [15.5 / 32; 15.5 / 32]
  2. From [15.5 / 32; 0.5 / 32] to [31.5 / 32; 31.5 / 32]
  3. From [0.5 / 32; 15.5 / 32] to [15.5 / 32; 31.5 / 32]
  4. From [15.5 / 32; 15.5 / 32] to [31.5 / 32; 31.5 / 32]

If you make the UVs go from [0; 1] and then simply add 0.5 / 32, it will be off because it will go from 0.015625 (correct) to 1.015625 (incorrect), because it should go from 0.015625 to 0.984375.

Cheers

So, doing this for one of the four tiles (manually overriding the UVs for it to avoid any mess-ups), just results in the same behavior I ran into before with the half-pixel offset, where the outer half-pixel was just cut off, while displaying correctly at 1:1 scale (same as before again).

I've attached a couple screenshots again, one at 1:1 scale (no zoom-in) and one that's more zoomed in. The #1 red/white tile is the one that's had its UVs adjusted, with the rest just being the default (no offset or anything, just normal UVs):

Image

Image

User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5534
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1394

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by dark_sylinc »

AAAHHH I get what's going on now.

You need to snap the vertex positions to pixels.

Let's say your ortho projection goes from -16 to 16 and -9 to 9 and your render window resolution is 1920x1080 (it's often not if there is a title bar!).

Then your vertices must be snapped like this:

Code: Select all

pos.x = floor( ((pos.x / 32.0f) * 0.5f + 0.5f) * 1920.0f ) / 1920.0f - 0.5f * 2.0f;
pos.y = floor( ((pos.y / 18.0f) * 0.5f + 0.5f) * 1080.0f ) / 1080.0f - 0.5f * 2.0f;

Note that floor() is not the same as truncation for negative numbers. In floor(-1.2) returns -2.0 which is what we want.

Of course if the camera is not centered around 0 (i.e. your ortho projection goes from 20 to 52 instead of -16 to 16) you need to modify the math to account for this center (though I'm not sure if it's even necessary, all we care is the relative size of the pixels).

EDIT: Once you've snapped the vertices to pixels, it's possible you may have to revisit the UVs by +/- (0.5 / resolution). But snapping to pixels is definitely required.

User avatar
haloman30
Halfling
Posts: 43
Joined: Mon Aug 29, 2022 2:53 pm
x 4

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by haloman30 »

dark_sylinc wrote: Fri Nov 07, 2025 2:56 am

AAAHHH I get what's going on now.

You need to snap the vertex positions to pixels.

Let's say your ortho projection goes from -16 to 16 and -9 to 9 and your render window resolution is 1920x1080 (it's often not if there is a title bar!).

Then your vertices must be snapped like this:

Code: Select all

pos.x = floor( ((pos.x / 32.0f) * 0.5f + 0.5f) * 1920.0f ) / 1920.0f - 0.5f * 2.0f;
pos.y = floor( ((pos.y / 18.0f) * 0.5f + 0.5f) * 1080.0f ) / 1080.0f - 0.5f * 2.0f;

Note that floor() is not the same as truncation for negative numbers. In floor(-1.2) returns -2.0 which is what we want.

Of course if the camera is not centered around 0 (i.e. your ortho projection goes from 20 to 52 instead of -16 to 16) you need to modify the math to account for this center (though I'm not sure if it's even necessary, all we care is the relative size of the pixels).

EDIT: Once you've snapped the vertices to pixels, it's possible you may have to revisit the UVs by +/- (0.5 / resolution). But snapping to pixels is definitely required.

Forgive me if my math skills are a bit sub-par to say the least - but should these snap values be added to the pos.x/pos.y, rather than setting them outright? When putting them in as-is, the resulting quad is extremely small, and also weirdly tall (didn't know it was there until only snapping a couple vertices rather than all 4), whereas adding them onto the original values does actually seem to get things a bit closer - though with some slight "shimmering" (?) when the camera moves, mostly noticeable at 1:1 zoom (no zoom), with still some of the same distortion at some of the lower non-zero zoom levels.

Attached a few screenshots as well in to illustrate some of the stuff.

The very tiny quad, up close:
Image

Rendering at no zoom:
Image

Rendering at 2x zoom:
Image

rpgplayerrobin
Orc Shaman
Posts: 788
Joined: Wed Mar 18, 2009 3:03 am
x 447

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by rpgplayerrobin »

As he mentioned, you must know your exact ortho resolution/aspect ratio as well as the exact width/height of the texture being written to (most likely the window itself).

Then you need to adjust the code with those values.
Have you done that?
Because your screenshot uses this resolution (and not 1920x1080 like the code is using): 3840x2012

Also, make sure to not have any window scaling enabled in Windows (like the 150% zoom that laptops usually have), since the DPI might screw things up (you can also use SetProcessDPIAware() at the start of your application to just make sure it does not get affected by it).

should these snap values be added to the pos.x/pos.y, rather than setting them outright?

I think the snapping will not work if they are added to the pos, and it is quite standard to adjust a real position to a snapped position like this for certain things (my UI does the same kind of thing basically, otherwise some positions are in between pixels which creates holes in the UI).

the resulting quad is extremely small

It should be, since each pixel is 1 unit. So in your case it should only show as 32x32 if the texture itself is 32x32 I would imagine?

User avatar
haloman30
Halfling
Posts: 43
Joined: Mon Aug 29, 2022 2:53 pm
x 4

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by haloman30 »

rpgplayerrobin wrote: Sun Nov 09, 2025 7:48 pm

As he mentioned, you must know your exact ortho resolution/aspect ratio as well as the exact width/height of the texture being written to (most likely the window itself).

Then you need to adjust the code with those values.
Have you done that?
Because your screenshot uses this resolution (and not 1920x1080 like the code is using): 3840x2012

Also, make sure to not have any window scaling enabled in Windows (like the 150% zoom that laptops usually have), since the DPI might screw things up (you can also use SetProcessDPIAware() at the start of your application to just make sure it does not get affected by it).

should these snap values be added to the pos.x/pos.y, rather than setting them outright?

I think the snapping will not work if they are added to the pos, and it is quite standard to adjust a real position to a snapped position like this for certain things (my UI does the same kind of thing basically, otherwise some positions are in between pixels which creates holes in the UI).

the resulting quad is extremely small

It should be, since each pixel is 1 unit. So in your case it should only show as 32x32 if the texture itself is 32x32 I would imagine?

Yes, the specific resolution is passed in (uses render window size). Instead of the 32x18 for the ortho window, its using the same values I pass to setOrthoWindowSize, which at 1:1 scale would be same as the resolution.

The actual size of the quads aren't 16x16 (since each quad uses 1/4 of the texture), they're MUCH smaller. In perspective view they're actually quite large, but appear small in ortho view. With this calculation and using it as-is (replacing x/y instead of adding onto them), it goes from being 16x16 to almost nothing.

With the resolution and ortho window size both being 3840x2012, I had it print out the original and corrected positions when creating the object, this is what the positions for the first quad get corrected from/to:

Code: Select all

v0 pos corrected from 16.000000,0.000000 to -0.497917,-0.500000
v1 pos corrected from 16.000000,16.000000 to -0.497917,-0.496024
v2 pos corrected from 32.000000,0.000000 to -0.495833,-0.500000
v3 pos corrected from 32.000000,16.000000 to -0.495833,-0.496024

EDIT

Upon zooming in significantly, the quad does gradually become increasingly visible, slowly getting larger as zoom level increases. At 200x zoom, this is what ends up appearing:

Image

At this level, the ortho window size becomes (approximately, with some extra decimals due to how intense the zoom level is) 19x10, and the vertices get corrected as such:

Code: Select all

v0 pos corrected from 16.000000,0.000000 to -0.083594,-0.500000
v1 pos corrected from 16.000000,16.000000 to -0.083594,0.295229
v2 pos corrected from 32.000000,0.000000 to 0.333073,-0.500000
v3 pos corrected from 32.000000,16.000000 to 0.333073,0.295229
rpgplayerrobin
Orc Shaman
Posts: 788
Joined: Wed Mar 18, 2009 3:03 am
x 447

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by rpgplayerrobin »

It is hard to say what exactly is wrong, but your image at "Rendering at no zoom:" shows it being 32x33, which is almost what you want (you probably want it pixel perfect at 32x32).

Exactly how does your code look?
Can you show me how you setup the ortho window and the code to snap the vertex positions? Because in that case I could try it myself to see the exact bug.
Also, if you just take the 32x32 texture you first posted and use that instead of your specific tilemap implementation, does your issue still happen? Because it is hard to know what your tilemap implementation exactly does.
The most important thing now is to be able to being able to recreate the issue with as minimal code/textures/shaders needed.

User avatar
haloman30
Halfling
Posts: 43
Joined: Mon Aug 29, 2022 2:53 pm
x 4

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by haloman30 »

rpgplayerrobin wrote: Mon Nov 10, 2025 1:09 am

It is hard to say what exactly is wrong, but your image at "Rendering at no zoom:" shows it being 32x33, which is almost what you want (you probably want it pixel perfect at 32x32).

Exactly how does your code look?
Can you show me how you setup the ortho window and the code to snap the vertex positions? Because in that case I could try it myself to see the exact bug.
Also, if you just take the 32x32 texture you first posted and use that instead of your specific tilemap implementation, does your issue still happen? Because it is hard to know what your tilemap implementation exactly does.
The most important thing now is to be able to being able to recreate the issue with as minimal code/textures/shaders needed.

Yeah, this is the function I'm using to snap the vertices:

Code: Select all

Ogre::Vector3 correct_pos(float x, float y)
{
    BlamDirector2D* director = Blam::Director::GetCamera2D();
    Ogre::Vector2 ortho_window_size = Ogre::Vector2();
    float zoom = director->zoom + 1;
    
if (zoom > 0.0f) { ortho_window_size = Ogre::Vector2(Blam::Rendering::GetRenderWidth() / zoom, Blam::Rendering::GetRenderHeight() / zoom); } else if (zoom < 0.0f) { ortho_window_size = Ogre::Vector2(Blam::Rendering::GetRenderWidth() * abs(zoom), Blam::Rendering::GetRenderHeight() * abs(zoom)); } else { ortho_window_size = Ogre::Vector2(Blam::Rendering::GetRenderWidth(), Blam::Rendering::GetRenderHeight()); } Ogre::Vector3 pos = Ogre::Vector3(x, y, 0); pos.x += floor(((x / ortho_window_size.x) * 0.5f + 0.5f) * Blam::Rendering::GetRenderWidth()) / Blam::Rendering::GetRenderWidth() - 0.5f * 2.0f; pos.y += floor(((y / ortho_window_size.y) * 0.5f + 0.5f) * Blam::Rendering::GetRenderHeight()) / Blam::Rendering::GetRenderHeight() - 0.5f * 2.0f; return pos; }

The Blam::Rendering::GetRenderWidth() and Blam::Rendering::GetRenderHeight() are both just convenience functions, they call getRequestedWidthPt() and getRequestedHeightPt() respectively for the active Ogre::Window.

The ortho window is setup using some of the same code as above:

Code: Select all

if (zoom > 0.0f)
{
    ogre_camera->setOrthoWindow(renderWindow->getWidth() / zoom, renderWindow->getHeight() / zoom);
}
else if (zoom < 0.0f)
{
    ogre_camera->setOrthoWindow(renderWindow->getWidth() * abs(zoom), renderWindow->getHeight() * abs(zoom));
}
else
{
    ogre_camera->setOrthoWindow(renderWindow->getWidth(), renderWindow->getHeight());
}

zoom is a float which gets incremented/decremented by 1 when scrolling in/out in 2D view. Switching between 2D and 3D view just changes the same ogre_camera between orthographic and perspective. Currently, this is done every frame to ensure the ogre camera is always up-to-date.

EDIT

Well... I did just try rendering one quad with the full texture, and it does actually appear to render just fine, funny enough - though I'm not sure why just yet, as I basically used the same code, just hardcoding the vertex positions and UVs rather than having them calculated at runtime from the tilemap data. Regardless, it seems like this may be the real issue - will be digging into it further on my end.

EDIT 2

I have solved the issue - turns out it was genuinely just as dead simple as my UVs being miscalculated, and I guess I never noticed or thought to double-check them again. Fixing my calculations for them and everything is just fine, no vertex snapping or any other special considerations anywhere. You can even see the UV issue in the original post, the entire missing pixel on the bottom and right edges on the "actual result" image, compared to the "expected result" image.

My bad. Sorry for wasting y'all's time with this, and I super appreciate how helpful you folks have been regardless!

Last edited by haloman30 on Mon Nov 10, 2025 3:32 am, edited 1 time in total.
rpgplayerrobin
Orc Shaman
Posts: 788
Joined: Wed Mar 18, 2009 3:03 am
x 447

Re: [4.0.0] Orthographic Projection Sampling Issues

Post by rpgplayerrobin »

Well... I did just try rendering one quad with the full texture, and it does actually appear to render just fine, funny enough - though I'm not sure why just yet, as I basically used the same code, just hardcoding the vertex positions and UVs rather than having them calculated at runtime from the tilemap data. Regardless, it seems like this may be the real issue - will be digging into it further on my end.

Good, in that case we know that the code works, and I will not have to test it for now.

Let us know what you find regarding the tilemap code!

EDIT: No worries! Making very small reproducable code has in many cases for me also shown the problem, making me able to fix it.