Camera zoom without changing perspective

Godot Version

4.2.2

Question

I am trying to create a camera zoom effect that can zoom in and out in a 3d space based on the mouse position. My camera will remain stationary. My first thought was to do something like the following,

func _input(event):
	#Zoom the camera in
	if event is InputEventKey and Input.is_key_pressed(KEY_Z):
		var mouse_pos: Vector3 = camera.project_position(get_viewport().get_mouse_position(), 2.89)
		# stupid godot syntax cant do x < y < z
		if camera.fov > 10 and (-2.5 < mouse_pos.x and mouse_pos.x < 2.5) and (-1.25 < mouse_pos.y and mouse_pos.y < 1.25):
			camera.set_h_offset(camera.get_h_offset() + VELOCITY * (mouse_pos.x - camera.get_h_offset()))
			camera.set_v_offset(camera.get_v_offset() + VELOCITY * (mouse_pos.y - camera.get_v_offset()))
			camera.fov = camera.fov - DELTA_FOV
	#Zoom the camera out
	if event is InputEventKey and Input.is_key_pressed(KEY_X):
		var mouse_pos: Vector3 = camera.project_position(get_viewport().get_mouse_position(), 2.89)
		if camera.fov < 80 and (-2.5 < mouse_pos.x and mouse_pos.x < 2.5) and (-1.25 < mouse_pos.y and mouse_pos.y < 1.25):
			camera.set_h_offset(camera.get_h_offset() + VELOCITY * (mouse_pos.x - camera.get_h_offset()))
			camera.set_v_offset(camera.get_v_offset() + VELOCITY * (mouse_pos.y - camera.get_v_offset()))
			camera.fov = camera.fov + DELTA_FOV

however I don’t want the camera perspective to change and changing the offset for the camera seems equivalent to just translating the camera which is not what I want to do.

I know that this is a good use case for frustum camera so I tried a similar approach by adjusting the frustum offset instead, unfortunately, it seems that camera.project_position breaks down for frustum camera (Camera3D project_* methods not accounting for frustum offset · Issue #61174 · godotengine/godot · GitHub).

Surely there is a way to get the camera to zoom without shifting the perspective. I really only need it to work on a 5x2.5 plane mesh centered at the origin.

Thank you for your help

1 Like

Can you explain a bit more?

When you say zoom without changing perspective, do you mean like as if you took a picture of the current view, then just enlarged a section?

Yes exactly, is there a way to do this? I know for camera2D there is zoom and obviously you can adjust the fov of the perspective camera to zoom toward the center but I want this behavior of being able to enlarge the viewport wherever my mouse is pointed.

1 Like

I think you can do this with a shader.

When you zoom in, do you want the detail to also increase? Or have the image resolution stay the same, just get bigger?

Can this be adapted to fit your needs?

I got you something to work with. I have gotten it to the proof of concept phase, you will need to take it the rest of the way.

First, setting up the scene:

Root Node3D
|_ MeshInstance2D (note this is 2D not 3D...this will display the zoomed in image)
|_Camera (your main camera)
    |_ SubViewPort (this will render the screen at a different resolution)
         |_ Camera (this is the Camera for the subviewport....it should be set identically to the main camera)

Set the Mesh of the MeshInstance2D to a new quad mesh. Edit that quad mesh and set it to some size. I chose 256 x 256 as I didnt want it to take up the whole screen for dev purposes.

Go to the 2D layout and position it so the whole thing is somewhere on the screen. I put it in the top left.

Now, for the MeshInstance2D change the material to new ShaderMaterial (it is under the canvas item group). Now edit that shader. Note that a shader and a shader material are two different things, we need both.

In the shader select “new shader”. we are making a canvas item shader.

here is the shader code:

shader_type canvas_item;

uniform sampler2D viewport_texture : source_color;
uniform vec2 rect_start = vec2(0.0, 0.0);
uniform vec2 rect_end = vec2(1.0, 1.0);

void fragment() {
    // Calculate the size of the selected rectangle
    vec2 rect_size = rect_end - rect_start;
    
    // Normalize the current UV coordinates to the selected rectangle
    vec2 normalized_uv = (UV * rect_size) + rect_start;
    
    // Flip the y-coordinate calculation
    normalized_uv.y = rect_end.y - (UV.y * rect_size.y);
    
    // Ensure we're sampling within the texture bounds
    normalized_uv = clamp(normalized_uv, vec2(0.0), vec2(1.0));
    
    // Sample the texture
    vec4 texture_color = texture(viewport_texture, normalized_uv);
    COLOR = texture_color;
}

Now save and go back to the ShaderMaterial.

You should see “shader parameters”. Expand that section.

You should see all the settings we can pick to adjust the zoom and such.

FIRST expand the “resource” section and make it local to scene. THEN under the shader parameters set the viewport texture to the viewport we set up earlier.

VOILA!

Now to explain how to use it…

The Shader parameters you will need to adjust to affect the zoom are rect start, and rect end. This is the top left and bottom right corner of a rectangle within the viewport texture you want to expand. The coordinates go from 0 to 1. so 0.25 would be a point 25% along the axis of the texture.

The SubViewPort size controls the resolution of the texture…you can upscale or downscale compared to your other camera. IT has its own resolution.

The position of the MEshInstance2D is, obviously, where this zoom portal appears. But the size of its mesh (remember the quadmesh we made) that is the size of the zoom window on your screen.

This is everything you need.

You can see I have a zoom window that is zoomed in on part of the screen.

You will probably want to write a decent script to tie this all together. Probably need some script to keep the two cameras in sync. Not everything I did needed to be done exactly that way. This is a proof of concept.

This is awesome! Thanks for the help

1 Like

You are welcome. Glad I could help.

Just be advised this is only a proof of concept and there are probably things I did that didnt need to be done or that should have been done another way entirely.

So be on the lookout for improvements and question everything :slight_smile:

I also just came across a “back buffer node”, which copies a portion of the screen for use in shaders.

I think this would be much simpler to use, if you werent worried about increasing the resolution as you zoom i.e. it would just look more pixelated as you zoom in.

I’m not gonna play with it, but if that is what works for you that looks simpler and less rendering involved.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.