Responsive renderer with limits

I understand but I wasn’t talking about resize handler, that you can still improve, check camera.aspectratio and renderer.setsize, you should find some useful post on stack overflow.

Anyway
I give you an example, I have a 3d scene in which I draw rectangles to cover the screen, from left to right and from bottom to top. In my drawing function, I draw rectangles in area that’s screen.width wide and screen.height tall.
what I mean? I mean that when you create your texture, you have to size it with screen properties instead of the window ones, regardless of how much the browser is big. This way, you can resize your window as you want but your first render will cover the whole screen. I hope I explained better. I never used pixi

Do you think that’s how they achieved the distortion? I can see it’s somewhat of a dense mesh but i couldn’t yet figure out what it was, I’m still trying to figure out how spector js works.

The other thing is both the foreground and background have displacement maps, which would also be used for the 3D. Soo mapping the textures to planes (like PlaneBufferGeometry) and using a displacement map (GPU side texture) to give them depth. If you are feeling brave you can unminify the source code to take a look, but that makes it pretty hard to read.

@ThoughtsRiff From what I understand of the question, nagman wants to load a single model once and have the 3D view scale responsively as the window is resized rather than redraw objects based on the initial viewport, which is what my method is based on. You can set up the defaults/optimal sizes on initialisation based on the viewport which would work for if someone was on desktop vs on mobile, and then add in responsive scaling based on those initial values.

1 Like

@calrk I’m suprised that the website is done with Pixi. I was convinced it was 3D.

I tried your solution and it does part of the job (I don’t see the left and right borders of the scene), but instead of scaling the whole view (like you’d do in photoshop), I does a camera-moving-like stuff:

tes2t

It seems to have something to do with renderer.setViewport, or maybe renderer.setScissor, but I’m not sure.

I’ve started something which is I thing the right approach:

