SkySim: add initial support for a moon (#9650)
- also update filament binaries DOCS_FORCE
This commit is contained in:
@@ -24,10 +24,17 @@ class SimulatedSkybox {
|
||||
this.starControl = [1.0, 1.0]; // x=Density (0-1), y=Enabled (0-1)
|
||||
this.planetRadius = 6360.0;
|
||||
|
||||
// Sun Halo
|
||||
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
|
||||
// Sun Halo
|
||||
// x=cos(rad), y=limbDarkening, z=intensity, w=enabled
|
||||
this.sunHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.5, 1.0, 1.0];
|
||||
|
||||
// Moon Parameters (Mapped to Secondary Sun)
|
||||
this.moonDirection = [-0.2, 0.8, -0.2]; // Default Moon Pos
|
||||
this.moonIntensity = 5000.0; // Dimmer than sun
|
||||
this.moonHalo = [Math.cos(0.5 * Math.PI / 180.0), 0.0, 1.0, 0.0]; // Disabled by default
|
||||
|
||||
this.initEntity();
|
||||
}
|
||||
|
||||
@@ -220,6 +227,31 @@ class SimulatedSkybox {
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMoonPosition(direction) {
|
||||
// normalize
|
||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||
if (len > 0) {
|
||||
this.moonDirection = [direction[0] / len, direction[1] / len, direction[2] / len];
|
||||
}
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMoonIntensity(intensity) {
|
||||
this.moonIntensity = Math.max(0.0, intensity);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMoonRadius(degrees) {
|
||||
const rad = degrees * (Math.PI / 180.0);
|
||||
this.moonHalo[0] = Math.cos(rad);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMoonEnabled(enabled) {
|
||||
this.moonHalo[3] = enabled ? 1.0 : 0.0;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
updateCoefficients() {
|
||||
if (!this.materialInstance) {
|
||||
console.warn("updateCoefficients called before material loaded");
|
||||
@@ -307,5 +339,19 @@ class SimulatedSkybox {
|
||||
this.materialInstance.setFloat2Parameter('starControl', new Float32Array(this.starControl));
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity', physicalSunIntensity);
|
||||
|
||||
// Moon Upload (Secondary Sun)
|
||||
this.materialInstance.setFloat3Parameter('sunDirection2', new Float32Array(this.moonDirection));
|
||||
this.materialInstance.setFloatParameter('sunIntensity2', this.moonIntensity); // No atmospheric fade needed for Moon light source itself?
|
||||
// Actually, Moon light should also fade near horizon if we wanted realism, but let's keep it simple for now.
|
||||
// Or apply same fade? Moon is outside atmosphere.
|
||||
// Let's apply basic zenith fade to it too? Maybe not.
|
||||
|
||||
// Moon Halo Upload
|
||||
const moonSolidAngle = 2.0 * F_PI * (1.0 - this.moonHalo[0]);
|
||||
const moonRadConv = 1.0 / Math.max(1e-9, moonSolidAngle);
|
||||
const moonHaloUpload = [...this.moonHalo];
|
||||
moonHaloUpload[2] *= moonRadConv;
|
||||
this.materialInstance.setFloat4Parameter('sunHalo2', new Float32Array(moonHaloUpload));
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -32,8 +32,8 @@
|
||||
<script src="lil-gui.js"></script>
|
||||
|
||||
<!-- App -->
|
||||
<script src="SimulatedSkybox.js?v=39"></script>
|
||||
<script src="main.js?v=39"></script>
|
||||
<script src="SimulatedSkybox.js?v=47"></script>
|
||||
<script src="main.js?v=47"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -21,7 +21,7 @@ class App {
|
||||
// But Filament.init assets are for internal or easy access via assets object if configured?
|
||||
// Let's just let SimulatedSkybox fetch it again or use a blob if we wanted.
|
||||
// Simpler: Just let SimulatedSkybox fetch it.
|
||||
this.skybox.loadMaterial('assets/simulated_skybox.filamat').then(() => {
|
||||
this.skybox.loadMaterial('assets/simulated_skybox.filamat?v=46').then(() => {
|
||||
this.initGUI();
|
||||
});
|
||||
|
||||
@@ -99,6 +99,12 @@ class App {
|
||||
const exposure = this.getExposure();
|
||||
const preExposedIntensity = this.params.sunIntensity * exposure;
|
||||
this.skybox.setSunIntensity(preExposedIntensity);
|
||||
|
||||
// Moon Exposure
|
||||
if (this.mParams) {
|
||||
const preExposedMoon = this.mParams.intensity * exposure;
|
||||
this.skybox.setMoonIntensity(preExposedMoon);
|
||||
}
|
||||
}
|
||||
|
||||
updateCameraProjection() {
|
||||
@@ -139,6 +145,42 @@ class App {
|
||||
// Updated: Controls params.sunIntensity and triggers updateSunIntensity
|
||||
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).onChange(v => this.updateSunIntensity());
|
||||
|
||||
const moonFolder = gui.addFolder('Moon');
|
||||
this.mParams = {
|
||||
enabled: false,
|
||||
azimuth: 180.0,
|
||||
elevation: 45.0,
|
||||
radius: 0.5,
|
||||
|
||||
intensity: 10.0
|
||||
};
|
||||
|
||||
const updateMoon = () => {
|
||||
const az = this.mParams.azimuth * (Math.PI / 180.0);
|
||||
const el = this.mParams.elevation * (Math.PI / 180.0);
|
||||
const theta = Math.PI / 2.0 - el;
|
||||
const phi = az;
|
||||
|
||||
const x = Math.sin(theta) * Math.cos(phi);
|
||||
const y = Math.cos(theta);
|
||||
const z = Math.sin(theta) * Math.sin(phi);
|
||||
|
||||
sky.setMoonPosition([x, y, z]);
|
||||
};
|
||||
|
||||
// Initial Moon Sync
|
||||
updateMoon();
|
||||
sky.setMoonEnabled(this.mParams.enabled);
|
||||
sky.setMoonRadius(this.mParams.radius);
|
||||
sky.setMoonIntensity(this.mParams.intensity);
|
||||
|
||||
moonFolder.add(this.mParams, 'enabled').name('Enabled').onChange(v => sky.setMoonEnabled(v));
|
||||
moonFolder.add(this.mParams, 'azimuth', 0.0, 360.0).onChange(updateMoon);
|
||||
moonFolder.add(this.mParams, 'elevation', -90.0, 90.0).onChange(updateMoon);
|
||||
moonFolder.add(this.mParams, 'radius', 0.1, 5.0).onChange(v => sky.setMoonRadius(v));
|
||||
moonFolder.add(this.mParams, 'intensity', 0.0, 1000.0).onChange(v => this.updateSunIntensity()); // Reuse update function to apply exposure
|
||||
moonFolder.close();
|
||||
|
||||
const sunDisk = sunFolder.addFolder('Disk');
|
||||
// We need local proxy for sunRadius due to conversion
|
||||
this.diskParams = {
|
||||
@@ -327,6 +369,7 @@ class App {
|
||||
const w = this.wParams;
|
||||
const s = this.sParams;
|
||||
const b = this.bParams;
|
||||
const m = this.mParams;
|
||||
const sk = this.skybox;
|
||||
|
||||
return {
|
||||
@@ -335,6 +378,7 @@ class App {
|
||||
w: { dt: w.derivativeTrick, st: w.strength, s: w.speed, o: w.octaves },
|
||||
s: { e: s.enabled, d: s.density },
|
||||
b: { e: b.enabled, lf: b.lensFlare },
|
||||
m: { e: m.enabled, az: m.azimuth, el: m.elevation, r: m.radius, i: m.intensity },
|
||||
k: {
|
||||
t: sk.turbidity,
|
||||
r: sk.rayleigh,
|
||||
@@ -356,6 +400,7 @@ class App {
|
||||
const w = state.w;
|
||||
const s = state.s;
|
||||
const b = state.b;
|
||||
const m = state.m;
|
||||
const k = state.k;
|
||||
|
||||
if (p) {
|
||||
@@ -437,6 +482,28 @@ class App {
|
||||
sky.setWaterControl(this.wParams.strength, this.wParams.speed, this.wParams.derivativeTrick ? 1.0 : 0.0, this.wParams.octaves);
|
||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
||||
|
||||
if (m) {
|
||||
if (m.e !== undefined) this.mParams.enabled = m.e;
|
||||
if (m.az !== undefined) this.mParams.azimuth = m.az;
|
||||
if (m.el !== undefined) this.mParams.elevation = m.el;
|
||||
if (m.r !== undefined) this.mParams.radius = m.r;
|
||||
if (m.i !== undefined) this.mParams.intensity = m.i;
|
||||
|
||||
// Sync Moon
|
||||
const az = this.mParams.azimuth * (Math.PI / 180.0);
|
||||
const el = this.mParams.elevation * (Math.PI / 180.0);
|
||||
const theta = Math.PI / 2.0 - el;
|
||||
const phi = az;
|
||||
const x = Math.sin(theta) * Math.cos(phi);
|
||||
const y = Math.cos(theta);
|
||||
const z = Math.sin(theta) * Math.sin(phi);
|
||||
|
||||
sky.setMoonPosition([x, y, z]);
|
||||
sky.setMoonEnabled(this.mParams.enabled);
|
||||
sky.setMoonRadius(this.mParams.radius);
|
||||
sky.setMoonIntensity(this.mParams.intensity);
|
||||
}
|
||||
|
||||
this.view.setBloomOptions({
|
||||
enabled: this.bParams.enabled,
|
||||
lensFlare: this.bParams.lensFlare
|
||||
|
||||
@@ -693,11 +693,93 @@ fragment {
|
||||
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
|
||||
// @return Water surface color.
|
||||
// ------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Moon Disk (Phased)
|
||||
// ------------------------------------------------------------------------
|
||||
// Renders the Moon as a 3D sphere with lighting from the Sun.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L_moon Normalized Moon Vector.
|
||||
// @param L_sun Normalized Sun Vector.
|
||||
// @param moonParams x=CosRadius, y=LimbDarkening, z=IntensityBoost, w=Enabled.
|
||||
// @param moonIntensity Peak Moon Illuminance (Lux).
|
||||
// @param transmittance Atmospheric Transmittance (Moon Color Tint).
|
||||
// @return Radiance of the moon disk.
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getMoonDisk(highp vec3 V, highp vec3 L_moon, highp vec3 L_sun,
|
||||
highp vec4 moonParams, highp float moonIntensity,
|
||||
highp vec3 transmittance) {
|
||||
|
||||
highp float moonCosRadius = moonParams.x;
|
||||
// highp float limbDarkening = moonParams.y; // Unused for moon currently, maybe for earthshine?
|
||||
highp float moonDiskIntensity = moonParams.z;
|
||||
bool moonEnabled = moonParams.w > 0.5;
|
||||
|
||||
highp float cosTheta = dot(V, L_moon);
|
||||
highp float dist = 1.0 - cosTheta;
|
||||
highp float diskRadius = max(1e-6, 1.0 - moonCosRadius); // Approx Radius^2 / 2
|
||||
|
||||
// Soft Edge
|
||||
highp float moonProfile = 1.0 - smoothstep(diskRadius, diskRadius + 0.00002, dist);
|
||||
|
||||
if (moonEnabled && moonProfile > 0.0) {
|
||||
// 1. Reconstruct Sphere Normal
|
||||
// Tangent vector T = V - (V.L)L
|
||||
// This vector T points from the center of the disk towards V, in the disk plane.
|
||||
// Magnitude |T| is the distance from center (sine of angle).
|
||||
// Since we are working with small angles:
|
||||
// T approx V - L_moon.
|
||||
highp vec3 T = V - cosTheta * L_moon;
|
||||
|
||||
// Normalize T to get radial direction strength
|
||||
// We want N_perp magnitude to be 1.0 at the edge (where |T| ~ sin(angularRadius))
|
||||
// radius in "T space" is sin(acos(moonCosRadius)).
|
||||
highp float sinRadius = sqrt(1.0 - moonCosRadius * moonCosRadius);
|
||||
highp vec3 N_perp = T / sinRadius;
|
||||
|
||||
// Parallel component (pointing to viewer)
|
||||
// N_para = -L_moon * sqrt(1 - |N_perp|^2)
|
||||
highp float perpSq = dot(N_perp, N_perp);
|
||||
highp vec3 N_local = vec3(0.0);
|
||||
|
||||
if (perpSq < 1.0) {
|
||||
highp float para = sqrt(1.0 - perpSq);
|
||||
N_local = N_perp - L_moon * para;
|
||||
} else {
|
||||
// Edge case precision fix
|
||||
N_local = N_perp;
|
||||
}
|
||||
|
||||
highp vec3 N = normalize(N_local);
|
||||
|
||||
// 2. Compute Phase (Sun Lighting)
|
||||
highp float NdotL = dot(N, L_sun);
|
||||
|
||||
// Terminator softening (0.1 radian width)
|
||||
highp float phase = smoothstep(-0.05, 0.05, NdotL);
|
||||
|
||||
// Earthshine (Ambient fill on dark side)
|
||||
// Fake it as a small constant + maybe limb effect?
|
||||
highp float earthshine = 0.02; // 2% brightness on dark side
|
||||
|
||||
highp float lighting = max(earthshine, phase);
|
||||
|
||||
// Texture? (Procedural Craters - Future work)
|
||||
// For now just white disk
|
||||
|
||||
return moonIntensity * transmittance * lighting * moonDiskIntensity * moonProfile;
|
||||
}
|
||||
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
highp vec3 getWaterColor(highp vec3 V, highp vec3 L,
|
||||
highp float sunIntensity,
|
||||
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
|
||||
highp vec4 multiScatParams, highp vec2 miePhaseParams,
|
||||
highp vec4 sunHalo,
|
||||
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
||||
highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||
highp vec4 shimmerControl, highp vec4 waterControl) {
|
||||
|
||||
@@ -793,6 +875,11 @@ fragment {
|
||||
highp float reflSunAccess = 1.0 - smoothstep(0.0, 0.7, reflCloudDensity * 1.5);
|
||||
reflection += getSunDisk(R, L, sunHalo, sunIntensity, transRefl) * reflSunAccess;
|
||||
|
||||
// Add Moon Disk to reflection
|
||||
if (sunHalo2.w > 0.5) {
|
||||
reflection += getMoonDisk(R, L2, L, sunHalo2, sunIntensity2, transRefl) * reflSunAccess;
|
||||
}
|
||||
|
||||
// Apply clouds to reflection
|
||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||
|
||||
@@ -810,6 +897,8 @@ fragment {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
|
||||
@@ -832,13 +921,15 @@ fragment {
|
||||
transmittance);
|
||||
|
||||
|
||||
// Sun 2 (Optional)
|
||||
// Sun 2 / Moon (Optional)
|
||||
// We reuse the same Transmittance (view dependent) and Phase params.
|
||||
// We do NOT add extra Multi-Scattering (Ambient) for the second sun to save cost/complexity.
|
||||
// It contributes Direct In-Scattering (Beams/Glow) only.
|
||||
highp vec3 inScatter2 = vec3(0.0);
|
||||
|
||||
highp vec3 L2 = normalize(materialParams.sunDirection2);
|
||||
|
||||
if (materialParams.sunHalo2.w > 0.5) {
|
||||
highp vec3 L2 = normalize(materialParams.sunDirection2);
|
||||
inScatter2 = getSecondarySunScattering(V, L2,
|
||||
materialParams.sunIntensity2,
|
||||
materialParams.depthR,
|
||||
@@ -866,19 +957,15 @@ fragment {
|
||||
finalColor += getStarLayer(V, L, cloudDensity, transmittance, materialParams.starControl);
|
||||
|
||||
// 3. Sun Disks - Occluded by clouds
|
||||
// Sun Access is (1.0 - cloudDensity) but arguably non-linear for sharp disk
|
||||
highp float sunAccess = 1.0 - smoothstep(0.0, 0.7, cloudDensity * 1.5);
|
||||
|
||||
finalColor += getSunDisk(V, L, materialParams.sunHalo,
|
||||
materialParams.sunIntensity, transmittance) * sunAccess;
|
||||
|
||||
if (materialParams.sunHalo2.w > 0.5) {
|
||||
highp vec3 L2 = normalize(materialParams.sunDirection2);
|
||||
// Note: Ideally we should compute cloud density for L2 direction if clouds are 3D...
|
||||
// But here we use V direction clouds (view-based).
|
||||
// Since clouds are in front of everything, this is correct for view-based occlusion.
|
||||
finalColor += getSunDisk(V, L2, materialParams.sunHalo2,
|
||||
materialParams.sunIntensity2, transmittance) * sunAccess;
|
||||
// Pass L (Sun) as the light source for the Moon Phase
|
||||
finalColor += getMoonDisk(V, L2, L, materialParams.sunHalo2,
|
||||
materialParams.sunIntensity2, transmittance) * sunAccess;
|
||||
}
|
||||
|
||||
// 4. Night Sky Offset
|
||||
@@ -896,6 +983,7 @@ fragment {
|
||||
materialParams.ozone, materialParams.multiScatParams,
|
||||
materialParams.miePhaseParams,
|
||||
materialParams.sunHalo,
|
||||
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
||||
materialParams.cloudControl, materialParams.cloudControl2,
|
||||
materialParams.shimmerControl,
|
||||
materialParams.waterControl);
|
||||
|
||||
Reference in New Issue
Block a user