How do I get started with a 3D Magnifying Glass Effect

Hi,
I want to add (optical) glasses to my character in my game, and have the effect of the 3d space behind the glasses being magnified bigger, with some specular effects. Optionally, I would also like a “fish eye” effect, with the centre of the lens distorted to be larger.


shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_disabled,diffuse_burley,specular_schlick_ggx,unshaded;
uniform vec4 albedo : source_color = vec4(1.0, 1.0, 1.0, 0.0);

uniform float specular;
varying flat vec3 norm;
varying flat float norm_val;

uniform sampler2D SCREEN_TEXTURE: hint_screen_texture,filter_linear_mipmap;

void vertex() {
norm = NORMAL;
}

vec2 scale(vec2 uv, float x, float y)
{
mat2 scale = mat2(vec2(x, 0.0), vec2(0.0, y));
uv -= 0.5;
uv = uv * scale;
uv += 0.5;
return uv;
}

vec2 shift(vec2 uv){
return uv;
}

void fragment() {
vec3 normx = NORMAL;
NORMAL = norm;
vec4 alpha = vec4(1.0);
alpha = texture(SCREEN_TEXTURE, scale(SCREEN_UV, 0.7,0.7));
ALBEDO = mix(alpha.rgb, albedo.rgb, albedo.a);
SPECULAR = specular;
}

This is what I’ve got so far. The problem in the picture is obvious, I shouldn’t be able to see the glasses themselves magnified in the lens. And I know that this is because the UV increases from it’s centre, not the shader’s local centre.

In my head I know what I need to do. I think I just need to shift the uv slightly to follow the meshs position relative to the camera. But for the life of me I can’t figure it out, none-of-the-less apply a fishbowl effect over it.

Many thanks for your time. - Godot shader noob.

Hey, I was looking to do the same thing, were you ever able to figure this out? I am completely new to shaders and have zero clue how to use them

If you put a material on your glasses that has ALPHA = 1 and depth_draw_alpha, you won’t see them in the magnifying glass. (And you won’t see any other object that has alpha and doesn’t have alpha cutoff really)

Hi.
So what i understand is that you want to scale around the lenses center?

varying vec2 object_center_uv;

void vertex() {
	UV = UV * uv1_scale.xy + uv1_offset.xy;
	
	//calculate the node center uv
	vec4 p = PROJECTION_MATRIX * vec4(NODE_POSITION_VIEW,1.0);
	object_center_uv = (p.xy / p.w ) * 0.5 + 0.5;
}

vec2 scale_around_uv(vec2 uv, vec2 center, vec2 scale)
{
	return center + (uv - center) * (vec2(1.0) / scale);
}

then in the fragment

	float fishyness = 1.5;
	float scalyness = 2.0;
	vec2 scale_factor = vec2(1.0) * (1.0 - distance(SCREEN_UV,object_center_uv) * fishyness) * scalyness;
	ALBEDO = texture(SCREEN_TEXTURE, scale_around_uv(SCREEN_UV, object_center_uv,scale_factor)).rgb;



this isn’t a real fish eye, this would more complex.

So I created this code with the help of a number of sources:

shader_type spatial;
render_mode unshaded;

uniform sampler2D screen_texture : hint_screen_texture;

vec2 convert_to_local_screen_space(vec2 screen_UV, vec2 screen_centre_UV, vec2 model_UV){
	// Calculate the distance, in terms of screen uv values, on the centre of the model to any corner bounds.
	vec2 distance_from_centre = abs(1.0/(abs(0.5-model_UV)*2.0)) * abs(screen_UV-screen_centre_UV);
	vec2 full_size = distance_from_centre * 2.0;
	// Convert distance from centre to the minimum bounds (this should be our 0.0).
	vec2 minimum_bounds = screen_centre_UV - distance_from_centre;
	// Adjust our current position to get the local bounds.
	vec2 local_screen_UV = (screen_UV - minimum_bounds) / full_size;
	return local_screen_UV;
}

vec2 convert_to_full_screen_space(vec2 local_screen_UV, vec2 screen_UV, vec2 screen_centre_UV, vec2 model_UV){
	// Calculate the distance, in terms of screen uv values, on the centre of the model to any corner bounds.
	vec2 distance_from_centre = abs(1.0/(abs(0.5-model_UV)*2.0)) * abs(screen_UV-screen_centre_UV);
	vec2 full_size = distance_from_centre * 2.0;
	// Convert distance from centre to the minimum bounds (this should be our 0.0).
	vec2 minimum_bounds = screen_centre_UV - distance_from_centre;
	return local_screen_UV * full_size + minimum_bounds;
}

float calculate_model_screen_aspect_ratio(vec2 screen_UV, vec2 screen_centre_UV, vec2 model_UV){
	// Calculate the distance, in terms of screen uv values, on the centre of the model to any corner bounds.
	vec2 distance_from_centre = abs(1.0/(abs(0.5-model_UV)*2.0)) * abs(screen_UV-screen_centre_UV);
	vec2 full_size = distance_from_centre * 2.0;
	return full_size.y / full_size.x;
}