function onWindowResize() {
  const width = window.innerWidth;
  const height = window.innerHeight;

  const optimalRatio = 16/9;
  const actualRatio = width / height;

  if (actualRatio > optimalRatio) { // window too large
    // something to do maybe with renderer.setViewport, or setSize, or setScissor, and some math stuff using optimalRatio with width or height
  } else { // window too narrow
    // idem, but replacing the dimension (width or height) with the other
  }

  camera.aspect = width / height;
  camera.updateProjectionMatrix();

What I don’t understand is if @nagman 's texture/3d object has a fixed size that he can choose. Because if it is, when he creates the object he can sets its width and height as screen’s ones. This way, when he resizes window from shrinked to wide, the texture/image gets to its original size ( as screen ) and doesn’t stretch.
I’m gonna create a codepen, I’ll edit this reply.

EDIT 1: Notice also that if you use perspectiveCamera, FOV and distance are factors that you should count too.

@ThoughtsRiff I do use a perspectiveCamera, and this is why I cannot just set the size of the object.

It shouldn’t be that complicated. It is exactly the same effect as a simple object-fit: cover; in CSS, or a background-size: cover;.

Three.js should have something like “stop rendering everything that exceeds this frame”, without having to move the camera, its FOV, the meshes sizes, and so forth.

Are you able to post the full sample code you are working with, including the model?

Ouch, kinda hard because it is in a Next.js environment, but I can try.

The example you’ve posted seems to be working with an image, you seem to be working with a mesh. They have the width and height available to them and can apply it to logic. You don’t have that, and only have height basically by setting camera.fov.

What you need is something that relates to both that camera.fov and your subject, you need to frame it somehow. You could use a bounding box, geometric checks and whatnot, but the easiest thing would be to just pick some world size rectangle, and apply the same logic you would to an image/quad.

So what is happening in your example?

Let’s say the image has an aspect ratio of 2:1. When you set your window to be 1000px x 500px it exactly aligns with the image, no overflow no gaps on either side. Cool, lets make the window narrower first, 500px.

So now at 500px x 500px we have overflow in the horizontal axis. This is cool and it’s how THREE.PerspectiveCamera behaves “by default”. More on that in a second.

Let’s resize it to be wider than the image, 1500px x 500px. You’re doing that in your code and you experience what would be seen as a gap in the example demo. You’d see the entire image and 250px x 500px gaps on both sides.

Ok we don’t want that, so we need to do something to the image. We want the image to “fill the entire window”, so it needs to cover the 1500 x 500. The aspect ratio of that is 3:1, and the entire thing is 2:1. We want the 1000px of the image width to fit 1500px, so we will scale the image up by 1.5x. This gives us a height of 750px, and we can only fit 500px.

If the image was sitting on a mesh,

const image = new THREE.Mesh(new THREE.PlaneGeometry(1000,500))

And if you picked some constant fov:

const myFov = 45

There is only one ever position of the camera that would render this mesh to fit a 1000px x 500px screen:

cam.position.z = heightHalf / Math.tan( THREE.Math.degToRad(myFov/2)) 
cam.position.x = cam.position.y = 0

Sweet. Now when we apply the usual resize logic:

cam.aspect = aspect
cam.updateProjectionMatrix()

And we resize the screen to 500px x 500px we get your result. But when we go to 1500px x 500px we see gaps.

We have:

500 x 500
1000 x 500
1500 x 500

Let’s divide all this with 500:

1x1 
2x1
3x1

So the aspect of 1/2 is ideal and aligns with the image with no overflow or gaps. 1x1 has overflow, 3x1 has gaps. We like the behavior of < 1/2 but we don’t like the behavior of > 1/2.

How about this?

if(aspect < myAspect)
  defaultCameraResize()
else 
  differentCameraResize()

The default behavior has already been mentioned above, we just compute the aspect and apply it to the camera. Since we don’t want to change the position of the camera, we need to change something else. The aspect always has to correspond to the viewport, so we still have to just compute that and pass it, otherwise our geometry would deform. How about we change the fov?

renderer.setSize( window.innerWidth, window.innerHeight );
camera.aspect = window.innerWidth / window.innerHeight
const viewAspect = viewWidth / viewHeight 
const special = camera.aspect > viewAspect
uniform.value = special ? 1 : 0

if(special){
  const camH = Math.tan(THREE.Math.degToRad(myFov/2))
  const ratio = camera.aspect / viewAspect
  const newH = camH / ratio
  const newFov = THREE.Math.radToDeg(Math.atan(newH)) * 2
  camera.fov = newFov
 } else {
  camera.fov = myFov
 }
camera.updateProjectionMatrix()

Fiddle:
https://jsfiddle.net/pailhead/qt5j2fbz/

So this is a generic implementation of that example. You can just find these numbers by trial and error based on your scene scale, or use some heuristic to compute it.

2 Likes

@pailhead THANKS!!! You’re my savior :grin:
I’ve not understood everything, but my first test is a succeed!

I was thinking that playing with the fov would influence the perspective, but no.

I’ll try to write an article for this with some illustrations. I think this kind of a question pops up often. I don’t like just giving the solution hence the step by step explanation.

Can you tell me which parts you did not understand?

1 Like

I think you lost me at this point.

I was the best student at Maths in my school class, but as no one has ever told me what was the use of that, I’ve forgotten absolutely everything :confused:

Also you said that the fov was related to the height?

Also, do you think it’s easy to implement a sort of “justify” on this?
I would like my scene to be aligned on the bottom left corner, and not the center.
Like if you did in CSS:

background-position: bottom left;
background-size: cover;

Or even more accurate:

background-position: 20% 80%;
background-size: cover;

I’d like to reproduce this layout with 3D:
next

I suppose it has to do with camera position?

Up. Any clue here?

how is this different?

@pailhead

Before:
4

After:
5

Still no clue?

It’s been a while now, but here’s another topic that explains better the responsive issue and the solution : Keeping an object scaled based on the bounds of the canvas (really battling to explain this one) - #10 by nagman