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

250 lines
11 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 里(按分组排列),可以实时调、不用重新编译。
云与星星全部由 hash / value-noise / fbm 现场生成。
=========================================================================== */
// ----------------------------------------------------------- 渐变天空 -----
group_uniforms Gradient;
uniform vec3 zenith_color : source_color = vec3(0.118, 0.357, 0.851); // 天顶
uniform vec3 mid_color : source_color = vec3(0.353, 0.651, 0.941); // 中段
uniform vec3 horizon_color : source_color = vec3(0.851, 0.933, 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) = 0.85; // 渐变挤压
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.624, 0.761, 0.910, 1.0); // 背光面
uniform vec4 cloud_rim_color : source_color = vec4(1.000, 0.914, 0.722, 1.0); // 边缘辉光
uniform float cloud_coverage : hint_range(0.0, 1.0) = 0.46; // 云量
uniform float cloud_scale : hint_range(0.50, 12.0) = 4.0; // 云团大小(越大云越小越多)
uniform float cloud_horizon_fade : hint_range(0.0, 0.60) = 0.18; // 地平线淡出高度(sin 仰角)
uniform float cloud_edge : hint_range(0.005, 0.60) = 0.10; // 轮廓软硬
uniform float cloud_warp : hint_range(0.0, 3.0) = 0.55; // 揉皱程度(蓬松感)
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.851, 0.929, 1.000);
uniform float haze_strength : hint_range(0.0, 1.0) = 0.55;
uniform float haze_height : hint_range(0.0, 0.60) = 0.14;
// ===========================================================================
// 噪声工具
// ===========================================================================
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 个倍频的分形噪声:每个倍频先旋转再平移,避免出现轴向条纹
float fbm(vec2 p) {
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;
}
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) {
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);
}
// ===========================================================================
// 主函数
// ===========================================================================
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);
// --------------------------------------------------------------- 云层 --
// 球极投影:把上半球保角地映到一张圆盘上(天顶落在圆心,地平线落在半径 1)。
// 保角 = 云朵形状不被拉伸;天顶只是圆盘的圆心,不会像方位角等距投影那样
// 把云朝一个点收拢。云的角尺寸从地平线到天顶平滑放大 2 倍,接近真实透视。
vec2 uv = (dir.xz / (1.0 + dir.y)) * cloud_scale;
vec2 wind = cloud_direction;
if (length(wind) < 0.0001) {
wind = vec2(1.0, 0.0);
}
uv += normalize(wind) * (TIME * cloud_speed * 3.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);
float raw_lit = cloud_field(uv + light_off, cloud_warp);
// 原始 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);
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;
}