初始天空盒-2

This commit is contained in:
2026-09-22 00:23:45 +08:00
parent 6e345bc296
commit 22256c6789
3 changed files with 90 additions and 29 deletions
+49
View File
@@ -0,0 +1,49 @@
# 2026-09-22
## 修正云层的透视(world/sky/cartoon_sky.gdshader)
用户指出云的透视不对:球极投影虽然保角、无极点、无缝,但**代价是完全没有空气透视** ——
云从地平线到天顶一样大,看着像一块贴在天上的平顶。这一条是保角投影的固有性质,
不是参数能救的,必须换投影。
### 三种投影的取舍(结论:用软化分母的平面投影)
| 投影 | 半径 | 天顶 | 透视 | 结论 |
|---|---|---|---|---|
| 平面 `dir.xz/dir.y` | 无上限 | 奇点 | 正确(4~6 倍) | 低仰角频率爆炸 → 摩尔纹/地毯 |
| 球极 `dir.xz/(1+dir.y)` | ≤1 | 圆心(安全) | **无** | 平顶感 |
| 软化平面 `dir.xz/(dir.y+k)` | ≤1/k | 安全 | 2~3 倍 | **采用** |
`k`(uniform `cloud_plane_offset`,默认 0.6)是唯一的调节量:
- 半径上限 `1/k` → 噪声频率有界,不会在地平线处爆炸
- 近天顶处 `p ≈ (π/2−e)/(1+k)`,是局部均匀缩放,**不是奇点**,云不会朝一点收拢
- 角尺寸比 地平线:天顶 = `(1+k)/k²`,k=0.6 时约 4.4;云尺寸从 45° 的 17°
平滑降到 10° 的 7.8°
- 方位角方向天然无缝
- 副作用:云的宽高比恒为 1.4~1.7(略扁),对卡通云反而是好事
### 配套的两个抑制
1. **距离细节衰减**:`fbm(p, detail)` 里 `amp *= 0.5 * detail`,`detail = mix(1.0, 0.35, far)`。
远处云的屏幕频率更高,不砍高频倍频会闪烁。
2. **距离云量抑制**:`thresh += far * 0.10`。低仰角处每单位立体角的云数量更多,
不抑制就糊成一片「地毯」。`far = 1.0 - smoothstep(0.06, 0.45, dir.y)`。
最终取值:coverage 0.55 / scale 4.0 / plane_offset 0.60 / horizon_fade 0.08 / edge 0.16。
## 重要:.tres 里的 shader_parameter 会被 Godot 清掉
`cartoon_sky.tres` 被 Godot 重存过一次,`shader_parameter/*` 全部丢失(同时补上了 uid),
天空悄悄退回着色器默认值 —— 而且不会报任何错,只是画面跑偏。
**对策:把调好的美术取值直接写成 uniform 的默认值**,`.tres` 只保留对 shader 的引用。
这样即使参数被清掉,画面也不会变。Inspector 里未覆盖的参数本来就会显示着色器默认值,
所以用起来没区别。
顺带:`.tres` 现在被 Godot 补上了 `uid://bi632suyifms5`,`main.tscn` 里仍按 path 引用,能用。
## 待办 / 可选后续
- 地面还是灰白色 (0.55,0.58,0.62),被天空的蓝色环境光染成了淡紫;另外天空下半球的
`ground_color` (0.29,0.33,0.38) 在地面边缘外露出一条灰带。两处一起调会协调很多。
- `star_amount` 默认 0,太阳落到地平线以下会自动淡入星空,还没实测过夜景。
+41 -29
View File
@@ -6,16 +6,17 @@ shader_type sky;
渲染顺序: 渐变天空 → 地面半球 → 星星 → 太阳 → 云层 → 地平线雾霭
所有参数都暴露在 Inspector 里(按分组排列),可以实时调、不用重新编译。
云与星星全部由 hash / value-noise / fbm 现场生成。
调好的美术取值直接写在下面的 uniform 默认值里 —— 这样即使 .tres 里的
shader_parameter 被清掉,画面也不会跑偏。
=========================================================================== */
// ----------------------------------------------------------- 渐变天空 -----
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 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) = 0.85; // 渐变挤压
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; // 色阶硬度
@@ -43,13 +44,14 @@ 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 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; // 明暗对比
@@ -58,9 +60,9 @@ 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;
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;
// ===========================================================================
// 噪声工具
@@ -94,8 +96,9 @@ float gradient_noise(vec2 p) {
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}
// 5 个倍频的分形噪声:每个倍频先旋转再平移,避免出现轴向条纹
float fbm(vec2 p) {
// 5 个倍频的分形噪声:每个倍频先旋转再平移,避免出现轴向条纹。
// detail < 1 时高频倍频快速衰减 —— 用于远处的云,防止屏幕频率掉到像素级别以下闪烁。
float fbm(vec2 p, float detail) {
float sum = 0.0;
float amp = 0.5;
float norm = 0.0;
@@ -104,7 +107,7 @@ float fbm(vec2 p) {
sum += amp * gradient_noise(p);
norm += amp;
p = rot * p * 2.03 + 11.31;
amp *= 0.5;
amp *= 0.5 * detail;
}
return sum / norm;
}
@@ -123,13 +126,13 @@ float posterize(float t, float n, float sharp) {
// 云密度场:先做一次廉价的 domain warp 把噪声揉皱,云朵才会蓬松。
// 返回未归一化的原始 fbm —— 调用方需要它来算方向性明暗。
float cloud_field(vec2 p, float warp_amt) {
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);
return fbm(p, detail);
}
// ===========================================================================
@@ -194,16 +197,24 @@ void sky() {
col += sun_col * (1.0 - below);
// --------------------------------------------------------------- 云层 --
// 球极投影:把上半球保角地映到一张圆盘上(天顶落在圆心,地平线落在半径 1)。
// 保角 = 云朵形状不被拉伸;天顶只是圆盘的圆心,不会像方位角等距投影那样
// 把云朝一个点收拢。云的角尺寸从地平线到天顶平滑放大 2 倍,接近真实透视。
vec2 uv = (dir.xz / (1.0 + dir.y)) * cloud_scale;
// 云层投影: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 * 3.0);
uv += normalize(wind) * (TIME * cloud_speed * 12.0);
// 形状采样 + 朝太阳方位偏移一次的采样 → 得到方向性明暗
vec2 sun_az = sun_dir.xz;
@@ -213,14 +224,15 @@ void sky() {
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);
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);
// 云量控制阈值:coverage 越大,阈值越低,云越多。
// 远处再抬高一截 —— 低仰角处每单位立体角里的云更多,不抑制会糊成一片「地毯」。
float thresh = mix(0.80, 0.10, cloud_coverage) + far * 0.10;
float mask = smoothstep(thresh, thresh + cloud_edge, dens);
// 受光判定:朝太阳方向采样到的密度更低 → 当前点处在迎光侧。