diff --git a/.workbuddy-ai/memory/2026-09-22.md b/.workbuddy-ai/memory/2026-09-22.md new file mode 100644 index 0000000..20de1e8 --- /dev/null +++ b/.workbuddy-ai/memory/2026-09-22.md @@ -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,太阳落到地平线以下会自动淡入星空,还没实测过夜景。 diff --git a/addons/godotsteam/win64/~libgodotsteam.windows.template_debug.x86_64.dll b/addons/godotsteam/win64/~libgodotsteam.windows.template_debug.x86_64.dll deleted file mode 100644 index 1a2b8a5..0000000 Binary files a/addons/godotsteam/win64/~libgodotsteam.windows.template_debug.x86_64.dll and /dev/null differ diff --git a/world/sky/cartoon_sky.gdshader b/world/sky/cartoon_sky.gdshader index 01db25d..7b0c9f5 100644 --- a/world/sky/cartoon_sky.gdshader +++ b/world/sky/cartoon_sky.gdshader @@ -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); // 受光判定:朝太阳方向采样到的密度更低 → 当前点处在迎光侧。