vec2 fish_eye(vec2 UV_position, vec2 screen_UV, vec2 centre_UV) {
	// Shift the initial and final coordinate to adjust it to the model shape.
	vec2 shifted_UV = UV_position * vec2(2.0) - vec2(1.0);
	float UV_distance = length(shifted_UV);
	float z = sqrt(1.0 - (shifted_UV.x * shifted_UV.x) - (shifted_UV.y * shifted_UV.y));
	float r = atan(UV_distance, z) / PI;
	float phi = atan(shifted_UV.y, shifted_UV.x);
	return convert_to_full_screen_space(vec2(r * cos(phi) + 0.5, r * sin(phi) + 0.5), screen_UV, centre_UV, UV_position);
}

void fragment() {
	/*
	1) Get the centre of the object in terms of SCREEN_UV.
	2) Pass the fragment SCREEN_UV and centre SCREEN_UV to the distort function.
	3) Feed the distorted SCREEN_UV to the texture sampler.
	*/
	vec4 clip_position = PROJECTION_MATRIX * vec4(NODE_POSITION_VIEW, 1.0);
	vec2 centre_screen_uv = (clip_position.xy * vec2(0.5) + vec2(clip_position.w/2.0)) / clip_position.w;

	vec2 distorted_screen_uv = fish_eye(UV, SCREEN_UV, centre_screen_uv);

	ALBEDO = textureLod(screen_texture, distorted_screen_uv, 0.0).rgb;
}

This is pretty good, and conveys the effect nicely:

However, I won’t be setting this as a solution though, as it has (currently) has a major flaw that I can’t figure out how to fix:


At particular angles, the shader develops this weird artifact along the bisecting lines running through it’s centre

My theory is for some reason, when you move towards a high x and low y UV in the local space, or vice versa, it seemingly mirrors the pixel on its side, instead of sampling within the circle.

Anyone able to help me close this out?

Usable shader with an artifact bug:

shader_type spatial;
render_mode unshaded;

uniform sampler2D screen_texture : hint_screen_texture;

vec2 convert_to_local_screen_space(vec2 screen_UV, vec2 screen_centre_UV, vec2 model_UV){
	// Calculate the distance, in terms of screen uv values, on the centre of the model to any corner bounds.
	vec2 distance_from_centre = abs(1.0/(abs(0.5-model_UV)*2.0)) * abs(screen_UV-screen_centre_UV);
	vec2 full_size = distance_from_centre * 2.0;
	// Convert distance from centre to the minimum bounds (this should be our 0.0).
	vec2 minimum_bounds = screen_centre_UV - distance_from_centre;
	// Adjust our current position to get the local bounds.
	vec2 local_screen_UV = (screen_UV - minimum_bounds) / full_size;
	return local_screen_UV;
}

vec2 convert_to_full_screen_space(vec2 local_screen_UV, vec2 screen_UV, vec2 screen_centre_UV, vec2 model_UV){
	// Calculate the distance, in terms of screen uv values, on the centre of the model to any corner bounds.
	vec2 distance_from_centre = abs(1.0/(abs(0.5-model_UV)*2.0)) * abs(screen_UV-screen_centre_UV);
	vec2 full_size = distance_from_centre * 2.0;
	// Convert distance from centre to the minimum bounds (this should be our 0.0).
	vec2 minimum_bounds = screen_centre_UV - distance_from_centre;
	return local_screen_UV * full_size + minimum_bounds;
}

float calculate_model_screen_aspect_ratio(vec2 screen_UV, vec2 screen_centre_UV, vec2 model_UV){
	// Calculate the distance, in terms of screen uv values, on the centre of the model to any corner bounds.
	vec2 distance_from_centre = abs(1.0/(abs(0.5-model_UV)*2.0)) * abs(screen_UV-screen_centre_UV);
	vec2 full_size = distance_from_centre * 2.0;
	return full_size.y / full_size.x;
}

vec2 fish_eye(vec2 UV_position, vec2 screen_UV, vec2 centre_UV) {
	// Shift the initial and final coordinate to adjust it to the model shape.
	vec2 shifted_UV = UV_position * vec2(2.0) - vec2(1.0);
	float UV_distance = length(shifted_UV);
	float z = sqrt(1.0 - (shifted_UV.x * shifted_UV.x) - (shifted_UV.y * shifted_UV.y));
	float r = atan(UV_distance, z) / PI;
	float phi = atan(shifted_UV.y, shifted_UV.x);
	return convert_to_full_screen_space(vec2(r * cos(phi) + 0.5, r * sin(phi) + 0.5), screen_UV, centre_UV, UV_position);
}

void fragment() {
	/*
	1) Get the centre of the object in terms of SCREEN_UV.
	2) Pass the fragment SCREEN_UV and centre SCREEN_UV to the distort function.
	3) Feed the distorted SCREEN_UV to the texture sampler.
	*/
	vec4 clip_position = PROJECTION_MATRIX * vec4(NODE_POSITION_VIEW, 1.0);
	vec2 centre_screen_uv = (clip_position.xy * vec2(0.5) + vec2(clip_position.w/2.0)) / clip_position.w;

	vec2 distorted_screen_uv = fish_eye(UV, SCREEN_UV, centre_screen_uv);

	ALBEDO = textureLod(screen_texture, distorted_screen_uv, 0.0).rgb;
}