3D-perspective TextureRect with Touch

Hi! As I was working for my project, I wanted a fake 3D perspective effect on an image when touched. I tried to use some of the shaders already published in this platform but none of them seemed to work since my TextureRect was inside so many control nodes.

Then I found this one shader and tweaked it so that it worked when touched upon the image, and the result is just what I wanted! I limited the rotation to 30º so it would look like when hovering a card in any card game. I hope it’s useful for you too!

NOTE: For it to work you will need to assign a script to the Item you want and paste the one I provide you. See screenshots for more details.

The shader I tweaked:
https://godotshaders.com/shader/2d-perspective/

 

Script—————————————————————

extends TextureRect

 

@export var rotation_speed: float = 100.0

@export var max_rotation: float = 30.0  # Límite de rotación en grados

@export var return_speed: float = 5.0  # Velocidad de retorno a la posición original

var last_touch_position: Vector2

var is_dragging: bool = false

 

func _process(delta):

if not is_dragging:

var current_y_rot = material.get_shader_parameter(“y_rot”)

var current_x_rot = material.get_shader_parameter(“x_rot”)

 

# Interpolación suave hacia 0

var new_y_rot = lerp(current_y_rot, 0.0, return_speed * delta)

var new_x_rot = lerp(current_x_rot, 0.0, return_speed * delta)

 

material.set_shader_parameter(“y_rot”, new_y_rot)

material.set_shader_parameter(“x_rot”, new_x_rot)

 

func _input(event):

if event is InputEventScreenDrag:

is_dragging = true

var delta_x = event.position.x – last_touch_position.x

var delta_y = event.position.y – last_touch_position.y

var half_width = size.x * 0.5

var half_height = size.y * 0.5

var current_y_rot = material.get_shader_parameter(“y_rot”)

var current_x_rot = material.get_shader_parameter(“x_rot”)

var new_y_rot = current_y_rot

var new_x_rot = current_x_rot

 

if event.position.x > half_width:

# Mitad derecha → Yrot negativo

new_y_rot -= delta_x * rotation_speed * 0.01

else:

# Mitad izquierda → Yrot positivo

new_y_rot += delta_x * rotation_speed * 0.01

 

if event.position.y > half_height:

# Mitad inferior → Xrot positivo

new_x_rot += delta_y * rotation_speed * 0.01

else:

# Mitad superior → Xrot negativo

new_x_rot -= delta_y * rotation_speed * 0.01

 

# Limitar la rotación entre -30 y 30 grados

new_y_rot = clamp(new_y_rot, -max_rotation, max_rotation)

new_x_rot = clamp(new_x_rot, -max_rotation, max_rotation)

material.set_shader_parameter(“y_rot”, new_y_rot)

material.set_shader_parameter(“x_rot”, new_x_rot)

last_touch_position = event.position

elif event is InputEventScreenTouch:

if event.pressed:

last_touch_position = event.position

else:

# Cuando se suelta el dedo, activa el retorno automático

is_dragging = false

 

Shader code
shader_type canvas_item;

// Camera FOV
uniform float fov : hint_range(1, 179) = 90;
uniform bool cull_back = true;
uniform float y_rot : hint_range(-30, 30) = 0.0; // Limitado a ±30 grados
uniform float x_rot : hint_range(-30, 30) = 0.0; // Limitado a ±30 grados
uniform float inset : hint_range(0, 1) = 0.0;

varying flat vec2 o;
varying vec3 p;

void vertex() {
    // Limitar los valores dentro del shader por seguridad
    float limited_y_rot = clamp(y_rot, -30.0, 30.0);
    float limited_x_rot = clamp(x_rot, -30.0, 30.0);

    float sin_b = sin(limited_y_rot / 180.0 * PI);
    float cos_b = cos(limited_y_rot / 180.0 * PI);
    float sin_c = sin(limited_x_rot / 180.0 * PI);
    float cos_c = cos(limited_x_rot / 180.0 * PI);

    mat3 inv_rot_mat;
    inv_rot_mat[0][0] = cos_b;
    inv_rot_mat[0][1] = 0.0;
    inv_rot_mat[0][2] = -sin_b;

    inv_rot_mat[1][0] = sin_b * sin_c;
    inv_rot_mat[1][1] = cos_c;
    inv_rot_mat[1][2] = cos_b * sin_c;

    inv_rot_mat[2][0] = sin_b * cos_c;
    inv_rot_mat[2][1] = -sin_c;
    inv_rot_mat[2][2] = cos_b * cos_c;

    float t = tan(fov / 360.0 * PI);
    p = inv_rot_mat * vec3((UV - 0.5), 0.5 / t);
    float v = (0.5 / t) + 0.5;
    p.xy *= v * inv_rot_mat[2].z;
    o = v * inv_rot_mat[2].xy;

    VERTEX += (UV - 0.5) / TEXTURE_PIXEL_SIZE * t * (1.0 - inset);
}

void fragment() {
    if (cull_back && p.z <= 0.0) discard;
    vec2 uv = (p.xy / p.z).xy - o;
    COLOR = texture(TEXTURE, uv + 0.5);
    COLOR.a *= step(max(abs(uv.x), abs(uv.y)), 0.5);
}
Live Preview
Tags
canvas, canvas item, card, fake 3d, Hover, perspective, TextureRect
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
123
123
2 months ago

good