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);
}

