Files
agame/world/sky/cartoon_sky.gdshader
T
2026-09-22 00:23:45 +08:00

262 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
shader_type sky;
/* ===========================================================================
Cartoon Sky · 纯程序化,零贴图
---------------------------------------------------------------------------
渲染顺序: 渐变天空 → 地面半球 → 星星 → 太阳 → 云层 → 地平线雾霭
所有参数都暴露在 Inspector 里(按分组排列),可以实时调、不用重新编译。
调好的美术取值直接写在下面的 uniform 默认值里 —— 这样即使 .tres 里的
shader_parameter 被清掉,画面也不会跑偏。
=========================================================================== */
// ----------------------------------------------------------- 渐变天空 -----
group_uniforms Gradient;
uniform vec3 zenith_color : source_color = vec3(0.090, 0.300, 0.780); // 天顶
uniform vec3 mid_color : source_color = vec3(0.280, 0.600, 0.950); // 中段
uniform vec3 horizon_color : source_color = vec3(0.780, 0.880, 1.000); // 地平线
uniform vec3 ground_color : source_color = vec3(0.290, 0.329, 0.384); // 地面半球
uniform float gradient_bias : hint_range(0.10, 6.00) = 1.40; // 渐变挤压
uniform float band_count : hint_range(0.00, 20.00) = 0.00; // 0=平滑,>0=色阶数
uniform float band_sharpness : hint_range(0.00, 1.00) = 0.90; // 色阶硬度
// ---------------------------------------------------------------- 太阳 -----
group_uniforms Sun;
uniform float follow_scene_light : hint_range(0.0, 1.0) = 1.0; // 跟随场景平行光
uniform vec3 sun_direction = vec3(0.35, 0.55, 0.35); // 不跟随时手动指定
uniform vec3 sun_core_color : source_color = vec3(1.000, 0.976, 0.878);
uniform vec3 sun_halo_color : source_color = vec3(1.000, 0.827, 0.478);
uniform float sun_disk_size : hint_range(0.0, 0.40) = 0.035; // 弧度
uniform float sun_disk_softness : hint_range(0.0, 1.0) = 0.20;
uniform float sun_halo_size : hint_range(0.0, 2.00) = 0.55;
uniform float sun_halo_strength : hint_range(0.0, 4.00) = 0.65;
uniform float sun_bloom_strength : hint_range(0.0, 4.00) = 0.35;
uniform float sun_intensity : hint_range(0.0, 8.00) = 1.70;
// ---------------------------------------------------------------- 星空 -----
group_uniforms Stars;
uniform float star_amount : hint_range(0.0, 1.0) = 0.0; // 太阳落山后自动淡入
uniform float star_density : hint_range(10.0, 160.0) = 60.0;
uniform float star_size : hint_range(0.005, 0.30) = 0.050;
uniform float star_twinkle : hint_range(0.0, 1.0) = 0.65;
uniform vec3 star_color : source_color = vec3(0.85, 0.90, 1.00);
// ---------------------------------------------------------------- 云层 -----
group_uniforms Clouds;
uniform vec4 cloud_color : source_color = vec4(1.000, 1.000, 1.000, 1.0); // 受光面
uniform vec4 cloud_shadow_color : source_color = vec4(0.450, 0.620, 0.880, 1.0); // 背光面
uniform vec4 cloud_rim_color : source_color = vec4(1.000, 0.930, 0.760, 1.0); // 边缘辉光
uniform float cloud_coverage : hint_range(0.0, 1.0) = 0.55; // 云量
uniform float cloud_scale : hint_range(0.50, 12.0) = 4.0; // 云团大小(越大云越小越多)
uniform float cloud_plane_offset : hint_range(0.05, 2.00) = 0.60; // 云层投影软化常数 k
uniform float cloud_horizon_fade : hint_range(0.0, 0.60) = 0.08; // 地平线淡出高度(sin 仰角)
uniform float cloud_edge : hint_range(0.005, 0.60) = 0.16; // 轮廓软硬
uniform float cloud_warp : hint_range(0.0, 3.0) = 0.30; // 揉皱程度(蓬松感)
uniform float cloud_speed : hint_range(0.0, 1.0) = 0.010; // 飘动速度
uniform vec2 cloud_direction = vec2(1.0, 0.25); // 风向
uniform float cloud_shading : hint_range(0.0, 1.0) = 0.55; // 明暗对比
uniform float cloud_rim : hint_range(0.0, 2.0) = 0.40; // 描边辉光强度
uniform float cloud_opacity : hint_range(0.0, 1.0) = 1.0;
// ------------------------------------------------------------ 地平线雾霭 ---
group_uniforms Haze;
uniform vec3 haze_color : source_color = vec3(0.800, 0.890, 1.000);
uniform float haze_strength : hint_range(0.0, 1.0) = 0.35;
uniform float haze_height : hint_range(0.0, 0.60) = 0.09;
// ===========================================================================
// 噪声工具
// ===========================================================================
float hash21(vec2 p) {
p = fract(p * vec2(123.34, 456.21));
p += dot(p, p + 45.32);
return fract(p.x * p.y);
}
// 伪随机梯度方向(每个分量 -1..1)。用 fract 混合而不是 sin,
// 大坐标下也不会掉精度。
vec2 hash22(vec2 p) {
vec3 p3 = fract(vec3(p.x, p.y, p.x) * vec3(0.1031, 0.1030, 0.0973));
p3 += dot(p3, vec3(p3.y, p3.z, p3.x) + 33.33);
return fract(vec2((p3.x + p3.y) * p3.z, (p3.x + p3.z) * p3.y)) * 2.0 - 1.0;
}
// 梯度噪声(Perlin)。相比 value noise,它在格点处不会出现「梯度为零」的平台,
// 等值线是有机的团块 —— 阈值化之后不会切出一块块多边形的棱角。
float gradient_noise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
// 五次插值:二阶导连续
vec2 u = f * f * f * (f * (f * 6.0 - 15.0) + 10.0);
float a = dot(hash22(i), f);
float b = dot(hash22(i + vec2(1.0, 0.0)), f - vec2(1.0, 0.0));
float c = dot(hash22(i + vec2(0.0, 1.0)), f - vec2(0.0, 1.0));
float d = dot(hash22(i + vec2(1.0, 1.0)), f - vec2(1.0, 1.0));
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
// 5 个倍频的分形噪声:每个倍频先旋转再平移,避免出现轴向条纹。
// detail < 1 时高频倍频快速衰减 —— 用于远处的云,防止屏幕频率掉到像素级别以下闪烁。
float fbm(vec2 p, float detail) {
float sum = 0.0;
float amp = 0.5;
float norm = 0.0;
mat2 rot = mat2(vec2(0.80, 0.60), vec2(-0.60, 0.80));
for (int i = 0; i < 5; i++) {
sum += amp * gradient_noise(p);
norm += amp;
p = rot * p * 2.03 + 11.31;
amp *= 0.5 * detail;
}
return sum / norm;
}
// 卡通色阶:把 0..1 压成 n 段硬边色块(n<0.5 时原样返回 = 平滑)
float posterize(float t, float n, float sharp) {
if (n < 0.5) {
return t;
}
float s = t * n;
float base = floor(s);
float fr = s - base;
float e = mix(0.5, 0.015, clamp(sharp, 0.0, 1.0));
return (base + smoothstep(0.5 - e, 0.5 + e, fr)) / n;
}
// 云密度场:先做一次廉价的 domain warp 把噪声揉皱,云朵才会蓬松。
// 返回未归一化的原始 fbm —— 调用方需要它来算方向性明暗。
float cloud_field(vec2 p, float warp_amt, float detail) {
if (warp_amt > 0.001) {
vec2 w = vec2(gradient_noise(p * 0.5 + vec2(13.7, 5.1)),
gradient_noise(p * 0.5 + vec2(2.3, 21.9)));
p += w * warp_amt;
}
return fbm(p, detail);
}
// ===========================================================================
// 主函数
// ===========================================================================
void sky() {
vec3 dir = normalize(EYEDIR);
// ---------------------------------------------------------- 渐变天空 --
float up = clamp(dir.y, 0.0, 1.0);
float t = pow(up, 1.0 / max(gradient_bias, 0.05));
t = posterize(t, band_count, band_sharpness);
vec3 col = mix(horizon_color, mid_color, smoothstep(0.02, 0.30, t));
col = mix(col, zenith_color, smoothstep(0.30, 0.80, t));
// 地平线以下:压向地面色
float below = 1.0 - smoothstep(-0.05, 0.005, dir.y);
col = mix(col, ground_color, below * 0.9);
// --------------------------------------------------------------- 太阳 --
vec3 sun_dir = normalize(sun_direction);
if (LIGHT0_ENABLED && follow_scene_light > 0.5) {
sun_dir = normalize(LIGHT0_DIRECTION);
}
float sun_dot = clamp(dot(dir, sun_dir), -1.0, 1.0);
// 小角度近似,比 acos 数值稳定得多
float ang = sqrt(max(2.0 * (1.0 - sun_dot), 0.0));
// 太阳低于地平线时自动入夜
float night = 1.0 - smoothstep(-0.10, 0.08, sun_dir.y);
// --------------------------------------------------------------- 星空 --
if (star_amount > 0.001 && night > 0.001 && dir.y > -0.02) {
vec2 suv = vec2(atan(dir.x, dir.z), dir.y);
vec2 gv = suv * star_density;
vec2 id = floor(gv);
vec2 f = fract(gv) - 0.5;
float rnd = hash21(id);
// 稀疏化:只有一小部分格子长星星
float keep = step(0.93, rnd);
vec2 offset = (vec2(hash21(id + 3.7), hash21(id + 9.1)) - 0.5) * 0.7;
float d = length(f - offset);
float twinkle = mix(1.0, 0.30 + 0.70 * sin(TIME * (1.5 + rnd * 4.0) + rnd * 40.0), star_twinkle);
float star = (1.0 - smoothstep(0.0, max(star_size, 0.0001), d)) * keep * twinkle;
col += star_color * star * star_amount * night * smoothstep(-0.02, 0.10, dir.y);
}
// --------------------------------------------------------------- 太阳 --
// 硬边圆盘
float disk_in = max(sun_disk_size * (1.0 - sun_disk_softness * 0.85), 0.0001);
float disk = 1.0 - smoothstep(disk_in, max(sun_disk_size, disk_in + 0.0001), ang);
// 光晕
float halo = pow(1.0 - smoothstep(sun_disk_size, sun_disk_size + max(sun_halo_size, 0.0001), ang), 2.0);
// 大范围泛光
float bloom = pow(max(sun_dot, 0.0), 12.0);
vec3 sun_col = sun_core_color * (disk * sun_intensity)
+ sun_halo_color * (halo * sun_halo_strength * sun_intensity)
+ sun_halo_color * (bloom * sun_bloom_strength);
col += sun_col * (1.0 - below);
// --------------------------------------------------------------- 云层 --
// 云层投影:uv = dir.xz / (dir.y + k),即把视线投到一张水平云层上,
// 但分母里加了个软化常数 k。一次解决四件事:
// · 半径有上限 1/k —— 噪声频率不会在地平线处爆炸,不会拉出拖影或摩尔纹
// · 天顶不再是奇点 —— 近天顶处映射退化成局部均匀缩放,云不会朝一点收拢
// · 云的角尺寸从地平线到天顶平滑放大 2~3 倍 —— 保留空气透视;而保角投影
// 整片天的云一样大,看上去像一块贴图的平顶
// · 方位角方向天然无缝
vec2 uv = (dir.xz / max(dir.y + cloud_plane_offset, 0.02)) * cloud_scale;
float far = 1.0 - smoothstep(0.06, 0.45, dir.y);
// 远处云的屏幕频率更高 —— 按距离砍掉高频倍频,避免闪烁
float detail = mix(1.0, 0.35, far);
vec2 wind = cloud_direction;
if (length(wind) < 0.0001) {
wind = vec2(1.0, 0.0);
}
uv += normalize(wind) * (TIME * cloud_speed * 12.0);
// 形状采样 + 朝太阳方位偏移一次的采样 → 得到方向性明暗
vec2 sun_az = sun_dir.xz;
if (length(sun_az) < 0.0001) {
sun_az = vec2(1.0, 0.0);
}
sun_az = normalize(sun_az);
vec2 light_off = sun_az * (cloud_shading * 0.8);
float raw = cloud_field(uv, cloud_warp, detail);
float raw_lit = cloud_field(uv + light_off, cloud_warp, detail);
// 原始 fbm 落在 -0.4..0.4 附近,拉开对比后映射到 0..1 再交给阈值
float dens = clamp(raw * 1.3 + 0.5, 0.0, 1.0);
// 云量控制阈值:coverage 越大,阈值越低,云越多。
// 远处再抬高一截 —— 低仰角处每单位立体角里的云更多,不抑制会糊成一片「地毯」。
float thresh = mix(0.80, 0.10, cloud_coverage) + far * 0.10;
float mask = smoothstep(thresh, thresh + cloud_edge, dens);
// 受光判定:朝太阳方向采样到的密度更低 → 当前点处在迎光侧。
// 再按厚度压暗云芯,最后压成 3 段,得到赛璐璐风格的两面明暗。
float lit = clamp((raw - raw_lit) * cloud_shading * 20.0 + 0.5, 0.0, 1.0);
float thickness = smoothstep(thresh, thresh + 0.5, dens);
lit = clamp(lit - thickness * 0.32, 0.0, 1.0);
lit = posterize(lit, 3.0, 0.9);
vec3 cloud_col = mix(cloud_shadow_color.rgb, cloud_color.rgb, lit);
// 边缘辉光:靠近轮廓、且朝太阳那一侧
float interior = smoothstep(thresh, thresh + cloud_edge * 2.0, dens);
float rim = mask * (1.0 - interior) * smoothstep(0.45, 0.95, lit) * cloud_rim;
cloud_col += cloud_rim_color.rgb * rim;
// 越贴近地平线越淡
float cloud_a = mask * cloud_opacity * smoothstep(0.0, max(cloud_horizon_fade, 0.001), dir.y);
col = mix(col, cloud_col, cloud_a);
// ----------------------------------------------------------- 地平线雾 --
float haze = pow(1.0 - smoothstep(0.0, max(haze_height, 0.001), abs(dir.y)), 2.0);
col = mix(col, haze_color, haze * haze_strength * (1.0 - cloud_a * 0.7));
COLOR = col;
}