Skip to content

Commit 1fd13ce

Browse files
hyqzzclaude
andcommitted
fix(atmosphere): restore Mars daytime sky to correct amber-tan color
修复火星白昼天空错误呈现为黑色的问题。 根本原因:rayleigh 系数过小(5e-7 量级)导致大气层散射亮度趋近于零, 背景黑色直接透出,星星可见——与真实火星不符。 Rayleigh [R,G,B] 提升至 [5e-6, 3e-6, 1.2e-6](保持 R>G>B 比例, 沿用尘埃主导的奶油黄褐色调),Mie 同步上调至 [2e-6, 2.8e-6, 4e-6], haze 增至 0.55,multiplier 调至 2.8,使白昼天空呈现琥珀/金黄色并遮蔽星星。 分光 mieG [0.62, 0.74, 0.88] 保持不变,蓝色日落光晕依然显著( 日落时 phM_blue 约为 phM_red 的 4~8 倍,蓝光贡献约 11 倍于红光)。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent bf99ce7 commit 1fd13ce

3 files changed

Lines changed: 262 additions & 8 deletions

File tree

src/astro/bodies.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,19 @@ export const BODIES = {
4848
rotation: 'iau', landable: true,
4949
surface: { ampKm: 8, roughness: 0.6, craters: 0.6, palette: 'mars' },
5050
atmosphere: {
51-
// 白昼:Rayleigh 红>蓝 + Mie 漫射 → 奶油黄褐色天空(海盗号/好奇号/毅力号实拍一致)
52-
// 日落:Fe₂O₃ 尘埃(~1.5 µm)对蓝光的米氏前向散射系数约为红光的 3.5 倍
51+
// 白昼:Rayleigh 红>蓝(模拟尘埃主导的黄褐色调)+ Mie 漫射 → 奶油黄褐色天空。
52+
// 日落:Fe₂O₃ 尘埃(~1.5 µm)分光 mieG 使蓝光前向散射峰远强于红光
5353
// 太阳周围形成标志性蓝色/青色光晕——Curiosity Sol-956、Perseverance Sol-257 实拍印证。
5454
heightKm: 80, rayleighScaleKm: 11.1, mieScaleKm: 9,
55-
rayleigh: [5.0e-7, 3.0e-7, 1.2e-7],
56-
// betaM [R,G,B]:蓝光消光系数略高(细尘,~1.5µm),保持温和比例
57-
mie: [0.7e-6, 0.9e-6, 1.3e-6],
58-
// mieG [R,G,B]:核心参数——蓝光 Henyey-Greenstein 各向异性峰更尖(0.88),
55+
// Rayleigh 系数比地球小,但须足够大使白昼天空可见(×10 仍在 CO₂ 大气合理范围)
56+
rayleigh: [5.0e-6, 3.0e-6, 1.2e-6],
57+
// betaM [R,G,B]:蓝光消光系数略高,强化白昼尘霾亮度与日落分光效果
58+
mie: [2.0e-6, 2.8e-6, 4.0e-6],
59+
// mieG [R,G,B]:核心——蓝光 Henyey-Greenstein 各向异性峰更尖(0.88),
5960
// 日落时(µ→1) phM_blue 比 phM_red 高 4~8 倍,产生标志性蓝色光晕
6061
mieG: [0.62, 0.74, 0.88],
61-
multiplier: 2.5,
62-
haze: 0.40, // 近地面 Fe₂O₃ 尘埃雾霾(地平线奶油带)
62+
multiplier: 2.8,
63+
haze: 0.55, // 近地面 Fe₂O₃ 尘埃雾霾(地平线奶油带),白昼可见
6364
},
6465
textures: { map: 'mars.jpg' },
6566
desc: '红色行星。拥有太阳系最高的火山(奥林帕斯山 21.9 km)与最长的峡谷(水手谷 4000 km)。稀薄 CO₂ 大气,尘暴可席卷全球。',

tools/mars-daytime-check.mjs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import puppeteer from 'puppeteer';
2+
import { mkdirSync } from 'node:fs';
3+
const OUT = 'docs/sdlc/screenshots/';
4+
mkdirSync(OUT, { recursive: true });
5+
6+
const browser = await puppeteer.launch({
7+
executablePath: 'C:/Program Files/Google/Chrome/Application/chrome.exe',
8+
headless: true,
9+
args: ['--no-sandbox','--use-angle=swiftshader','--enable-unsafe-swiftshader',
10+
'--window-size=1920,1080','--hide-scrollbars','--disable-setuid-sandbox'],
11+
defaultViewport: { width: 1920, height: 1080 }
12+
});
13+
const page = await browser.newPage();
14+
const sleep = ms => new Promise(r => setTimeout(r, ms));
15+
page.on('pageerror', e => console.log('ERR:', e.message));
16+
17+
async function waitArrival(ms=30000) {
18+
const t0 = Date.now();
19+
while (Date.now()-t0<ms) {
20+
try { if (!(await page.evaluate(()=>!!window.__game?.orbitCam?.flight))) return true; } catch{}
21+
await sleep(300);
22+
}
23+
}
24+
async function waitMode(target, ms=15000) {
25+
const t0 = Date.now();
26+
while (Date.now()-t0<ms) {
27+
try { if ((await page.evaluate(()=>window.__game?.getMode?.()))===target) return true; } catch{}
28+
await sleep(250);
29+
}
30+
}
31+
32+
await page.goto('http://localhost:5173/?quality=high', {waitUntil:'networkidle2',timeout:60000});
33+
await page.waitForSelector('#start-btn', {visible:true,timeout:30000});
34+
await page.click('#start-btn');
35+
await sleep(2500);
36+
37+
await page.evaluate(()=>window.__game.flyTo('mars'));
38+
await waitArrival(20000);
39+
await sleep(800);
40+
await page.evaluate(()=>{ window.__game.orbitCam.distTarget=1; });
41+
await waitMode('walk',15000);
42+
await sleep(2000);
43+
44+
// 推进时钟找白昼正午(太阳仰角 > 45°)
45+
const noonInfo = await page.evaluate(async ()=>{
46+
const game = window.__game;
47+
const w = game.ship.walk;
48+
const clock = game.simClock;
49+
const STEP = 88642.663/86400 * 0.05; // 0.05 火星日
50+
51+
function getSunElev() {
52+
const me = game.builder.bodies.get('mars');
53+
const mp = me.posKm;
54+
const len = Math.sqrt(mp[0]**2+mp[1]**2+mp[2]**2);
55+
const seEcl = [-mp[0]/len,-mp[1]/len,-mp[2]/len];
56+
const sun3 = [seEcl[0],seEcl[2],-seEcl[1]];
57+
const lp = w.localPos;
58+
const lpLen = Math.sqrt(lp.x**2+lp.y**2+lp.z**2)||1;
59+
const upL = {x:lp.x/lpLen,y:lp.y/lpLen,z:lp.z/lpLen};
60+
const q = me.mesh.quaternion;
61+
const {x:qx,y:qy,z:qz,w:qw}=q;
62+
const vx=upL.x,vy=upL.y,vz=upL.z;
63+
const cx=qy*vz-qz*vy,cy=qz*vx-qx*vz,cz=qx*vy-qy*vx;
64+
const ccx=qy*cz-qz*cy,ccy=qz*cx-qx*cz,ccz=qx*cy-qy*cx;
65+
const ux=vx+2*(qw*cx+ccx),uy=vy+2*(qw*cy+ccy),uz=vz+2*(qw*cz+ccz);
66+
const dot=sun3[0]*ux+sun3[1]*uy+sun3[2]*uz;
67+
return Math.asin(Math.max(-1,Math.min(1,dot)))*180/Math.PI;
68+
}
69+
70+
let best = null;
71+
for (let i=0; i<40; i++) {
72+
await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)));
73+
const e = getSunElev();
74+
if (e > 45) { best = { elev:e, step:i }; break; }
75+
if (e > (best?.elev ?? -90)) best = {elev:e, step:i};
76+
clock.jdTT += STEP;
77+
await new Promise(r=>requestAnimationFrame(r));
78+
}
79+
80+
// 设置行走方向:朝太阳,45° 仰角望天
81+
const me = game.builder.bodies.get('mars');
82+
const mp = me.posKm;
83+
const len = Math.sqrt(mp[0]**2+mp[1]**2+mp[2]**2);
84+
const seEcl = [-mp[0]/len,-mp[1]/len,-mp[2]/len];
85+
const sun3 = [seEcl[0],seEcl[2],-seEcl[1]];
86+
const lp = w.localPos;
87+
const lpLen = Math.sqrt(lp.x**2+lp.y**2+lp.z**2)||1;
88+
const upL = {x:lp.x/lpLen,y:lp.y/lpLen,z:lp.z/lpLen};
89+
const q = me.mesh.quaternion;
90+
const {x:qx,y:qy,z:qz,w:qw}=q;
91+
const vx=upL.x,vy=upL.y,vz=upL.z;
92+
const cx=qy*vz-qz*vy,cy=qz*vx-qx*vz,cz=qx*vy-qy*vx;
93+
const ccx=qy*cz-qz*cy,ccy=qz*cx-qx*cz,ccz=qx*cy-qy*cx;
94+
const ux=vx+2*(qw*cx+ccx),uy=vy+2*(qw*cy+ccy),uz=vz+2*(qw*cz+ccz);
95+
const dot=sun3[0]*ux+sun3[1]*uy+sun3[2]*uz;
96+
const hx=sun3[0]-dot*ux,hy=sun3[1]-dot*uy,hz=sun3[2]-dot*uz;
97+
const hl=Math.sqrt(hx*hx+hy*hy+hz*hz)||1;
98+
w.yaw = Math.atan2(hx/hl, hz/hl);
99+
w.pitch = 0.7; // ~40° 仰角
100+
w.smx=w.yaw; w.smy=w.pitch;
101+
return { elev: getSunElev(), step: best?.step };
102+
});
103+
104+
console.log('白昼:太阳仰角', noonInfo.elev?.toFixed(1), '°');
105+
await sleep(3000);
106+
107+
// 截图1:朝太阳方向仰望(白昼天空)
108+
await page.screenshot({ path: OUT + 'mars-day-towardsun.png' });
109+
console.log('✅ 朝太阳截图');
110+
111+
// 截图2:背对太阳仰望(白昼背景天空)
112+
await page.evaluate(()=>{ const w=window.__game.ship.walk; w.yaw+=Math.PI; w.smx=w.yaw; });
113+
await sleep(1500);
114+
await page.screenshot({ path: OUT + 'mars-day-awaysun.png' });
115+
console.log('✅ 背太阳截图');
116+
117+
// 截图3:地平线(水平朝任意方向)
118+
await page.evaluate(()=>{ const w=window.__game.ship.walk; w.pitch=0.02; w.smy=0.02; });
119+
await sleep(1500);
120+
await page.screenshot({ path: OUT + 'mars-day-horizon.png' });
121+
console.log('✅ 水平截图');
122+
123+
await browser.close();

tools/mars-final-check.mjs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import puppeteer from 'puppeteer';
2+
import { mkdirSync } from 'node:fs';
3+
const OUT = 'docs/sdlc/screenshots/';
4+
mkdirSync(OUT, { recursive: true });
5+
6+
const browser = await puppeteer.launch({
7+
executablePath: 'C:/Program Files/Google/Chrome/Application/chrome.exe',
8+
headless: true,
9+
args: ['--no-sandbox','--use-angle=swiftshader','--enable-unsafe-swiftshader',
10+
'--window-size=1920,1080','--hide-scrollbars','--disable-setuid-sandbox'],
11+
defaultViewport: { width: 1920, height: 1080 }
12+
});
13+
const page = await browser.newPage();
14+
const sleep = ms => new Promise(r => setTimeout(r, ms));
15+
16+
async function waitArrival(ms=30000) {
17+
const t0=Date.now();
18+
while(Date.now()-t0<ms){try{if(!(await page.evaluate(()=>!!window.__game?.orbitCam?.flight)))return true;}catch{}await sleep(300);}
19+
}
20+
async function waitMode(t,ms=15000){
21+
const t0=Date.now();
22+
while(Date.now()-t0<ms){try{if((await page.evaluate(()=>window.__game?.getMode?.()))===t)return true;}catch{}await sleep(250);}
23+
}
24+
25+
await page.goto('http://localhost:5173/?quality=high',{waitUntil:'networkidle2',timeout:60000});
26+
await page.waitForSelector('#start-btn',{visible:true,timeout:30000});
27+
await page.click('#start-btn');
28+
await sleep(2500);
29+
30+
await page.evaluate(()=>window.__game.flyTo('mars'));
31+
await waitArrival(20000);
32+
await sleep(800);
33+
34+
// ── 白昼:降落后直接仰望
35+
await page.evaluate(()=>{ window.__game.orbitCam.distTarget=1; });
36+
await waitMode('walk',15000);
37+
await sleep(2000);
38+
39+
// 找正午(太阳仰角 > 50°)
40+
await page.evaluate(async ()=>{
41+
const game=window.__game, w=game.ship.walk, clock=game.simClock;
42+
const STEP=88642.663/86400*0.05;
43+
function elev(){
44+
const me=game.builder.bodies.get('mars'), mp=me.posKm;
45+
const len=Math.sqrt(mp[0]**2+mp[1]**2+mp[2]**2);
46+
const s=[-mp[0]/len,mp[2]/-len,-mp[1]/len];
47+
const lp=w.localPos, ll=Math.sqrt(lp.x**2+lp.y**2+lp.z**2)||1;
48+
const u={x:lp.x/ll,y:lp.y/ll,z:lp.z/ll};
49+
const q=me.mesh.quaternion,{x:qx,y:qy,z:qz,w:qw}=q;
50+
const vx=u.x,vy=u.y,vz=u.z;
51+
const cx=qy*vz-qz*vy,cy=qz*vx-qx*vz,cz=qx*vy-qy*vx;
52+
const ccx=qy*cz-qz*cy,ccy=qz*cx-qx*cz,ccz=qx*cy-qy*cx;
53+
const ux=vx+2*(qw*cx+ccx),uy=vy+2*(qw*cy+ccy),uz=vz+2*(qw*cz+ccz);
54+
return Math.asin(Math.max(-1,Math.min(1,s[0]*ux+s[1]*uy+s[2]*uz)))*180/Math.PI;
55+
}
56+
for(let i=0;i<40;i++){
57+
await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)));
58+
if(elev()>50) break;
59+
clock.jdTT+=STEP;
60+
await new Promise(r=>requestAnimationFrame(r));
61+
}
62+
// 朝背太阳方向仰望 60°(典型白昼天空色调)
63+
const me=game.builder.bodies.get('mars'),mp=me.posKm;
64+
const len=Math.sqrt(mp[0]**2+mp[1]**2+mp[2]**2);
65+
const seEcl=[-mp[0]/len,-mp[1]/len,-mp[2]/len];
66+
const sun3=[seEcl[0],seEcl[2],-seEcl[1]];
67+
const lp=w.localPos,ll=Math.sqrt(lp.x**2+lp.y**2+lp.z**2)||1;
68+
const upL={x:lp.x/ll,y:lp.y/ll,z:lp.z/ll};
69+
const q=me.mesh.quaternion,{x:qx,y:qy,z:qz,w:qw}=q;
70+
const vx=upL.x,vy=upL.y,vz=upL.z;
71+
const cx=qy*vz-qz*vy,cy=qz*vx-qx*vz,cz=qx*vy-qy*vx;
72+
const ccx=qy*cz-qz*cy,ccy=qz*cx-qx*cz,ccz=qx*cy-qy*cx;
73+
const ux=vx+2*(qw*cx+ccx),uy=vy+2*(qw*cy+ccy),uz=vz+2*(qw*cz+ccz);
74+
const dot=sun3[0]*ux+sun3[1]*uy+sun3[2]*uz;
75+
const hx=sun3[0]-dot*ux,hy=sun3[1]-dot*uy,hz=sun3[2]-dot*uz;
76+
const hl=Math.sqrt(hx*hx+hy*hy+hz*hz)||1;
77+
w.yaw=Math.atan2(hx/hl,hz/hl)+Math.PI; // 背太阳
78+
w.pitch=1.0; // 约 57° 仰望天空
79+
w.smx=w.yaw; w.smy=w.pitch;
80+
});
81+
await sleep(4000);
82+
await page.screenshot({ path: OUT+'mars-day-zenith.png' });
83+
console.log('✅ 白昼天顶截图');
84+
85+
// ── 日落
86+
await page.evaluate(async ()=>{
87+
const game=window.__game, w=game.ship.walk, clock=game.simClock;
88+
const STEP=88642.663/86400*0.05;
89+
function sunElevAndDir(){
90+
const me=game.builder.bodies.get('mars'), mp=me.posKm;
91+
const len=Math.sqrt(mp[0]**2+mp[1]**2+mp[2]**2);
92+
const seEcl=[-mp[0]/len,-mp[1]/len,-mp[2]/len];
93+
const sun3=[seEcl[0],seEcl[2],-seEcl[1]];
94+
const lp=w.localPos,ll=Math.sqrt(lp.x**2+lp.y**2+lp.z**2)||1;
95+
const upL={x:lp.x/ll,y:lp.y/ll,z:lp.z/ll};
96+
const q=me.mesh.quaternion,{x:qx,y:qy,z:qz,w:qw}=q;
97+
const vx=upL.x,vy=upL.y,vz=upL.z;
98+
const cx=qy*vz-qz*vy,cy=qz*vx-qx*vz,cz=qx*vy-qy*vx;
99+
const ccx=qy*cz-qz*cy,ccy=qz*cx-qx*cz,ccz=qx*cy-qy*cx;
100+
const ux=vx+2*(qw*cx+ccx),uy=vy+2*(qw*cy+ccy),uz=vz+2*(qw*cz+ccz);
101+
const dot=sun3[0]*ux+sun3[1]*uy+sun3[2]*uz;
102+
const hx=sun3[0]-dot*ux,hy=sun3[1]-dot*uy,hz=sun3[2]-dot*uz;
103+
const hl=Math.sqrt(hx*hx+hy*hy+hz*hz)||1;
104+
return { elev:Math.asin(Math.max(-1,Math.min(1,dot)))*180/Math.PI, hx:hx/hl, hz:hz/hl };
105+
}
106+
for(let i=0;i<40;i++){
107+
await new Promise(r=>requestAnimationFrame(()=>requestAnimationFrame(r)));
108+
const {elev}=sunElevAndDir();
109+
if(elev>=4&&elev<=14) break;
110+
clock.jdTT+=STEP;
111+
await new Promise(r=>requestAnimationFrame(r));
112+
}
113+
const {elev,hx,hz}=sunElevAndDir();
114+
// 朝向太阳,仰角约 3° 看地平线
115+
w.yaw=Math.atan2(hx,hz);
116+
w.pitch=0.05;
117+
w.smx=w.yaw; w.smy=w.pitch;
118+
return elev;
119+
});
120+
await sleep(5000);
121+
await page.screenshot({ path: OUT+'mars-sunset-final.png' });
122+
console.log('✅ 日落截图');
123+
124+
// 仰头 45° 看太阳周围天空
125+
await page.evaluate(()=>{ const w=window.__game.ship.walk; w.pitch=0.8; w.smy=0.8; });
126+
await sleep(2000);
127+
await page.screenshot({ path: OUT+'mars-sunset-corona.png' });
128+
console.log('✅ 日落光晕截图');
129+
130+
await browser.close();

0 commit comments

Comments
 (0)