Improve Moon/Earthshine, add Touch support (#9659)
- Fix Moon normal calculation in shader (was inverted). - Implement physically based Earthshine (dynamic based on phase). - Add Moon scattering to Water reflection. - Fix Star occlusion (masked by Moon). - Improved Stars - Add mobile touch support (Orbit control) to Camera. DOCS_FORCE
This commit is contained in:
76
docs_src/src_raw/wip/sky/README.md
Normal file
76
docs_src/src_raw/wip/sky/README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Analytic Skybox Sample
|
||||
|
||||
This sample demonstrates a **fully procedural, single-pass skybox shader** capable of simulating a dynamic day-night cycle, atmospheric scattering, volumetric clouds, and water reflections.
|
||||
|
||||
It is designed for graphics engineers and technical artists who need a lightweight yet physically plausible environment background without relying on static HDRI textures.
|
||||
|
||||
## Features & Performance
|
||||
|
||||
The shader uses a "Uber-Shader" approach where all features are computed per-pixel. Features can be toggled or tuned via uniforms to balance quality vs performance.
|
||||
|
||||
| Feature | Cost | Control | Description |
|
||||
| :--- | :---: | :--- | :--- |
|
||||
| **Atmosphere** | 🟡 **Medium** | `turbidity`, `rayleigh`, `mie` | Analytic Rayleigh & Mie scattering. Physically based colors. |
|
||||
| **Sun Disk** | 🟢 **Low** | `sunHalo` (Radius, Limb) | Analytic sphere intersection with limb darkening. Conservation of energy (Lux). |
|
||||
| **Moon & Earthshine** | 🟢 **Low** | `sunHalo2`, `moonIntensity` | Resolved moon disk with geometric phases and dynamic Earthshine. |
|
||||
| **Stars** | 🟢 **Low** | `starControl` (Density) | High-frequency procedural noise. Occluded by clouds/moon. |
|
||||
| **Clouds** | 🔴 **High** | `cloudControl` (Coverage, Density) | 4-Octave 3D Fractal Brownian Motion (FBM). Dominates the cost when enabled. |
|
||||
| **Heat Shimmer** | 🟡 **Medium** | `shimmerControl` | UV perturbation near the horizon to simulate mirages. |
|
||||
| **Water Reflection** | 🟣 **Very High** | `waterControl` | **Renders the sky twice**. Includes procedural waves (FBM) and fresnel. |
|
||||
|
||||
> **Note**: Rendering water (`V.y < 0`) is significantly more expensive (~2.5x) than the sky because it requires re-evaluating the atmospheric scattering and cloud noise for the reflection vector.
|
||||
|
||||
## Shader Techniques
|
||||
|
||||
### 1. Analytic Atmospheric Scattering
|
||||
Based on the **Hoffman & Preetham** model. It solves the single-scattering integral analytically for air molecules (Rayleigh) and aerosols (Mie).
|
||||
- **Rayleigh**: Produces the deep blue sky and red sunset colors.
|
||||
- **Mie**: Produces the white halo around the sun and general haziness.
|
||||
- **Optimization**: Uses a simplified optical depth approximation ("Air Mass") to avoid expensive ray-marching.
|
||||
|
||||
### 2. Procedural Clouds (3D Noise)
|
||||
Clouds are rendered as a spherical shell at a specific altitude.
|
||||
- **Technique**: Ray-sphere intersection finds the entry point, then **3D FBM Noise** determines density.
|
||||
- **Lighting**: Uses a "Silver Lining" approximation (strong forward scattering) and Beers-Lambert attenuation for dark underbellies.
|
||||
- **Animation**: The noise coordinate logic helps simulate wind drift and shape evolution over time.
|
||||
|
||||
### 3. Infinite Water Ocean
|
||||
When looking below the horizon, the shader switches to "Water Mode".
|
||||
- **Geometry**: A flat plane at $y=0$.
|
||||
- **Waves**: Generated using **Derivative-Based Noise** (or Finite Difference). This creates slope vectors that perturb the normal without needing actual geometry.
|
||||
- **Reflection**: A ray is cast from the water surface back into the sky ($R = \text{reflect}(V, N)$). The sky function is called again with $R$ to get the reflected color.
|
||||
|
||||
### 4. Dynamic Tone Mapping
|
||||
Applies a custom tone mapping curve that varies with Sun Elevation.
|
||||
- **Noon**: Linear/Gamma (Standard).
|
||||
- **Sunset**: Higher contrast curve to compress the dynamic range and enhance the rich sunset oranges/purples.
|
||||
|
||||
## Integration
|
||||
|
||||
To use this in your own Filament application:
|
||||
|
||||
1. **Compile the Material**:
|
||||
Use `matc` to compile `simulated_skybox.mat` into a `.filamat` file.
|
||||
```bash
|
||||
matc -p mobile -a opengl -o assets/simulated_skybox.filamat simulated_skybox.mat
|
||||
```
|
||||
|
||||
2. **Load in JavaScript/C++**:
|
||||
Create a Skybox entity and assign the material.
|
||||
|
||||
```javascript
|
||||
// JavaScript Example
|
||||
const material = engine.createMaterial('assets/simulated_skybox.filamat');
|
||||
const skybox = engine.createSkybox(material);
|
||||
scene.setSkybox(skybox);
|
||||
```
|
||||
|
||||
3. **Update Uniforms**:
|
||||
The shader requires specific uniforms (Sun Direction, Time, etc.) to be updated every frame. See `SimulatedSkybox.js` for a reference implementation of the uniform buffer management.
|
||||
|
||||
## References
|
||||
|
||||
* **Hoffman & Preetham (2002)**: *"Real-time Light-Atmosphere Interactions"*
|
||||
* **Henyey & Greenstein (1941)**: *"Diffuse radiation in the galaxy"* (Mie Phase Function)
|
||||
* **Kasten & Young (1989)**: *"Revised optical air mass tables"*
|
||||
* **Three.js / Sky.js**: Empirical adjustments for "Golden Hour" aesthetics.
|
||||
@@ -21,19 +21,21 @@ class SimulatedSkybox {
|
||||
this.cloudControl = [0.0, 0.1, 8000.0, 0.0];
|
||||
this.cloudControl2 = [0.0, 0.0, 0.0, 0.0];
|
||||
this.waterControl = [50.0, 1.0, 1.0, 4.0]; // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
||||
this.starControl = [1.0, 1.0]; // x=Density (0-1), y=Enabled (0-1)
|
||||
this.starControl = [0.001, 1.0, 350.0, 0.01]; // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
this.focalLength = 24.0;
|
||||
this.height = 1000.0;
|
||||
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.moonIntensity = 1.0; // Scale Factor (1.0 = Physical Peak)
|
||||
// x=cos(rad), y=sin(rad) [Precision Fix], z=intensity, w=enabled
|
||||
this.moonHalo = [Math.cos(0.5 * Math.PI / 180.0), Math.sin(0.5 * Math.PI / 180.0), 1.0, 0.0]; // Disabled by default
|
||||
|
||||
this.initEntity();
|
||||
}
|
||||
@@ -222,11 +224,48 @@ class SimulatedSkybox {
|
||||
}
|
||||
|
||||
setStarControl(density, enabled) {
|
||||
this.starControl[0] = Math.max(0.0, Math.min(1.0, density));
|
||||
// Compensate for grid frequency reduction (350 -> 100)
|
||||
// Fewer cells = fewer stars, so we increase density threshold.
|
||||
// Factor ~ (350/100)^2 = 12.25
|
||||
const compensatedDensity = density * 12.0;
|
||||
this.starControl[0] = Math.max(0.0, Math.min(1.0, compensatedDensity));
|
||||
this.starControl[1] = enabled ? 1.0 : 0.0;
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setFocalLength(mm) {
|
||||
this.focalLength = Math.max(1.0, mm);
|
||||
this.updateStarFrequency();
|
||||
}
|
||||
|
||||
setResolution(height) {
|
||||
this.height = Math.max(1.0, height);
|
||||
this.updateStarFrequency();
|
||||
}
|
||||
|
||||
updateStarFrequency() {
|
||||
// World-Anchored Stars
|
||||
// z = Fixed Frequency (World Space Grid)
|
||||
// w = Pixel Scale (Screen Space Radius)
|
||||
|
||||
// Fixed Frequency: Defines the "Universe" coordinate system.
|
||||
// Reduced to 100.0 to allow larger stars without clipping (square artifacts).
|
||||
this.starControl[2] = 100.0;
|
||||
|
||||
// Pixel Scale in Radians
|
||||
// We use linear scaling (24/f) instead of atan(fov) to ensure star size remains
|
||||
// constant in pixels across all focal lengths (Perspective Projection).
|
||||
const fovFactor = 24.0 / this.focalLength;
|
||||
const pixelScale = (1.0 / this.height) * fovFactor;
|
||||
|
||||
// Pass to shader (w component)
|
||||
// Target radius: 1.3 pixels (Diameter 2.6 pixels)
|
||||
// Visible but sharp.
|
||||
this.starControl[3] = pixelScale * 1.3;
|
||||
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
setMoonPosition(direction) {
|
||||
// normalize
|
||||
const len = Math.hypot(direction[0], direction[1], direction[2]);
|
||||
@@ -244,6 +283,7 @@ class SimulatedSkybox {
|
||||
setMoonRadius(degrees) {
|
||||
const rad = degrees * (Math.PI / 180.0);
|
||||
this.moonHalo[0] = Math.cos(rad);
|
||||
this.moonHalo[1] = Math.sin(rad);
|
||||
this.updateCoefficients();
|
||||
}
|
||||
|
||||
@@ -329,29 +369,119 @@ class SimulatedSkybox {
|
||||
|
||||
this.materialInstance.setFloatParameter('contrast', this.contrast);
|
||||
|
||||
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity);
|
||||
const nightColorScaled = this.nightColor.map(v => v * this.sunIntensity); // Lux scaling
|
||||
this.materialInstance.setFloat3Parameter('nightColor', new Float32Array(nightColorScaled));
|
||||
|
||||
this.materialInstance.setFloat4Parameter('shimmerControl', new Float32Array(shimmerUniform));
|
||||
this.materialInstance.setFloat4Parameter('cloudControl', new Float32Array(cloudUniform));
|
||||
this.materialInstance.setFloat4Parameter('cloudControl2', new Float32Array(this.cloudControl2));
|
||||
this.materialInstance.setFloat4Parameter('waterControl', new Float32Array(this.waterControl));
|
||||
this.materialInstance.setFloat2Parameter('starControl', new Float32Array(this.starControl));
|
||||
this.materialInstance.setFloat4Parameter('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
|
||||
// Calculate Moon Phase Factor (Lambertian Sphere)
|
||||
// We model the moon as a Lambertian sphere to calculate its integrated brightness (illuminance)
|
||||
// based on the phase angle (angle between Sun-Moon and Observer-Moon vectors).
|
||||
//
|
||||
// Phase Angle (alpha):
|
||||
// For a distant observer (Earth), the phase angle can be approximated as the angle between
|
||||
// the vector to the Sun and the vector to the Earth (from the Moon).
|
||||
// cos(alpha) = -dot(L_moon, L_sun)
|
||||
//
|
||||
// Lambertian Phase Law:
|
||||
// The integrated flux of a lit sphere varies as:
|
||||
// Phi(alpha) = (1/PI) * (sin(alpha) + (PI - alpha) * cos(alpha))
|
||||
// This gives 1.0 at Full Moon (alpha=0) and 0.0 at New Moon (alpha=PI).
|
||||
|
||||
const dotSM = this.sunDirection[0] * this.moonDirection[0] +
|
||||
this.sunDirection[1] * this.moonDirection[1] +
|
||||
this.sunDirection[2] * this.moonDirection[2];
|
||||
|
||||
// Final Intensity = Peak * Scale (No Phase Factor - Phase is handled in Shader via N.L)
|
||||
const MOON_PEAK_LUX = 5000.0;
|
||||
const finalMoonIntensity = MOON_PEAK_LUX * this.moonIntensity;
|
||||
|
||||
this.materialInstance.setFloatParameter('sunIntensity2', finalMoonIntensity);
|
||||
|
||||
// Moon Halo Upload (Disk Visualization)
|
||||
// Multiplier = 1.0 / SolidAngle
|
||||
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));
|
||||
|
||||
// Solar Eclipse (CPU Calculation)
|
||||
const sunRadius = Math.acos(this.sunHalo[0]);
|
||||
const moonRadius = Math.acos(this.moonHalo[0]);
|
||||
// Dot product of Sun and Moon directions
|
||||
const dot = this.sunDirection[0] * this.moonDirection[0] +
|
||||
this.sunDirection[1] * this.moonDirection[1] +
|
||||
this.sunDirection[2] * this.moonDirection[2];
|
||||
const separation = Math.acos(Math.max(-1.0, Math.min(1.0, dot)));
|
||||
|
||||
let eclipseFactor = 1.0;
|
||||
// Only calculate if moon is enabled
|
||||
if (this.moonHalo[3] > 0.5) {
|
||||
const overlap = this.areaIntersection(sunRadius, moonRadius, separation);
|
||||
const sunArea = Math.PI * sunRadius * sunRadius;
|
||||
// Ensure we don't divide by zero and clamp result
|
||||
const ratio = overlap / Math.max(1e-9, sunArea);
|
||||
eclipseFactor = 1.0 - Math.max(0.0, Math.min(1.0, ratio));
|
||||
|
||||
}
|
||||
|
||||
// Safety check for NaN
|
||||
if (isNaN(eclipseFactor)) {
|
||||
console.warn("SimulatedSkybox: eclipseFactor is NaN, resetting to 1.0");
|
||||
eclipseFactor = 1.0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
this.materialInstance.setFloatParameter('eclipseFactor', eclipseFactor);
|
||||
}
|
||||
|
||||
areaIntersection(r1, r2, d) {
|
||||
// Circle intersection area
|
||||
// r1, r2: radii
|
||||
// d: distance between centers
|
||||
|
||||
// Case 1: Too far apart
|
||||
if (d >= r1 + r2) {
|
||||
return 0.0;
|
||||
}
|
||||
// Case 2: One inside another
|
||||
if (d <= Math.abs(r1 - r2)) {
|
||||
return Math.PI * Math.min(r1, r2) * Math.min(r1, r2);
|
||||
}
|
||||
|
||||
const r1sq = r1 * r1;
|
||||
const r2sq = r2 * r2;
|
||||
|
||||
// Law of Cosines for sector angles
|
||||
// c1 = (d^2 + r1^2 - r2^2) / (2 * d * r1)
|
||||
// c2 = (d^2 + r2^2 - r1^2) / (2 * d * r2)
|
||||
// We clamp to [-1, 1] to avoid NaN from floating point errors
|
||||
const c1 = Math.max(-1.0, Math.min(1.0, (d * d + r1sq - r2sq) / (2.0 * d * r1)));
|
||||
const c2 = Math.max(-1.0, Math.min(1.0, (d * d + r2sq - r1sq) / (2.0 * d * r2)));
|
||||
|
||||
const part1 = r1sq * Math.acos(c1);
|
||||
const part2 = r2sq * Math.acos(c2);
|
||||
|
||||
// Heron's formula for the triangle area * 2 (or just 0.5 * sin(angle) *r*r but we have sides)
|
||||
// part3 is Area of kite? No, part3 is sum of two triangles?
|
||||
// Formula: Area = r1^2 * acos(c1) + r2^2 * acos(c2) - 0.5 * sqrt...
|
||||
// The sqrt term represents the area of the two triangles formed by the chord and centers.
|
||||
|
||||
// Robust sqrt
|
||||
const val = (-d + r1 + r2) * (d + r1 - r2) * (d - r1 + r2) * (d + r1 + r2);
|
||||
const part3 = 0.5 * Math.sqrt(Math.max(0.0, val));
|
||||
|
||||
return part1 + part2 - part3;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -24,6 +24,7 @@
|
||||
|
||||
<body>
|
||||
<canvas></canvas>
|
||||
|
||||
<!-- Filament -->
|
||||
<script src="filament.js"></script>
|
||||
<script src="gl-matrix-min.js"></script>
|
||||
@@ -32,8 +33,8 @@
|
||||
<script src="lil-gui.js"></script>
|
||||
|
||||
<!-- App -->
|
||||
<script src="SimulatedSkybox.js?v=47"></script>
|
||||
<script src="main.js?v=47"></script>
|
||||
<script src="SimulatedSkybox.js?v=51"></script>
|
||||
<script src="main.js?v=51"></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?v=46').then(() => {
|
||||
this.skybox.loadMaterial('assets/simulated_skybox.filamat?v=56').then(() => {
|
||||
this.initGUI();
|
||||
});
|
||||
|
||||
@@ -112,6 +112,7 @@ class App {
|
||||
const height = this.canvas.height;
|
||||
const aspect = width / height;
|
||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||
if (this.skybox) this.skybox.setFocalLength(this.params.focalLength);
|
||||
}
|
||||
|
||||
initGUI() {
|
||||
@@ -134,31 +135,39 @@ class App {
|
||||
sky.setSunPosition([x, y, z]);
|
||||
};
|
||||
|
||||
// Sun UI Proxy
|
||||
this.sunUI = {
|
||||
azimuth: (this.params.sunPhi * 180.0 / Math.PI) % 360.0,
|
||||
height: Math.cos(this.params.sunTheta)
|
||||
};
|
||||
if (this.sunUI.azimuth < 0) this.sunUI.azimuth += 360.0;
|
||||
|
||||
const sunFolder = gui.addFolder('Sun');
|
||||
// Helper for "Sun Height" cosine slider like C++
|
||||
this.sunHeightParam = { height: Math.cos(this.params.sunTheta) };
|
||||
sunFolder.add(this.sunHeightParam, 'height', -0.2, 1.0).name('Height (Cos)').onChange(v => {
|
||||
|
||||
sunFolder.add(this.sunUI, 'azimuth', 0.0, 360.0, 0.1).name('Azimuth').onChange(v => {
|
||||
this.params.sunPhi = v * (Math.PI / 180.0);
|
||||
updateSun();
|
||||
});
|
||||
|
||||
sunFolder.add(this.sunUI, 'height', -0.2, 1.0).name('Height (Cos)').onChange(v => {
|
||||
this.params.sunTheta = Math.acos(v);
|
||||
updateSun();
|
||||
});
|
||||
sunFolder.add(this.params, 'sunPhi', 0.0, Math.PI * 2).name('Azimuth').onChange(updateSun);
|
||||
// Updated: Controls params.sunIntensity and triggers updateSunIntensity
|
||||
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).onChange(v => this.updateSunIntensity());
|
||||
|
||||
sunFolder.add(this.params, 'sunIntensity', 0.0, 500000.0).name('Intensity').onChange(v => this.updateSunIntensity());
|
||||
|
||||
const moonFolder = gui.addFolder('Moon');
|
||||
this.mParams = {
|
||||
enabled: false,
|
||||
enabled: true,
|
||||
azimuth: 180.0,
|
||||
elevation: 45.0,
|
||||
height: Math.cos(45.0 * Math.PI / 180.0), // Default 45 degrees elevation -> cos(45) ~ 0.707
|
||||
radius: 0.5,
|
||||
|
||||
intensity: 10.0
|
||||
intensity: 1.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 theta = Math.acos(this.mParams.height);
|
||||
const phi = az;
|
||||
|
||||
const x = Math.sin(theta) * Math.cos(phi);
|
||||
@@ -175,10 +184,10 @@ class App {
|
||||
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.add(this.mParams, 'azimuth', 0.0, 360.0, 0.1).name('Azimuth').onChange(updateMoon);
|
||||
moonFolder.add(this.mParams, 'height', -0.2, 1.0).name('Height (Cos)').onChange(updateMoon);
|
||||
moonFolder.add(this.mParams, 'intensity', 0.0, 10.0).name('Intensity').onChange(v => this.updateSunIntensity());
|
||||
moonFolder.add(this.mParams, 'radius', 0.1, 5.0).name('Radius').onChange(v => sky.setMoonRadius(v));
|
||||
moonFolder.close();
|
||||
|
||||
const sunDisk = sunFolder.addFolder('Disk');
|
||||
@@ -204,11 +213,11 @@ class App {
|
||||
atmFolder.add(sky, 'mieG', 0.0, 0.999).onChange(v => sky.setMieG(v));
|
||||
|
||||
const artFolder = gui.addFolder('Artistic');
|
||||
// Set Horizon Glow default to 1.0
|
||||
sky.setHorizonGlow(1.0);
|
||||
sky.msFactors[2] = 1.0;
|
||||
// Set Contrast default to 0.85
|
||||
sky.setContrast(0.85);
|
||||
// Set Horizon Glow default to 0.0
|
||||
sky.setHorizonGlow(0.0);
|
||||
sky.msFactors[2] = 0.0;
|
||||
// Set Contrast default to 1.0
|
||||
sky.setContrast(1.0);
|
||||
|
||||
artFolder.add(sky.msFactors, 0, 0.0, 2.0).name('MS Rayleigh').onChange(v => sky.setMultiScattering(v, sky.msFactors[1]));
|
||||
artFolder.add(sky.msFactors, 1, 0.0, 2.0).name('MS Mie').onChange(v => sky.setMultiScattering(sky.msFactors[0], v));
|
||||
@@ -269,17 +278,17 @@ class App {
|
||||
const starFolder = gui.addFolder('Stars');
|
||||
this.sParams = {
|
||||
enabled: true,
|
||||
density: 1.0
|
||||
density: 0.001
|
||||
};
|
||||
// Initialize defaults (Density 1.0, Enabled True)
|
||||
sky.setStarControl(1.0, true);
|
||||
// Initialize defaults (Density 0.001, Enabled True)
|
||||
sky.setStarControl(0.001, true);
|
||||
|
||||
const updateStars = () => {
|
||||
sky.setStarControl(this.sParams.density, this.sParams.enabled);
|
||||
};
|
||||
|
||||
starFolder.add(this.sParams, 'enabled').name('Enabled').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'density', 0.0, 1.0).name('Density').onChange(updateStars);
|
||||
starFolder.add(this.sParams, 'density', 0.0, 0.01, 0.0001).name('Density').onChange(updateStars);
|
||||
starFolder.close();
|
||||
|
||||
const camFolder = gui.addFolder('Camera');
|
||||
@@ -378,7 +387,8 @@ 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 },
|
||||
m: { e: m.enabled, az: m.azimuth, h: m.height, r: m.radius, i: m.intensity },
|
||||
cm: { t: this.camState.theta, p: this.camState.phi },
|
||||
k: {
|
||||
t: sk.turbidity,
|
||||
r: sk.rayleigh,
|
||||
@@ -392,6 +402,7 @@ class App {
|
||||
hl: [...sk.sunHalo]
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
applyURLState(state) {
|
||||
@@ -402,6 +413,7 @@ class App {
|
||||
const b = state.b;
|
||||
const m = state.m;
|
||||
const k = state.k;
|
||||
const cm = state.cm;
|
||||
|
||||
if (p) {
|
||||
if (p.a !== undefined) this.params.aperture = p.a;
|
||||
@@ -469,9 +481,16 @@ class App {
|
||||
sky.updateCoefficients();
|
||||
}
|
||||
|
||||
// Update derived Sun Height param for UI
|
||||
if (this.sunHeightParam) {
|
||||
this.sunHeightParam.height = Math.cos(this.params.sunTheta);
|
||||
if (cm) {
|
||||
if (cm.t !== undefined) this.camState.theta = cm.t;
|
||||
if (cm.p !== undefined) this.camState.phi = cm.p;
|
||||
}
|
||||
|
||||
// Update derived Sun UI
|
||||
if (this.sunUI) {
|
||||
this.sunUI.height = Math.cos(this.params.sunTheta);
|
||||
this.sunUI.azimuth = (this.params.sunPhi * 180.0 / Math.PI) % 360.0;
|
||||
if (this.sunUI.azimuth < 0) this.sunUI.azimuth += 360.0;
|
||||
}
|
||||
|
||||
// Apply Local Params via Setters
|
||||
@@ -485,14 +504,19 @@ class App {
|
||||
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;
|
||||
// Compat: if 'el' exists (old link) convert to 'h'
|
||||
if (m.h !== undefined) {
|
||||
this.mParams.height = m.h;
|
||||
} else if (m.el !== undefined) {
|
||||
// Convert elevation degrees to height cos
|
||||
this.mParams.height = Math.cos(m.el * Math.PI / 180.0);
|
||||
}
|
||||
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 theta = Math.acos(this.mParams.height);
|
||||
const phi = az;
|
||||
const x = Math.sin(theta) * Math.cos(phi);
|
||||
const y = Math.cos(theta);
|
||||
@@ -504,6 +528,11 @@ class App {
|
||||
sky.setMoonIntensity(this.mParams.intensity);
|
||||
}
|
||||
|
||||
if (cm) {
|
||||
if (cm.t !== undefined) this.camState.theta = cm.t;
|
||||
if (cm.p !== undefined) this.camState.phi = cm.p;
|
||||
}
|
||||
|
||||
this.view.setBloomOptions({
|
||||
enabled: this.bParams.enabled,
|
||||
lensFlare: this.bParams.lensFlare
|
||||
@@ -516,7 +545,10 @@ class App {
|
||||
const y = Math.cos(theta);
|
||||
const z = Math.sin(theta) * Math.sin(phi);
|
||||
sky.setSunPosition([x, y, z]);
|
||||
this.updateSunIntensity();
|
||||
|
||||
// Update Camera Projection (Focal Length) and Exposure (Aperture/Shutter/ISO)
|
||||
this.updateCameraProjection();
|
||||
this.updateCameraExposure();
|
||||
}
|
||||
|
||||
initControls() {
|
||||
@@ -546,6 +578,36 @@ class App {
|
||||
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
||||
});
|
||||
|
||||
// Touch support
|
||||
this.canvas.addEventListener('touchstart', e => {
|
||||
if (e.touches.length > 0) {
|
||||
e.preventDefault(); // Prevent scroll/long-press
|
||||
this.camState.dragging = true;
|
||||
this.camState.lastX = e.touches[0].clientX;
|
||||
this.camState.lastY = e.touches[0].clientY;
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
window.addEventListener('touchend', () => {
|
||||
this.camState.dragging = false;
|
||||
});
|
||||
|
||||
window.addEventListener('touchmove', e => {
|
||||
if (!this.camState.dragging || e.touches.length === 0) return;
|
||||
e.preventDefault(); // Prevent scrolling
|
||||
const x = e.touches[0].clientX;
|
||||
const y = e.touches[0].clientY;
|
||||
const dx = x - this.camState.lastX;
|
||||
const dy = y - this.camState.lastY;
|
||||
this.camState.lastX = x;
|
||||
this.camState.lastY = y;
|
||||
|
||||
const sensitivity = 0.005;
|
||||
this.camState.theta -= dx * sensitivity;
|
||||
this.camState.phi += dy * sensitivity;
|
||||
this.camState.phi = Math.max(-Math.PI / 2 + 0.01, Math.min(Math.PI / 2 - 0.01, this.camState.phi));
|
||||
}, { passive: false });
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -582,5 +644,6 @@ class App {
|
||||
const aspect = width / height;
|
||||
// near=0.1, far=5000.0
|
||||
this.camera.setLensProjection(this.params.focalLength, aspect, 0.1, 5000.0);
|
||||
if (this.skybox) this.skybox.setResolution(height);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,11 @@ material {
|
||||
name : ozone,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : eclipseFactor,
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float4,
|
||||
name : multiScatParams, // xyz=MultiScatteringColor, w=HorizonGlow
|
||||
@@ -85,8 +90,8 @@ material {
|
||||
precision : high
|
||||
},
|
||||
{
|
||||
type : float2,
|
||||
name : starControl, // x=Density, y=Enabled
|
||||
type : float4,
|
||||
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
||||
precision : high
|
||||
}
|
||||
],
|
||||
@@ -126,6 +131,38 @@ fragment {
|
||||
|
||||
#define PI 3.14159265359
|
||||
|
||||
// --- CONFIGURATION ---
|
||||
|
||||
// Stars
|
||||
#define STAR_GLOBAL_INTENSITY 150.0 // Master brightness multiplier [0.0 - 500.0]
|
||||
#define STAR_BRIGHTNESS_BASE 0.5 // Minimum random brightness [0.0 - 1.0]
|
||||
#define STAR_BRIGHTNESS_VAR 4.0 // Random brightness variance range [0.0 - 10.0]
|
||||
#define STAR_FADE_SUN_ELV_HIGH 0.10 // Sun elevation (sin) where stars are fully hidden [0.0 - 0.5]
|
||||
#define STAR_FADE_SUN_ELV_LOW -0.20 // Sun elevation (sin) where stars are fully visible [-0.5 - 0.0]
|
||||
#define STAR_CLOUD_OCCLUSION 0.1 // Visibility when covered by clouds [0.0 - 1.0]
|
||||
|
||||
// Clouds
|
||||
#define CLOUD_UV_SCALE 0.05 // Texture scale in km^-1 [0.01 - 0.1]
|
||||
#define CLOUD_EXTINCTION 20.0 // Beer's law coefficient (Opacity) [1.0 - 50.0]
|
||||
#define CLOUD_SILVER_INTENSITY 40.0 // Silver lining brightness boost [0.0 - 100.0]
|
||||
#define CLOUD_SILVER_G 0.9 // Silver lining anisotropy (0.0=iso, 1.0=forward)
|
||||
#define CLOUD_AMBIENT_MIN 0.1 // Minimum ambient light in thick clouds [0.0 - 0.5]
|
||||
#define VOLUMETRIC_SHADOW_STR 0.7 // Center darkening strength for "volume" [0.0 - 1.0]
|
||||
|
||||
// Water
|
||||
#define WATER_PLANE_HEIGHT -10.0 // Virtual plane height relative to camera [meters]
|
||||
#define WATER_UV_SCALE 0.05 // Texture scale for waves [0.01 - 0.2]
|
||||
#define WATER_FRESNEL_F0 0.02 // Base reflectivity (Water is ~0.02) [0.0 - 1.0]
|
||||
#define WATER_DEEP_COLOR vec3(0.0, 0.005, 0.02) // Deep water absorption color [RGB]
|
||||
#define REFLECTION_HORIZON_FADE 0.1 // Normalized height to fade star reflections [0.0 - 0.5]
|
||||
|
||||
// Moon
|
||||
#define MOON_EARTHSHINE 0.002 // Dark side brightness scalar [0.0 - 0.01]
|
||||
#define MOON_LIMB_SOFTNESS 0.05 // Edge softness relative to radius [0.0 - 0.1]
|
||||
|
||||
// Atmosphere
|
||||
#define HORIZON_GLOW_POWER 5.0 // Horizon glow falloff curve [1.0 - 10.0]
|
||||
|
||||
void dummy() {} // squash editor syntax highlighting bugs
|
||||
|
||||
// Rayleigh Phase Function: Scattering distribution for small particles (air molecules)
|
||||
@@ -277,8 +314,8 @@ fragment {
|
||||
// @param depthR Rayleigh Optical Depth (Precalculated).
|
||||
// @param depthM Mie Optical Depth (Precalculated).
|
||||
// @param ozone Ozone Absorption (Precalculated).
|
||||
// @param msFactors Multi-Scattering factors (Rayleigh, Mie, Glow).
|
||||
// @param mieG Mie Phase Anisotropy.
|
||||
// @param multiScatParams Multi-Scattering factors (Rayleigh, Mie, Glow).
|
||||
// @param mieParams Mie Phase Params (x=1+g^2, y=-2g).
|
||||
// @param outTransmittance Output: Atmospheric Transmittance (0..1) along V.
|
||||
// @return Output: In-Scattered Radiance (The sky color).
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -323,7 +360,7 @@ fragment {
|
||||
// 5. Horizon "Glow" Mix (Artistic Hack)
|
||||
// multiScatParams.w contains the Horizon Glow Strength
|
||||
// Uses Sun Elevation (L.y) to only activate during golden hour/twilight.
|
||||
mediump float horizonMix = saturate(pow(1.0 - L.y, 5.0)) * multiScatParams.w;
|
||||
mediump float horizonMix = saturate(pow(1.0 - L.y, HORIZON_GLOW_POWER)) * multiScatParams.w;
|
||||
highp vec3 horizonGlow = sqrt(inScattering * outTransmittance);
|
||||
sunLight *= mix(vec3(1.0), horizonGlow, horizonMix);
|
||||
|
||||
@@ -430,7 +467,6 @@ fragment {
|
||||
// - Lighting includes Silver Lining (HG Phase) and Atmospheric Extinction.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param background Current Sky Color (to be blended with).
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param control x=Coverage, y=Density, z=QuadraticConst(C), w=WindSpeed.
|
||||
@@ -438,23 +474,8 @@ fragment {
|
||||
// @param geometry w=PlanetRadius (Re).
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param transmittance Atmospheric Transmittance (Cloud Color Tint).
|
||||
// @return Sky color composed with clouds.
|
||||
// ------------------------------------------------------------------------
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Cirrus Clouds
|
||||
// ------------------------------------------------------------------------
|
||||
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V .
|
||||
// @param L .
|
||||
// @param control .
|
||||
// @param control2 .
|
||||
// @param geometry .
|
||||
// @param sunIntensity .
|
||||
// @param transmittance.
|
||||
// @param outDensity Output: Cloud Density (0..1).
|
||||
// @return Cloud Lit Color (pre-multiplied by density? No, just lit color).
|
||||
// @param outDensity Output: Cloud pixel density (0..1) for compositing.
|
||||
// @return Cloud Radiance (Lit color * attenuation * shading).
|
||||
// ------------------------------------------------------------------------
|
||||
highp vec3 getCloudLayer(highp vec3 V, highp vec3 L,
|
||||
highp vec4 control, highp vec4 control2, highp vec4 geometry,
|
||||
@@ -478,8 +499,8 @@ fragment {
|
||||
highp float time = getUserTime().x;
|
||||
|
||||
// UV Mapping (Planar projected onto sphere cap is sufficient for skybox)
|
||||
// Scale factor 0.05 km^-1
|
||||
highp vec2 uv = (p.xz * 0.05) + vec2(time * speed * 2.0, 0.0);
|
||||
// Scale factor CLOUD_UV_SCALE km^-1
|
||||
highp vec2 uv = (p.xz * CLOUD_UV_SCALE) + vec2(time * speed * 2.0, 0.0);
|
||||
|
||||
// 3D Noise for Morphing
|
||||
highp float noiseVal = fbm(vec3(uv, time * morphSpeed));
|
||||
@@ -502,15 +523,18 @@ fragment {
|
||||
|
||||
// Attenuation (Beer's Law)
|
||||
// Thick clouds block light.
|
||||
// 20.0 is an artistic extinction coefficient.
|
||||
highp float extinction = exp(-cloudDensity * 20.0);
|
||||
// CLOUD_EXTINCTION is an artistic extinction coefficient.
|
||||
highp float extinction = exp(-cloudDensity * CLOUD_EXTINCTION);
|
||||
|
||||
highp float silver = hgPhase(cosTheta, vec2(1.81, -1.8)) * 40.0 * extinction;
|
||||
// 1 + g^2 = 1.0 + CLOUD_SILVER_G*CLOUD_SILVER_G
|
||||
// -2*g = -2.0 * CLOUD_SILVER_G
|
||||
// We use hardcoded derived values for g=0.9: 1.81, -1.8
|
||||
highp float silver = hgPhase(cosTheta, vec2(1.81, -1.8)) * CLOUD_SILVER_INTENSITY * extinction;
|
||||
|
||||
// Ambient/Diffuse term.
|
||||
// We allow some ambient light to pass through even thick clouds (0.05 min)
|
||||
// We allow some ambient light to pass through even thick clouds (CLOUD_AMBIENT_MIN min)
|
||||
// so they don't look like black holes.
|
||||
highp float ambient = 0.1 + 0.4 * extinction;
|
||||
highp float ambient = CLOUD_AMBIENT_MIN + 0.4 * extinction;
|
||||
|
||||
// Diffuse term (Sun Color) + Silver Lining
|
||||
highp vec3 cloudLight = sunIntensity * transmittance * (ambient + silver);
|
||||
@@ -540,8 +564,8 @@ fragment {
|
||||
shading = mix(0.3, 1.0, shading * 0.5 + 0.5);
|
||||
|
||||
// Darken thick parts (Beer's Law approximation)
|
||||
// Aggressively darken center of clouds
|
||||
shading *= (1.0 - cloudDensity * 0.7);
|
||||
// Aggressively darken center of clouds
|
||||
shading *= (1.0 - cloudDensity * VOLUMETRIC_SHADOW_STR);
|
||||
}
|
||||
|
||||
return cloudLight * shading;
|
||||
@@ -577,75 +601,83 @@ fragment {
|
||||
return pow(max(vec3(0.0), color), vec3(exponent));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Procedural Water Surface
|
||||
// ------------------------------------------------------------------------
|
||||
// Simulates an infinite ocean plane at y=0 using screen-space derivatives for normals.
|
||||
//
|
||||
// FEATURES:
|
||||
// - Projected grid for infinite surface.
|
||||
// - Screen-space wave normal reconstruction (no geometry required).
|
||||
// - Fresnel reflection of Atmosphere, Sun, and Clouds.
|
||||
// - Specular highlights (Blinn-Phong).
|
||||
//
|
||||
// PARAMETERS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param depthR Rayleigh Optical Depth.
|
||||
// @param depthM Mie Optical Depth.
|
||||
// @param ozone Ozone Absorption.
|
||||
// @param multiScatParams Multi-Scattering Params.
|
||||
// @param miePhaseParams Mie Phase Params.
|
||||
// @param sunHalo Sun Halo Params.
|
||||
// @param cloudControl Cloud Control Params.
|
||||
// @param cloudControl2 Cloud Evolution Params.
|
||||
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
|
||||
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
|
||||
// @return Water surface color.
|
||||
// ------------------------------------------------------------------------
|
||||
// 3D Noise for Stars
|
||||
highp float hash31(highp vec3 p) {
|
||||
p = fract(p * 0.1031);
|
||||
p += dot(p, p.yzx + 33.33);
|
||||
return fract((p.x + p.y) * p.z);
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// STARS (World-Anchored)
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
// 4-component hash for jitter (xyz) and intensity (w)
|
||||
highp vec4 hash43(highp vec3 p) {
|
||||
highp vec4 p4 = fract(vec4(p.xyzx) * vec4(0.1031, 0.1030, 0.0973, 0.1099));
|
||||
p4 += dot(p4, p4.wzxy + 33.33);
|
||||
return fract((p4.xxyz + p4.yzzw) * p4.zywx);
|
||||
}
|
||||
|
||||
highp float getStars(highp vec3 V, highp float density) {
|
||||
// Simple procedural stars
|
||||
// We use view vector direction to tile the sky
|
||||
// Higher frequency = smaller stars
|
||||
highp float frequency = 300.0;
|
||||
highp vec3 p = floor(V * frequency);
|
||||
highp float getStars(highp vec3 V) {
|
||||
bool enabled = materialParams.starControl.y > 0.5;
|
||||
if (!enabled) return 0.0;
|
||||
|
||||
highp float h = hash31(p);
|
||||
highp float density = materialParams.starControl.x;
|
||||
if (density <= 0.0) return 0.0;
|
||||
|
||||
// Threshold for stars (very sparse)
|
||||
// param density: 0.0 (none) to 1.0 (max)
|
||||
// Default threshold was 0.995 (0.5% stars)
|
||||
// We map density 0.0 -> 1.0 threshold (no stars)
|
||||
// density 1.0 -> 0.990 threshold (1.0% stars)
|
||||
highp float threshold = 1.0 - (0.001 + density * 0.009);
|
||||
|
||||
highp float star = 0.0;
|
||||
if (h > threshold) {
|
||||
// Random brightness
|
||||
highp float brightness = (h - threshold) / (1.0 - threshold);
|
||||
star = brightness * 15.0; // Reduced from 50.0 to 15.0
|
||||
// 1. Fixed World Grid (Anchor)
|
||||
// We use a FIXED frequency for the grid, so stars don't move when focal length changes.
|
||||
highp float gridFreq = materialParams.starControl.z; // e.g. 350.0
|
||||
|
||||
highp vec3 p = V * gridFreq;
|
||||
highp vec3 id = floor(p);
|
||||
|
||||
// 2. Scan neighbors (1-tap with jitter constraint)
|
||||
// We constrain jitter to be well within cell to avoid edge artifacts with 1 tap.
|
||||
highp vec4 h = hash43(id);
|
||||
|
||||
// Density check (Probability)
|
||||
if (h.w > density) return 0.0;
|
||||
|
||||
// 3. World Position of Star Center
|
||||
// Jitter range [0.2, 0.8] to avoid clipping neighbors
|
||||
highp vec3 jitter = 0.2 + 0.6 * h.xyz;
|
||||
highp vec3 starCenter = (id + jitter) / gridFreq;
|
||||
starCenter = normalize(starCenter);
|
||||
|
||||
// 4. Angular Distance
|
||||
// distSq ~ 2 * (1 - cosC)
|
||||
highp float cosC = dot(V, starCenter);
|
||||
highp float distSq = 2.0 * (1.0 - cosC);
|
||||
|
||||
// 5. Screen-Adaptive Radius
|
||||
// materialParams.starControl.w contains "Pixel Scale in Radians"
|
||||
// Target radius: 1.5 pixels
|
||||
highp float pixelScaleRad = materialParams.starControl.w;
|
||||
highp float starRadiusRad = 1.0 * pixelScaleRad; // Tunable size
|
||||
highp float starRadiusSq = starRadiusRad * starRadiusRad;
|
||||
|
||||
// 6. Rendering (Soft Disk)
|
||||
highp float intensity = 0.0;
|
||||
if (distSq < starRadiusSq) {
|
||||
// Falloff
|
||||
highp float x = distSq / starRadiusSq;
|
||||
intensity = 1.0 - x;
|
||||
intensity *= intensity; // Cubic-ish
|
||||
|
||||
// Random Brightness
|
||||
// Range STAR_BRIGHTNESS_BASE - (STAR_BRIGHTNESS_BASE + STAR_BRIGHTNESS_VAR)
|
||||
highp float brightness = STAR_BRIGHTNESS_BASE + STAR_BRIGHTNESS_VAR * h.w;
|
||||
intensity *= brightness * STAR_GLOBAL_INTENSITY;
|
||||
}
|
||||
return star;
|
||||
|
||||
return intensity;
|
||||
}
|
||||
|
||||
// New helper to handle Star Compositing (Fade, Rotation, Occlusion)
|
||||
highp vec3 getStarLayer(highp vec3 V, highp vec3 L, highp float cloudDensity, highp vec3 transmittance, highp vec2 starControl) {
|
||||
highp vec3 getStarLayer(highp vec3 V, highp vec3 L, highp float cloudDensity, highp vec3 transmittance, highp vec4 starControl) {
|
||||
// starControl.x = Density, .y = Enabled
|
||||
if (starControl.y < 0.5) return vec3(0.0);
|
||||
|
||||
// 1. Fade by Sun Elevation
|
||||
// Start appearing sooner (when sun is still slightly up), but stay dim.
|
||||
// 0.10 (5.7 deg up) -> 0.0
|
||||
// -0.20 (11.5 deg down) -> 1.0
|
||||
highp float starFade = 1.0 - smoothstep(-0.20, 0.10, L.y);
|
||||
// STAR_FADE_SUN_ELV_HIGH (5.7 deg up) -> 0.0
|
||||
// STAR_FADE_SUN_ELV_LOW (11.5 deg down) -> 1.0
|
||||
highp float starFade = 1.0 - smoothstep(STAR_FADE_SUN_ELV_LOW, STAR_FADE_SUN_ELV_HIGH, L.y);
|
||||
starFade *= starFade;
|
||||
|
||||
if (starFade <= 0.0) return vec3(0.0);
|
||||
@@ -657,13 +689,96 @@ fragment {
|
||||
V.z
|
||||
);
|
||||
|
||||
highp float starVal = getStars(rotV, starControl.x);
|
||||
highp float starVal = getStars(rotV);
|
||||
if (starVal <= 0.0) return vec3(0.0);
|
||||
|
||||
// 3. Cloud Occlusion (Aggressive)
|
||||
highp float cloudOcclusion = 1.0 - smoothstep(0.0, 1.0, pow(cloudDensity, 0.1));
|
||||
|
||||
return vec3(starVal) * transmittance * starFade * cloudOcclusion * 0.1;
|
||||
// 3. Cloud Occlusion (Aggressive)
|
||||
highp float cloudOcclusion = 1.0 - smoothstep(0.0, 1.0, pow(cloudDensity, 0.1));
|
||||
|
||||
return vec3(starVal) * transmittance * starFade * cloudOcclusion * STAR_CLOUD_OCCLUSION;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 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=SinRadius, 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 moonSinRadius = moonParams.y; // Used for precision
|
||||
highp float moonDiskIntensity = moonParams.z;
|
||||
bool moonEnabled = moonParams.w > 0.5;
|
||||
|
||||
highp float cosTheta = dot(V, L_moon);
|
||||
highp float dist = 1.0 - cosTheta; // Angular distance (approx theta^2/2 for small angles)
|
||||
highp float diskRadius = max(1e-6, 1.0 - moonCosRadius); // Approx Radius^2 / 2
|
||||
|
||||
// Soft Edge
|
||||
// Make edge width proportional to radius to handle small moons relative to the sun.
|
||||
// For the sun, we used a fixed width (0.00002) which creates a glow.
|
||||
// For the moon, we want a sharper limb, so we use MOON_LIMB_SOFTNESS of radius.
|
||||
highp float edgeWidth = max(2.0e-7, diskRadius * MOON_LIMB_SOFTNESS);
|
||||
highp float moonProfile = 1.0 - smoothstep(diskRadius, diskRadius + edgeWidth, 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(radius), which we now pass explicitly in .y
|
||||
highp float sinRadius = max(1e-9, moonSinRadius);
|
||||
highp vec3 N_perp = T / sinRadius;
|
||||
|
||||
// Project onto sphere to get full normal
|
||||
// Z component is along L_moon (towards viewer approx)
|
||||
// |N| = 1. N.z = sqrt(1 - |N_perp|^2)
|
||||
highp float nPerpSq = dot(N_perp, N_perp);
|
||||
|
||||
// N_sphere = N_perp - L_moon * zComp
|
||||
// Vector points OUT from center.
|
||||
// Since L_moon points from Earth to Moon, and the visible face normal points roughly towards Earth,
|
||||
// the normal component along L_moon is negative.
|
||||
highp vec3 N_sphere = N_perp - L_moon * sqrt(max(0.0, 1.0 - nPerpSq));
|
||||
|
||||
highp float NdotL = max(0.0, dot(N_sphere, L_sun));
|
||||
|
||||
// Earthshine (Ambient)
|
||||
// Earth Phase (approx):
|
||||
// When Sun & Moon are same direction (dot=1), Earth is Full (from Moon).
|
||||
// When Sun & Moon are opposite (dot=-1), Earth is New (from Moon).
|
||||
// EarthPhase ~ 0.5 * (1.0 + dot(L_sun, L_moon)).
|
||||
highp float earthPhase = 0.5 * (1.0 + dot(L_sun, L_moon));
|
||||
|
||||
// Intensity relative to Sun ~ 0.0001 to 0.01 depending on albedo/size.
|
||||
// We use 0.002 as a base scale.
|
||||
// Visibility: The dark side faces Earth?
|
||||
// Yes, dot(N_sphere, -L_moon) > 0 covers the face seen from Earth.
|
||||
// But simpler: Is this pixel in shadow (NdotL == 0)?
|
||||
// Earthshine adds ambient to the whole sphere, but is overwhelmed by Sun.
|
||||
highp float earthshine = MOON_EARTHSHINE * earthPhase * clamp(dot(N_sphere, -L_moon) + 1.0, 0.0, 1.0); // Hemisphere ambient
|
||||
|
||||
return ((NdotL + earthshine) * moonIntensity * moonDiskIntensity * moonProfile) * transmittance;
|
||||
}
|
||||
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -680,7 +795,8 @@ fragment {
|
||||
// PARAMETERS:
|
||||
// @param V Normalized View Vector.
|
||||
// @param L Normalized Sun Vector.
|
||||
// @param sunIntensity Sun Illuminance.
|
||||
// @param sunIntensity Sun Illuminance (Lit/Dimmed).
|
||||
// @param sunDiskIntensity Sun Illuminance (Un-Dimmed) for specular.
|
||||
// @param depthR Rayleigh Optical Depth.
|
||||
// @param depthM Mie Optical Depth.
|
||||
// @param ozone Ozone Absorption.
|
||||
@@ -691,117 +807,31 @@ fragment {
|
||||
// @param cloudControl2 Cloud Evolution Params.
|
||||
// @param shimmerControl Shimmer Control (w component used as PlanetRadius for clouds).
|
||||
// @param waterControl Water Control (x=Strength, y=Speed, z=DerivativeTrick).
|
||||
// @param L2 Secondary Sun Direction.
|
||||
// @param sunIntensity2 Secondary Sun Intensity.
|
||||
// @param sunHalo2 Secondary Sun Halo Params.
|
||||
// @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 getWaterColor(highp vec3 V, highp vec3 L, highp float sunIntensity, highp float sunDiskIntensity,
|
||||
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) {
|
||||
highp vec4 multiScatParams, highp vec2 miePhaseParams,
|
||||
highp vec4 sunHalo, highp vec4 cloudControl, highp vec4 cloudControl2,
|
||||
highp vec4 shimmerControl, highp vec4 waterControl,
|
||||
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2) {
|
||||
|
||||
// Project to plane y=0
|
||||
highp float t = -10.0 / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||
highp vec2 uv = V.xz * t * 0.05;
|
||||
highp float t = WATER_PLANE_HEIGHT / min(V.y, -0.0002); // Reduced clamp to minimize "wall" artifact
|
||||
highp vec2 uv = V.xz * t * WATER_UV_SCALE;
|
||||
|
||||
highp float time = getUserTime().x;
|
||||
highp float speed = waterControl.y;
|
||||
uv += vec2(time * 0.5 * speed, time * 0.2 * speed);
|
||||
|
||||
// Wave Normal
|
||||
// Use screen-space derivatives to compute world-space normal perturbation
|
||||
// Wave Normal
|
||||
// Use screen-space derivatives to compute world-space normal perturbation
|
||||
int octaves = int(max(1.0, waterControl.w));
|
||||
highp float h = fbm(vec3(uv, time * 0.1 * speed), octaves);
|
||||
|
||||
// Reconstruct screen-space basis in world space
|
||||
// highp vec3 sRight = normalize(dFdx(V)); // Moved inside block
|
||||
// highp vec3 sUp = normalize(dFdy(V)); // Moved inside block
|
||||
|
||||
// Perturb normal based on height gradient
|
||||
// If h increases in screen-X direction, normal tilts against sRight.
|
||||
// Fade out perturbation near horizon (V.y -> 0) to reduce aliasing
|
||||
@@ -848,47 +878,77 @@ fragment {
|
||||
// Reflection
|
||||
highp vec3 R = reflect(V, N_water);
|
||||
|
||||
highp vec3 transRefl;
|
||||
highp vec3 reflection = getAtmosphere(R, L, sunIntensity,
|
||||
depthR, depthM,
|
||||
ozone, multiScatParams,
|
||||
miePhaseParams,
|
||||
transRefl);
|
||||
// Specular (Sun)
|
||||
// We use `sunDiskIntensity` (Un-Dimmed) for the disk reflection to preserve its appearance,
|
||||
// while `sunIntensity` (Lit/Dimmed) is used for general atmospheric scattering to match the sky.
|
||||
// The reflection's occlusion is handled separately by `reflMoonOcclusion` later.
|
||||
|
||||
// Re-deriving Sky Color for Reflection
|
||||
|
||||
highp vec3 outTransmittance;
|
||||
highp vec3 reflection = getAtmosphere(R, L, sunIntensity * 1.0 /* ensure match */,
|
||||
depthR, depthM, ozone,
|
||||
multiScatParams, miePhaseParams,
|
||||
outTransmittance);
|
||||
|
||||
// Add Moon Scattering to Reflection
|
||||
if (sunHalo2.w > 0.5) {
|
||||
reflection += getSecondarySunScattering(R, L2, sunIntensity2,
|
||||
depthR, depthM, ozone,
|
||||
miePhaseParams,
|
||||
outTransmittance);
|
||||
}
|
||||
|
||||
// Clouds in reflection
|
||||
highp float reflCloudDensity;
|
||||
highp vec3 reflCloudLayer = getCloudLayer(R, L, materialParams.cloudControl, materialParams.cloudControl2,
|
||||
materialParams.shimmerControl, materialParams.sunIntensity, transRefl,
|
||||
highp vec3 reflCloudLayer = getCloudLayer(R, L, cloudControl, cloudControl2,
|
||||
shimmerControl, sunIntensity, outTransmittance,
|
||||
reflCloudDensity);
|
||||
|
||||
// Add Stars to Reflection
|
||||
// Use helper with Reflection Vector and Reflection Cloud Density
|
||||
// Horizon Mask: Fade out star reflections that are deep in the water (high R.y)
|
||||
// Restricted to very close to horizon (0.0 to 0.1) as requested.
|
||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, 0.1, R.y);
|
||||
// Restricted to very close to horizon (0.0 to REFLECTION_HORIZON_FADE) as requested.
|
||||
highp float rHorizonMask = 1.0 - smoothstep(0.0, REFLECTION_HORIZON_FADE, R.y);
|
||||
|
||||
if (rHorizonMask > 0.0) {
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, transRefl, materialParams.starControl) * rHorizonMask;
|
||||
reflection += getStarLayer(R, L, reflCloudDensity, outTransmittance, materialParams.starControl) * rHorizonMask;
|
||||
}
|
||||
|
||||
// Add Sun Disk to reflection (Occluded)
|
||||
highp float reflSunAccess = 1.0 - smoothstep(0.0, 0.7, reflCloudDensity * 1.5);
|
||||
reflection += getSunDisk(R, L, sunHalo, sunIntensity, transRefl) * reflSunAccess;
|
||||
// Sun Disk Reflection
|
||||
// Use sunDiskIntensity (Un-Dimmed) + Moon Occlusion Mask
|
||||
highp vec3 sunDiskRefl = getSunDisk(R, L, sunHalo, sunDiskIntensity, outTransmittance);
|
||||
|
||||
// Apply Moon Occlusion to Reflection
|
||||
// We calculate occlusion for R.
|
||||
highp float reflDiskRadius = max(1e-6, 1.0 - sunHalo.x); // Approx
|
||||
highp float cosThetaRefl = dot(R, L2); // Moon vs Reflection Vector
|
||||
highp float distRefl = 1.0 - cosThetaRefl;
|
||||
highp float moonDiskRadius = max(1e-6, 1.0 - sunHalo2.x);
|
||||
|
||||
// Create a mask for the moon in the reflection
|
||||
highp float reflMoonOcclusion = 1.0 - smoothstep(moonDiskRadius, moonDiskRadius + 0.00002, distRefl);
|
||||
|
||||
// Mask the Sun Disk
|
||||
sunDiskRefl *= (1.0 - reflMoonOcclusion);
|
||||
|
||||
// Add Sun Disk to Sky Color
|
||||
reflection += sunDiskRefl;
|
||||
|
||||
// Add Moon Disk to reflection
|
||||
if (sunHalo2.w > 0.5) {
|
||||
reflection += getMoonDisk(R, L2, L, sunHalo2, sunIntensity2, transRefl) * reflSunAccess;
|
||||
reflection += getMoonDisk(R, L2, L, sunHalo2, sunIntensity2, outTransmittance);
|
||||
}
|
||||
|
||||
// Apply clouds to reflection
|
||||
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
||||
|
||||
// Fresnel
|
||||
highp float F0 = 0.02; // Water
|
||||
highp float F0 = WATER_FRESNEL_F0; // Water
|
||||
highp float cosTheta = clamp(dot(-V, N_water), 0.0, 1.0);
|
||||
highp float F = F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
|
||||
|
||||
highp vec3 deepColor = vec3(0.0, 0.005, 0.02); // Deep blue/black
|
||||
highp vec3 deepColor = WATER_DEEP_COLOR; // Deep blue/black
|
||||
|
||||
highp vec3 waterColor = mix(deepColor, reflection, F);
|
||||
|
||||
@@ -896,9 +956,6 @@ fragment {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
|
||||
@@ -914,13 +971,7 @@ fragment {
|
||||
|
||||
// 2. Atmospheric Scattering
|
||||
highp vec3 transmittance;
|
||||
highp vec3 inScatter1 = getAtmosphere(V, L, materialParams.sunIntensity,
|
||||
materialParams.depthR, materialParams.depthM,
|
||||
materialParams.ozone, materialParams.multiScatParams,
|
||||
materialParams.miePhaseParams,
|
||||
transmittance);
|
||||
|
||||
|
||||
|
||||
// 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.
|
||||
@@ -929,6 +980,20 @@ fragment {
|
||||
|
||||
highp vec3 L2 = normalize(materialParams.sunDirection2);
|
||||
|
||||
// Calculate Solar Eclipse (Light Reduction)
|
||||
// Dim the Sun Light used for Atmosphere/Clouds/Water
|
||||
highp float safeEclipseFactor = materialParams.eclipseFactor;
|
||||
// Safety: If moon is disabled, ensure no eclipse
|
||||
if (materialParams.sunHalo2.w < 0.5) {
|
||||
safeEclipseFactor = 1.0;
|
||||
}
|
||||
highp float sunIntensityLit = materialParams.sunIntensity * safeEclipseFactor;
|
||||
|
||||
// 1. Atmosphere Scattering
|
||||
highp vec3 color = getAtmosphere(V, L, sunIntensityLit, materialParams.depthR, materialParams.depthM, materialParams.ozone,
|
||||
materialParams.multiScatParams, materialParams.miePhaseParams, transmittance);
|
||||
|
||||
// 2. Secondary Sun/Moon Scattering
|
||||
if (materialParams.sunHalo2.w > 0.5) {
|
||||
inScatter2 = getSecondarySunScattering(V, L2,
|
||||
materialParams.sunIntensity2,
|
||||
@@ -937,60 +1002,73 @@ fragment {
|
||||
materialParams.ozone,
|
||||
materialParams.miePhaseParams,
|
||||
transmittance);
|
||||
color += inScatter2;
|
||||
}
|
||||
|
||||
highp vec3 finalColor = inScatter1 + inScatter2;
|
||||
|
||||
// 5. Procedural Clouds
|
||||
highp float cloudDensity;
|
||||
highp vec3 cloudLayer = getCloudLayer(V, L,
|
||||
materialParams.cloudControl,
|
||||
materialParams.cloudControl2,
|
||||
materialParams.shimmerControl, // reusing w=PlanetRadius
|
||||
materialParams.sunIntensity,
|
||||
transmittance,
|
||||
cloudDensity);
|
||||
|
||||
// Add Stars
|
||||
// Stars are at infinity.
|
||||
// Use helper function.
|
||||
finalColor += getStarLayer(V, L, cloudDensity, transmittance, materialParams.starControl);
|
||||
// 3. Clouds
|
||||
highp float cloudDensityVal;
|
||||
highp vec3 clouds = getCloudLayer(V, L, materialParams.cloudControl, materialParams.cloudControl2, materialParams.shimmerControl,
|
||||
sunIntensityLit, transmittance, cloudDensityVal);
|
||||
|
||||
// 3. Sun Disks - Occluded by clouds
|
||||
highp float sunAccess = 1.0 - smoothstep(0.0, 0.7, cloudDensity * 1.5);
|
||||
// 4. Sun Disk (Direct)
|
||||
// Use ORIGINAL sunIntensity (Un-dimmed) so the disk looks bright
|
||||
// We mask it by Moon Occlusion AND Cloud Occlusion
|
||||
highp vec3 sunDisk = getSunDisk(V, L, materialParams.sunHalo, materialParams.sunIntensity, transmittance);
|
||||
|
||||
finalColor += getSunDisk(V, L, materialParams.sunHalo,
|
||||
materialParams.sunIntensity, transmittance) * sunAccess;
|
||||
|
||||
// Moon Occlusion Mask
|
||||
highp float moonOcclusion = 0.0;
|
||||
if (materialParams.sunHalo2.w > 0.5) {
|
||||
// Pass L (Sun) as the light source for the Moon Phase
|
||||
finalColor += getMoonDisk(V, L2, L, materialParams.sunHalo2,
|
||||
materialParams.sunIntensity2, transmittance) * sunAccess;
|
||||
highp float distToMoon = 1.0 - dot(V, L2);
|
||||
highp float moonDiskRad = max(1e-6, 1.0 - materialParams.sunHalo2.x);
|
||||
moonOcclusion = 1.0 - smoothstep(moonDiskRad, moonDiskRad + 0.00002, distToMoon);
|
||||
}
|
||||
|
||||
// Cloud Occlusion Mask (Sun Access)
|
||||
highp float sunAccess = 1.0 - smoothstep(0.0, 0.7, cloudDensityVal * 1.5);
|
||||
|
||||
// Apply Masks
|
||||
// Note: moonOcclusion is 1.0 if occluded. So we want (1.0 - moonOcclusion).
|
||||
// Since we logic-ed it as: if dist < rad, occlusion = 1.0. Correct.
|
||||
sunDisk *= (1.0 - moonOcclusion) * sunAccess;
|
||||
|
||||
color += sunDisk;
|
||||
|
||||
// 5. Moon Disk
|
||||
if (materialParams.sunHalo2.w > 0.5) {
|
||||
color += getMoonDisk(V, L2, L, materialParams.sunHalo2,
|
||||
materialParams.sunIntensity2, transmittance) * sunAccess;
|
||||
}
|
||||
|
||||
// 4. Night Sky Offset
|
||||
finalColor += materialParams.nightColor;
|
||||
// 6. Stars
|
||||
// Add stars before clouds (clouds cover stars)
|
||||
color += getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl) * (1.0 - moonOcclusion);
|
||||
|
||||
// 5. Apply Clouds
|
||||
finalColor = mix(finalColor, cloudLayer, cloudDensity);
|
||||
// 7. Composite Clouds
|
||||
// Clouds are alpha blended on top of the sky (Atmos + Stars + SunDisk)
|
||||
// The cloud color `clouds` includes its own ambient and forward scattering terms.
|
||||
color = mix(color, clouds, cloudDensityVal);
|
||||
|
||||
// 6. Dynamic Tone Mapping
|
||||
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
|
||||
// 8. Night Sky Offset
|
||||
color += materialParams.nightColor;
|
||||
|
||||
// 9. Dynamic Tone Mapping
|
||||
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
||||
|
||||
// 10. Water Surface (Reflection)
|
||||
if (V.y < 0.0) {
|
||||
finalColor = getWaterColor(V, L, materialParams.sunIntensity,
|
||||
color = getWaterColor(V, L, sunIntensityLit, materialParams.sunIntensity,
|
||||
materialParams.depthR, materialParams.depthM,
|
||||
materialParams.ozone, materialParams.multiScatParams,
|
||||
materialParams.miePhaseParams,
|
||||
materialParams.sunHalo,
|
||||
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
||||
materialParams.cloudControl, materialParams.cloudControl2,
|
||||
materialParams.shimmerControl,
|
||||
materialParams.waterControl);
|
||||
finalColor = applyDynamicToneMapping(finalColor, L, materialParams.contrast);
|
||||
materialParams.waterControl,
|
||||
L2, materialParams.sunIntensity2, materialParams.sunHalo2);
|
||||
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
||||
}
|
||||
|
||||
material.baseColor = vec4(finalColor, 1.0);
|
||||
material.baseColor = vec4(color, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user