Coordinate Alignment of Render Target and/or Shader is off after canvas/renderer resize

I found an example for code for creating a magnifying glass effect which I have adapted for my own project. I have successfully rebuilt the effect and have it working properly when the page is first loaded. However, if the canvas is resized larger or smaller, the center point of the magnification no longer lines up properly with the actual location in the base scene.

If the canvas width is reduced the magnification point shifts one direction and if the canvas is made wider it shifts the other direction.

This is the original code demo that I’d found, which also exhibits this problem. Resize the window and note that the mag-center point is off.

Original render screenshot - note magnification center point the same location on base and magnified scene.


After resizing (making width of canvas wider, without reload) the magnification center, target point is shifted in the magnified region.

The readable code that demo is apparently bundled from is found here.

However, I found that code not to work as is, and have uploaded my implementation in a codesandbox.

The mouse pointer should be in the center of the magnifying region, and it is on my local code (which is much more involved than the codesandbox ex). However, in the codesandbox ex I built, the mousepointer is off center on initial render, but that is corrected on a window resize. This might be a clue to the issue though I haven’t been able to figure out what it’s pointing to or why that part is behaving differently than in my local code.

Codesandbox initial render:
sandbox_initial_render

After resizing
sandbox_after_width_widdened_aligment_shifted

I’ve tried resizing the render target and requesting various object updates (these attempts are commented out in codesandbox), but none of that seems to have an effect on the center point shifting.

Does anyone have an idea what the issue might be, appreciate the help.

FYI, the codesandbox cannot be accessed.

Darn, sorry about that. I didn’t realize it wasn’t public by default. The codesanbox should be publicly available now. Thanks so much for the heads up.

I found the problem. The ‘magnify3d’ class object does not internally update its ‘zoomTarget’ renderTarget, so unlike the main renderer view whose size is pulled from the context for each render, the ‘zoomTarget’ was not being updated.
To fix this, I just needed to manually resize the ‘zoomTarget’ property of the ‘magnify3d’ object, inside the component resize function.

function resizeRendererToDisplaySize( renderer: THREE.WebGLRenderer ) {
    const canvas = renderer.domElement;
    const pixelRatio = window.devicePixelRatio;
    const width = Math.floor( canvas.clientWidth * pixelRatio );
    const height = Math.floor( canvas.clientHeight * pixelRatio );
    const needResize = canvas.width !== width || canvas.height !== height;
    if ( needResize ) {

        renderer.setSize( width, height, false );
       // Corrects magnifier alignment shift after resize
        magnify3d.zoomTarget.setSize(width, height);

    }
    return needResize;
}

This correction has been added to my the sandbox for this post, but you can still see the error on the original code demo with the teapots.

1 Like

Actually the best solution is for the ‘zoomTarget’ resize to be in the render() function of the ‘Magnify3d’ class.

I see that resizing of the zoomTarget is being done inside magnify3d.render() using:

this.zoomTarget.width = width
this.zoomTarget.height = height

But for some reason, this is not behaving the same as using
this.zoomTarget.setSize(width, height).

Regardless, the reason though, we can move the zoomTarget resize using setSize into the render() and remove the old zoomTarget.width = code. Note that the zoomTarget resize must come before the zoomTarget.viewport update is done.

updated ‘Mag’ resize function (no magnify3d object updates needed):

function resizeRendererToDisplaySize( renderer: THREE.WebGLRenderer ) {
    const canvas = renderer.domElement;
    const pixelRatio = window.devicePixelRatio;
    const width = Math.floor( canvas.clientWidth * pixelRatio );
    const height = Math.floor( canvas.clientHeight * pixelRatio );
    const needResize = canvas.width !== width || canvas.height !== height;
    if ( needResize ) {
        renderer.setSize( width, height, false );
    }
    return needResize;
}

partial of ‘Magnify3d’ class ‘render()’ where setSize is now applied:

........
   const zv = {
       x: -pos.x * (zoom - 1) * width / resWidth,
       y: -pos.y * (zoom - 1) * height / resHeight,
       z: width * width / resWidth * zoom,
       w: height * height / resHeight * zoom
   };

  // Not sure why, but setting size this way does not update alignment properly
   // this.zoomTarget.width = width;
   // this.zoomTarget.height = height;

   // Updating zoom using 'setSize' seems to provided desired result
   // This resize must come before the viewport update
   this.zoomTarget.setSize(width, height);

   // this needs to be done beacause viewport.set can't take a spread directly
   // const vpSpread = {...zoomedViewport};
   this.zoomTarget.viewport.set(zv.x, zv.y, zv.z, zv.w );
......
1 Like