初始天空盒

This commit is contained in:
2026-09-22 00:07:58 +08:00
parent 8b6b5572be
commit 6e345bc296
6 changed files with 337 additions and 11 deletions
+249
View File
@@ -0,0 +1,249 @@
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;
}
+1
View File
@@ -0,0 +1 @@
uid://de7313djio1xm
+9
View File
@@ -0,0 +1,9 @@
[gd_resource type="Sky" format=3 uid="uid://bi632suyifms5"]
[ext_resource type="Shader" uid="uid://de7313djio1xm" path="res://world/sky/cartoon_sky.gdshader" id="1_cartoon"]
[sub_resource type="ShaderMaterial" id="ShaderMaterial_cartoon"]
shader = ExtResource("1_cartoon")
[resource]
sky_material = SubResource("ShaderMaterial_cartoon")