Billiard Sphere Shader (2D)

Renders a pseudo-3D rotating sphere on a 2D canvas using spherical projection,

vertex-level rotation, toon shading, and Blinn-Phong highlights.

通过球面投影、顶点旋转、卡通光照与 Blinn-Phong 高光在 2D 中模拟 3D 球体。

Shader code
shader_type canvas_item;

// ============================================================================
// Billiard Sphere Shader (2D)
// 台球球体着色器(2D)
//
// Renders a pseudo-3D rotating sphere on a 2D canvas using spherical projection,
// vertex-level rotation, toon shading, and Blinn-Phong highlights.
// 通过球面投影、顶点旋转、卡通光照与 Blinn-Phong 高光在 2D 中模拟 3D 球体。
// ============================================================================


// -----------------------------
// Rotation Controls / 旋转控制
// -----------------------------
uniform vec3 roll_axis = vec3(0.0, 1.0, 0.0);   // Rotation axis / 旋转轴
uniform float roll_speed = 0.6;                 // Rotation speed / 旋转速度
uniform float roll_angle = 0.0;                 // Initial angle / 初始角度


// -----------------------------
// Appearance / 外观设置
// -----------------------------
uniform float aspect_ratio = 1.0;               // Width/Height correction / 宽高比修正
uniform vec2 tile_factor = vec2(1.0, 1.0);      // Texture tiling / 纹理平铺
uniform vec2 texture_offset = vec2(0.0, 0.0);   // Texture offset / 纹理偏移


// -----------------------------
// Lighting / 光照参数
// -----------------------------
uniform vec3 light_vec_normalized = vec3(-0.35, -0.75, 0.55); // Light direction / 光源方向
uniform float ambient_light = 0.25;             // Ambient light / 环境光
uniform float light_intensity = 1.15;            // Diffuse intensity / 漫反射强度
uniform float highlight_size = 48.0;            // Specular sharpness / 高光锐度
uniform float highlight_opacity = 0.8;          // Specular opacity / 高光不透明度


// -----------------------------
// Toon Shading / 卡通着色
// -----------------------------
uniform float toon_bands = 4.0;                 // Band count / 色阶数量
uniform float toon_smoothness = 0.2;            // Band blend / 色阶平滑度


// -----------------------------
// Varyings / 顶点传递变量
// -----------------------------
varying mat3 v_rot_matrix;      // Rotation matrix / 旋转矩阵
varying vec2 v_centered_uv;     // Centered UV / 中心化 UV
varying vec3 v_half_vector;


// ------------------------------------------------------
// Rodrigues Rotation Matrix / 罗德里格旋转矩阵
// ------------------------------------------------------
mat3 get_rotation_matrix(vec3 axis, float angle) {
    float c = cos(angle);
    float s = sin(angle);
    float t = 1.0 - c;

    return mat3(
        vec3(t * axis.x * axis.x + c,          t * axis.x * axis.y - s * axis.z, t * axis.x * axis.z + s * axis.y),
        vec3(t * axis.x * axis.y + s * axis.z, t * axis.y * axis.y + c,          t * axis.y * axis.z - s * axis.x),
        vec3(t * axis.x * axis.z - s * axis.y, t * axis.y * axis.z + s * axis.x, t * axis.z * axis.z + c)
    );
}


void vertex() {
    // Center UV + correct aspect / 中心化UV并修正宽高比
    v_centered_uv = (UV - 0.5) * 2.0;
    v_centered_uv.x *= aspect_ratio;

    // Per-vertex rotation matrix / 顶点级旋转矩阵
    if (abs(roll_speed) > 0.001 || abs(roll_angle) > 0.001) {
        float angle = roll_angle + TIME * roll_speed;
        v_rot_matrix = get_rotation_matrix(normalize(roll_axis), angle);
    } else {
        v_rot_matrix = mat3(1.0); // Identity / 单位矩阵
    }

    vec3 view_dir = vec3(0.0, 0.0, 1.0);
    v_half_vector = normalize(light_vec_normalized + view_dir);
}


void fragment() {
    vec2 centered = v_centered_uv;
    float dist_sq = dot(centered, centered);

    // Circle clip / 圆形裁剪
    if (dist_sq > 1.0) { discard; }

    // Edge AA / 边缘抗锯齿
    float aa = fwidth(dist_sq);
    float alpha = 1.0 - smoothstep(1.0 - aa, 1.0, dist_sq);

    // Reconstruct sphere normal / 重建球体法线
    float z = sqrt(1.0 - dist_sq);
    vec3 normal = vec3(centered, z);

    // Apply rotation / 应用旋转
    vec3 rotated_normal = v_rot_matrix * normal;

    // Spherical UV projection / 球面 UV 投影
    float u = 0.5 + atan(rotated_normal.x, rotated_normal.z) * 0.1591549; // 1/(2π)
    float v = 0.5 - asin(rotated_normal.y) * 0.3183098;                   // 1/π
    vec4 tex = texture(TEXTURE, vec2(u, v) * tile_factor + texture_offset);

    // Mixed normal for stable highlight / 稳定高光用混合法线
    vec3 shading_normal = normalize(mix(normal, rotated_normal, 0.15));

    // Diffuse / 漫反射
    float NdotL = max(dot(shading_normal, light_vec_normalized), 0.0);
    float light_strength = ambient_light + 0.5 * (NdotL - ambient_light);

    // Toon quantization / 卡通量化
    float toon = light_strength;
    if (toon_bands > 1.0) {
        float qb = light_strength * toon_bands;
        float base = floor(qb) / toon_bands;
        float pos = fract(qb);
        float t = smoothstep(0.5 - toon_smoothness, 0.5 + toon_smoothness, pos);
        toon = mix(base, base + (1.0 / toon_bands), t);
    }

    // Specular / 高光
    float spec = 0.0;
    if (highlight_opacity > 0.01) {
        float NdotH = max(dot(shading_normal, v_half_vector), 0.0);
        spec = pow(NdotH, highlight_size);
    }

    // Final color / 最终颜色
    vec3 final_rgb = tex.rgb * toon * light_intensity + vec3(spec * highlight_opacity);
    COLOR = vec4(final_rgb, tex.a * alpha);
}
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

Noise-Displaced Space Sphere

[Auto control] Health(Mana) bar in ball container (sphere) (ver 2.3.2)

Fractal Rotation Sphere

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments