-
Notifications
You must be signed in to change notification settings - Fork 0
Fix specular half vector for water reflections #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| # moon-keeper | ||
|
|
||
| ポルノグラフィティで一番好きな楽曲は「月飼い」です。 | ||
|
|
||
| ## ドキュメント | ||
|
|
||
| - [月を水面で飼う — 月相実装 最終設計ドキュメント](moon_phase_design.md) |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
|
|
@@ -81,6 +81,52 @@ | |||
| opacity: 0.7; | ||||
| pointer-events: auto; | ||||
| } | ||||
|
|
||||
| /* 月相スライダー(左下・ホバー表示) */ | ||||
| #phase-area { | ||||
| position: fixed; | ||||
| bottom: 0; | ||||
| left: 0; | ||||
| width: 150px; | ||||
| height: 130px; | ||||
| z-index: 101; | ||||
| } | ||||
| #phase-ui { | ||||
| position: fixed; | ||||
| bottom: 16px; | ||||
| left: 16px; | ||||
| padding: 10px 12px; | ||||
| background: rgba(0, 0, 0, 0.45); | ||||
| border: 1px solid rgba(255, 255, 255, 0.08); | ||||
| border-radius: 8px; | ||||
| color: rgba(255, 255, 255, 0.65); | ||||
| font-family: "Yu Gothic", "Hiragino Kaku Gothic ProN", sans-serif; | ||||
| font-size: 11px; | ||||
| line-height: 1.6; | ||||
| opacity: 0; | ||||
| pointer-events: none; | ||||
| transition: opacity 0.3s ease; | ||||
| z-index: 100; | ||||
| width: 180px; | ||||
| } | ||||
| #phase-area:hover ~ #phase-ui, | ||||
| #phase-area:hover ~ #phase-ui.hidden { | ||||
| opacity: 0.9; | ||||
| pointer-events: auto; | ||||
| } | ||||
| #phase-ui label { | ||||
| display: flex; | ||||
| align-items: center; | ||||
| gap: 8px; | ||||
| } | ||||
| #phase-ui input[type="range"] { | ||||
| width: 120px; | ||||
| } | ||||
| #phase-ui .value { | ||||
| min-width: 48px; | ||||
| text-align: right; | ||||
| font-variant-numeric: tabular-nums; | ||||
| } | ||||
| </style> | ||||
| </head> | ||||
| <body> | ||||
|
|
@@ -92,6 +138,15 @@ | |||
| Moon texture: <a href="https://www.solarsystemscope.com/textures/" target="_blank" rel="noopener">Solar System Scope</a><br> | ||||
| Repository: <a href="https://github.com/clockcrockwork/moon-keeper" target="_blank" rel="noopener">moon-keeper</a> | ||||
| </div> | ||||
| <div id="phase-area"></div> | ||||
| <div id="phase-ui" class="hidden"> | ||||
| <label> | ||||
| 月相 | ||||
| <input id="phase-slider" type="range" min="0" max="1" step="0.001" value="0.5" aria-label="moon phase" /> | ||||
| <span class="value" id="phase-value">0.500</span> | ||||
| </label> | ||||
| <div>左下ホバーで表示(制作・検証用)</div> | ||||
| </div> | ||||
|
|
||||
| <script type="importmap"> | ||||
| { | ||||
|
|
@@ -110,8 +165,32 @@ | |||
| const CONFIG = { | ||||
| moonDepth: 5, | ||||
| moonSize: 2.0, | ||||
| starCount: 250, | ||||
| starCount: 300, | ||||
| starDensityMin: 0.35, | ||||
| starDensityMax: 1.0, | ||||
| }; | ||||
|
|
||||
| // ======================================== | ||||
| // 月相計算 | ||||
| // ======================================== | ||||
| function illuminatedFraction(phase01) { | ||||
| // 0.0 = 新月, 0.5 = 満月 | ||||
| return 0.5 * (1 - Math.cos(phase01 * Math.PI * 2)); | ||||
| } | ||||
|
|
||||
| function calculateMoonPhase(date = new Date()) { | ||||
| // 2000-01-06 18:14 UTC の新月を基準にする簡易モデル | ||||
| const synodicMonth = 29.53058867; | ||||
| const knownNewMoon = Date.UTC(2000, 0, 6, 18, 14, 0); | ||||
| const daysSinceKnown = (date.getTime() - knownNewMoon) / 86400000; | ||||
| const phase01 = ((daysSinceKnown % synodicMonth) + synodicMonth) % synodicMonth / synodicMonth; | ||||
| return { | ||||
| phase01, | ||||
| fraction01: illuminatedFraction(phase01), | ||||
| }; | ||||
| } | ||||
|
|
||||
| const phaseState = calculateMoonPhase(); | ||||
|
|
||||
| // ======================================== | ||||
| // レンダラー・カメラ | ||||
|
|
@@ -156,17 +235,18 @@ | |||
| geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); | ||||
| geo.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1)); | ||||
| geo.setAttribute('alpha', new THREE.Float32BufferAttribute(alphas, 1)); | ||||
|
|
||||
| const mat = new THREE.ShaderMaterial({ | ||||
| uniforms: { time: { value: 0 } }, | ||||
| uniforms: { time: { value: 0 }, density: { value: CONFIG.starDensityMax } }, | ||||
| vertexShader: ` | ||||
| attribute float size; | ||||
| attribute float alpha; | ||||
| varying float vAlpha; | ||||
| uniform float time; | ||||
| uniform float density; | ||||
| void main() { | ||||
| float twinkle = sin(time * 0.4 + position.x * 1.5 + position.z) * 0.2 + 0.8; | ||||
| vAlpha = alpha * twinkle; | ||||
| vAlpha = alpha * twinkle * density; | ||||
| vec4 mv = modelViewMatrix * vec4(position, 1.0); | ||||
| gl_PointSize = size * (120.0 / -mv.z); | ||||
| gl_Position = projectionMatrix * mv; | ||||
|
|
@@ -200,11 +280,17 @@ | |||
| 'moon.webp', | ||||
| t => { t.colorSpace = THREE.SRGBColorSpace; } | ||||
| ); | ||||
| const mat = new THREE.MeshBasicMaterial({ map: tex }); | ||||
| const mat = new THREE.MeshStandardMaterial({ | ||||
| map: tex, | ||||
| roughness: 0.8, | ||||
| metalness: 0.0, | ||||
| emissive: new THREE.Color(0x0d0d0d), | ||||
| emissiveIntensity: 0.25, | ||||
| }); | ||||
| const mesh = new THREE.Mesh(geo, mat); | ||||
| return mesh; | ||||
| } | ||||
|
|
||||
| const moon = createMoon(); | ||||
| const moonInitialRotation = Math.random() * Math.PI * 2; | ||||
|
|
||||
|
|
@@ -220,7 +306,7 @@ | |||
| const moonGlow = new THREE.Mesh( | ||||
| new THREE.PlaneGeometry(CONFIG.moonSize * 5, CONFIG.moonSize * 5), | ||||
| new THREE.ShaderMaterial({ | ||||
| uniforms: {}, | ||||
| uniforms: { glowStrength: { value: 0.35 } }, | ||||
| vertexShader: ` | ||||
| varying vec2 vUv; | ||||
| void main() { | ||||
|
|
@@ -230,20 +316,33 @@ | |||
| `, | ||||
| fragmentShader: ` | ||||
| varying vec2 vUv; | ||||
| uniform float glowStrength; | ||||
| void main() { | ||||
| float dist = length(vUv - 0.5) * 2.0; | ||||
| float glow = smoothstep(1.0, 0.3, dist) * 0.15; | ||||
| float glow = smoothstep(1.0, 0.3, dist) * glowStrength; | ||||
| gl_FragColor = vec4(0.9, 0.85, 0.7, glow); | ||||
| } | ||||
| `, | ||||
| transparent: true, | ||||
| blending: THREE.AdditiveBlending, | ||||
| depthWrite: false | ||||
| depthWrite: false, | ||||
| side: THREE.DoubleSide | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||
| }) | ||||
| ); | ||||
| moonGlow.rotation.x = -Math.PI / 2; | ||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||
| moonGlow.rotation.x = -Math.PI / 2; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic numbers
29.53058867and86400000are used directly. Defining them as named constants likeSYNODIC_MONTH_DAYSorMILLISECONDS_PER_DAYwould make the code's intent clearer and improve maintainability.