1285 lines
58 KiB
Plaintext
1285 lines
58 KiB
Plaintext
material {
|
|
name : SimulatedSkybox,
|
|
parameters : [
|
|
{
|
|
type : float3,
|
|
name : sunDirection,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float3,
|
|
name : sunDirection2,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float3,
|
|
name : depthR,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float3,
|
|
name : depthM,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float2,
|
|
name : miePhaseParams, // x=(1+g^2), y=(-2*g)
|
|
precision : high
|
|
},
|
|
{
|
|
type : float,
|
|
name : sunIntensity,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float,
|
|
name : contrast,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float3,
|
|
name : nightColor,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float3,
|
|
name : ozone,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float,
|
|
name : eclipseFactor,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float4,
|
|
name : multiScatParams, // xyz=MultiScatteringColor, w=HorizonGlow
|
|
precision : high
|
|
},
|
|
{
|
|
type : float4,
|
|
name : sunHalo, // x=Size, y=Limb, z=Intensity, w=Enabled
|
|
precision : high
|
|
},
|
|
{
|
|
type : float4,
|
|
name : shimmerControl, // x=Strength, y=Frequency, z=MaskHeight, w=PlanetRadius
|
|
precision : high
|
|
},
|
|
{
|
|
type : float4,
|
|
name : cloudControl, // x=Coverage, y=Density, z=QuadraticConst, w=WindSpeed
|
|
precision : high
|
|
},
|
|
{
|
|
type : float4,
|
|
name : cloudControl2, // x=EvolutionSpeed
|
|
precision : high
|
|
},
|
|
{
|
|
type : float,
|
|
name : sunIntensity2,
|
|
precision : high
|
|
},
|
|
{
|
|
type : float4,
|
|
name : sunHalo2, // x=Size, y=Limb, z=Intensity, w=Enabled
|
|
precision : high
|
|
},
|
|
{
|
|
type : float4,
|
|
name : waterControl, // x=Strength, y=Speed, z=DerivativeTrick, w=Octaves
|
|
precision : high
|
|
},
|
|
{
|
|
type : float4,
|
|
name : starControl, // x=Density, y=Enabled, z=Frequency, w=PixelScale
|
|
precision : high
|
|
},
|
|
{
|
|
type : float,
|
|
name : starIntensity,
|
|
precision : high
|
|
},
|
|
{
|
|
type : sampler2d,
|
|
name : moonTexture
|
|
},
|
|
{
|
|
type : float,
|
|
name : exposure,
|
|
precision : high
|
|
},
|
|
{
|
|
type : sampler2d,
|
|
name : moonNormal
|
|
},
|
|
{
|
|
type : sampler2d,
|
|
name : milkyWayTexture
|
|
},
|
|
{
|
|
type : float3,
|
|
name : milkyWayControl, // x=Intensity, y=Saturation, z=Unused
|
|
precision : high
|
|
},
|
|
{
|
|
type : mat3,
|
|
name : milkyWayRotation,
|
|
precision : high
|
|
}
|
|
],
|
|
|
|
variables : [
|
|
{
|
|
name : "eyeDirection",
|
|
precision : "high"
|
|
}
|
|
],
|
|
vertexDomain : device,
|
|
depthWrite : false,
|
|
shadingModel : unlit,
|
|
culling: none
|
|
}
|
|
|
|
vertex {
|
|
void materialVertex(inout MaterialVertexInputs material) {
|
|
// This code is taken from computeWorldPosition and assumes the vertex domain is 'device'.
|
|
highp vec4 p = getPosition();
|
|
// GL convention to inverted DX convention
|
|
p.z = p.z * -0.5 + 0.5;
|
|
highp vec4 worldPosition = getWorldFromClipMatrix() * p;
|
|
// Getting the true world position would require dividing by w, but since this is a skybox
|
|
// at inifinity, this results in very large numbers for material.eyeDirection.
|
|
// Since the eyeDirection is only used as a direction vector in the fragment shader, we can
|
|
// skip that step to improve precision.
|
|
material.eyeDirection.xyz = worldPosition.xyz;
|
|
}
|
|
}
|
|
|
|
fragment {
|
|
// ------------------------------------------------------------------------
|
|
// Analytic Rayleigh and Mie Scattering (Physics Based)
|
|
// Derived from:
|
|
// - Hoffman & Preetham (2002): "Real-time Light-Atmosphere Interactions"
|
|
// - Henyey & Greenstein (1941): "Diffuse radiation in the galaxy" (Mie Phase)
|
|
// - Kasten & Young (1989): "Revised optical air mass tables" (Air Mass)
|
|
// - "Simulated Sky" / Three.js (Sky.js): Empirical adjustments for aesthetics
|
|
// ------------------------------------------------------------------------
|
|
|
|
#define PI 3.14159265359
|
|
|
|
// --- CONFIGURATION ---
|
|
|
|
// Stars
|
|
#define STAR_GLOBAL_INTENSITY 100.0 // Master brightness multiplier [0.0 - 500.0]
|
|
#define STAR_BRIGHTNESS_BASE 0.01 // Minimum random brightness [0.0 - 1.0]
|
|
#define STAR_BRIGHTNESS_VAR 15.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)
|
|
// Lord Rayleigh (1871)
|
|
// Normalized to integrate to 4*PI (Boosting brightness by factor PI vs standard 1-normalization)
|
|
float rayleighPhase(float cosTheta) {
|
|
const float THREE_SIXTEENTH = (3.0 / 16.0);
|
|
return THREE_SIXTEENTH * (1.0 + cosTheta * cosTheta);
|
|
}
|
|
|
|
// Henyey-Greenstein Phase Function (Mie)
|
|
// Henyey & Greenstein (1941)
|
|
// Controls the forward scattering peak (sun halo) via anisotropy parameter 'g'
|
|
// Optimized: params.x = (1 + g^2), params.y = (-2 * g)
|
|
float hgPhase(float cosTheta, vec2 params) {
|
|
const float ONE_FOURTH = (1.0 / 4.0);
|
|
// Recover (1 - g^2) => 2.0 - (1 + g^2)
|
|
float oneMinusG2 = 2.0 - params.x;
|
|
float inverse = 1.0 / pow(params.x + params.y * cosTheta, 1.5);
|
|
return ONE_FOURTH * (oneMinusG2 * inverse);
|
|
}
|
|
|
|
// --- Noise Functions for Clouds ---
|
|
highp float hash13(highp vec3 p3) {
|
|
p3 = fract(p3 * .1031);
|
|
p3 += dot(p3, p3.yzx + 33.33);
|
|
return fract((p3.x + p3.y) * p3.z);
|
|
}
|
|
|
|
// Safe mod for negative values
|
|
// Why 289?
|
|
// 1. Historical: 17*17 = 289. Used in identifying permutations in standard GLSL noise (Ashima/stegu).
|
|
// 2. Precision: Small enough to prevent float precision loss during internal calculations (e.g. squaring).
|
|
// 3. Quality: Large enough to hide repetition and coprime to standard powers-of-two (like 256), avoiding grid resonance.
|
|
highp vec3 mod289(highp vec3 x) {
|
|
return x - floor(x * (1.0 / 289.0)) * 289.0;
|
|
}
|
|
|
|
highp float noise(highp vec3 p) {
|
|
// Wrap input to [0, 289] to preserve precision of fract() when p is large.
|
|
// This fixes grid artifacts on mobile caused by large world coordinates.
|
|
p = mod289(p);
|
|
|
|
highp vec3 i = floor(p);
|
|
highp vec3 f = fract(p);
|
|
// Cubic Hermite Interpolation
|
|
highp vec3 u = f*f*(3.0-2.0*f);
|
|
|
|
// i is already wrapped by p=mod289(p), but mod289(i) is safe (identity for 0..289).
|
|
// Actually mod289(p) ensures p is 0..289.
|
|
// So floor(p) is 0..288.
|
|
// mod289(i) is redundant but harmless.
|
|
highp vec3 i0 = i;
|
|
highp vec3 i1 = mod289(i + 1.0);
|
|
|
|
// Wrap neighbors to ensure valid domain [0, 289] and seamless scrolling
|
|
// Optimized to use precomputed wrapped indices (2 mods instead of 9)
|
|
return mix(mix(mix(hash13(vec3(i0.x, i0.y, i0.z)), hash13(vec3(i1.x, i0.y, i0.z)), u.x),
|
|
mix(hash13(vec3(i0.x, i1.y, i0.z)), hash13(vec3(i1.x, i1.y, i0.z)), u.x), u.y),
|
|
mix(mix(hash13(vec3(i0.x, i0.y, i1.z)), hash13(vec3(i1.x, i0.y, i1.z)), u.x),
|
|
mix(hash13(vec3(i0.x, i1.y, i1.z)), hash13(vec3(i1.x, i1.y, i1.z)), u.x), u.y), u.z);
|
|
}
|
|
|
|
// Fractal Brownian Motion (4 Octaves)
|
|
highp float fbm(highp vec3 p) {
|
|
highp float total = 0.0;
|
|
highp float amplitude = 0.5;
|
|
for (int i = 0; i < 4; i++) {
|
|
total += noise(p) * amplitude;
|
|
p *= 2.02; // Lacunarity
|
|
p += 100.0; // Shift to avoid artifacts
|
|
amplitude *= 0.5; // Gain
|
|
}
|
|
return total;
|
|
}
|
|
|
|
highp float fbm(highp vec3 p, int octaves) {
|
|
highp float total = 0.0;
|
|
highp float amplitude = 0.5;
|
|
for (int i = 0; i < 8; i++) {
|
|
if (i >= octaves) break;
|
|
total += noise(p) * amplitude;
|
|
p *= 2.02;
|
|
p += 100.0;
|
|
amplitude *= 0.5;
|
|
}
|
|
return total;
|
|
}
|
|
|
|
// Ray-Sphere Intersection
|
|
// Returns distance to intersection or -1.0 if none.
|
|
// Re = Planet Radius.
|
|
// C = Re^2 - (Re + height)^2 (Precalculated on CPU for precision).
|
|
highp float raySphereIntersect(highp vec3 rd, highp float Re, highp float C) {
|
|
// Ray Origin is (0, Re, 0) relative to Planet Center (0, 0, 0)
|
|
// We solve |(0, Re, 0) + t*rd|^2 = Rm^2
|
|
// |O + tD|^2 = R^2
|
|
// t^2 + 2t(O.D) + (O^2 - R^2) = 0
|
|
// a=1, b=2(O.D), c = O^2 - R^2 = C
|
|
// Reduced quadratic: t = -b' +/- sqrt(b'^2 - c) where b' = O.D
|
|
|
|
highp float b = Re * rd.y; // dot(vec3(0, Re, 0), rd)
|
|
highp float disc = b*b - C;
|
|
|
|
if (disc < 0.0) return -1.0;
|
|
|
|
// t = -b + sqrt(disc)
|
|
return -b + sqrt(disc);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Atmospheric Heat Shimmer (Mirage)
|
|
// ------------------------------------------------------------------------
|
|
// Simulates heat convection turbulence near the horizon (e.g., hot desert road effect).
|
|
//
|
|
// PHYSICS:
|
|
// Heat rising from the ground creates pockets of varying air density (refractive index).
|
|
// This bends light rays, causing a visual "shimmer" or displacement.
|
|
//
|
|
// IMPLEMENTATION:
|
|
// - Perturbs the view vector `V.y` using interleaved sine waves.
|
|
// - Uses World Space `V` so the noise is stable under camera rotation.
|
|
// - Masked to only affect the horizon line.
|
|
//
|
|
// PARAMETERS:
|
|
// @param V Normalized World View Vector (modified in place).
|
|
// @param strength Max vertical displacement amplitude. (e.g. 0.002).
|
|
// @param freq Ripple frequency/density. (e.g. 20.0).
|
|
// @param maskHeight Horizon mask height (0.0 to 1.0). (e.g. 0.1).
|
|
// ------------------------------------------------------------------------
|
|
float applyHeatShimmer(inout highp vec3 V, highp float strength, highp float freq, highp float maskHeight) {
|
|
if (strength <= 0.0) return 0.0;
|
|
|
|
// Mask: Strongest at horizon (0.0), fades out by maskHeight.
|
|
highp float mask = 1.0 - smoothstep(0.0, maskHeight, abs(V.y));
|
|
|
|
if (mask > 0.0) {
|
|
// Use FBM for organic turbulence (rising heat waves)
|
|
highp float time = getUserTime().x;
|
|
// Animate upward (y) and slightly drift (x)
|
|
highp vec3 p = vec3(V.x * freq, V.y * freq + time * 2.0, time * 0.5);
|
|
|
|
// We use a cheap noise or FBM. Since we have FBM:
|
|
// Use fewer octaves for performance if possible, but 4 is fine.
|
|
highp float distortion = fbm(p);
|
|
|
|
// Remap noise from [0, 1] to [-1, 1] for perturbation
|
|
distortion = distortion * 2.0 - 1.0;
|
|
|
|
// Apply vertical perturbation
|
|
V.y += distortion * strength * mask * 0.1;
|
|
V = normalize(V);
|
|
}
|
|
return mask;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Analytic Sky Model (Rayleigh + Mie + Ozone)
|
|
// ------------------------------------------------------------------------
|
|
// Computes the scattering and transmittance of the atmosphere along the view ray.
|
|
//
|
|
// PHYSICS:
|
|
// - Rayleigh: Scattering by air molecules (Blue sky). High frequency (lambda^-4).
|
|
// - Mie: Scattering by aerosols/dust (White haze). Low frequency (lambda^-1.3).
|
|
// - Ozone: Absorption layer (Pink sunset). Absorbs green light.
|
|
// - Optical Mass: Approximation of path length through spherical atmosphere.
|
|
//
|
|
// OUTPUTS:
|
|
// @param V Normalized View Vector.
|
|
// @param L Normalized Sun Vector.
|
|
// @param sunIntensity Sun Illuminance (Lux).
|
|
// @param depthR Rayleigh Optical Depth (Precalculated).
|
|
// @param depthM Mie Optical Depth (Precalculated).
|
|
// @param ozone Ozone Absorption (Precalculated).
|
|
// @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).
|
|
// ------------------------------------------------------------------------
|
|
highp vec3 getAtmosphere(highp vec3 V, highp vec3 L, highp float sunIntensity,
|
|
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
|
|
highp vec4 multiScatParams, highp vec2 mieParams,
|
|
out highp vec3 outTransmittance) {
|
|
|
|
highp float cosTheta = dot(V, L);
|
|
|
|
// 1. Phase Functions
|
|
// "Golden Hour" Hack (Three.js Sky.js):
|
|
// Remapping cosTheta from [-1, 1] to [0, 1] breaks the symmetry of Rayleigh scattering.
|
|
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
|
|
highp float mPhase = hgPhase(cosTheta, mieParams);
|
|
|
|
// 2. Optical Depth (Air Mass)
|
|
// Kasten and Young (1989) - Relative Air Mass Model
|
|
highp float zenithCos = clamp(V.y, 0.0, 1.0);
|
|
highp float zenithAngle = acos(zenithCos);
|
|
highp float zenithAngleDeg = zenithAngle * (180.0 / PI);
|
|
highp float opticalMass = 1.0 / (zenithCos + 0.15 * pow(93.885 - zenithAngleDeg, -1.253));
|
|
|
|
// 3. Extinction & Transmittance
|
|
highp vec3 totalExtinction = depthR + depthM + ozone;
|
|
highp vec3 extinction = totalExtinction * opticalMass;
|
|
outTransmittance = exp(-extinction);
|
|
|
|
// 4. In-Scattering
|
|
// Approximate Multi-Scattering (Isotropic Fill) precomputed in C++.
|
|
highp vec3 multiScattering = multiScatParams.xyz;
|
|
|
|
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase) + multiScattering;
|
|
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
|
|
|
|
// Equilibrium Radiance (Source Function)
|
|
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
|
|
|
|
// Single-Scattering Integral: L = L_inf * (1 - exp(-opticalDepth))
|
|
highp vec3 sunLight = inScattering * (1.0 - outTransmittance);
|
|
|
|
// 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, HORIZON_GLOW_POWER)) * multiScatParams.w;
|
|
highp vec3 horizonGlow = sqrt(inScattering * outTransmittance);
|
|
sunLight *= mix(vec3(1.0), horizonGlow, horizonMix);
|
|
|
|
return sunLight;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Secondary Sun Scattering (Simplified)
|
|
// ------------------------------------------------------------------------
|
|
// Computes in-scattering for a second light source, reusing precomputed optical depths.
|
|
// Skips multi-scattering (ambient) for performance, providing only direct beams/glow.
|
|
//
|
|
// @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 mieParams Mie Phase Params.
|
|
// @param transmittance Precomputed Atmospheric Transmittance.
|
|
// @return In-Scattered Radiance.
|
|
// ------------------------------------------------------------------------
|
|
highp vec3 getSecondarySunScattering(highp vec3 V, highp vec3 L, highp float sunIntensity,
|
|
highp vec3 depthR, highp vec3 depthM, highp vec3 ozone,
|
|
highp vec2 miePhaseParams, highp vec3 transmittance) {
|
|
highp float cosTheta = dot(V, L);
|
|
|
|
// Phase Functions
|
|
highp float rPhase = rayleighPhase(cosTheta * 0.5 + 0.5);
|
|
highp float mPhase = hgPhase(cosTheta, miePhaseParams);
|
|
|
|
// Scattering
|
|
highp vec3 scatteringTerm = (depthR * rPhase) + (depthM * mPhase);
|
|
highp vec3 totalExtinction = depthR + depthM + ozone;
|
|
highp vec3 extinctionTerm = max(vec3(1e-6), totalExtinction);
|
|
|
|
highp vec3 inScattering = sunIntensity * (scatteringTerm / extinctionTerm);
|
|
return inScattering * (1.0 - transmittance);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Physical Sun Disk
|
|
// ------------------------------------------------------------------------
|
|
// Renders the Solar Photosphere with limb darkening.
|
|
//
|
|
// PHYSICS:
|
|
// - The sun is not a point light; it has an angular size (~0.53 deg).
|
|
// - Limb Darkening: The sun is darker at the edges (limbs) because we see cooler outer layers.
|
|
// - Drawn "Behind" the atmosphere, so it is attenuated by Transmittance.
|
|
//
|
|
// PARAMETERS:
|
|
// @param V Normalized View Vector.
|
|
// @param L Normalized Sun Vector.
|
|
// @param sunParams x=CosRadius, y=LimbDarkening, z=IntensityBoost, w=Enabled.
|
|
// @param sunIntensity Peak Sun Illuminance (Lux).
|
|
// @param transmittance Atmospheric Transmittance (0..1).
|
|
// @return Radiance of the sun disk (if visible and enabled).
|
|
// ------------------------------------------------------------------------
|
|
vec3 getSunDisk(highp vec3 V, highp vec3 L, highp vec4 sunParams,
|
|
highp float sunIntensity, highp vec3 transmittance) {
|
|
|
|
highp float sunCosRadius = sunParams.x;
|
|
highp float limbDarkening = sunParams.y;
|
|
highp float sunDiskIntensity = sunParams.z;
|
|
bool sunEnabled = sunParams.w > 0.5;
|
|
|
|
highp float cosTheta = dot(V, L);
|
|
|
|
// Robust edge detection for small angles using (1 - cos)
|
|
highp float dist = 1.0 - cosTheta;
|
|
highp float diskRadius = max(1e-6, 1.0 - sunCosRadius);
|
|
|
|
// AA Edge: smoothstep from radius to radius+epsilon
|
|
// We invert it because we want 1.0 inside (dist < radius)
|
|
highp float sunDiskProfile = 1.0 - smoothstep(diskRadius, diskRadius + 0.00002, dist);
|
|
|
|
if (sunEnabled && sunDiskProfile > 0.0) {
|
|
// Limb Darkening approximation: mu = sqrt(1 - (r/R)^2)
|
|
// dist/diskRadius is approx (r/R)^2 for small angles
|
|
highp float relativeDist = min(1.0, dist / diskRadius);
|
|
highp float mu = sqrt(1.0 - relativeDist);
|
|
|
|
// Avoid pow(0, 0) which causes NaNs
|
|
highp float limbFactor = (limbDarkening < 1e-4) ? 1.0 : pow(mu, limbDarkening);
|
|
|
|
// Direct Sun Light (Radiance)
|
|
// SunIntensity * Transmittance -> Physical Sun Color
|
|
// SunDiskIntensity -> Artistic Boost to punch through Mie halo
|
|
return sunIntensity * transmittance * limbFactor * sunDiskIntensity * sunDiskProfile;
|
|
}
|
|
|
|
return vec3(0.0);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Procedural Cirrus Clouds
|
|
// ------------------------------------------------------------------------
|
|
// Renders a thin layer of high-altitude clouds (Cirrus) using 3D Noise.
|
|
//
|
|
// IMPLEMENTATION:
|
|
// - Modeled as a spherical shell at a specific altitude.
|
|
// - Ray-Sphere intersection determines UV layout and distance.
|
|
// - Animated using 3D FBM (Fractal Brownian Motion) for shape evolution + Wind drift.
|
|
// - Lighting includes Silver Lining (HG Phase) and Atmospheric Extinction.
|
|
//
|
|
// PARAMETERS:
|
|
// @param V Normalized View Vector.
|
|
// @param L Normalized Sun Vector.
|
|
// @param control x=Coverage, y=Density, z=QuadraticConst(C), w=WindSpeed.
|
|
// @param control2 x=EvolutionSpeed.
|
|
// @param geometry w=PlanetRadius (Re).
|
|
// @param sunIntensity Sun Illuminance.
|
|
// @param transmittance Atmospheric Transmittance (Cloud Color Tint).
|
|
// @param outDensity Output: Cloud pixel density (0..1) for compositing.
|
|
// @return Cloud Radiance (Lit color * attenuation * shading).
|
|
// ------------------------------------------------------------------------
|
|
vec3 getCloudLayer(highp vec3 V, highp vec3 L,
|
|
highp vec4 control, highp vec4 control2, highp vec4 geometry,
|
|
highp float sunIntensity, highp vec3 transmittance,
|
|
out float outDensity) {
|
|
|
|
outDensity = 0.0;
|
|
highp float cloudCoverage = control.x;
|
|
|
|
// Clip clouds below the horizon (Earth occlusion)
|
|
// Simple check V.y > 0.0 is sufficient for skybox provided camera is near ground.
|
|
if (cloudCoverage > 0.0 && V.y > 0.0) {
|
|
highp float Re = geometry.w;
|
|
highp float intersectC = control.z;
|
|
highp float distToCloud = raySphereIntersect(V, Re, intersectC);
|
|
|
|
if (distToCloud > 0.0) {
|
|
highp vec3 p = V * distToCloud;
|
|
highp float speed = control.w;
|
|
highp float morphSpeed = control2.x;
|
|
highp float time = getUserTime().x;
|
|
|
|
// UV Mapping (Planar projected onto sphere cap is sufficient for skybox)
|
|
// 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));
|
|
|
|
// Remap noise based on coverage.
|
|
// Coverage 0.5 -> threshold 0.5. Coverage 1.0 -> threshold 0.0.
|
|
highp float threshold = 1.0 - cloudCoverage;
|
|
highp float cloudDensity = smoothstep(threshold, threshold + 0.3, noiseVal);
|
|
|
|
if (cloudDensity > 0.0) {
|
|
cloudDensity *= control.y; // Global Density Scalar
|
|
cloudDensity = clamp(cloudDensity, 0.0, 1.0);
|
|
outDensity = cloudDensity;
|
|
|
|
// Cloud Lighting
|
|
// Silver Lining: Strong forward scattering (Fixed g=0.9 for clouds)
|
|
highp float cosTheta = dot(V, L);
|
|
// We need separate params for cloud silver lining (g=0.9).
|
|
// 1 + 0.9^2 = 1.81. -2*0.9 = -1.8.
|
|
|
|
// Attenuation (Beer's Law)
|
|
// Thick clouds block light.
|
|
// CLOUD_EXTINCTION is an artistic extinction coefficient.
|
|
highp float extinction = exp(-cloudDensity * CLOUD_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 (CLOUD_AMBIENT_MIN min)
|
|
// so they don't look like black holes.
|
|
highp float ambient = CLOUD_AMBIENT_MIN + 0.4 * extinction;
|
|
|
|
// Diffuse term (Sun Color) + Silver Lining
|
|
highp vec3 cloudLight = sunIntensity * transmittance * (ambient + silver);
|
|
|
|
// Mix based on density
|
|
highp float volumetric = control2.y;
|
|
highp float shading = 1.0;
|
|
|
|
if (volumetric > 0.5) {
|
|
// Gradient Lighting (Fake Volumetric Bump)
|
|
highp float gradX = dFdx(cloudDensity);
|
|
highp float gradY = dFdy(cloudDensity);
|
|
// Smaller Z = Steeper Bumps.
|
|
// dFdx(density) is typically small (e.g. 0.001).
|
|
// We want Normal to have significant X/Y component.
|
|
highp vec3 N = normalize(vec3(-gradX, -gradY, 0.001));
|
|
|
|
// Screen Space Sun Direction
|
|
highp vec3 sRight = normalize(dFdx(V));
|
|
highp vec3 sUp = normalize(dFdy(V));
|
|
highp vec3 L_screen = vec3(dot(L, sRight), dot(L, sUp), 0.5);
|
|
L_screen = normalize(L_screen);
|
|
|
|
shading = dot(N, L_screen);
|
|
// Increase contrast: Darker shadows
|
|
// dot is [-1, 1]. Map to [0.3, 1.0]
|
|
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 * VOLUMETRIC_SHADOW_STR);
|
|
}
|
|
|
|
return cloudLight * shading;
|
|
}
|
|
}
|
|
}
|
|
return vec3(0.0);
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Dynamic Tone Mapping
|
|
// ------------------------------------------------------------------------
|
|
// Applies a contrast curve that varies with sun elevation.
|
|
//
|
|
// PROBLEM:
|
|
// Default linear/gamma tone mapping can make sunsets look washing out.
|
|
// Real eyes accept much higher dynamic range at twilight.
|
|
//
|
|
// SOLUTION:
|
|
// - Zenith (Noon): Linear gamma (Exponent 1.0). Physically accurate.
|
|
// - Horizon (Sunset): High contrast (Exponent > 1.0). Crushes shadows, boosts color.
|
|
//
|
|
// @param color Input HDR color.
|
|
// @param L Normalized Sun Vector.
|
|
// @param contrast Maximum contrast exponent (at horizon). e.g. 1.5.
|
|
// @return Tone mapped color.
|
|
// ------------------------------------------------------------------------
|
|
vec3 applyDynamicToneMapping(vec3 color, highp vec3 L, float contrast) {
|
|
highp float c = saturate(L.y);
|
|
// Exponent blends from 'contrast' (at L.y=0) to 1.0 (at L.y=1)
|
|
float exponent = mix(contrast, 1.0, sqrt(c));
|
|
return pow(max(vec3(0.0), color), vec3(exponent));
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------------------
|
|
// 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);
|
|
}
|
|
|
|
float getStars(highp vec3 V) {
|
|
bool enabled = materialParams.starControl.y > 0.5;
|
|
if (!enabled) return 0.0;
|
|
|
|
highp float density = materialParams.starControl.x;
|
|
if (density <= 0.0) return 0.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 intensity;
|
|
}
|
|
|
|
// New helper to handle Star Compositing (Fade, Rotation, Occlusion)
|
|
vec3 getStarLayer(highp vec3 V, highp vec3 L, 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.
|
|
// 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);
|
|
|
|
// 2. Rotate to break grid alignment
|
|
highp vec3 rotV = vec3(
|
|
dot(V, vec3(0.6, 0.8, 0.0)),
|
|
dot(V, vec3(-0.8, 0.6, 0.0)),
|
|
V.z
|
|
);
|
|
|
|
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 * STAR_CLOUD_OCCLUSION;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Milky Way
|
|
// ------------------------------------------------------------------------
|
|
// Renders the Milky Way background from an equirectangular texture.
|
|
//
|
|
// @param V Normalized View Vector.
|
|
// @param rotation Rotation matrix (Galactic -> World).
|
|
// @return Milky Way Color.
|
|
// ------------------------------------------------------------------------
|
|
vec3 getMilkyWay(highp vec3 V, highp mat3 rotation, sampler2D tex, highp vec3 control) {
|
|
highp float intensity = control.x;
|
|
if (intensity <= 0.0) return vec3(0.0);
|
|
|
|
// Rotate V into Galactic coordinates
|
|
highp vec3 Vg = rotation * V;
|
|
|
|
// Equirectangular mapping
|
|
// u = atan(z, x) / 2pi + 0.5
|
|
// v = asin(y) / pi + 0.5
|
|
highp float u = atan(Vg.z, Vg.x) * (0.1591549) + 0.5; // 1/(2*PI)
|
|
highp float v = asin(clamp(Vg.y, -1.0, 1.0)) * (0.3183098) + 0.5; // 1/PI
|
|
|
|
// Sample
|
|
highp vec3 color = texture(tex, vec2(u, v)).rgb;
|
|
|
|
// Black Point (Remove baked-in haze)
|
|
highp float blackPoint = control.z;
|
|
color = max(vec3(0.0), color - blackPoint);
|
|
|
|
// Saturation
|
|
highp float saturation = control.y;
|
|
highp float gray = dot(color, vec3(0.299, 0.587, 0.114));
|
|
color = mix(vec3(gray), color, saturation);
|
|
|
|
return color * intensity;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// 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, sampler2D moonTex, sampler2D moonNormal) {
|
|
|
|
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));
|
|
|
|
// 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));
|
|
|
|
// Texture Mapping
|
|
// The moon texture is an orthographic projection of the Near Side.
|
|
// We align the texture using a stable "Up" vector (World Up projected onto the Moon plane).
|
|
|
|
highp vec3 Up = vec3(0.0, 1.0, 0.0);
|
|
if (abs(L_moon.y) > 0.99) Up = vec3(0.0, 0.0, 1.0);
|
|
|
|
highp vec3 Right = normalize(cross(Up, L_moon));
|
|
highp vec3 MoonUp = cross(L_moon, Right);
|
|
|
|
// Project N_sphere onto the Right/MoonUp plane to get UVs.
|
|
highp float u = dot(N_sphere, Right) * 0.5 + 0.5;
|
|
highp float v = dot(N_sphere, MoonUp) * 0.5 + 0.5;
|
|
|
|
// Sample Texture
|
|
// We assume the texture is stored such that (0,0) is bottom-left.
|
|
// Sample Albedo
|
|
// Flip V here as well. Flip U to fix horizontal mirroring.
|
|
highp vec3 moonColorSample = texture(moonTex, vec2(1.0 - u, 1.0 - v)).rgb;
|
|
// The original code used a float 'moonAlbedo' derived from .rrr.
|
|
// To support "3 channels" properly if it had color, we use the full RGB sample.
|
|
highp vec3 moonAlbedo = moonColorSample;
|
|
|
|
// Normal Mapping
|
|
// Convert Tangent Space Normal (from texture) to World Space
|
|
// Tangent Basis: T=Right, B=MoonUp, N=N_sphere
|
|
// We need to invert V because texturing coordinates are usually bottom-up
|
|
// but our procedural disk generation (and the original texture) might be top-down oriented
|
|
// relative to the sphere mapping we did.
|
|
highp vec3 N_map = texture(moonNormal, vec2(1.0 - u, 1.0 - v)).rgb * 2.0 - 1.0;
|
|
// Scale down the normal map strength (it was too strong)
|
|
N_map.xy *= 0.2;
|
|
|
|
// Important: Since we flipped U (1.0 - u), we effectively mirrored the texture.
|
|
// A slope that was "right" is now "left" visually, but the normal map data still points "right".
|
|
// We must invert the X component of the normal to match the mirrored visual geometry.
|
|
N_map.x *= -1.0;
|
|
|
|
// Perturb N_sphere
|
|
// N_final = N_map.x * T + N_map.y * B + N_map.z * N
|
|
// Note: N_map.z is the component along the surface normal.
|
|
N_sphere = normalize(N_map.x * Right + N_map.y * MoonUp + N_map.z * N_sphere);
|
|
|
|
highp float NdotL = max(0.0, dot(N_sphere, L_sun));
|
|
|
|
highp vec3 litColor = moonIntensity * transmittance * NdotL * moonAlbedo;
|
|
highp vec3 unlitColor = moonIntensity * transmittance * MOON_EARTHSHINE * earthPhase * moonAlbedo;
|
|
|
|
// Smooth transition at terminator to avoid hard lines
|
|
return mix(unlitColor, litColor, smoothstep(0.0, 0.1, NdotL)) * moonDiskIntensity * moonProfile;
|
|
}
|
|
|
|
return vec3(0.0);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// Phase Factor (Lambertian Sphere)
|
|
// ------------------------------------------------------------------------
|
|
// Calculates the integrated flux factor for a lit sphere based on phase angle.
|
|
// Normalized to 1.0 at Full Moon (Phase=0) and 0.0 at New Moon (Phase=PI).
|
|
//
|
|
// @param L_sun Normalized vector to Sun
|
|
// @param L_moon Normalized vector to Moon
|
|
// @return Phase factor [0.0, 1.0]
|
|
// ------------------------------------------------------------------------
|
|
highp float getPhaseFactor(highp vec3 L_sun, highp vec3 L_moon) {
|
|
highp float cosAlpha = -dot(L_sun, L_moon);
|
|
highp float alpha = acos(clamp(cosAlpha, -1.0, 1.0));
|
|
return (1.0 / PI) * (sin(alpha) + (PI - alpha) * cosAlpha);
|
|
}
|
|
|
|
// ------------------------------------------------------------------------
|
|
// 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 (Lit/Dimmed).
|
|
// @param sunDiskIntensity Sun Illuminance (Un-Dimmed) for specular.
|
|
// @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).
|
|
// @param L2 Secondary Sun Direction.
|
|
// @param sunIntensity2 Secondary Sun Intensity.
|
|
// @param sunHalo2 Secondary Sun Halo Params.
|
|
// @return Water surface color.
|
|
// ------------------------------------------------------------------------
|
|
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 vec4 cloudControl, highp vec4 cloudControl2,
|
|
highp vec4 shimmerControl, highp vec4 waterControl,
|
|
highp vec3 L2, highp float sunIntensity2, highp vec4 sunHalo2,
|
|
sampler2D moonTex, sampler2D moonNormal,
|
|
highp vec3 nightColor,
|
|
highp mat3 milkyWayRotation,
|
|
highp vec3 milkyWayControl,
|
|
sampler2D milkyWayTexture) {
|
|
|
|
// Project to plane y=0
|
|
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
|
|
int octaves = int(max(1.0, waterControl.w));
|
|
highp float h = fbm(vec3(uv, time * 0.1 * speed), octaves);
|
|
|
|
// 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
|
|
highp float horizonFade = smoothstep(0.0, 0.5, abs(V.y));
|
|
highp float strength = waterControl.x;
|
|
|
|
highp vec3 N_perturb;
|
|
|
|
// Derivative Trick Toggle
|
|
if (waterControl.z > 0.5) {
|
|
// Screen-Space Derivatives (Fast, 1 tap)
|
|
// Reconstruct screen-space basis in world space
|
|
// If h increases in screen-X direction, normal tilts against sRight.
|
|
highp vec3 sRight = normalize(dFdx(V));
|
|
highp vec3 sUp = normalize(dFdy(V));
|
|
N_perturb = (sRight * dFdx(h) + sUp * dFdy(h)) * strength * horizonFade;
|
|
} else {
|
|
// Finite Difference (Standard, 3 taps)
|
|
// More expensive but analytically correct in world space (independent of view resolution/derivatives)
|
|
highp float eps = 0.02; // Epsilon for gradient
|
|
highp vec3 p = vec3(uv, time * 0.1 * speed);
|
|
highp float hx = fbm(p + vec3(eps, 0.0, 0.0), octaves);
|
|
highp float hy = fbm(p + vec3(0.0, eps, 0.0), octaves);
|
|
|
|
// Gradient
|
|
highp float dx = (hx - h) / eps;
|
|
highp float dy = (hy - h) / eps;
|
|
|
|
// Construct World Space Perturbation
|
|
// Gradient (dx, dy) acts on XZ plane.
|
|
// Normal = normalize(-dx, 1, -dy).
|
|
// We want N_perturb to SUBTRACT from (0,1,0).
|
|
// N_water = normalize(Up - Perturb).
|
|
// So Perturb = (dx, 0, dy).
|
|
// Note: Strength needs to be calibrated to match derivative trick roughly, or just raw.
|
|
// Derivative trick Strength was ~50.0.
|
|
// Here dx/dy are raw noise slopes.
|
|
// Reduced to 0.002 to match visual range of derivative trick and prevent black artifacts.
|
|
N_perturb = vec3(dx, 0.0, dy) * (strength * 0.002) * horizonFade;
|
|
}
|
|
|
|
highp vec3 N_water = normalize(vec3(0.0, 1.0, 0.0) - N_perturb);
|
|
|
|
// Reflection
|
|
highp vec3 R = reflect(V, N_water);
|
|
|
|
// 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) {
|
|
float phaseFactor = getPhaseFactor(L, L2);
|
|
reflection += getSecondarySunScattering(R, L2, sunIntensity2 * phaseFactor,
|
|
depthR, depthM, ozone,
|
|
miePhaseParams,
|
|
outTransmittance);
|
|
}
|
|
|
|
// Clouds in reflection
|
|
highp float reflCloudDensity;
|
|
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 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, outTransmittance, materialParams.starControl) * rHorizonMask * materialParams.exposure;
|
|
}
|
|
|
|
// 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, outTransmittance, moonTex, moonNormal);
|
|
}
|
|
|
|
// Apply clouds to reflection
|
|
reflection = mix(reflection, reflCloudLayer, reflCloudDensity);
|
|
|
|
// Add Milky Way to Reflection
|
|
// Calculate Fade based on Sun Elevation
|
|
highp float sunElvSin = L.y;
|
|
highp float mwFade = smoothstep(STAR_FADE_SUN_ELV_HIGH, STAR_FADE_SUN_ELV_LOW, sunElvSin);
|
|
|
|
if (mwFade > 0.0) {
|
|
highp vec3 mwColor = getMilkyWay(R, milkyWayRotation, milkyWayTexture, milkyWayControl);
|
|
|
|
// Apply Atmosphere Transmittance (approximate)
|
|
mwColor *= outTransmittance;
|
|
|
|
// Apply Fades
|
|
mwColor *= mwFade;
|
|
mwColor *= (1.0 - reflMoonOcclusion); // Occlude by Moon
|
|
mwColor *= (1.0 - reflCloudDensity); // Occlude by Clouds
|
|
|
|
reflection += mwColor;
|
|
}
|
|
|
|
|
|
// Add Night Color to Reflection
|
|
reflection += nightColor;
|
|
|
|
// Fresnel
|
|
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 = WATER_DEEP_COLOR; // Deep blue/black
|
|
|
|
highp vec3 waterColor = mix(deepColor, reflection, F);
|
|
|
|
return waterColor;
|
|
}
|
|
|
|
|
|
void material(inout MaterialInputs material) {
|
|
prepareMaterial(material);
|
|
|
|
highp vec3 V = normalize(variable_eyeDirection.xyz);
|
|
highp vec3 L = normalize(materialParams.sunDirection);
|
|
|
|
// 1. Heat Shimmer
|
|
// Fade out as sun rises (Strongest at horizon, zero at 30 degrees up)
|
|
highp float sunFade = 1.0 - smoothstep(0.0, 0.5, abs(L.y));
|
|
highp float shimmerIntensity = applyHeatShimmer(V, materialParams.shimmerControl.x * sunFade,
|
|
materialParams.shimmerControl.y,
|
|
materialParams.shimmerControl.z);
|
|
|
|
// 2. Atmospheric Scattering
|
|
highp vec3 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.
|
|
// It contributes Direct In-Scattering (Beams/Glow) only.
|
|
highp vec3 inScatter2 = vec3(0.0);
|
|
|
|
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;
|
|
|
|
// 2. Atmospheric Scattering
|
|
highp vec3 color = getAtmosphere(V, L, sunIntensityLit, materialParams.depthR, materialParams.depthM, materialParams.ozone,
|
|
materialParams.multiScatParams, materialParams.miePhaseParams, transmittance);
|
|
|
|
// 3. Secondary Scattering (Moon)
|
|
if (materialParams.sunHalo2.w > 0.5) {
|
|
float phaseFactor = getPhaseFactor(L, L2);
|
|
inScatter2 = getSecondarySunScattering(V, L2,
|
|
materialParams.sunIntensity2 * phaseFactor,
|
|
materialParams.depthR,
|
|
materialParams.depthM,
|
|
materialParams.ozone,
|
|
materialParams.miePhaseParams,
|
|
transmittance);
|
|
color += inScatter2;
|
|
}
|
|
|
|
// 4. Clouds
|
|
highp float cloudDensityVal;
|
|
highp vec3 clouds = getCloudLayer(V, L, materialParams.cloudControl, materialParams.cloudControl2, materialParams.shimmerControl,
|
|
sunIntensityLit, transmittance, cloudDensityVal);
|
|
|
|
// 5. 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);
|
|
|
|
// Moon Occlusion Mask
|
|
highp float moonOcclusion = 0.0;
|
|
if (materialParams.sunHalo2.w > 0.5) {
|
|
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).
|
|
|
|
sunDisk *= (1.0 - moonOcclusion) * sunAccess;
|
|
|
|
color += sunDisk;
|
|
|
|
// 6. Moon Disk
|
|
if (materialParams.sunHalo2.w > 0.5) {
|
|
color += getMoonDisk(V, L2, L, materialParams.sunHalo2,
|
|
materialParams.sunIntensity2, transmittance, materialParams_moonTexture, materialParams_moonNormal) * sunAccess;
|
|
}
|
|
|
|
// 7. Stars
|
|
// Add stars before clouds (clouds cover stars)
|
|
highp vec3 starColor = getStarLayer(V, L, cloudDensityVal, transmittance, materialParams.starControl) * materialParams.exposure * materialParams.starIntensity;
|
|
|
|
// 7b. Milky Way
|
|
// Add Milky Way behind stars (conceptually) but handled similarly
|
|
// We fade it by Sun Elevation just like stars
|
|
// Re-use starFade logic implicity or calculate it?
|
|
// Let's use the starControl.y (enabled) check inside getMilkyWay if needed, or just assume it's always on if intensity > 0.
|
|
// We reuse the fade from stars for consistency:
|
|
// STAR_FADE_SUN_ELV_HIGH...
|
|
// We recalculate fade:
|
|
highp float mwFade = 1.0 - smoothstep(STAR_FADE_SUN_ELV_LOW, STAR_FADE_SUN_ELV_HIGH, L.y);
|
|
mwFade *= mwFade;
|
|
|
|
highp vec3 milkyWay = vec3(0.0);
|
|
if (mwFade > 0.0) {
|
|
milkyWay = getMilkyWay(V, materialParams.milkyWayRotation, materialParams_milkyWayTexture, materialParams.milkyWayControl);
|
|
milkyWay *= mwFade * transmittance * (1.0 - moonOcclusion);
|
|
// Apply cloud occlusion
|
|
milkyWay *= (1.0 - smoothstep(0.0, 1.0, pow(cloudDensityVal, 0.1)));
|
|
}
|
|
|
|
color += (starColor + milkyWay) * (1.0 - moonOcclusion);
|
|
|
|
// 8. 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);
|
|
|
|
// 9. Night Sky Offset
|
|
color += materialParams.nightColor;
|
|
|
|
// 10. Dynamic Tone Mapping
|
|
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
|
|
|
// 11. Water Surface (Reflection)
|
|
if (V.y < 0.0) {
|
|
color = getWaterColor(V, L, sunIntensityLit, materialParams.sunIntensity,
|
|
materialParams.depthR, materialParams.depthM,
|
|
materialParams.ozone, materialParams.multiScatParams,
|
|
materialParams.miePhaseParams,
|
|
materialParams.sunHalo,
|
|
materialParams.cloudControl, materialParams.cloudControl2,
|
|
materialParams.shimmerControl,
|
|
materialParams.waterControl,
|
|
L2, materialParams.sunIntensity2, materialParams.sunHalo2,
|
|
materialParams_moonTexture, materialParams_moonNormal,
|
|
materialParams.nightColor,
|
|
materialParams.milkyWayRotation,
|
|
materialParams.milkyWayControl,
|
|
materialParams_milkyWayTexture);
|
|
color = applyDynamicToneMapping(color, L, materialParams.contrast);
|
|
}
|
|
|
|
material.baseColor = vec4(color, 1.0);
|
|
}
|
|
}
|
|
|
|
|