2D Spherical Rotations in any Direction From Image

This shader allows you to get a UV mapped png and turn it into a sphere that rotates in a 2D engine! It took me a long time to make, and large credits to alxl, who wrote something similar and I took inspiration from. The key difference is that my code uses quaternions, so the sphere can now rotate in different directions instead of just one.

 

Here’s an example script that worked for me; the UV map I used had an aspect ratio of 2:1. Simply add the shader on a Sprite2D and it should work!

extends Sprite2D

var current_rotation: Quaternion = Quaternion.IDENTITY
var accumulated_rotation: Quaternion = Quaternion.IDENTITY
var rotationSpeed = 0.1
var shader_material: ShaderMaterial
var xAxis = Vector3(1.0, 0.0, 0.0)
var velocity = Vector2.ZERO

func _ready():
    # Ensure we are working with a ShaderMaterial
    shader_material = material as ShaderMaterial
    if shader_material == null:
        print("Error: No ShaderMaterial found on Sprite2D.")
    else:
        # Initialize shader parameter
        shader_material.set_shader_parameter("quaternion", current_rotation)

func _process(delta: float) -> void:
    velocity = Vector2.ZERO
    var xRotation: float = 0.0
    var yRotation: float = 0.0

    # Handle input for rotation
    if Input.is_action_pressed("up"):
        velocity.y = -100 * delta
    if Input.is_action_pressed("down"):
        velocity.y = 100 * delta
    if Input.is_action_pressed("left"):
        velocity.x = -200 * delta
    if Input.is_action_pressed("right"):
        velocity.x = 100 * delta
    
    if velocity.length() > 0:
        velocity = velocity
        position += velocity
    
    xRotation = velocity.y * rotationSpeed
    yRotation = -velocity.x * rotationSpeed
    
    var xQuaternion: Quaternion = Quaternion(Vector3(1, 0, 0), xRotation)
    var yQuaternion: Quaternion = Quaternion(Vector3(0, 1, 0), yRotation)
    current_rotation = current_rotation * xQuaternion * yQuaternion
    current_rotation = current_rotation.normalized()
    
    if shader_material:
        shader_material.set_shader_parameter("quaternion", current_rotation)

Shader code
shader_type canvas_item;

uniform vec4 quaternion = vec4(0.0, 0.0, 0.0, 1.0); // Default: identity quaternion

// Function to rotate a vector using a quaternion
vec3 rotate_with_quaternion(vec3 v, vec4 q) {
    vec3 t = 2.0 * cross(q.xyz, v);
    return v + q.w * t + cross(q.xyz, t);
}

void fragment() {
    // Map UVs to normalized coordinates (-1, 1)
    float px = 2.0 * (UV.x - 0.5);
    float py = 2.0 * (UV.y - 0.5);

    if (px * px + py * py > 1.0) {
        COLOR.a = 0.0; // Outside of sphere
    } else {
        // Map to a 3D unit sphere
        float pz = sqrt(max(0.0, 1.0 - px * px - py * py));
        vec3 sphere_pos = vec3(px, py, pz);

        // Apply quaternion rotation
        sphere_pos = rotate_with_quaternion(sphere_pos, quaternion);

        // Convert rotated position to UV coordinates
        float theta = atan(sphere_pos.z, sphere_pos.x);
        float phi = acos(clamp(sphere_pos.y, -1.0, 1.0));

        // Map spherical coordinates to UV space
        vec2 spherical_uv = vec2(0.5 + theta / (2.0 * PI), phi / PI);

        // Sample texture using spherical coordinates
        COLOR = texture(TEXTURE, spherical_uv);
    }
}
Tags
2d, ball, rotation, sphere
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

2D shadow with bottom offset, image ratio independent shadow direction, and minimal vertex increase

Visual Effects with an Image Mask

Image Warp Shader

Subscribe
Notify of
guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Phakin
Phakin
6 months ago

Any way use in pixel style art? Make the border of sphere pixelated.