初始天空盒-2
This commit is contained in:
@@ -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,太阳落到地平线以下会自动淡入星空,还没实测过夜景。
|
||||||
Binary file not shown.
@@ -6,16 +6,17 @@ shader_type sky;
|
|||||||
渲染顺序: 渐变天空 → 地面半球 → 星星 → 太阳 → 云层 → 地平线雾霭
|
渲染顺序: 渐变天空 → 地面半球 → 星星 → 太阳 → 云层 → 地平线雾霭
|
||||||
|
|
||||||
所有参数都暴露在 Inspector 里(按分组排列),可以实时调、不用重新编译。
|
所有参数都暴露在 Inspector 里(按分组排列),可以实时调、不用重新编译。
|
||||||
云与星星全部由 hash / value-noise / fbm 现场生成。
|
调好的美术取值直接写在下面的 uniform 默认值里 —— 这样即使 .tres 里的
|
||||||
|
shader_parameter 被清掉,画面也不会跑偏。
|
||||||
=========================================================================== */
|
=========================================================================== */
|
||||||
|
|
||||||
// ----------------------------------------------------------- 渐变天空 -----
|
// ----------------------------------------------------------- 渐变天空 -----
|
||||||
group_uniforms Gradient;
|
group_uniforms Gradient;
|
||||||
uniform vec3 zenith_color : source_color = vec3(0.118, 0.357, 0.851); // 天顶
|
uniform vec3 zenith_color : source_color = vec3(0.090, 0.300, 0.780); // 天顶
|
||||||
uniform vec3 mid_color : source_color = vec3(0.353, 0.651, 0.941); // 中段
|
uniform vec3 mid_color : source_color = vec3(0.280, 0.600, 0.950); // 中段
|
||||||
uniform vec3 horizon_color : source_color = vec3(0.851, 0.933, 1.000); // 地平线
|
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 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_count : hint_range(0.00, 20.00) = 0.00; // 0=平滑,>0=色阶数
|
||||||
uniform float band_sharpness : hint_range(0.00, 1.00) = 0.90; // 色阶硬度
|
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;
|
group_uniforms Clouds;
|
||||||
uniform vec4 cloud_color : source_color = vec4(1.000, 1.000, 1.000, 1.0); // 受光面
|
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_shadow_color : source_color = vec4(0.450, 0.620, 0.880, 1.0); // 背光面
|
||||||
uniform vec4 cloud_rim_color : source_color = vec4(1.000, 0.914, 0.722, 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.46; // 云量
|
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_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_plane_offset : hint_range(0.05, 2.00) = 0.60; // 云层投影软化常数 k
|
||||||
uniform float cloud_edge : hint_range(0.005, 0.60) = 0.10; // 轮廓软硬
|
uniform float cloud_horizon_fade : hint_range(0.0, 0.60) = 0.08; // 地平线淡出高度(sin 仰角)
|
||||||
uniform float cloud_warp : hint_range(0.0, 3.0) = 0.55; // 揉皱程度(蓬松感)
|
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 float cloud_speed : hint_range(0.0, 1.0) = 0.010; // 飘动速度
|
||||||
uniform vec2 cloud_direction = vec2(1.0, 0.25); // 风向
|
uniform vec2 cloud_direction = vec2(1.0, 0.25); // 风向
|
||||||
uniform float cloud_shading : hint_range(0.0, 1.0) = 0.55; // 明暗对比
|
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;
|
group_uniforms Haze;
|
||||||
uniform vec3 haze_color : source_color = vec3(0.851, 0.929, 1.000);
|
uniform vec3 haze_color : source_color = vec3(0.800, 0.890, 1.000);
|
||||||
uniform float haze_strength : hint_range(0.0, 1.0) = 0.55;
|
uniform float haze_strength : hint_range(0.0, 1.0) = 0.35;
|
||||||
uniform float haze_height : hint_range(0.0, 0.60) = 0.14;
|
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);
|
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5 个倍频的分形噪声:每个倍频先旋转再平移,避免出现轴向条纹
|
// 5 个倍频的分形噪声:每个倍频先旋转再平移,避免出现轴向条纹。
|
||||||
float fbm(vec2 p) {
|
// detail < 1 时高频倍频快速衰减 —— 用于远处的云,防止屏幕频率掉到像素级别以下闪烁。
|
||||||
|
float fbm(vec2 p, float detail) {
|
||||||
float sum = 0.0;
|
float sum = 0.0;
|
||||||
float amp = 0.5;
|
float amp = 0.5;
|
||||||
float norm = 0.0;
|
float norm = 0.0;
|
||||||
@@ -104,7 +107,7 @@ float fbm(vec2 p) {
|
|||||||
sum += amp * gradient_noise(p);
|
sum += amp * gradient_noise(p);
|
||||||
norm += amp;
|
norm += amp;
|
||||||
p = rot * p * 2.03 + 11.31;
|
p = rot * p * 2.03 + 11.31;
|
||||||
amp *= 0.5;
|
amp *= 0.5 * detail;
|
||||||
}
|
}
|
||||||
return sum / norm;
|
return sum / norm;
|
||||||
}
|
}
|
||||||
@@ -123,13 +126,13 @@ float posterize(float t, float n, float sharp) {
|
|||||||
|
|
||||||
// 云密度场:先做一次廉价的 domain warp 把噪声揉皱,云朵才会蓬松。
|
// 云密度场:先做一次廉价的 domain warp 把噪声揉皱,云朵才会蓬松。
|
||||||
// 返回未归一化的原始 fbm —— 调用方需要它来算方向性明暗。
|
// 返回未归一化的原始 fbm —— 调用方需要它来算方向性明暗。
|
||||||
float cloud_field(vec2 p, float warp_amt) {
|
float cloud_field(vec2 p, float warp_amt, float detail) {
|
||||||
if (warp_amt > 0.001) {
|
if (warp_amt > 0.001) {
|
||||||
vec2 w = vec2(gradient_noise(p * 0.5 + vec2(13.7, 5.1)),
|
vec2 w = vec2(gradient_noise(p * 0.5 + vec2(13.7, 5.1)),
|
||||||
gradient_noise(p * 0.5 + vec2(2.3, 21.9)));
|
gradient_noise(p * 0.5 + vec2(2.3, 21.9)));
|
||||||
p += w * warp_amt;
|
p += w * warp_amt;
|
||||||
}
|
}
|
||||||
return fbm(p);
|
return fbm(p, detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===========================================================================
|
// ===========================================================================
|
||||||
@@ -194,16 +197,24 @@ void sky() {
|
|||||||
col += sun_col * (1.0 - below);
|
col += sun_col * (1.0 - below);
|
||||||
|
|
||||||
// --------------------------------------------------------------- 云层 --
|
// --------------------------------------------------------------- 云层 --
|
||||||
// 球极投影:把上半球保角地映到一张圆盘上(天顶落在圆心,地平线落在半径 1)。
|
// 云层投影:uv = dir.xz / (dir.y + k),即把视线投到一张水平云层上,
|
||||||
// 保角 = 云朵形状不被拉伸;天顶只是圆盘的圆心,不会像方位角等距投影那样
|
// 但分母里加了个软化常数 k。一次解决四件事:
|
||||||
// 把云朝一个点收拢。云的角尺寸从地平线到天顶平滑放大 2 倍,接近真实透视。
|
// · 半径有上限 1/k —— 噪声频率不会在地平线处爆炸,不会拉出拖影或摩尔纹
|
||||||
vec2 uv = (dir.xz / (1.0 + dir.y)) * cloud_scale;
|
// · 天顶不再是奇点 —— 近天顶处映射退化成局部均匀缩放,云不会朝一点收拢
|
||||||
|
// · 云的角尺寸从地平线到天顶平滑放大 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;
|
vec2 wind = cloud_direction;
|
||||||
if (length(wind) < 0.0001) {
|
if (length(wind) < 0.0001) {
|
||||||
wind = vec2(1.0, 0.0);
|
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;
|
vec2 sun_az = sun_dir.xz;
|
||||||
@@ -213,14 +224,15 @@ void sky() {
|
|||||||
sun_az = normalize(sun_az);
|
sun_az = normalize(sun_az);
|
||||||
vec2 light_off = sun_az * (cloud_shading * 0.8);
|
vec2 light_off = sun_az * (cloud_shading * 0.8);
|
||||||
|
|
||||||
float raw = cloud_field(uv, cloud_warp);
|
float raw = cloud_field(uv, cloud_warp, detail);
|
||||||
float raw_lit = cloud_field(uv + light_off, cloud_warp);
|
float raw_lit = cloud_field(uv + light_off, cloud_warp, detail);
|
||||||
|
|
||||||
// 原始 fbm 落在 -0.4..0.4 附近,拉开对比后映射到 0..1 再交给阈值
|
// 原始 fbm 落在 -0.4..0.4 附近,拉开对比后映射到 0..1 再交给阈值
|
||||||
float dens = clamp(raw * 1.3 + 0.5, 0.0, 1.0);
|
float dens = clamp(raw * 1.3 + 0.5, 0.0, 1.0);
|
||||||
|
|
||||||
// 云量控制阈值:coverage 越大,阈值越低,云越多
|
// 云量控制阈值:coverage 越大,阈值越低,云越多。
|
||||||
float thresh = mix(0.80, 0.10, cloud_coverage);
|
// 远处再抬高一截 —— 低仰角处每单位立体角里的云更多,不抑制会糊成一片「地毯」。
|
||||||
|
float thresh = mix(0.80, 0.10, cloud_coverage) + far * 0.10;
|
||||||
float mask = smoothstep(thresh, thresh + cloud_edge, dens);
|
float mask = smoothstep(thresh, thresh + cloud_edge, dens);
|
||||||
|
|
||||||
// 受光判定:朝太阳方向采样到的密度更低 → 当前点处在迎光侧。
|
// 受光判定:朝太阳方向采样到的密度更低 → 当前点处在迎光侧。
|
||||||
|
|||||||
Reference in New Issue
Block a user