| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071 |
- /**
- * ThreeGeospatialPipeline - 精炼版体积云 + Bruneton 大气 + 空中透视一体化管线。
- *
- * 渲染顺序(与 three-geospatial 对齐):
- * 1. PostProcessStage: 体积云 raymarch(含 BSM 采样、shadowLength、haze)
- * 2. PostProcessStage: AtmospherePostProcess 天空
- * 3. PostProcessStage: AerialPerspectiveEffect 几何透视 + tonemap
- *
- * BSM(Beer Shadow Map)和 TAA 通过原生 WebGL 在 preRender/postRender 执行。
- * BSM 数据通过 setCloudShadow 同步到大气和 Aerial 两侧,实现丁达尔与地面云影。
- */
- import * as dat from "dat.gui";
- import { AtmosphereParameters, PRECOMPUTE_CONSTANTS, getPrecomputeDefines, flattenAtmosphereUniform } from "./AtmosphereFromThreeGeospatial/AtmosphereParameters.js";
- import { AtmospherePostProcess } from "./AtmosphereFromThreeGeospatial/AtmospherePostProcess.js";
- import { AerialPerspectiveEffect } from "./AtmosphereFromThreeGeospatial/AerialPerspectiveEffect.js";
- import { loadBinThreeGeospatial, bindData3DTextureToCesiumContext } from "./loadBinThreeGeospatial.js";
- import { loadShaderSource } from "./shaderLoader.js";
- import {
- DEFAULT_CLOUDS_ASSETS_BASE,
- DEFAULT_BRUNETON_SHADER_BASE,
- DEFAULT_BLUE_NOISE_URL,
- DEFAULT_ATMOSPHERE_ASSETS_BASE,
- DEFAULT_ATMOSPHERE_SHADER_BASE,
- } from "./assetPaths.js";
- const SHADOW_MAP_SIZE = 1024;
- const SHADOW_CASCADE_COUNT = 4;
- const SHADOW_RAY_FAR = 500000.0;
- const BSM_BLIT_SIZE = 1024;
- // ─── Cloud fragment shader (Bruneton integrated, no debug branches) ────────
- function getCloudFragmentShader() {
- return /* glsl */ `
- const float RECIPROCAL_PI4 = 0.07957747154594767;
- const float EVOLUTION_SCALE = 2e4;
- uniform sampler2D colorTexture;
- uniform sampler2D depthTexture;
- uniform sampler3D u_shapeTexture;
- uniform sampler3D u_shapeDetailTexture;
- uniform sampler3D u_stbnTexture;
- uniform sampler2D u_weatherTexture;
- uniform sampler2D u_turbulenceTexture;
- uniform sampler2D u_blueNoise;
- uniform float u_blueNoiseScale;
- uniform float u_jitterStrength;
- uniform vec3 u_cameraPosition;
- uniform vec3 u_altitudeCorrection;
- uniform float u_cameraHeight;
- uniform float u_bottomRadius;
- uniform float u_minHeight;
- uniform float u_maxHeight;
- uniform vec4 u_minLayerHeights;
- uniform vec4 u_maxLayerHeights;
- uniform vec4 u_densityScales;
- uniform vec4 u_shapeAmounts;
- uniform vec4 u_shapeDetailAmounts;
- uniform vec4 u_weatherExponents;
- uniform vec4 u_shapeAlteringBiases;
- uniform vec4 u_coverageFilterWidths;
- uniform float u_maxSteps;
- uniform float u_maxStepsToSun;
- uniform float u_minStepSize;
- uniform float u_maxStepSize;
- uniform float u_maxRayDistance;
- uniform float u_cameraNear;
- uniform float u_shadowTopHeight;
- uniform int u_shadowLengthEnabled;
- uniform int u_hazeEnabled;
- uniform int u_maxShadowLengthIterationCount;
- uniform float u_minShadowLengthStepSize;
- uniform float u_maxShadowLengthRayDistance;
- uniform float u_hazeDensityScale;
- uniform float u_hazeExponent;
- uniform float u_hazeScatteringCoefficient;
- uniform float u_hazeAbsorptionCoefficient;
- uniform sampler2D u_shadowBuffer;
- uniform vec2 u_shadowTexelSize;
- uniform vec2 u_shadowIntervals[4];
- uniform mat4 u_shadowMatrices[4];
- uniform float u_shadowFar;
- uniform float u_maxShadowFilterRadius;
- uniform int u_useShadowBuffer;
- uniform float u_skyLightScale;
- uniform float u_weatherRepeat;
- uniform vec2 u_localWeatherOffset;
- uniform float u_shapeRepeat;
- uniform vec3 u_shapeOffset;
- uniform float u_shapeDetailRepeat;
- uniform vec3 u_shapeDetailOffset;
- uniform float u_turbulenceRepeat;
- uniform float u_turbulenceDisplacement;
- uniform vec4 u_coverages;
- uniform float u_coverageHaze;
- uniform float u_scatteringCoefficient;
- uniform float u_absorptionCoefficient;
- uniform float u_scatterG1;
- uniform float u_scatterG2;
- uniform float u_scatterMix;
- uniform float u_sunIntensity;
- uniform float u_skyToSunRatio;
- uniform float u_powderScale;
- uniform float u_powderExponent;
- uniform float u_aerialPerspectiveScale;
- uniform float u_cloudExposure;
- uniform float u_magentaFixStrength;
- uniform float u_edgeAlphaCutoff;
- uniform vec2 u_resolution;
- uniform float u_mipLevelScale;
- uniform float u_perspectiveStepScale;
- uniform float u_minDensity;
- uniform float u_minExtinction;
- uniform float u_minTransmittance;
- // 远处云密度距离衰减:从 u_distFadeStart(米)开始线性降到0,到 u_distFadeEnd 完全消失
- // 消除天际线附近云"堆在一起"的视觉拥挤
- uniform float u_distFadeStart;
- uniform float u_distFadeEnd;
- uniform float u_minSecondaryStepSize;
- uniform float u_secondaryStepScale;
- uniform int u_multiScatteringOctaves;
- uniform float u_lowLayerDensityBoost;
- uniform vec4 u_densityProfileExpTerms;
- uniform vec4 u_densityProfileExponents;
- uniform vec4 u_densityProfileLinearTerms;
- uniform vec4 u_densityProfileConstantTerms;
- uniform vec3 u_minIntervalHeights;
- uniform vec3 u_maxIntervalHeights;
- uniform sampler2D u_historyTexture;
- uniform mat4 u_prevViewProjection;
- uniform float u_temporalAlpha;
- uniform int u_temporalEnabled;
- uniform int u_frame;
- in vec2 v_textureCoordinates;
- vec3 ACESFilmic(vec3 x) {
- float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;
- return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
- }
- float saturate(float x) { return clamp(x, 0.0, 1.0); }
- vec4 saturate(vec4 x) { return clamp(x, 0.0, 1.0); }
- float remap(float v, float a, float b, float c, float d) { return c + (v - a) * (d - c) / (b - a); }
- float remapClamped(float v, float a, float b, float c, float d) { return clamp(remap(v, a, b, c, d), min(c, d), max(c, d)); }
- float remapClamped(float v, float a, float b) { return clamp((v - a) / (b - a), 0.0, 1.0); }
- vec4 remap(vec4 v, vec4 a, vec4 b, vec4 c, vec4 d) { return c + (v - a) * (d - c) / (b - a); }
- vec4 remapClamped(vec4 v, vec4 a, vec4 b, vec4 c, vec4 d) { return clamp(remap(v, a, b, c, d), min(c, d), max(c, d)); }
- vec4 remapClamped(vec4 v, vec4 a, vec4 b) { return clamp((v - a) / (b - a), 0.0, 1.0); }
- vec3 reduceMagenta(vec3 color, float strength) {
- float magenta = max(0.0, min(color.r, color.b) - color.g);
- float fix = clamp(magenta * 5.0 * max(strength, 0.0), 0.0, 1.0);
- float target = color.g;
- color.r = mix(color.r, target, fix);
- color.b = mix(color.b, target, fix);
- return color;
- }
- vec2 raySphereIntersect(vec3 ro, vec3 rd, float radius) {
- float b = dot(ro, rd);
- float c = dot(ro, ro) - radius * radius;
- float h = b * b - c;
- if (h < 0.0) return vec2(-1.0);
- h = sqrt(h);
- return vec2(-b - h, -b + h);
- }
- void reconstructRay(out vec3 ro, out vec3 rd) {
- ro = u_cameraPosition + u_altitudeCorrection;
- vec2 uv = v_textureCoordinates * 2.0 - 1.0;
- vec4 clipPos = vec4(uv, 1.0, 1.0);
- vec4 viewPos = czm_inverseProjection * clipPos;
- viewPos /= viewPos.w;
- vec4 worldPos4 = czm_inverseView * viewPos;
- vec3 worldPos = worldPos4.xyz + u_altitudeCorrection;
- rd = normalize(worldPos - ro);
- }
- float getSTBN() {
- // 与 three-geospatial 一致:按帧在 3D STBN 的 z 维切片轮换
- ivec3 size = textureSize(u_stbnTexture, 0);
- vec3 scale = 1.0 / vec3(size);
- return texture(
- u_stbnTexture,
- vec3(gl_FragCoord.xy, float(u_frame % size.z)) * scale
- ).r;
- }
- vec2 getCubeSphereUv(vec3 position) {
- vec3 n = normalize(position);
- vec3 f = abs(n);
- vec3 c = n / max(f.x, max(f.y, f.z));
- vec2 m;
- if (f.y >= f.x && f.y >= f.z) { m = c.y > 0.0 ? vec2(-n.x, n.z) : n.xz; }
- else if (f.x >= f.y && f.x >= f.z) { m = c.x > 0.0 ? n.yz : vec2(-n.y, n.z); }
- else { m = c.z > 0.0 ? n.xy : vec2(n.x, -n.y); }
- vec2 m2 = m * m;
- float q = dot(m2.xy, vec2(-2.0, 2.0)) - 3.0;
- float q2 = q * q;
- vec2 uv;
- uv.x = sqrt(1.5 + m2.x - m2.y - 0.5 * sqrt(max(0.0, -24.0 * m2.x + q2))) * (m.x > 0.0 ? 1.0 : -1.0);
- uv.y = sqrt(6.0 / max(0.001, 3.0 - uv.x * uv.x)) * m.y;
- return uv * 0.5 + 0.5;
- }
- vec2 getGlobeUv(vec3 position) { return getCubeSphereUv(position); }
- float getMipLevel(vec2 uv) {
- vec2 coord = uv * u_resolution;
- vec2 ddx_v = dFdx(coord);
- vec2 ddy_v = dFdy(coord);
- float deltaMaxSqr = max(dot(ddx_v, ddx_v), dot(ddy_v, ddy_v)) * 0.1;
- return max(0.0, 0.5 * log2(max(1.0, deltaMaxSqr)));
- }
- bool inEmptySpace(float height) {
- bvec3 gt = greaterThan(vec3(height), u_minIntervalHeights);
- bvec3 lt = lessThan(vec3(height), u_maxIntervalHeights);
- return gt.x && lt.x || gt.y && lt.y || gt.z && lt.z;
- }
- vec4 getLayerDensity(vec4 hf) {
- return u_densityProfileExpTerms * exp(u_densityProfileExponents * hf) + u_densityProfileLinearTerms * hf + u_densityProfileConstantTerms;
- }
- vec4 getHeightFractions(float height) {
- vec4 range = u_maxLayerHeights - u_minLayerHeights;
- return clamp((vec4(height) - u_minLayerHeights) / max(range, vec4(0.0001)), 0.0, 1.0);
- }
- struct WeatherSample { vec4 heightFraction; vec4 density; };
- struct MediaSample { float density; vec4 weight; float scattering; float extinction; };
- vec4 shapeAlteringFunction(vec4 hf, vec4 bias) {
- vec4 biased = pow(hf, bias);
- vec4 x = clamp(biased * 2.0 - 1.0, -1.0, 1.0);
- return 1.0 - x * x;
- }
- WeatherSample sampleWeather(vec2 uv, float height, float mipLevel) {
- WeatherSample w;
- w.heightFraction = getHeightFractions(height);
- vec2 wUv = uv * u_weatherRepeat + u_localWeatherOffset;
- vec4 localW = pow(textureLod(u_weatherTexture, wUv, mipLevel).rgba, u_weatherExponents);
- vec4 hs = shapeAlteringFunction(w.heightFraction, u_shapeAlteringBiases);
- vec4 factor = 1.0 - u_coverages * hs;
- w.density = remapClamped(mix(localW, vec4(1.0), u_coverageFilterWidths), factor, factor + u_coverageFilterWidths);
- return w;
- }
- MediaSample sampleMedia(WeatherSample weather, vec3 position, vec2 uv, float mipLevel, float jitter) {
- vec4 density = weather.density;
- vec3 sn = normalize(position);
- vec3 evolution = -sn * length(u_localWeatherOffset) * EVOLUTION_SCALE;
- vec2 tUv = uv * u_weatherRepeat * u_turbulenceRepeat;
- vec3 turb = u_turbulenceDisplacement * (texture(u_turbulenceTexture, tUv).rgb * 2.0 - 1.0)
- * dot(density, remapClamped(weather.heightFraction, vec4(0.3), vec4(0.0)));
- vec3 sp = (position + evolution + turb) * u_shapeRepeat + u_shapeOffset;
- float shapeTex = texture(u_shapeTexture, fract(sp)).r;
- density = remapClamped(density, vec4(1.0 - shapeTex) * u_shapeAmounts, vec4(1.0));
- if (mipLevel * 0.5 + (jitter - 0.5) * 0.5 < 0.5) {
- vec3 dp = (position + turb) * u_shapeDetailRepeat + u_shapeDetailOffset;
- float detail = texture(u_shapeDetailTexture, dp).r;
- vec4 modifier = mix(vec4(pow(detail, 6.0)), vec4(1.0 - detail),
- remapClamped(weather.heightFraction, vec4(0.2), vec4(0.4), vec4(0.0), vec4(1.0)));
- modifier = mix(vec4(0.0), modifier, u_shapeDetailAmounts);
- density = remapClamped(density * 2.0, vec4(modifier * 0.5), vec4(1.0));
- }
- density = saturate(density * u_densityScales * getLayerDensity(weather.heightFraction));
- float ds = density.x + density.y + density.z + density.w;
- MediaSample m;
- m.density = ds;
- m.weight = density / max(ds, 1e-7);
- m.scattering = ds * u_scatteringCoefficient;
- m.extinction = ds * u_absorptionCoefficient + m.scattering;
- return m;
- }
- float henyeyGreenstein(float g, float cosTheta) {
- float g2 = g * g;
- return RECIPROCAL_PI4 * (1.0 - g2) / pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5);
- }
- float phaseFunction(float cosTheta, float attenuation) {
- return mix(henyeyGreenstein(u_scatterG1 * attenuation, cosTheta),
- henyeyGreenstein(u_scatterG2 * attenuation, cosTheta), u_scatterMix);
- }
- float approximateMultipleScattering(float opticalDepth, float cosTheta) {
- vec3 coeffs = vec3(1.0);
- const vec3 attenuation = vec3(0.5);
- float scattering = 0.0;
- for (int i = 0; i < 12; i++) {
- if (i >= u_multiScatteringOctaves) break;
- scattering += coeffs.x * exp(-opticalDepth * coeffs.y) * phaseFunction(cosTheta, coeffs.z);
- coeffs *= attenuation;
- }
- return scattering;
- }
- float marchOpticalDepthToSun(vec3 rayOrigin, vec3 rayDirection, float mipLevel, float jitter, out float sunRayDist) {
- float iterCount = max(0.0, remap(mipLevel, 0.0, 1.0, float(u_maxStepsToSun) + 1.0, 1.0) - jitter);
- int ic = int(iterCount);
- if (ic == 0) return 0.5;
- float stepSize = u_minSecondaryStepSize / iterCount;
- float nextDist = stepSize * jitter;
- float od = 0.0;
- sunRayDist = 0.0;
- for (int i = 0; i < 8; i++) {
- if (i >= ic) break;
- sunRayDist = nextDist;
- vec3 pos = rayDirection * nextDist + rayOrigin;
- vec2 uv = getGlobeUv(pos);
- float h = length(pos) - u_bottomRadius;
- WeatherSample ws = sampleWeather(uv, h, mipLevel);
- MediaSample ms = sampleMedia(ws, pos, uv, mipLevel, jitter);
- od += ms.extinction * stepSize;
- nextDist += stepSize;
- stepSize *= u_secondaryStepScale;
- }
- return od;
- }
- bool rayIntersectsGround(vec3 camPos, vec3 rd) {
- float r = length(camPos);
- float mu = dot(camPos, rd) / r;
- return mu < 0.0 && r * r * (mu * mu - 1.0) + u_bottomRadius * u_bottomRadius >= 0.0;
- }
- void raySphereIntersections(vec3 origin, vec3 direction, vec4 radius, out vec4 i1, out vec4 i2) {
- float b = 2.0 * dot(direction, origin);
- vec4 c = dot(origin, origin) - radius * radius;
- vec4 disc = b * b - 4.0 * c;
- vec4 mask = step(disc, vec4(0.0));
- vec4 Q = sqrt(max(vec4(0.0), disc));
- i1 = mix((-b - Q) * 0.5, vec4(-1.0), mask);
- i2 = mix((-b + Q) * 0.5, vec4(-1.0), mask);
- }
- void getIntersections(vec3 camPos, vec3 rd, out bool ground, out vec4 first, out vec4 second) {
- ground = rayIntersectsGround(camPos, rd);
- vec4 radii = u_bottomRadius + vec4(0.0, u_minHeight, u_maxHeight, u_shadowTopHeight);
- raySphereIntersections(camPos, rd, radii, first, second);
- }
- vec2 getRayNearFar(bool ground, vec4 first, vec4 second) {
- vec2 nearFar;
- if (u_cameraHeight < u_minHeight) {
- if (ground) {
- nearFar = vec2(-1.0);
- } else {
- nearFar = vec2(second.y, second.z);
- nearFar.y = min(nearFar.y, u_maxRayDistance);
- }
- } else if (u_cameraHeight < u_maxHeight) {
- if (ground) {
- // 地面相交时,采样从相机近裁面到云层下边界
- nearFar = vec2(u_cameraNear, first.y);
- if (nearFar.y <= nearFar.x) nearFar = vec2(-1.0);
- } else {
- float farExit = max(max(first.y, second.y), max(first.z, second.z));
- if (farExit <= 0.0) {
- // 无有效远边界时,强制采样到最大射线距离
- farExit = u_maxRayDistance;
- }
- farExit = min(farExit, u_maxRayDistance);
- farExit = max(farExit, u_cameraNear + u_minStepSize * 0.5);
- nearFar = vec2(u_cameraNear, farExit);
- }
- } else {
- float farExit = max(max(first.y, second.y), max(first.z, second.z));
- if (farExit > 0.0) {
- farExit = min(farExit, u_maxRayDistance);
- farExit = max(farExit, u_cameraNear + u_minStepSize * 0.5);
- nearFar = vec2(u_cameraNear, farExit);
- }
- }
- return nearFar;
- }
- vec2 getShadowRayNearFar(bool ground, vec4 first, vec4 second) {
- vec2 nf;
- if (u_cameraHeight < u_shadowTopHeight) {
- nf = ground ? vec2(u_cameraNear, first.x) : vec2(u_cameraNear, second.w);
- } else {
- nf = vec2(first.w, second.w);
- if (ground) nf.y = first.x;
- }
- nf.y = min(nf.y, u_maxShadowLengthRayDistance);
- return nf;
- }
- vec2 getHazeRayNearFar(bool ground, vec4 first, vec4 second) {
- vec2 nf;
- if (u_cameraHeight < u_maxHeight) {
- nf = ground ? vec2(u_cameraNear, first.x) : vec2(u_cameraNear, second.z);
- } else {
- nf = vec2(u_cameraNear, second.z);
- if (ground) nf.y = first.x;
- }
- return nf;
- }
- // ── BSM sampling ──
- float sampleShadowOpticalDepth(vec3 rayPosition, float distanceOffset, float radius, float jitter);
- float getDistanceToShadowTop(vec3 rayPos) {
- vec3 rd = czm_sunDirectionWC;
- float R = u_bottomRadius + u_shadowTopHeight;
- float b = dot(rayPos, rd);
- float c = dot(rayPos, rayPos) - R * R;
- float h = b * b - c;
- if (h < 0.0) return -1.0;
- return -b + sqrt(h);
- }
- // three.js / CloudShadowPass intervals=(d-near)/(far-near) 一致
- float viewZToOrthographicDepth(float viewZ, float near, float far) {
- return (viewZ + near) / (near - far);
- }
- int getFadedCascadeIndex(mat4 viewMat, vec3 worldPos, vec2 intervals[4], float near, float far, float jitter) {
- vec4 vp = viewMat * vec4(worldPos, 1.0);
- float depth = viewZToOrthographicDepth(vp.z, near, far);
- int nextIndex = -1, prevIndex = -1;
- float alpha = 1.0;
- for (int i = 0; i < 4; ++i) {
- vec2 interval = intervals[i];
- float intervalCenter = (interval.x + interval.y) * 0.5;
- float closestEdge = depth < intervalCenter ? interval.x : interval.y;
- float margin = closestEdge * closestEdge * 0.5;
- interval += margin * vec2(-0.5, 0.5);
- if (i < 3) {
- if (depth >= interval.x && depth < interval.y) { prevIndex = nextIndex; nextIndex = i; alpha = saturate(min(depth - interval.x, interval.y - depth) / max(margin, 1e-6)); }
- } else {
- if (depth >= interval.x) { prevIndex = nextIndex; nextIndex = i; alpha = saturate((depth - interval.x) / max(margin, 1e-6)); }
- }
- }
- return jitter <= alpha ? nextIndex : prevIndex;
- }
- vec2 getShadowUv(vec3 pos, int ci) { vec4 clip = u_shadowMatrices[ci] * vec4(pos, 1.0); clip /= clip.w; return clip.xy * 0.5 + 0.5; }
- vec2 getShadowAtlasOffset(int ci) { return vec2(mod(float(ci), 2.0) * 0.5, (ci < 2) ? 0.5 : 0.0); }
- float readShadowOpticalDepth(vec2 uv, int ci, float distToTop, float distOff) {
- if (u_useShadowBuffer == 0) return 0.0;
- vec2 atlasUv = getShadowAtlasOffset(ci) + uv * 0.5;
- vec4 shadow = texture(u_shadowBuffer, atlasUv);
- float distToFront = max(0.0, distToTop - distOff - shadow.r);
- return min(shadow.b + shadow.a, shadow.g * distToFront);
- }
- float interleavedGradientNoise(vec2 coord) {
- const vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189);
- return fract(magic.z * fract(dot(coord, magic.xy)));
- }
- vec2 vogelDisk(int index, int count, float phi) {
- const float goldenAngle = 2.39996322972865332;
- float r = sqrt(float(index) + 0.5) / sqrt(float(count));
- float theta = float(index) * goldenAngle + phi;
- return r * vec2(cos(theta), sin(theta));
- }
- float sampleShadowOpticalDepthPCF(vec3 worldPos, float distToTop, float distOff, float radius, int ci) {
- vec2 uv = getShadowUv(worldPos, ci);
- if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return 0.0;
- if (radius < 0.1) return readShadowOpticalDepth(uv, ci, distToTop, distOff);
- float sum = 0.0;
- float phi = interleavedGradientNoise(gl_FragCoord.xy) * 3.14159265 * 2.0;
- for (int i = 0; i < 16; ++i) sum += readShadowOpticalDepth(uv + vogelDisk(i, 16, phi) * radius * u_shadowTexelSize, ci, distToTop, distOff);
- return sum / 16.0;
- }
- float sampleShadowOpticalDepth(vec3 rayPos, float distOff, float radius, float jitter) {
- float distToTop = getDistanceToShadowTop(rayPos);
- if (distToTop <= 0.0) return 0.0;
- int ci = getFadedCascadeIndex(czm_view, rayPos, u_shadowIntervals, u_cameraNear, u_shadowFar, jitter);
- return ci >= 0 ? sampleShadowOpticalDepthPCF(rayPos, distToTop, distOff, radius, ci) : 0.0;
- }
- float marchShadowLength(vec3 rayOrigin, vec3 rayDir, vec2 rayNearFar, float jitter) {
- float shadowLen = 0.0;
- float maxDist = rayNearFar.y - rayNearFar.x;
- float stepSize = u_minShadowLengthStepSize;
- float rayDist = stepSize * jitter;
- for (int i = 0; i < 512; i++) {
- if (float(i) >= float(u_maxShadowLengthIterationCount)) break;
- if (rayDist > maxDist) break;
- vec3 pos = rayDir * rayDist + rayOrigin;
- float od = sampleShadowOpticalDepth(pos, 0.0, 0.0, jitter);
- shadowLen += (1.0 - exp(-od)) * stepSize;
- stepSize *= u_perspectiveStepScale;
- rayDist += stepSize;
- }
- return shadowLen;
- }
- #ifdef USE_ATMOSPHERE_IRRADIANCE
- void applyAerialPerspective(vec3 camPos, vec3 frontPos, float dist, float shadowLen, inout vec4 color) {
- vec3 transmittance;
- vec3 inscatter = GetSkyRadianceToPoint(camPos * METER_TO_LENGTH_UNIT, frontPos * METER_TO_LENGTH_UNIT, shadowLen * METER_TO_LENGTH_UNIT, sunDirection, transmittance);
- float horizonBias = smoothstep(20.0, 80.0, dist * METER_TO_LENGTH_UNIT);
- // 【新增】根据太阳高度计算可见度,晚上太阳沉下地平线时 sunVis 为 0
- float sunVis = smoothstep(-0.02, 0.05, dot(normalize(camPos), sunDirection));
- vec3 fakeHorizonColor = vec3(0.5, 0.6, 0.8) * 0.2 * sunVis;
- inscatter = mix(inscatter, inscatter * 0.4 + fakeHorizonColor, horizonBias);
- color.rgb = color.rgb * transmittance + inscatter * color.a * u_aerialPerspectiveScale;
- }
- #else
- void applyAerialPerspective(vec3 camPos, vec3 frontPos, float dist, float shadowLen, inout vec4 color) {
- vec3 rayleigh = vec3(0.005802, 0.013558, 0.033100) * 0.001;
- float h = length(frontPos) - u_bottomRadius;
- float density = exp(-h / 8000.0);
- vec3 transmittance = exp(-dist * rayleigh * density * u_aerialPerspectiveScale);
- // 【新增】太阳可见度衰减
- float sunVis = smoothstep(-0.05, 0.1, dot(normalize(camPos), sunDirection));
- vec3 skyColor = vec3(0.4, 0.6, 1.0) * u_sunIntensity * 0.02 * sunVis;
- color.rgb = color.rgb * transmittance + skyColor * (1.0 - transmittance) * color.a;
- }
- #endif
- vec4 approximateHaze(vec3 ro, vec3 rd, float maxDist, float cosTheta, float shadowLen) {
- float modulation = remapClamped(u_coverageHaze, 0.2, 0.4);
- if (u_cameraHeight * modulation < 0.0) return vec4(0.0);
- float density = modulation * u_hazeDensityScale * exp(-u_cameraHeight * u_hazeExponent);
- if (density < 1e-7) return vec4(0.0);
- vec3 nOrigin = normalize(ro);
- float sunHeight = dot(nOrigin, sunDirection);
- float sunVis = smoothstep(-0.02, 0.05, sunHeight);
- float viewZenith = abs(rd.y);
- float horizonTaming = smoothstep(0.0, 0.15, viewZenith);
- // 即使在天际线,也要保留一点点基础亮度,但不能是 1.0
- horizonTaming = mix(0.3, 1.0, horizonTaming);
- vec3 nHoriz = (ro - dot(ro, rd) * rd) / u_bottomRadius;
- float alpha = remapClamped(dot(nOrigin, nHoriz), 0.9, 1.0);
- vec3 normal = mix(nOrigin, nHoriz, alpha);
- float angle = max(dot(normal, rd), 1e-5);
- float exponent = angle * u_hazeExponent;
- float linearTerm = density / u_hazeExponent / angle;
- float expTerm = 1.0 - exp(-maxDist * exponent);
- float shadowExpTerm = 1.0 - exp(-min(maxDist, shadowLen) * exponent);
- float opticalDepth = expTerm * linearTerm;
- float shadowOD = max((expTerm - shadowExpTerm) * linearTerm, 0.0);
- float transmittance = saturate(1.0 - exp(-opticalDepth));
- float shadowTransmittance = saturate(1.0 - exp(-shadowOD));
- // 【修改】将硬编码的光源强度乘以太阳可见度
- vec3 skyIrradiance = vec3(0.4, 0.6, 1.0) * u_sunIntensity * 0.04 * sunVis * horizonTaming;
- vec3 sunIrradiance = vec3(1.0, 0.95, 0.9) * u_sunIntensity * sunVis;
- float ph = henyeyGreenstein(u_scatterG1, cosTheta) * (1.0 - u_scatterMix) + henyeyGreenstein(u_scatterG2, cosTheta) * u_scatterMix;
- vec3 inscatter = sunIrradiance * ph * shadowTransmittance + skyIrradiance * RECIPROCAL_PI4 * u_skyLightScale * transmittance;
- inscatter *= u_hazeScatteringCoefficient / (u_hazeAbsorptionCoefficient + u_hazeScatteringCoefficient);
- return vec4(inscatter, transmittance);
- }
- // ── Main raymarch ──
- vec4 marchClouds(vec3 rayOrigin, vec3 rd, vec2 rayNearFar, float cosTheta, float jitter, float rayStartTexels, out float frontDepth) {
- float maxDist = min(rayNearFar.y - rayNearFar.x, u_maxRayDistance);
- vec3 radInt = vec3(0.0);
- float transInt = 1.0, wdSum = 0.0, tSum = 0.0;
- float perspDist = min(rayNearFar.x, 3000.0);
- float stepSize = u_minStepSize + (u_perspectiveStepScale - 1.0) * perspDist;
- float rayDist = stepSize * jitter * 2.0;
- #ifdef USE_ATMOSPHERE_IRRADIANCE
- float refRadius = u_bottomRadius;
- #else
- vec3 sunColorBase = vec3(1.0, 0.95, 0.9) * u_sunIntensity;
- vec3 skyColorBase = vec3(0.4, 0.6, 1.0) * u_sunIntensity * u_skyToSunRatio;
- float refRadius = u_bottomRadius;
- vec3 sunDirection = czm_sunDirectionWC;
- #endif
- for (int i = 0; i < 512; i++) {
- if (float(i) >= u_maxSteps) break;
- if (rayDist > maxDist) break;
- if (transInt <= u_minTransmittance) break;
- vec3 position = rayOrigin + rd * rayDist;
- float height = length(position) - refRadius;
- float mipLevel = log2(max(1.0, rayStartTexels + rayDist * 1e-5));
- if (inEmptySpace(height)) { stepSize *= u_perspectiveStepScale; rayDist += mix(stepSize, u_maxStepSize, min(1.0, mipLevel)); continue; }
- vec2 uv = getGlobeUv(position);
- WeatherSample weather = sampleWeather(uv, height, mipLevel);
- if (!any(greaterThan(weather.density, vec4(u_minDensity)))) { stepSize *= u_perspectiveStepScale; rayDist += mix(stepSize, u_maxStepSize, min(1.0, mipLevel)); continue; }
- weather.density.xy *= u_lowLayerDensityBoost;
- MediaSample media = sampleMedia(weather, position, uv, mipLevel, jitter);
- if (media.extinction > u_minExtinction) {
- #ifdef USE_ATMOSPHERE_IRRADIANCE
- vec3 skyIrradiance;
- vec3 sunIrradiance = GetSunAndSkyScalarIrradiance(position * METER_TO_LENGTH_UNIT, sunDirection, skyIrradiance);
- float skyGradient = dot(weather.heightFraction * 0.5 + 0.5, media.weight);
- vec3 sunColor = sunIrradiance * u_sunIntensity;
- vec3 skyColor = skyIrradiance * u_sunIntensity * u_skyToSunRatio;
- #else
- float heightAlpha = clamp((height - u_minHeight) / max(u_maxHeight - u_minHeight, 1.0), 0.0, 1.0);
- vec3 sunColor = mix(sunColorBase * 0.85, sunColorBase, heightAlpha);
- vec3 skyColor = mix(skyColorBase * 0.6, skyColorBase, heightAlpha);
- float skyGradient = dot(weather.heightFraction * 0.5 + 0.5, media.weight);
- #endif
- float sunRayDist;
- float opticalDepth = marchOpticalDepthToSun(position, sunDirection, mipLevel, jitter, sunRayDist);
- if (length(position) - refRadius < u_shadowTopHeight && u_useShadowBuffer == 1) {
- vec3 sn = normalize(position);
- float r = u_maxShadowFilterRadius * remapClamped(dot(sunDirection, sn), 0.1, 0.0);
- opticalDepth += sampleShadowOpticalDepth(position, sunRayDist, r, jitter);
- }
- vec3 radiance = sunColor * approximateMultipleScattering(opticalDepth, cosTheta);
- radiance += skyColor * RECIPROCAL_PI4 * skyGradient * u_skyLightScale;
- radiance *= media.scattering * (1.0 - u_powderScale * exp(-media.extinction * u_powderExponent));
- float transmittance = exp(-media.extinction * stepSize);
- vec3 scatInt = (radiance - radiance * transmittance) / max(media.extinction, 1e-7);
- radInt += transInt * scatInt;
- transInt *= transmittance;
- wdSum += rayDist * transInt;
- tSum += transInt;
- }
- stepSize *= u_perspectiveStepScale;
- rayDist += stepSize;
- }
- frontDepth = tSum > 0.0 ? wdSum / tSum : -1.0;
- float alpha = saturate(remapClamped(transInt, 1.0, u_minTransmittance));
- return vec4(radInt, alpha);
- }
- void main() {
- vec4 sceneColor = texture(colorTexture, v_textureCoordinates);
- float depth = czm_readDepth(depthTexture, v_textureCoordinates);
- vec3 ro, rd;
- reconstructRay(ro, rd);
- #ifndef USE_ATMOSPHERE_IRRADIANCE
- vec3 sunDirection = czm_sunDirectionWC;
- #endif
- float jitter = getSTBN();
- bool ground; vec4 first, second;
- getIntersections(ro, rd, ground, first, second);
- vec2 rayNearFar = getRayNearFar(ground, first, second);
- vec2 shadowNF = vec2(-1.0), hazeNF = vec2(-1.0);
- if (u_shadowLengthEnabled == 1) shadowNF = getShadowRayNearFar(ground, first, second);
- if (u_hazeEnabled == 1) hazeNF = getHazeRayNearFar(ground, first, second);
- // depthTestAgainstTerrain 只影响 Globe/贴地物体与地形网格的深度关系,不能替后处理修正「沿像素射线」的距离。
- // 此处必须用 inverseView 还原命中点,再沿 rd 求距离;用 -viewZ/dot(rd, forward) 在离轴像素上会偏大 → 云压在地形前。
- float rayDistToScene = 0.0;
- if (depth < 1.0 - 1e-7) {
- vec4 eyePos = czm_windowToEyeCoordinates(vec4(gl_FragCoord.xy, depth, 1.0));
- if (abs(eyePos.w) > 1e-6) {
- eyePos /= eyePos.w;
- if (eyePos.z < 0.0) {
- vec4 worldPos4 = czm_inverseView * eyePos;
- vec3 worldHit = worldPos4.xyz + u_altitudeCorrection;
- rayDistToScene = max(0.0, dot(worldHit - ro, rd));
- }
- }
- }
- float tMax = rayNearFar.y;
- // 原逻辑:低于云层且 !ground 时跳过深度钳位 —— 平视/看山体时 ground 常为 false,会整屏不钳位 → 云盖住地形。
- // 仅当该像素无场景深度(天空)时才允许跳过;有地形/几何时必须用 rayDistToScene 截断射线。
- const float DEPTH_SKY = 1.0 - 1e-7;
- bool skipDepthClamp =
- (depth >= DEPTH_SKY) && (u_cameraHeight < u_minHeight) && (!ground);
- if (rayDistToScene > 0.0 && !skipDepthClamp) {
- tMax = min(tMax, rayDistToScene);
- if (u_shadowLengthEnabled == 1 && shadowNF.y > 0.0) shadowNF.y = min(shadowNF.y, rayDistToScene);
- if (u_hazeEnabled == 1 && hazeNF.y > 0.0) hazeNF.y = min(hazeNF.y, rayDistToScene);
- }
- if (rayNearFar.x >= tMax) { gl_FragColor = sceneColor; return; }
- float frontDepth;
- float cosTheta = dot(rd, sunDirection);
- vec2 globeUv = getGlobeUv(ro + rd * rayNearFar.x);
- float mipLevel = getMipLevel(globeUv * u_weatherRepeat) * u_mipLevelScale;
- mipLevel = mix(0.0, mipLevel, min(1.0, 0.2 * u_cameraHeight / max(u_maxHeight, 1.0)));
- vec4 cloudColor = marchClouds(ro + rd * rayNearFar.x, rd, vec2(rayNearFar.x, tMax), cosTheta, jitter, pow(2.0, mipLevel), frontDepth);
- // 远处云透明度距离衰减:用"相机到云层入口距离"(rayNearFar.x)衰减 alpha,
- // 而非云内穿行距离。天顶云入口近不衰减,天际线云入口远衰减——只压远处透明度,不影响各层密度。
- // 这解决斜射时云层路径长导致 alpha 堆积的问题,且不误伤高空稀疏层(层2 近处不衰减)。
- float entryFade = 1.0 - smoothstep(u_distFadeStart, u_distFadeEnd, rayNearFar.x);
- cloudColor.a *= entryFade;
- cloudColor.rgb *= entryFade;
- float shadowLen = 0.0;
- bool hitClouds = frontDepth > 0.0 && cloudColor.a > max(u_edgeAlphaCutoff, 0.02);
- float rayFrontT = rayNearFar.x + frontDepth;
- if (hitClouds) {
- if (u_shadowLengthEnabled == 1 && all(greaterThanEqual(shadowNF, vec2(0.0)))) {
- shadowNF.y = mix(shadowNF.y, min(rayFrontT, shadowNF.y), cloudColor.a);
- shadowLen = marchShadowLength(ro + rd * shadowNF.x, rd, shadowNF, jitter);
- }
- if (u_hazeEnabled == 1 && all(greaterThanEqual(hazeNF, vec2(0.0))))
- hazeNF.y = mix(hazeNF.y, min(rayFrontT, hazeNF.y), cloudColor.a);
- applyAerialPerspective(ro, ro + rd * rayFrontT, rayFrontT, shadowLen, cloudColor);
- } else if (u_shadowLengthEnabled == 1 && all(greaterThanEqual(shadowNF, vec2(0.0)))) {
- shadowLen = marchShadowLength(ro + rd * shadowNF.x, rd, shadowNF, jitter);
- }
- if (u_hazeEnabled == 1) {
- float hazeDist = all(greaterThanEqual(hazeNF, vec2(0.0))) ? (hazeNF.y - hazeNF.x) : 0.0;
- vec4 haze = approximateHaze(ro, rd, hazeDist, cosTheta, shadowLen);
- cloudColor.rgb = mix(cloudColor.rgb, haze.rgb, haze.a);
- cloudColor.a = cloudColor.a * (1.0 - haze.a) + haze.a;
- }
- // 边缘裁剪:低 alpha 区域直接清零,避免云边缘细碎噪点与闪烁
- if (cloudColor.a < u_edgeAlphaCutoff) {
- cloudColor = vec4(0.0);
- }
- // 边缘裁剪后再判一次:防止“已被裁掉的薄云像素”仍进入 TAA,导致底层模型抖动
- hitClouds = hitClouds && (cloudColor.a > max(u_edgeAlphaCutoff, 0.02));
- // 边缘稳噪:低 alpha 处直接除以 alpha 会把随机误差放大成亮点/闪点
- float edgeSafeAlpha = max(cloudColor.a, 0.08);
- vec3 cloudActual = cloudColor.rgb / edgeSafeAlpha;
- cloudActual = ACESFilmic(cloudActual * u_cloudExposure);
- cloudActual = pow(cloudActual, vec3(1.0 / 2.2));
- vec4 composited = vec4(
- sceneColor.rgb * (1.0 - cloudColor.a) + cloudActual * cloudColor.a,
- // 让 history.a 表示“云覆盖度”,用于后续 TAA 历史有效性判定
- cloudColor.a
- );
- // 在最终云合成色上去品红,按云覆盖度加权,确保无云区域不受影响
- vec3 compositedNoMagenta = reduceMagenta(composited.rgb, u_magentaFixStrength);
- float cloudW = smoothstep(0.02, 0.3, cloudColor.a);
- composited.rgb = mix(composited.rgb, compositedNoMagenta, cloudW);
- if (u_temporalEnabled > 0 && hitClouds) {
- vec3 worldPos = ro + rd * rayFrontT - u_altitudeCorrection;
- vec4 prevClip = u_prevViewProjection * vec4(worldPos, 1.0);
- vec2 prevUv = (prevClip.xy / prevClip.w) * 0.5 + 0.5;
- if (prevUv.x >= 0.0 && prevUv.x <= 1.0 && prevUv.y >= 0.0 && prevUv.y <= 1.0) {
- vec4 history = texture(u_historyTexture, prevUv);
- // TAA 仅对“云增量”做融合,底层模型保持当前帧,减少模型虚影
- vec3 deltaNow = composited.rgb - sceneColor.rgb;
- vec3 deltaHist = history.rgb - sceneColor.rgb;
- float maxDiff = max(abs(deltaHist.r - deltaNow.r), max(abs(deltaHist.g - deltaNow.g), abs(deltaHist.b - deltaNow.b)));
- float reject = max(
- smoothstep(0.35, 0.75, maxDiff),
- smoothstep(0.004, 0.03, length(prevUv - v_textureCoordinates))
- );
- // 让低透明边缘也参与历史融合,抑制云边缘噪点“跳闪”
- float opacityW = smoothstep(0.015, 0.25, cloudColor.a);
- float a = mix(1.0, mix(u_temporalAlpha, 1.0, reject), opacityW);
- // 仅当“当前与历史”都存在足够云覆盖时才使用历史,避免把模型底色抖动带入
- float historyCloudW = smoothstep(0.02, 0.12, history.a);
- float currentCloudW = smoothstep(0.02, 0.12, cloudColor.a);
- float cloudHistoryValidity = min(historyCloudW, currentCloudW);
- a = mix(1.0, a, cloudHistoryValidity);
- vec3 deltaFiltered = mix(deltaHist, deltaNow, a);
- composited.rgb = sceneColor.rgb + deltaFiltered;
- composited.a = cloudColor.a;
- }
- }
- gl_FragColor = composited;
- }
- `;
- }
- // ─── Helper: compile & link GL program ─────────────────────────────────────
- function createGLProgram(gl, vsSource, fsSource, label) {
- const vs = gl.createShader(gl.VERTEX_SHADER);
- gl.shaderSource(vs, vsSource);
- gl.compileShader(vs);
- if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) { console.error(`[${label}] VS:`, gl.getShaderInfoLog(vs)); gl.deleteShader(vs); return null; }
- const fs = gl.createShader(gl.FRAGMENT_SHADER);
- gl.shaderSource(fs, fsSource);
- gl.compileShader(fs);
- if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) { console.error(`[${label}] FS:`, gl.getShaderInfoLog(fs)); gl.deleteShader(vs); gl.deleteShader(fs); return null; }
- const prog = gl.createProgram();
- gl.attachShader(prog, vs); gl.attachShader(prog, fs); gl.linkProgram(prog);
- gl.deleteShader(vs); gl.deleteShader(fs);
- if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { console.error(`[${label}] link:`, gl.getProgramInfoLog(prog)); gl.deleteProgram(prog); return null; }
- return prog;
- }
- // ─── Main pipeline class ──────────────────────────────────────────────────
- export class ThreeGeospatialPipeline {
- constructor(viewer, options = {}) {
- this.viewer = viewer;
- this.atmosphereParams = options.atmosphereParams ?? new AtmosphereParameters();
- this._frameCount = 0;
- this._gui = null;
- // 可配置的资源/shader 根路径(均带默认值,便于在任意部署路径下使用)
- this.assetsBase = options.cloudsAssetsBase ?? DEFAULT_CLOUDS_ASSETS_BASE;
- this.brunetonShaderBase = options.brunetonShaderBase ?? DEFAULT_BRUNETON_SHADER_BASE;
- this.blueNoiseUrl = options.blueNoiseUrl ?? DEFAULT_BLUE_NOISE_URL;
- this.atmosphereAssetsBase = options.atmosphereAssetsBase ?? DEFAULT_ATMOSPHERE_ASSETS_BASE;
- this.atmosphereShaderBase = options.atmosphereShaderBase ?? DEFAULT_ATMOSPHERE_SHADER_BASE;
- this.params = {
- cloudsVisible: true,
- bottomRadius: 6371860,
- layers: [
- { channel: 'r', altitude: 1800, height: 650, densityScale: 0.2, shapeAmount: 1.0, shapeDetailAmount: 1.0, weatherExponent: 1.0, shapeAlteringBias: 0.35, coverageFilterWidth: 0.6, coverage: 0.3, densityProfile: { expTerm: 0, exponent: 0, linearTerm: 0.75, constantTerm: 0.25 } },
- { channel: 'g', altitude: 2400, height: 1200, densityScale: 0.2, shapeAmount: 1.0, shapeDetailAmount: 1.0, weatherExponent: 1.0, shapeAlteringBias: 0.35, coverageFilterWidth: 0.6, coverage: 0.3, densityProfile: { expTerm: 0, exponent: 0, linearTerm: 0.75, constantTerm: 0.25 } },
- { channel: 'b', altitude: 7500, height: 500, densityScale: 0.003, shapeAmount: 0.4, shapeDetailAmount: 0.0, weatherExponent: 1.0, shapeAlteringBias: 0.35, coverageFilterWidth: 0.5, coverage: 0.3, densityProfile: { expTerm: 0, exponent: 0, linearTerm: 0.75, constantTerm: 0.25 } },
- { channel: 'a' }
- ],
- maxSteps: 500, maxStepsToSun: 8, minStepSize: 20.0, maxStepSize: 1000.0, maxRayDistance: 200000.0,
- perspectiveStepScale: 1.005, minDensity: 1e-5, minExtinction: 1e-5, minTransmittance: 0.01,
- // 远处云距离衰减(米):天际线附近射线斜穿云层累积过密,从 distFadeStart 起线性衰减到 distFadeEnd 完全消失
- distFadeStart: 11000.0, distFadeEnd: 51000.0,
- minSecondaryStepSize: 100.0, secondaryStepScale: 2.0, multiScatteringOctaves: 8, lowLayerDensityBoost: 1.0,
- shadowLengthEnabled: true, useShadowBuffer: true, hazeEnabled: false,
- maxShadowLengthIterationCount: 500, minShadowLengthStepSize: 50.0, maxShadowLengthRayDistance: 200000.0,
- hazeDensityScale: 3e-5, hazeExponent: 1e-3, hazeScatteringCoefficient: 0.9, hazeAbsorptionCoefficient: 0.5,
- weatherRepeat: 100.0, shapeRepeat: 4.1, shapeDetailRepeat: 0.0005,
- turbulenceRepeat: 2.0, turbulenceDisplacement: 400.0,
- scatteringCoefficient: 1.0, absorptionCoefficient: 0.0,
- scatterG1: 0.7, scatterG2: -0.2, scatterMix: 0.5,
- sunIntensity: 20.0, skyLightScale: 1.0, skyToSunRatio: 0.28,
- powderScale: 0.8, powderExponent: 150.0,
- aerialPerspectiveScale: 1.3, cloudExposure: 3.0, magentaFixStrength: 2.0, edgeAlphaCutoff: 0.0, mipLevelScale: 0.35,
- windSpeed: 0.0, evolutionSpeed: 0.005,
- temporalEnabled: false, temporalAlpha: 0.1,
- blueNoiseScale: 1.0, jitterStrength: 1.0,
- // BSM cascade 几何:shadowFar 控制覆盖最远距离,splitLambda 控制近处分配,fadeScale 扩大 ortho radius 防切割
- // fadeScale 提高以扩大 ortho 覆盖,避免 cascade 矩形外硬切;不再依赖 UV edgeFade
- shadowFar: 40000, shadowSplitLambda: 1.0, shadowFadeScale: 5.0,
- };
- this.atmosphere = null;
- this.aerial = null;
- this.cloudStage = null;
- this.textures = null;
- this._ready = null;
- // BSM state
- this._bsm = { pass: null, resolve: null, blitFbo: null, blitProg: null, blitVbo: null };
- // TAA state
- this._taa = { texA: null, texB: null, current: 0, pbo: null, pboReady: false, w: 0, h: 0, frameCount: 0, prevVP: null, curVP: null };
- // Wind offsets
- this._weatherOffsetX = 0; this._weatherOffsetY = 0;
- this._shapeOffsetX = 0; this._shapeOffsetY = 0; this._shapeOffsetZ = 0;
- this._shapeDetailOffsetX = 0; this._shapeDetailOffsetY = 0; this._shapeDetailOffsetZ = 0;
- this._lastFrameTime = undefined;
- this._listeners = [];
- // 原始 WebGL2 云渲染状态
- this._raw = {
- prog: null, // 云渲染着色器程序
- vao: null, // 全屏四边形 VAO
- vbo: null, // 全屏四边形 VBO
- sceneColor: null, // 场景颜色拷贝纹理
- depthTex: null, // 场景深度拷贝纹理
- depthFbo: null, // 深度拷贝用 FBO
- depthW: 0,
- depthH: 0,
- texUnits: {}, // 纹理 uniform 到纹理单元映射
- };
- }
- // ── Raw WebGL2 云渲染片段着色器(czm_* 替换为原始 uniform)────────────
- /**
- * 返回一个自包含的 GLSL #version 100 片段着色器。
- * 将 Cesium PostProcessStage 中的 czm_* 内置变量替换为显式 uniform,
- * 移除了 USE_ATMOSPHERE_IRRADIANCE(改用简化 Rayleigh 天光模型)。
- * 输出使用 gl_FragColor。
- */
- _getRawCloudFragmentShader() {
- let src = getCloudFragmentShader();
- // #version 100 兼容:in → varying
- src = src.replace('in vec2 v_textureCoordinates;', 'varying vec2 v_textureCoordinates;');
- // 在文件头部插入 extension 声明 + 新增 uniform
- const header = `#extension GL_OES_texture_3D : enable
- precision highp float;
- precision highp sampler2D;
- precision highp sampler3D;
- uniform mat4 u_inverseProjection;
- uniform mat4 u_inverseView;
- uniform mat4 u_viewMatrix;
- uniform vec3 u_sunDirection;
- `;
- // 找到第一个非注释行(跳过开头的 const 声明、main 之前的代码)
- // 直接在所有代码之前插入 header
- // getCloudFragmentShader() 以模板字面量 \` 开始,内容以 const float 开头
- // 我们先移除 precision 和 extension 行(如果有的话),再插入
- // 但原始 shader 在 _buildCloudFragmentShader 里才加 precision,这里裸的 getCloudFragmentShader 没有 precision 行
- // 直接在开头插入即可
- // 在第一个非空白、非注释行之前插入 header
- // getCloudFragmentShader() 第一行是 `\nconst float RECIPROCAL_PI4...`
- // 我们把 header 插在 const float RECIPROCAL_PI4 之前
- src = header + src;
- // #define USE_ATMOSPHERE_IRRADIANCE 相关代码不再需要,但 shader 默认没有 #define USE_ATMOSPHERE_IRRADIANCE
- // (它仅在 _buildCloudFragmentShader 中被定义),所以 else 分支会走简化 Rayleigh 模型。
- // 不需要额外操作。
- // 替换 czm_* 为 uniform
- // 注意替换顺序:先长后短,避免部分匹配
- src = src.replace(/czm_windowToEyeCoordinates\(vec4\(gl_FragCoord\.xy,\s*depth,\s*1\.0\)\)/g,
- '(u_inverseProjection * vec4(gl_FragCoord.x * 2.0 / u_resolution.x - 1.0, gl_FragCoord.y * 2.0 / u_resolution.y - 1.0, depth * 2.0 - 1.0, 1.0))');
- src = src.replace(/czm_inverseProjection/g, 'u_inverseProjection');
- src = src.replace(/czm_inverseView/g, 'u_inverseView');
- src = src.replace(/czm_sunDirectionWC/g, 'u_sunDirection');
- src = src.replace(/czm_view/g, 'u_viewMatrix');
- src = src.replace(/czm_readDepth\(depthTexture,\s*v_textureCoordinates\)/g,
- 'texture2D(depthTexture, v_textureCoordinates).r');
- return src;
- }
- // ── Texture loading ────────────────────────────────────────────────────
- async _load3DTexture(url, size) {
- const data3D = await loadBinThreeGeospatial(url, size);
- return bindData3DTextureToCesiumContext(this.viewer, data3D, Cesium);
- }
- async _load3DTextureWHD(url, width, height, depth) {
- const arrayBuffer = await Cesium.Resource.fetchArrayBuffer(url);
- const raw = new Uint8Array(arrayBuffer);
- const context = this.viewer.scene.context;
- const gl = context._gl;
- try {
- return new Cesium.Texture3D({
- context,
- width,
- height,
- depth,
- pixelFormat: Cesium.PixelFormat.RED,
- pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE,
- source: {
- arrayBufferView: raw,
- width,
- height,
- depth,
- },
- sampler: new Cesium.Sampler({
- minificationFilter: Cesium.TextureMinificationFilter.LINEAR,
- magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR,
- wrapS: Cesium.TextureWrap.REPEAT,
- wrapT: Cesium.TextureWrap.REPEAT,
- wrapR: Cesium.TextureWrap.REPEAT,
- }),
- });
- } catch (e) {
- console.warn('[Pipeline] Cesium.Texture3D 失败,使用原生 WebGL2 回退:', e.message);
- const tex = gl.createTexture();
- gl.bindTexture(gl.TEXTURE_3D, tex);
- gl.texImage3D(gl.TEXTURE_3D, 0, gl.R8, width, height, depth, 0, gl.RED, gl.UNSIGNED_BYTE, raw);
- gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
- gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
- gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.REPEAT);
- gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.REPEAT);
- gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.REPEAT);
- gl.bindTexture(gl.TEXTURE_3D, null);
- return {
- _texture: tex,
- _textureTarget: gl.TEXTURE_3D,
- _target: gl.TEXTURE_3D,
- width, height, depth,
- bind: function(textureUnit) {
- gl.activeTexture(gl.TEXTURE0 + textureUnit);
- gl.bindTexture(gl.TEXTURE_3D, tex);
- },
- destroy: function() { gl.deleteTexture(tex); }
- };
- }
- }
- async _load2DTexture(url) {
- const img = await Cesium.Resource.fetchImage(url);
- if (!img || img.width <= 2 || img.height <= 2) throw new Error(`Invalid image: ${url}`);
- return new Cesium.Texture({
- context: this.viewer.scene.context, source: img,
- sampler: new Cesium.Sampler({ minificationFilter: Cesium.TextureMinificationFilter.LINEAR, magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR, wrapS: Cesium.TextureWrap.REPEAT, wrapT: Cesium.TextureWrap.REPEAT })
- });
- }
- async _loadTextures() {
- const bp = this.assetsBase;
- const [shape, detail, stbn, weather, turb, noise] = await Promise.all([
- this._load3DTexture(bp + "shape.bin", 128).catch(() => null),
- this._load3DTexture(bp + "shape_detail.bin", 32).catch(() => null),
- this._load3DTextureWHD(bp + "stbn.bin", 128, 128, 64).catch(() => null),
- this._load2DTexture(bp + "local_weather.png").catch(() => null),
- this._load2DTexture(bp + "turbulence.png").catch(() => null),
- this._load2DTexture(this.blueNoiseUrl).catch(() => null),
- ]);
- this.textures = { shape, shapeDetail: detail, stbn, weather, turbulence: turb, blueNoise: noise };
- console.log("[Pipeline] textures:", Object.fromEntries(Object.entries(this.textures).map(([k, v]) => [k, !!v])));
- }
- // ── Shader loading for Bruneton prefix ─────────────────────────────────
- async _loadShader(name) {
- return loadShaderSource(name, { shaderBaseUrl: this.brunetonShaderBase });
- }
- async _buildCloudFragmentShader() {
- const provider = this.atmosphere.getAtmosphereForClouds();
- const [definitions, common, runtime] = await Promise.all([
- this._loadShader("definitions.glsl"),
- this._loadShader("common.glsl"),
- this._loadShader("runtime.glsl"),
- ]);
- const defines = "precision highp float;\nprecision highp sampler2D;\nprecision highp sampler3D;\n"
- + provider.constants.getShaderDefines()
- + "\n#define METER_TO_LENGTH_UNIT 0.001\n#define USE_ATMOSPHERE_IRRADIANCE\n";
- const globalU = `
- uniform vec3 sunDirection;
- uniform AtmosphereParameters ATMOSPHERE;
- uniform vec3 SUN_SPECTRAL_RADIANCE_TO_LUMINANCE;
- uniform vec3 SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
- uniform sampler2D transmittance_texture;
- uniform sampler3D scattering_texture;
- uniform sampler3D single_mie_scattering_texture;
- uniform sampler2D irradiance_texture;
- `;
- return defines + definitions + "\n" + common + "\n" + globalU + "\n" + runtime + "\n" + getCloudFragmentShader();
- }
- // ── Wind animation ─────────────────────────────────────────────────────
- _advanceOffsets() {
- const now = performance.now() / 1000;
- if (this._lastFrameTime !== undefined) {
- const dt = now - this._lastFrameTime;
- this._weatherOffsetX += (this.params.windSpeed || 0) * dt;
- this._shapeOffsetX += (this.params.evolutionSpeed || 0) * dt;
- this._shapeDetailOffsetX += (this.params.evolutionSpeed || 0) * 2 * dt;
- }
- this._lastFrameTime = now;
- }
- // ── Helpers ────────────────────────────────────────────────────────────
- _getDensityProfileVec4(key) {
- const ls = this.params.layers, def = k => k === "linearTerm" ? 0.75 : k === "constantTerm" ? 0.25 : 0;
- return new Cesium.Cartesian4(...[0,1,2,3].map(i => {
- const val = ls[i]?.densityProfile?.[key];
- return val !== undefined ? Number(val) : def(key);
- }));
- }
- _getIntervalHeights() {
- const ls = this.params.layers, entries = [];
- for (let i = 0; i < 4; i++) { const a = Number(ls[i]?.altitude) || 0, h = Number(ls[i]?.height) || 0; entries.push({ v: a, flag: 0 }, { v: a + h, flag: 1 }); }
- entries.sort((a, b) => a.v !== b.v ? a.v - b.v : a.flag - b.flag);
- const intervals = [{ min: 0, max: 0 }, { min: 0, max: 0 }, { min: 0, max: 0 }];
- let idx = 0, balance = 0;
- for (let i = 0; i < entries.length; i++) { if (balance === 0 && i > 0) { intervals[idx] = { min: entries[i - 1].v, max: entries[i].v }; idx++; } balance += entries[i].flag === 0 ? 1 : -1; }
- return { min: new Cesium.Cartesian3(intervals[0].min, intervals[1].min, intervals[2].min), max: new Cesium.Cartesian3(intervals[0].max, intervals[1].max, intervals[2].max) };
- }
- _getLayerVec4(key, fallback = 0) {
- const ls = this.params.layers;
- return new Cesium.Cartesian4(...[0,1,2,3].map(i => {
- const val = ls[i]?.[key];
- return val !== undefined ? Number(val) : fallback;
- }));
- }
- _getAltitudeCorrectionOffset(bottomRadius) {
- const ellipsoid = this.viewer?.scene?.globe?.ellipsoid;
- const cameraPos = this.viewer?.camera?.positionWC;
- if (!ellipsoid || !cameraPos) return Cesium.Cartesian3.ZERO.clone();
- const carto = Cesium.Cartographic.fromCartesian(cameraPos, ellipsoid);
- if (!carto) return Cesium.Cartesian3.ZERO.clone();
- const surface = Cesium.Cartesian3.fromRadians(
- carto.longitude,
- carto.latitude,
- 0.0,
- ellipsoid
- );
- const normal = ellipsoid.geodeticSurfaceNormal(surface, new Cesium.Cartesian3());
- const center = Cesium.Cartesian3.subtract(
- surface,
- Cesium.Cartesian3.multiplyByScalar(normal, Number(bottomRadius) || 0, new Cesium.Cartesian3()),
- new Cesium.Cartesian3()
- );
- return Cesium.Cartesian3.negate(center, new Cesium.Cartesian3());
- }
- _getMinHeight() { const ls = this.params.layers; let m = Infinity; for (let i = 0; i < 4; i++) { if ((Number(ls[i]?.height) || 0) > 0) m = Math.min(m, Number(ls[i]?.altitude) || 0); } return Number.isFinite(m) ? m : 0; }
- _getMaxHeight() { const ls = this.params.layers; let m = 0; for (let i = 0; i < 4; i++) { const h = Number(ls[i]?.height) || 0; if (h > 0) m = Math.max(m, (Number(ls[i]?.altitude) || 0) + h); } return m; }
- // ── Cloud PostProcessStage uniform map ─────────────────────────────────
- _buildCloudUniforms() {
- const self = this, p = () => self.params, tex = () => self.textures;
- const provider = this.atmosphere.getAtmosphereForClouds();
- const atm = provider.getUniforms();
- const u = {
- u_shapeTexture: () => tex()?.shape,
- u_shapeDetailTexture: () => tex()?.shapeDetail,
- u_stbnTexture: () => tex()?.stbn || tex()?.shape,
- u_weatherTexture: () => tex()?.weather,
- u_turbulenceTexture: () => tex()?.turbulence,
- u_blueNoise: () => tex()?.blueNoise,
- u_blueNoiseScale: () => p().blueNoiseScale ?? 1.0,
- u_jitterStrength: () => p().jitterStrength ?? 1.0,
- u_cameraPosition: () => self.viewer.camera.positionWC,
- u_altitudeCorrection: () => {
- const br = Number(atm.bottomRadius()) || Number(p().bottomRadius) || 0;
- return self._getAltitudeCorrectionOffset(br);
- },
- u_cameraHeight: () => {
- const corr = u.u_altitudeCorrection();
- const pos = Cesium.Cartesian3.add(self.viewer.camera.positionWC, corr, new Cesium.Cartesian3());
- const br = Number(atm.bottomRadius()) || Number(p().bottomRadius) || 0;
- return Math.max(0, Cesium.Cartesian3.magnitude(pos) - br);
- },
- u_bottomRadius: () => Number(p().bottomRadius),
- u_minHeight: () => self._getMinHeight(),
- u_maxHeight: () => self._getMaxHeight(),
- u_minLayerHeights: () => self._getLayerVec4("altitude", 0),
- u_maxLayerHeights: () => { const ls = p().layers; return new Cesium.Cartesian4(...[0,1,2,3].map(i => (Number(ls[i]?.altitude)||0)+(Number(ls[i]?.height)||0))); },
- u_densityScales: () => self._getLayerVec4("densityScale", 0),
- u_shapeAmounts: () => self._getLayerVec4("shapeAmount", 0),
- u_shapeDetailAmounts: () => self._getLayerVec4("shapeDetailAmount", 0),
- u_weatherExponents: () => self._getLayerVec4("weatherExponent", 1),
- u_shapeAlteringBiases: () => self._getLayerVec4("shapeAlteringBias", 0.35),
- u_coverageFilterWidths: () => self._getLayerVec4("coverageFilterWidth", 0.6),
- u_maxSteps: () => p().maxSteps, u_maxStepsToSun: () => p().maxStepsToSun,
- u_minStepSize: () => p().minStepSize, u_maxStepSize: () => p().maxStepSize,
- u_maxRayDistance: () => p().maxRayDistance,
- u_distFadeStart: () => Number(p().distFadeStart) || 30000.0,
- u_distFadeEnd: () => Number(p().distFadeEnd) || 150000.0,
- u_cameraNear: () => Number(self.viewer.camera.frustum?.near) || 0,
- u_shadowTopHeight: () => self._getMaxHeight(),
- u_shadowLengthEnabled: () => p().shadowLengthEnabled ? 1 : 0,
- u_hazeEnabled: () => p().hazeEnabled ? 1 : 0,
- u_maxShadowLengthIterationCount: () => p().maxShadowLengthIterationCount,
- u_minShadowLengthStepSize: () => p().minShadowLengthStepSize,
- u_maxShadowLengthRayDistance: () => p().maxShadowLengthRayDistance,
- u_hazeDensityScale: () => p().hazeDensityScale, u_hazeExponent: () => p().hazeExponent,
- u_hazeScatteringCoefficient: () => p().hazeScatteringCoefficient,
- u_hazeAbsorptionCoefficient: () => p().hazeAbsorptionCoefficient,
- u_shadowBuffer: () => {
- if (p().useShadowBuffer && self._bsm.resolve) {
- const t = self._bsmResolveGetTexture(); // 直接调用对象的原生方法
- if (t) return t;
- }
- return tex()?.weather;
- },
- u_shadowTexelSize: () => { const tile = self._bsm.pass ? Math.floor(SHADOW_MAP_SIZE / 2) : 512; return new Cesium.Cartesian2(1 / tile, 1 / tile); },
- u_shadowIntervals: () => { if (p().useShadowBuffer && self._bsm.pass) {
- const iv = self._bsm.pass.getShadowIntervals(); // 使用 Getter
- return iv.map(a => new Cesium.Cartesian2(a[0], a[1]));
- }
- return Array(4).fill(null).map(() => new Cesium.Cartesian2(0, 0)); },
- u_shadowMatrices: () => { if (p().useShadowBuffer && self._bsm.pass) return self._bsm.pass._shadowMatrices.map(m => Cesium.Matrix4.fromArray(m)); return Array(4).fill(null).map(() => Cesium.Matrix4.IDENTITY.clone()); },
- u_shadowFar: () => self._bsm.pass ? self._bsm.pass._shadowFar : p().maxShadowLengthRayDistance,
- u_maxShadowFilterRadius: () => 2.0,
- u_useShadowBuffer: () => p().useShadowBuffer ? 1 : 0,
- u_skyLightScale: () => p().skyLightScale,
- u_weatherRepeat: () => p().weatherRepeat,
- u_localWeatherOffset: () => { self._advanceOffsets(); return new Cesium.Cartesian2(self._weatherOffsetX, self._weatherOffsetY); },
- u_shapeRepeat: () => (Number(p().shapeRepeat) || 3) / 1e4,
- u_shapeOffset: () => { self._advanceOffsets(); return new Cesium.Cartesian3(self._shapeOffsetX, self._shapeOffsetY, self._shapeOffsetZ); },
- u_shapeDetailRepeat: () => p().shapeDetailRepeat,
- u_shapeDetailOffset: () => { self._advanceOffsets(); return new Cesium.Cartesian3(self._shapeDetailOffsetX, self._shapeDetailOffsetY, self._shapeDetailOffsetZ); },
- u_turbulenceRepeat: () => p().turbulenceRepeat, u_turbulenceDisplacement: () => p().turbulenceDisplacement,
- u_coverages: () => self._getLayerVec4("coverage", 0.3),
- u_coverageHaze: () => { const ls = p().layers; return Math.max(Number(ls[0]?.coverage) ?? 0.3, Number(ls[1]?.coverage) ?? 0.3, Number(ls[2]?.coverage) ?? 0.3); },
- u_scatteringCoefficient: () => p().scatteringCoefficient, u_absorptionCoefficient: () => p().absorptionCoefficient,
- u_scatterG1: () => p().scatterG1, u_scatterG2: () => p().scatterG2, u_scatterMix: () => p().scatterMix,
- u_sunIntensity: () => p().sunIntensity, u_skyToSunRatio: () => p().skyToSunRatio,
- u_powderScale: () => p().powderScale, u_powderExponent: () => p().powderExponent,
- u_aerialPerspectiveScale: () => p().aerialPerspectiveScale, u_cloudExposure: () => p().cloudExposure,
- u_magentaFixStrength: () => p().magentaFixStrength ?? 0.8,
- u_edgeAlphaCutoff: () => p().edgeAlphaCutoff ?? 0.03,
- u_resolution: () => { const ctx = self.viewer.scene.context; return new Cesium.Cartesian2(ctx.drawingBufferWidth || 1, ctx.drawingBufferHeight || 1); },
- u_mipLevelScale: () => Number(p().mipLevelScale) || 1.0,
- u_perspectiveStepScale: () => p().perspectiveStepScale ?? 1.01,
- u_minDensity: () => p().minDensity ?? 1e-5, u_minExtinction: () => p().minExtinction ?? 1e-5,
- u_minTransmittance: () => p().minTransmittance ?? 0.01,
- u_minSecondaryStepSize: () => p().minSecondaryStepSize ?? 100, u_secondaryStepScale: () => p().secondaryStepScale ?? 2,
- u_multiScatteringOctaves: () => Math.min(12, Math.max(1, p().multiScatteringOctaves ?? 8)),
- u_lowLayerDensityBoost: () => p().lowLayerDensityBoost ?? 1.0,
- u_densityProfileExpTerms: () => self._getDensityProfileVec4("expTerm"),
- u_densityProfileExponents: () => self._getDensityProfileVec4("exponent"),
- u_densityProfileLinearTerms: () => self._getDensityProfileVec4("linearTerm"),
- u_densityProfileConstantTerms: () => self._getDensityProfileVec4("constantTerm"),
- u_minIntervalHeights: () => self._getIntervalHeights().min,
- u_maxIntervalHeights: () => self._getIntervalHeights().max,
- u_historyTexture: () => { const t = self._taaGetHistoryTexture(); return t || tex()?.blueNoise; },
- u_prevViewProjection: () => self._taa.prevVP || Cesium.Matrix4.IDENTITY,
- u_temporalAlpha: () => p().temporalAlpha ?? 0.1,
- u_temporalEnabled: () => (p().temporalEnabled && self._taa.frameCount > 2 && self._taa.prevVP) ? 1 : 0,
- u_frame: () => self._frameCount || 0,
- };
- Object.assign(u, atm);
- u.u_cameraPosition = atm.cameraPosition;
- u.u_bottomRadius = atm.bottomRadius;
- return u;
- }
- // ── 原始 WebGL2 云渲染初始化 ────────────────────────────────────────────
- /**
- * 初始化原始 WebGL2 全屏四边形渲染管线。
- * @param {WebGL2RenderingContext} gl
- * @returns {boolean} 是否成功
- */
- _initRawWebGL(gl) {
- // 顶点着色器(#version 100,兼容 gl_FragColor)
- const vsSrc = `
- attribute vec2 a_position;
- varying vec2 v_textureCoordinates;
- void main() {
- v_textureCoordinates = a_position * 0.5 + 0.5;
- gl_Position = vec4(a_position, 0.0, 1.0);
- }`;
- const fsSrc = this._getRawCloudFragmentShader();
- this._raw.prog = createGLProgram(gl, vsSrc, fsSrc, 'RawCloud');
- if (!this._raw.prog) {
- console.error('[Pipeline] 原始 WebGL 云渲染程序编译失败');
- return false;
- }
- // 全屏四边形 VAO
- this._raw.vao = gl.createVertexArray();
- gl.bindVertexArray(this._raw.vao);
- this._raw.vbo = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, this._raw.vbo);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
- const posLoc = gl.getAttribLocation(this._raw.prog, 'a_position');
- if (posLoc >= 0) {
- gl.enableVertexAttribArray(posLoc);
- gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
- }
- gl.bindVertexArray(null);
- gl.bindBuffer(gl.ARRAY_BUFFER, null);
- // 场景颜色拷贝纹理
- this._raw.sceneColor = gl.createTexture();
- gl.bindTexture(gl.TEXTURE_2D, this._raw.sceneColor);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
- gl.bindTexture(gl.TEXTURE_2D, null);
- // 深度拷贝纹理 + FBO
- this._raw.depthTex = gl.createTexture();
- gl.bindTexture(gl.TEXTURE_2D, this._raw.depthTex);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
- gl.bindTexture(gl.TEXTURE_2D, null);
- this._raw.depthFbo = gl.createFramebuffer();
- // 建立纹理 uniform → 纹理单元映射表
- this._raw.texUnits = {
- colorTexture: 0,
- depthTexture: 1,
- u_shapeTexture: 2,
- u_shapeDetailTexture: 3,
- u_stbnTexture: 4,
- u_weatherTexture: 5,
- u_turbulenceTexture: 6,
- u_blueNoise: 7,
- u_shadowBuffer: 8,
- u_historyTexture: 9,
- };
- return true;
- }
- /**
- * 为原始 WebGL 云渲染设置所有 uniform。
- * 使用 _buildCloudUniforms() 获取值与 Cesium 一致的参数值。
- */
- _setRawCloudUniforms(gl, w, h) {
- const prog = this._raw.prog;
- if (!prog) return;
- const u = this._buildCloudUniforms();
- // 工具:尝试获取 uniform 位置,不存在时返回 -1
- const uloc = (name) => gl.getUniformLocation(prog, name);
- // ── 纹理绑定 ──
- const texUnits = this._raw.texUnits;
- // 场景颜色(从拷贝纹理读取)
- gl.activeTexture(gl.TEXTURE0 + texUnits.colorTexture);
- gl.bindTexture(gl.TEXTURE_2D, this._raw.sceneColor);
- const locColor = uloc('colorTexture');
- if (locColor) gl.uniform1i(locColor, texUnits.colorTexture);
- // 场景深度(从拷贝纹理读取)
- gl.activeTexture(gl.TEXTURE0 + texUnits.depthTexture);
- gl.bindTexture(gl.TEXTURE_2D, this._raw.depthTex);
- const locDepth = uloc('depthTexture');
- if (locDepth) gl.uniform1i(locDepth, texUnits.depthTexture);
- // 云 3D 纹理 + 2D 纹理
- const texBindings = [
- { name: 'u_shapeTexture', getter: u.u_shapeTexture, unit: texUnits.u_shapeTexture },
- { name: 'u_shapeDetailTexture', getter: u.u_shapeDetailTexture, unit: texUnits.u_shapeDetailTexture },
- { name: 'u_stbnTexture', getter: u.u_stbnTexture, unit: texUnits.u_stbnTexture },
- { name: 'u_weatherTexture', getter: u.u_weatherTexture, unit: texUnits.u_weatherTexture },
- { name: 'u_turbulenceTexture', getter: u.u_turbulenceTexture, unit: texUnits.u_turbulenceTexture },
- { name: 'u_blueNoise', getter: u.u_blueNoise, unit: texUnits.u_blueNoise },
- { name: 'u_shadowBuffer', getter: u.u_shadowBuffer, unit: texUnits.u_shadowBuffer },
- { name: 'u_historyTexture', getter: u.u_historyTexture, unit: texUnits.u_historyTexture },
- ];
- for (const tb of texBindings) {
- const texVal = tb.getter();
- const loc = uloc(tb.name);
- if (loc && texVal) {
- gl.activeTexture(gl.TEXTURE0 + tb.unit);
- if (texVal.bind) {
- texVal.bind(tb.unit);
- } else if (texVal._texture) {
- const target = texVal._target || gl.TEXTURE_2D;
- gl.bindTexture(target, texVal._texture);
- }
- gl.uniform1i(loc, tb.unit);
- }
- }
- // ── 数值/向量 uniform ──
- const uniformSetters = [
- { name: 'u_cameraPosition', getter: u.u_cameraPosition },
- { name: 'u_altitudeCorrection', getter: u.u_altitudeCorrection },
- { name: 'u_cameraHeight', getter: u.u_cameraHeight },
- { name: 'u_bottomRadius', getter: u.u_bottomRadius },
- { name: 'u_minHeight', getter: u.u_minHeight },
- { name: 'u_maxHeight', getter: u.u_maxHeight },
- { name: 'u_minLayerHeights', getter: u.u_minLayerHeights },
- { name: 'u_maxLayerHeights', getter: u.u_maxLayerHeights },
- { name: 'u_densityScales', getter: u.u_densityScales },
- { name: 'u_shapeAmounts', getter: u.u_shapeAmounts },
- { name: 'u_shapeDetailAmounts', getter: u.u_shapeDetailAmounts },
- { name: 'u_weatherExponents', getter: u.u_weatherExponents },
- { name: 'u_shapeAlteringBiases', getter: u.u_shapeAlteringBiases },
- { name: 'u_coverageFilterWidths', getter: u.u_coverageFilterWidths },
- { name: 'u_maxSteps', getter: u.u_maxSteps },
- { name: 'u_maxStepsToSun', getter: u.u_maxStepsToSun },
- { name: 'u_minStepSize', getter: u.u_minStepSize },
- { name: 'u_maxStepSize', getter: u.u_maxStepSize },
- { name: 'u_maxRayDistance', getter: u.u_maxRayDistance },
- { name: 'u_distFadeStart', getter: u.u_distFadeStart },
- { name: 'u_distFadeEnd', getter: u.u_distFadeEnd },
- { name: 'u_cameraNear', getter: u.u_cameraNear },
- { name: 'u_shadowTopHeight', getter: u.u_shadowTopHeight },
- { name: 'u_shadowLengthEnabled', getter: u.u_shadowLengthEnabled },
- { name: 'u_hazeEnabled', getter: u.u_hazeEnabled },
- { name: 'u_maxShadowLengthIterationCount', getter: u.u_maxShadowLengthIterationCount },
- { name: 'u_minShadowLengthStepSize', getter: u.u_minShadowLengthStepSize },
- { name: 'u_maxShadowLengthRayDistance', getter: u.u_maxShadowLengthRayDistance },
- { name: 'u_hazeDensityScale', getter: u.u_hazeDensityScale },
- { name: 'u_hazeExponent', getter: u.u_hazeExponent },
- { name: 'u_hazeScatteringCoefficient', getter: u.u_hazeScatteringCoefficient },
- { name: 'u_hazeAbsorptionCoefficient', getter: u.u_hazeAbsorptionCoefficient },
- { name: 'u_shadowTexelSize', getter: u.u_shadowTexelSize },
- { name: 'u_shadowFar', getter: u.u_shadowFar },
- { name: 'u_maxShadowFilterRadius', getter: () => 2.0 },
- { name: 'u_useShadowBuffer', getter: u.u_useShadowBuffer },
- { name: 'u_skyLightScale', getter: u.u_skyLightScale },
- { name: 'u_weatherRepeat', getter: u.u_weatherRepeat },
- { name: 'u_localWeatherOffset', getter: u.u_localWeatherOffset },
- { name: 'u_shapeRepeat', getter: u.u_shapeRepeat },
- { name: 'u_shapeOffset', getter: u.u_shapeOffset },
- { name: 'u_shapeDetailRepeat', getter: u.u_shapeDetailRepeat },
- { name: 'u_shapeDetailOffset', getter: u.u_shapeDetailOffset },
- { name: 'u_turbulenceRepeat', getter: u.u_turbulenceRepeat },
- { name: 'u_turbulenceDisplacement', getter: u.u_turbulenceDisplacement },
- { name: 'u_coverages', getter: u.u_coverages },
- { name: 'u_coverageHaze', getter: u.u_coverageHaze },
- { name: 'u_scatteringCoefficient', getter: u.u_scatteringCoefficient },
- { name: 'u_absorptionCoefficient', getter: u.u_absorptionCoefficient },
- { name: 'u_scatterG1', getter: u.u_scatterG1 },
- { name: 'u_scatterG2', getter: u.u_scatterG2 },
- { name: 'u_scatterMix', getter: u.u_scatterMix },
- { name: 'u_sunIntensity', getter: u.u_sunIntensity },
- { name: 'u_skyToSunRatio', getter: u.u_skyToSunRatio },
- { name: 'u_powderScale', getter: u.u_powderScale },
- { name: 'u_powderExponent', getter: u.u_powderExponent },
- { name: 'u_aerialPerspectiveScale', getter: u.u_aerialPerspectiveScale },
- { name: 'u_cloudExposure', getter: u.u_cloudExposure },
- { name: 'u_magentaFixStrength', getter: u.u_magentaFixStrength },
- { name: 'u_edgeAlphaCutoff', getter: u.u_edgeAlphaCutoff },
- { name: 'u_resolution', getter: () => new Cesium.Cartesian2(w, h) },
- { name: 'u_mipLevelScale', getter: u.u_mipLevelScale },
- { name: 'u_perspectiveStepScale', getter: u.u_perspectiveStepScale },
- { name: 'u_minDensity', getter: u.u_minDensity },
- { name: 'u_minExtinction', getter: u.u_minExtinction },
- { name: 'u_minTransmittance', getter: u.u_minTransmittance },
- { name: 'u_minSecondaryStepSize', getter: u.u_minSecondaryStepSize },
- { name: 'u_secondaryStepScale', getter: u.u_secondaryStepScale },
- { name: 'u_multiScatteringOctaves', getter: u.u_multiScatteringOctaves },
- { name: 'u_lowLayerDensityBoost', getter: u.u_lowLayerDensityBoost },
- { name: 'u_densityProfileExpTerms', getter: u.u_densityProfileExpTerms },
- { name: 'u_densityProfileExponents', getter: u.u_densityProfileExponents },
- { name: 'u_densityProfileLinearTerms', getter: u.u_densityProfileLinearTerms },
- { name: 'u_densityProfileConstantTerms', getter: u.u_densityProfileConstantTerms },
- { name: 'u_minIntervalHeights', getter: u.u_minIntervalHeights },
- { name: 'u_maxIntervalHeights', getter: u.u_maxIntervalHeights },
- { name: 'u_prevViewProjection', getter: u.u_prevViewProjection },
- { name: 'u_temporalAlpha', getter: u.u_temporalAlpha },
- { name: 'u_temporalEnabled', getter: u.u_temporalEnabled },
- { name: 'u_frame', getter: u.u_frame },
- { name: 'u_blueNoiseScale', getter: u.u_blueNoiseScale },
- { name: 'u_jitterStrength', getter: u.u_jitterStrength },
- // 新增原始 uniform(替代 czm_* 内置变量)
- { name: 'u_inverseProjection', getter: () => {
- const p = this.viewer.camera.frustum;
- const m = p.infiniteProjectionMatrix || p.projectionMatrix;
- const inv = new Cesium.Matrix4();
- return Cesium.Matrix4.inverse(m, inv) || Cesium.Matrix4.IDENTITY;
- }},
- { name: 'u_inverseView', getter: () => this.viewer.camera.inverseViewMatrix },
- { name: 'u_viewMatrix', getter: () => this.viewer.camera.viewMatrix },
- { name: 'u_sunDirection', getter: () => {
- // czm_sunDirectionWC 等价于从相机到太阳的归一化方向
- const sunPos = this.viewer.scene.sunPosition;
- if (!sunPos) return new Cesium.Cartesian3(0.577, 0.577, 0.577);
- return Cesium.Cartesian3.normalize(sunPos, new Cesium.Cartesian3());
- }},
- ];
- // 处理 shadowIntervals(数组)和 shadowMatrices(数组)
- // 特别注意 czm_view 和 czm_sunDirectionWC 已替换为 u_viewMatrix / u_sunDirection
- for (const s of uniformSetters) {
- const loc = uloc(s.name);
- if (!loc) continue;
- let val;
- try {
- val = s.getter();
- } catch (e) {
- // uniform getter 可能依赖未就绪的状态(如 TAA history),跳过
- continue;
- }
- if (val === null || val === undefined) continue;
- this._setRawGLUniform(gl, loc, val);
- }
- // ── 数组 uniform ──
- // u_shadowIntervals: uniform vec2[4]
- const locSI = uloc('u_shadowIntervals');
- if (locSI) {
- const intervals = u.u_shadowIntervals();
- if (intervals && intervals.length === 4) {
- const flat = [];
- for (let i = 0; i < 4; i++) {
- const iv = intervals[i];
- if (iv && iv.x !== undefined) { flat.push(iv.x, iv.y); }
- else { flat.push(0, 0); }
- }
- gl.uniform2fv(locSI, flat);
- }
- }
- // u_shadowMatrices: uniform mat4[4]
- const locSM = uloc('u_shadowMatrices');
- if (locSM) {
- const mats = u.u_shadowMatrices();
- if (mats && mats.length === 4) {
- for (let i = 0; i < 4; i++) {
- const m = mats[i];
- if (m && m.values) {
- gl.uniformMatrix4fv(uloc(`u_shadowMatrices[${i}]`) || locSM, false, m.values);
- }
- }
- }
- }
- }
- /**
- * 将 Cesium 类型值转换为原始 WebGL uniform 调用。
- */
- _setRawGLUniform(gl, loc, val) {
- if (val === null || val === undefined) return;
- // Cesium 类型判断
- const isCesiumObj = val.constructor && typeof val.constructor.name === 'string';
- const typeName = val.constructor?.name;
- if (typeof val === 'number') {
- gl.uniform1f(loc, val);
- } else if (typeof val === 'boolean') {
- gl.uniform1i(loc, val ? 1 : 0);
- } else if (typeName === 'Cartesian2' || (val.x !== undefined && val.y !== undefined && val.z === undefined && val.w === undefined)) {
- gl.uniform2f(loc, Number(val.x), Number(val.y));
- } else if (typeName === 'Cartesian3' || (val.x !== undefined && val.y !== undefined && val.z !== undefined && val.w === undefined)) {
- gl.uniform3f(loc, Number(val.x), Number(val.y), Number(val.z));
- } else if (typeName === 'Cartesian4' || (val.x !== undefined && val.y !== undefined && val.z !== undefined && val.w !== undefined)) {
- gl.uniform4f(loc, Number(val.x), Number(val.y), Number(val.z), Number(val.w));
- } else if (typeName === 'Matrix4') {
- gl.uniformMatrix4fv(loc, false, val.values);
- } else if (typeName === 'Matrix3') {
- gl.uniformMatrix3fv(loc, false, val.values);
- } else if (typeName === 'Matrix2') {
- gl.uniformMatrix2fv(loc, false, val.values);
- } else if (Array.isArray(val)) {
- if (val.length === 2) gl.uniform2fv(loc, val);
- else if (val.length === 3) gl.uniform3fv(loc, val);
- else if (val.length === 4) gl.uniform4fv(loc, val);
- else if (val.length === 9) gl.uniformMatrix3fv(loc, false, val);
- else if (val.length === 16) gl.uniformMatrix4fv(loc, false, val);
- else if (val.length === 1) gl.uniform1f(loc, val[0]);
- } else if (val._texture) {
- // 纹理 uniform 已在 _setRawCloudUniforms 中单独处理
- } else if (typeof val === 'object' && val !== null) {
- // 尝试作为 Cartesian 兼容对象处理
- const keys = Object.keys(val).filter(k => !isNaN(Number(k)));
- if (keys.length > 0) {
- const arr = keys.map(k => Number(val[k]));
- if (arr.length <= 4) {
- if (arr.length === 1) gl.uniform1f(loc, arr[0]);
- else if (arr.length === 2) gl.uniform2fv(loc, arr);
- else if (arr.length === 3) gl.uniform3fv(loc, arr);
- else if (arr.length === 4) gl.uniform4fv(loc, arr);
- }
- }
- }
- }
- /**
- * 在 postRender 中执行原始 WebGL2 云渲染。
- * 拷贝当前帧缓冲区的颜色和深度,然后渲染全屏四边形。
- */
- _renderRawClouds(gl) {
- if (!this._raw.prog || !this._raw.vao) return;
- const canvas = this.viewer.scene.canvas;
- const w = canvas.width;
- const h = canvas.height;
- if (w < 2 || h < 2) return;
- // 如果画布尺寸变化,重新分配纹理
- if (this._raw.depthW !== w || this._raw.depthH !== h) {
- gl.bindTexture(gl.TEXTURE_2D, this._raw.depthTex);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, w, h, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
- gl.bindTexture(gl.TEXTURE_2D, null);
- gl.bindTexture(gl.TEXTURE_2D, this._raw.sceneColor);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
- gl.bindTexture(gl.TEXTURE_2D, null);
- gl.bindFramebuffer(gl.FRAMEBUFFER, this._raw.depthFbo);
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._raw.depthTex, 0);
- // 深度拷贝 FBO 仅有深度附着,无颜色附着——需设置 NONE 以避免 FRAMEBUFFER_INCOMPLETE
- gl.drawBuffers([gl.NONE]);
- gl.readBuffer(gl.NONE);
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- this._raw.depthW = w;
- this._raw.depthH = h;
- }
- // 保存 GL 状态
- const prevFbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
- const prevVp = gl.getParameter(gl.VIEWPORT);
- const prevBlend = gl.getParameter(gl.BLEND);
- const prevDepthTest = gl.getParameter(gl.DEPTH_TEST);
- const prevCullFace = gl.getParameter(gl.CULL_FACE);
- const prevProg = gl.getParameter(gl.CURRENT_PROGRAM);
- const prevVAO = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
- // 1. 拷贝场景颜色到纹理
- gl.bindTexture(gl.TEXTURE_2D, this._raw.sceneColor);
- gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, w, h);
- // 2. 拷贝场景深度到纹理
- gl.bindFramebuffer(gl.READ_FRAMEBUFFER, prevFbo ? prevFbo : null);
- gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this._raw.depthFbo);
- try {
- gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, gl.DEPTH_BUFFER_BIT, gl.NEAREST);
- } catch (e) {
- // 深度拷贝可能因 FBO 不兼容而失败,静默忽略
- }
- // 3. 渲染云层到当前帧缓冲
- gl.bindFramebuffer(gl.FRAMEBUFFER, prevFbo ? prevFbo : null);
- gl.viewport(0, 0, w, h);
- gl.disable(gl.BLEND);
- gl.disable(gl.DEPTH_TEST);
- gl.disable(gl.CULL_FACE);
- gl.useProgram(this._raw.prog);
- gl.bindVertexArray(this._raw.vao);
- this._setRawCloudUniforms(gl, w, h);
- gl.drawArrays(gl.TRIANGLES, 0, 3);
- // 4. 恢复状态
- gl.bindVertexArray(prevVAO);
- gl.useProgram(prevProg);
- if (prevBlend) gl.enable(gl.BLEND); else gl.disable(gl.BLEND);
- if (prevDepthTest) gl.enable(gl.DEPTH_TEST); else gl.disable(gl.DEPTH_TEST);
- if (prevCullFace) gl.enable(gl.CULL_FACE); else gl.disable(gl.CULL_FACE);
- gl.viewport(prevVp[0], prevVp[1], prevVp[2], prevVp[3]);
- }
- // ── BSM helpers ─────────────────────────────────────────────────────────
- _bsmResolveGetTexture() {
- const r = this._bsm.resolve;
- const tex = r ? r._historyTex : (this._bsm.pass ? this._bsm.pass._colorTexture : null);
- if (!tex) return null;
- const gl = this.viewer.scene.context._gl;
- return {
- _texture: tex,
- _textureTarget: gl.TEXTURE_2D,
- _target: gl.TEXTURE_2D,
- // 【关键注入】Cesium 必须调用此方法才能把纹理挂载到 GPU
- bind: function(textureUnit) {
- gl.activeTexture(gl.TEXTURE0 + textureUnit);
- gl.bindTexture(gl.TEXTURE_2D, this._texture);
- }
- };
- }
- _taaGetHistoryTexture() {
- const gl = this.viewer.scene.context?._gl;
- if (!gl) return null;
- const tex = this._taa.current === 0 ? this._taa.texA : this._taa.texB;
- if (!tex) return null;
- return { _texture: tex, _textureTarget: gl.TEXTURE_2D, _target: gl.TEXTURE_2D };
- }
- // ── BSM blit to Cesium.Texture ─────────────────────────────────────────
- _blitBSM(sourceTex, targetCesiumTex, scale) {
- const gl = this.viewer.scene.context?._gl;
- if (!gl || !sourceTex?._texture || !targetCesiumTex?._texture) return;
- if (!this._bsm.blitFbo) {
- this._bsm.blitFbo = gl.createFramebuffer();
- this._bsm.blitProg = createGLProgram(gl,
- `#version 300 es\nin vec2 a_pos;\nout vec2 v_uv;\nvoid main(){v_uv=a_pos*0.5+0.5;gl_Position=vec4(a_pos,0,1);}`,
- `#version 300 es\nprecision highp float;\nuniform sampler2D u_src;\nuniform float u_scale;\nin vec2 v_uv;\nout vec4 o;\nvoid main(){vec4 raw=texture(u_src,v_uv);\n // 编码:rgba *= scale。HALF_FLOAT(scale=1)等价透传;RGBA8(scale=0.02)压到0..1,消费端 /scale 还原。\n o=vec4(raw.rgb*u_scale, raw.a*u_scale);}`,
- "BSMBlit");
- const vbo = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW);
- this._bsm.blitVbo = vbo;
- }
- const prevFbo = gl.getParameter(gl.FRAMEBUFFER_BINDING), prevVp = gl.getParameter(gl.VIEWPORT);
- gl.bindFramebuffer(gl.FRAMEBUFFER, this._bsm.blitFbo);
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetCesiumTex._texture, 0);
- if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { gl.bindFramebuffer(gl.FRAMEBUFFER, prevFbo); gl.viewport(...prevVp); return; }
- gl.viewport(0, 0, BSM_BLIT_SIZE, BSM_BLIT_SIZE);
- gl.useProgram(this._bsm.blitProg);
- gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, sourceTex._texture);
- gl.uniform1i(gl.getUniformLocation(this._bsm.blitProg, "u_src"), 0);
- gl.uniform1f(gl.getUniformLocation(this._bsm.blitProg, "u_scale"), scale);
- gl.bindBuffer(gl.ARRAY_BUFFER, this._bsm.blitVbo);
- const aloc = gl.getAttribLocation(this._bsm.blitProg, "a_pos");
- if (aloc >= 0) { gl.enableVertexAttribArray(aloc); gl.vertexAttribPointer(aloc, 2, gl.FLOAT, false, 0, 0); }
- gl.drawArrays(gl.TRIANGLES, 0, 3);
- if (aloc >= 0) gl.disableVertexAttribArray(aloc);
- gl.bindFramebuffer(gl.FRAMEBUFFER, prevFbo); gl.viewport(prevVp[0], prevVp[1], prevVp[2], prevVp[3]);
- }
- // ── BSM sync to Atmosphere + Aerial ────────────────────────────────────
- _syncBSM() {
- const sp = this._bsm.pass;
- if (!sp || !this.params.useShadowBuffer) {
- this.atmosphere?.setCloudShadow?.({ enabled: false });
- this.aerial?.setCloudShadow?.({ enabled: false });
- return;
- }
- sp.updateDynamicParams({
- localWeatherOffset: [this._weatherOffsetX || 0, this._weatherOffsetY || 0],
- shapeOffset: [this._shapeOffsetX || 0, this._shapeOffsetY || 0, this._shapeOffsetZ || 0],
- shapeDetailOffset: [this._shapeDetailOffsetX || 0, this._shapeDetailOffsetY || 0, this._shapeDetailOffsetZ || 0],
- bottomRadius: this.params.bottomRadius,
- // 每帧同步 shadow cascade far,限制到云层相关距离(避免 Cesium frustum.far~8e8 导致矩阵 NaN)
- shadowFar: Number(this.params.shadowFar) || Number(this.params.maxShadowLengthRayDistance) || 200000.0,
- maxShadowLengthRayDistance: Number(this.params.maxShadowLengthRayDistance) || 200000.0,
- shadowSplitLambda: Number(this.params.shadowSplitLambda) || 0.5,
- shadowFadeScale: Number(this.params.shadowFadeScale) || 1.0,
- // 同步 layer 参数(coverage/densityScale 等),否则 GUI 调 coverage 只影响主云,BSM 阴影不变
- // 用普通数组(非 Cartesian4),因为 BSM 的 set4f 走原生 gl.uniform4fv 只接受数组/Float32Array
- coverages: [0,1,2,3].map(i => { const v = this.params.layers[i]?.coverage; return v !== undefined ? Number(v) : 0.3; }),
- densityScales: [0,1,2,3].map(i => { const v = this.params.layers[i]?.densityScale; return v !== undefined ? Number(v) : 0; }),
- shapeAmounts: [0,1,2,3].map(i => { const v = this.params.layers[i]?.shapeAmount; return v !== undefined ? Number(v) : 0; }),
- shapeDetailAmounts: [0,1,2,3].map(i => { const v = this.params.layers[i]?.shapeDetailAmount; return v !== undefined ? Number(v) : 0; }),
- weatherExponents: [0,1,2,3].map(i => { const v = this.params.layers[i]?.weatherExponent; return v !== undefined ? Number(v) : 1; }),
- shapeAlteringBiases: [0,1,2,3].map(i => { const v = this.params.layers[i]?.shapeAlteringBias; return v !== undefined ? Number(v) : 0.35; }),
- coverageFilterWidths: [0,1,2,3].map(i => { const v = this.params.layers[i]?.coverageFilterWidth; return v !== undefined ? Number(v) : 0.6; }),
- scatteringCoefficient: Number(this.params.scatteringCoefficient) ?? 0.9,
- absorptionCoefficient: Number(this.params.absorptionCoefficient) ?? 1.0,
- });
- let tex = this._bsm.resolve ? this._bsmResolveGetTexture() : null;
- if (!tex) tex = sp.getTexture();
- if (!tex) { this.atmosphere?.setCloudShadow?.({ enabled: false }); this.aerial?.setCloudShadow?.({ enabled: false }); return; }
- const provider = this.atmosphere?.getAtmosphereForClouds?.();
- const targetTex = provider?.getCloudShadowTargetTexture?.();
- const clamp01 = provider?.getCloudShadowClamp01?.() ?? true;
- const scaleToPass = clamp01 ? 200.0 : 1.0;
- let textureToPass = tex;
- if (targetTex && tex._texture) { this._blitBSM(tex, targetTex, scaleToPass); textureToPass = targetTex; }
- const intervals = sp.getShadowIntervals();
- const mats = sp.getShadowMatrices();
- const tile = sp.getTileSize?.() || Math.floor(SHADOW_MAP_SIZE / 2);
- // 远距几何误差修正:相机越高/越远,越把 BSM 采样点拉向稳定球面(对齐 three-geospatial correctGeometricError)
- const camH = this.viewer.camera.positionCartographic?.height ?? 0;
- const geoAmt = Math.min(1, Math.max(0, (camH - 2000) / 25000));
- const opts = {
- enabled: true, texture: textureToPass, scale: scaleToPass,
- decode: { x: 1, y: 1, z: 1, w: 1 },
- near: sp.getShadowNear?.() ?? (Number(this.viewer.camera.frustum?.near) || 0.1),
- far: sp.getShadowFar(),
- topHeight: this._getMaxHeight(), bottomRadius: Number(this.params.bottomRadius) || 6371000,
- intervals: intervals.map(a => new Cesium.Cartesian2(a[0], a[1])),
- matrices: mats.map(m => Cesium.Matrix4.fromArray(m)),
- texelSize: { x: 1 / tile, y: 1 / tile },
- geometricErrorCorrectionAmount: geoAmt,
- };
- this.atmosphere?.setCloudShadow?.(opts);
- this.aerial?.setCloudShadow?.(opts);
- }
- // ── TAA (inline CloudsResolvePass) ─────────────────────────────────────
- _taaCapture() {
- const gl = this.viewer.scene.context?._gl;
- if (!gl) return;
- const canvas = this.viewer.scene.canvas;
- const w = canvas.width, h = canvas.height;
- if (w !== this._taa.w || h !== this._taa.h) {
- if (this._taa.texA) gl.deleteTexture(this._taa.texA);
- if (this._taa.texB) gl.deleteTexture(this._taa.texB);
- if (this._taa.pbo) gl.deleteBuffer(this._taa.pbo);
- const mkTex = () => { const t = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, t); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.bindTexture(gl.TEXTURE_2D, null); return t; };
- this._taa.texA = mkTex(); this._taa.texB = mkTex();
- this._taa.pbo = gl.createBuffer(); gl.bindBuffer(gl.PIXEL_PACK_BUFFER, this._taa.pbo); gl.bufferData(gl.PIXEL_PACK_BUFFER, w * h * 4, gl.STREAM_READ); gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
- this._taa.w = w; this._taa.h = h; this._taa.frameCount = 0; this._taa.pboReady = false;
- }
- const writeTex = this._taa.current === 0 ? this._taa.texB : this._taa.texA;
- if (this._taa.pboReady) {
- const prevTex = gl.getParameter(gl.TEXTURE_BINDING_2D);
- const flipY = gl.getParameter(gl.UNPACK_FLIP_Y_WEBGL), premul = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL);
- if (flipY) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
- if (premul) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
- gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, this._taa.pbo);
- gl.bindTexture(gl.TEXTURE_2D, writeTex);
- gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, 0);
- gl.bindTexture(gl.TEXTURE_2D, prevTex);
- gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
- if (flipY) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
- if (premul) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
- this._taa.current = 1 - this._taa.current;
- this._taa.frameCount++;
- }
- const prevFbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
- gl.bindFramebuffer(gl.FRAMEBUFFER, null);
- gl.bindBuffer(gl.PIXEL_PACK_BUFFER, this._taa.pbo);
- gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, 0);
- gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
- if (prevFbo) gl.bindFramebuffer(gl.FRAMEBUFFER, prevFbo);
- this._taa.pboReady = true;
- }
- _taaUpdateVP() {
- this._taa.prevVP = this._taa.curVP;
- const cam = this.viewer.camera;
- this._taa.curVP = Cesium.Matrix4.multiply(cam.frustum.projectionMatrix, cam.viewMatrix, new Cesium.Matrix4());
- }
- // ── GUI ────────────────────────────────────────────────────────────────
- _setupGUI() {
- if (this._gui) return;
- this._gui = new dat.GUI({ name: "体积云管线" });
- const p = this.params, ls = p.layers;
- const f = this._gui.addFolder("云层");
- f.add(ls[0], "altitude", 0, 20000, 50).name("层0底高(m)");
- f.add(ls[0], "height", 0, 10000, 50).name("层0厚度(m)");
- f.add(ls[0], "coverage", 0, 1, 0.01).name("层0覆盖度");
- f.add(ls[1], "altitude", 0, 20000, 50).name("层1底高(m)");
- f.add(ls[1], "height", 0, 10000, 50).name("层1厚度(m)");
- f.add(ls[1], "coverage", 0, 1, 0.01).name("层1覆盖度");
- f.add(ls[2], "altitude", 0, 20000, 50).name("层2底高(m)");
- f.add(ls[2], "height", 0, 10000, 50).name("层2厚度(m)");
- f.add(ls[2], "coverage", 0, 1, 0.01).name("层2覆盖度");
- // 降高频相关参数(用于抑制边缘噪点/闪烁)
- f.add(p, "shapeRepeat", 1.0, 8.0, 0.1).name("主体噪声频率");
- f.add(p, "shapeDetailRepeat", 0.0005, 0.02, 0.0001).name("细节噪声频率");
- f.add(ls[0], "shapeDetailAmount", 0.0, 1.5, 0.01).name("层0细节权重");
- f.add(ls[1], "shapeDetailAmount", 0.0, 1.5, 0.01).name("层1细节权重");
- f.add(ls[2], "shapeDetailAmount", 0.0, 1.5, 0.01).name("层2细节权重");
- f.add(ls[0], "weatherExponent", 0.2, 2.0, 0.01).name("层0天气指数");
- f.add(ls[1], "weatherExponent", 0.2, 2.0, 0.01).name("层1天气指数");
- f.add(ls[2], "weatherExponent", 0.2, 2.0, 0.01).name("层2天气指数");
- f.add(ls[0], "coverageFilterWidth", 0.1, 1.0, 0.01).name("层0覆盖过滤宽度");
- f.add(ls[1], "coverageFilterWidth", 0.1, 1.0, 0.01).name("层1覆盖过滤宽度");
- f.add(ls[2], "coverageFilterWidth", 0.1, 1.0, 0.01).name("层2覆盖过滤宽度");
- f.open();
- const l = this._gui.addFolder("光照");
- l.add(p, "sunIntensity", 0, 150, 5).name("太阳强度");
- l.add(p, "skyToSunRatio", 0.05, 0.6, 0.01).name("天空/太阳比");
- l.add(p, "cloudExposure", 0.1, 5.0, 0.1).name("云曝光");
- l.add(p, "magentaFixStrength", 0.0, 2.0, 0.05).name("落日去品红强度");
- l.add(p, "edgeAlphaCutoff", 0.0, 0.2, 0.005).name("边缘Alpha裁剪");
- l.add(p, "aerialPerspectiveScale", 0, 3, 0.1).name("大气透视");
- const s = this._gui.addFolder("散射");
- s.add(p, "scatterG1", 0, 0.99, 0.01).name("前向散射G");
- s.add(p, "scatterG2", -0.99, 0, 0.01).name("后向散射G");
- s.add(p, "multiScatteringOctaves", 1, 12, 1).name("多散射阶数");
- const a = this._gui.addFolder("动画");
- a.add(p, "windSpeed", 0, 1, 0.0001).name("风速");
- a.add(p, "evolutionSpeed", 0, 0.0001, 0.000001).name("演化速度");
- // 远处云距离衰减:调小 distFadeStart 让衰减更早开始(远处更疏),调大 distFadeEnd 让衰减更平缓
- a.add(p, "distFadeStart", 5000, 100000, 1000).name("远处衰减起点(m)");
- a.add(p, "distFadeEnd", 20000, 200000, 1000).name("远处衰减终点(m)");
- const o = this._gui.addFolder("开关");
- o.add(p, "cloudsVisible").name("显示云").onChange((v) => {
- if (this.cloudStage) this.cloudStage.enabled = v;
- });
- o.add(p, "useShadowBuffer").name("BSM(云阴影)");
- o.add(p, "shadowLengthEnabled").name("阴影长度(丁达尔)");
- o.add(p, "hazeEnabled").name("雾效(HAZE)");
- o.add(p, "temporalEnabled").name("TAA");
- o.add(p, "maxSteps", 64, 1200, 1).name("主采样步数");
- o.add(p, "minStepSize", 5.0, 200.0, 1.0).name("最小步长");
- o.add(p, "blueNoiseScale", 0.25, 4.0, 0.05).name("噪声采样缩放");
- o.add(p, "jitterStrength", 0.0, 1.0, 0.01).name("抖动强度");
- // BSM OD 缩放联动:地面阴影实际在 AerialPerspectiveEffect stage 渲染,
- // 但原 GUI 只绑了 AtmospherePostProcess 实例,调不动。这里统一驱动两侧。
- const bsm = this._gui.addFolder("BSM 缩放");
- const syncBsmScale = (key, val) => {
- if (this.atmosphere) this.atmosphere[`_${key}`] = val;
- if (this.aerial) this.aerial[`_${key}`] = val;
- };
- // 用 params 上的占位属性承载 GUI 值,初始与两侧默认(1.0)对齐
- p._bsmGroundScale = 0.3; p._bsmTyndallScale = 1.0;
- bsm.add(p, "_bsmGroundScale", 0.1, 20.0, 0.1).name("OD缩放(地面)").onChange((v) => syncBsmScale("bsmGroundOpticalDepthScale", v));
- bsm.add(p, "_bsmTyndallScale", 0.1, 20.0, 0.1).name("OD缩放(光柱)").onChange((v) => syncBsmScale("bsmTyndallOpticalDepthScale", v));
- // cascade 几何:调这三个解决"近处阴影被切割"。shadowFar=覆盖最远距离,splitLambda=近处分配(越大近处越多),fadeScale=ortho扩展
- bsm.add(p, "shadowFar", 20000, 500000, 5000).name("阴影覆盖距离");
- bsm.add(p, "shadowSplitLambda", 0.0, 1.0, 0.05).name("近处分配");
- bsm.add(p, "shadowFadeScale", 0.0, 5.0, 0.1).name("边缘扩展");
- // 关键:占位初始值不会自动触发 onChange,这里手动同步一次,否则启动时两侧 scale 仍是构造默认(1.0)
- syncBsmScale("bsmGroundOpticalDepthScale", p._bsmGroundScale);
- syncBsmScale("bsmTyndallOpticalDepthScale", p._bsmTyndallScale);
- }
- // ── BSM ShadowPass params (for CloudShadowPass) ────────────────────────
- _getShadowPassParams() {
- const ls = this.params.layers;
- const minLayerHeights = [], maxLayerHeights = [], densityProfileLinear = [], densityProfileConstant = [];
- const densityScales = [], shapeAmounts = [], shapeDetailAmounts = [], weatherExponents = [];
- const shapeAlteringBiases = [], coverageFilterWidths = [], coverages = [];
- let minAlt = 1e9, maxAltH = 0;
- for (let i = 0; i < 4; i++) {
- const a = Number(ls[i]?.altitude) || 0, h = Number(ls[i]?.height) || 0;
- if (a + h > 0) { minAlt = Math.min(minAlt, a); maxAltH = Math.max(maxAltH, a + h); }
- minLayerHeights[i] = a; maxLayerHeights[i] = a + h;
- densityProfileLinear[i] = Number(ls[i]?.densityProfile?.linearTerm) ?? 0.75;
- densityProfileConstant[i] = Number(ls[i]?.densityProfile?.constantTerm) ?? 0.25;
- densityScales[i] = Number(ls[i]?.densityScale) || 0;
- shapeAmounts[i] = Number(ls[i]?.shapeAmount) ?? 1;
- shapeDetailAmounts[i] = Number(ls[i]?.shapeDetailAmount) ?? 1;
- weatherExponents[i] = Number(ls[i]?.weatherExponent) ?? 1;
- shapeAlteringBiases[i] = Number(ls[i]?.shapeAlteringBias) ?? 0.35;
- coverageFilterWidths[i] = Number(ls[i]?.coverageFilterWidth) ?? 0.6;
- coverages[i] = Number(ls[i]?.coverage) ?? 0.3;
- }
- const iv = this._getIntervalHeights();
- const cBottom = Number.isFinite(minAlt) ? minAlt : 750;
- return {
- bottomRadius: Number(this.params.bottomRadius) || 6378137,
- cloudBottomHeight: cBottom, cloudTopHeight: Math.max(0, maxAltH - cBottom) || 1500,
- shadowBottomHeight: cBottom, shadowTopHeight: maxAltH || (cBottom + 1500),
- // shadow cascade far 必须限制到云层相关距离,否则会取 Cesium 相机 frustum.far(~8e8),
- // 导致 ortho proj radius 爆炸、数值精度崩坏产生 NaN、矩阵不可逆、BSM 全失效。
- shadowFar: Number(this.params.shadowFar) || Number(this.params.maxShadowLengthRayDistance) || 200000.0,
- maxShadowLengthRayDistance: Number(this.params.maxShadowLengthRayDistance) || 200000.0,
- shadowSplitLambda: Number(this.params.shadowSplitLambda) || 0.5,
- shadowFadeScale: Number(this.params.shadowFadeScale) || 1.0,
- weatherRepeat: Number(this.params.weatherRepeat) || 100, windSpeed: Number(this.params.windSpeed) || 0,
- shapeRepeat: (Number(this.params.shapeRepeat) || 3) / 1e4,
- shapeDetailRepeat: Number(this.params.shapeDetailRepeat) || 0.006,
- turbulenceRepeat: Number(this.params.turbulenceRepeat) || 2,
- turbulenceDisplacement: Number(this.params.turbulenceDisplacement) || 400,
- coverage: Math.max(...coverages), densityScale: Math.max(...densityScales),
- scatteringCoefficient: Number(this.params.scatteringCoefficient) ?? 0.9,
- absorptionCoefficient: Number(this.params.absorptionCoefficient) ?? 1.0,
- startTime: performance.now() / 1000, evolutionSpeed: Number(this.params.evolutionSpeed) || 0.005,
- maxSteps: this.params.maxSteps, minStepSize: this.params.minStepSize,
- minDensity: this.params.minDensity ?? 1e-5, minExtinction: this.params.minExtinction ?? 1e-5,
- minTransmittance: this.params.minTransmittance ?? 0.01, opticalDepthTailScale: 1.0,
- minLayerHeights, maxLayerHeights, densityProfileLinear, densityProfileConstant,
- densityProfileExpTerms: [0,0,0,0], densityProfileExponents: [0,0,0,0],
- densityScales, shapeAmounts, shapeDetailAmounts, weatherExponents,
- shapeAlteringBiases, coverageFilterWidths, coverages,
- minIntervalHeights: [iv.min.x, iv.min.y, iv.min.z],
- maxIntervalHeights: [iv.max.x, iv.max.y, iv.max.z],
- localWeatherOffset: [0, 0], shapeOffset: [0, 0, 0], shapeDetailOffset: [0, 0, 0],
- };
- }
- // ── Init ───────────────────────────────────────────────────────────────
- async init() {
- if (this._ready) return this._ready;
- this._ready = (async () => {
- const viewer = this.viewer;
- viewer.scene.globe.depthTestAgainstTerrain = true;
- // 1. Atmosphere
- this.atmosphere = new AtmospherePostProcess(viewer, {
- atmosphereParams: this.atmosphereParams, renderSky: true,
- applyGroundAtmosphere: false, autoAddStage: false,
- assetsBaseUrl: this.atmosphereAssetsBase, shaderBaseUrl: this.atmosphereShaderBase,
- });
- await this.atmosphere.init();
- // 2. Aerial
- this.aerial = new AerialPerspectiveEffect(viewer, {
- atmosphereParams: this.atmosphereParams, autoAddStage: false,
- assetsBaseUrl: this.atmosphereAssetsBase, shaderBaseUrl: this.atmosphereShaderBase,
- });
- // 3. Load cloud textures + build shader
- await this._loadTextures();
- const fragmentShader = await this._buildCloudFragmentShader();
- // 4. BSM passes (import dynamically to avoid circular deps)
- const { CloudShadowPass } = await import("./CloudShadowPass.js");
- const { ShadowResolvePass } = await import("./ShadowResolvePass.js");
- if (this.params.useShadowBuffer && this.textures) {
- this._bsm.pass = new CloudShadowPass(viewer, { textures: this.textures, params: this._getShadowPassParams() });
- this._bsm.pass.init();
- // 静止 temporalAlpha 对齐 three-geospatial≈0.01;运动时 ShadowResolvePass 内会抬高 alpha
- this._bsm.resolve = new ShadowResolvePass(viewer, { size: SHADOW_MAP_SIZE, temporalAlpha: 0.01 });
- this._bsm.resolve.setInputTextures(this._bsm.pass.getTexture(), this._bsm.pass.getDepthVelocityTexture());
- this._bsm.resolve.init();
- }
- // 5. Aerial init
- await this.aerial.init();
- // 6. 初始化原始 WebGL2 云渲染管线
- // 注意:大气/空中透视仍使用 Cesium PostProcessStage(提供天空背景),
- // 但它们的 sampler3D uniform 在 Cesium binding 中可能失败。
- // 因此这里不注册任何 PostProcessStage,只渲染云层。
- const gl = viewer.scene.context._gl;
- const rawOk = gl ? this._initRawWebGL(gl) : false;
- if (!rawOk) {
- console.warn('[Pipeline] 原始 WebGL2 云渲染初始化失败,回退使用 Cesium PostProcessStage');
- // 回退:使用原来的 PostProcessStage(但 sampler3D 可能失败)
- const uniforms = this._buildCloudUniforms();
- this.cloudStage = new Cesium.PostProcessStage({
- name: "GeospatialVolumetricClouds", fragmentShader, uniforms,
- });
- this.cloudStage.enabled = this.params.cloudsVisible;
- const stages = viewer.scene.postProcessStages;
- if (this.atmosphere.stage) stages.add(this.atmosphere.stage);
- if (this.aerial.stage) stages.add(this.aerial.stage);
- stages.add(this.cloudStage);
- } else {
- console.log('[Pipeline] 使用原始 WebGL2 渲染云层(跳过 PostProcessStage)');
- // 仅用 Cesium 的默认 skyBox 作为背景
- }
- // 7. preRender: BSM sync
- this._listeners.push(viewer.scene.preRender.addEventListener(() => this._syncBSM()));
- // 8. postRender: TAA capture + frame count + 原始 WebGL2 云渲染
- this._listeners.push(viewer.scene.postRender.addEventListener(() => {
- this._taaUpdateVP();
- if (this.params.temporalEnabled) this._taaCapture();
- this._frameCount++;
- // 如果使用原始 WebGL2 渲染,在 postRender 中执行
- if (rawOk && this.params.cloudsVisible) {
- try { this._renderRawClouds(gl); } catch (e) {
- console.warn('[Pipeline] 原始 WebGL2 云渲染失败:', e.message);
- }
- }
- }));
- this._listeners.push(viewer.camera.changed.addEventListener(() => {
- const c = Cesium.Cartographic.fromCartesian(
- viewer.camera.positionWC,
- viewer.scene.globe.ellipsoid
- );
- const ellipsoidHeight = Number(c?.height) || 0;
- const atmBottomRadius = Number(
- this.atmosphere?.getAtmosphereForClouds?.()?.getUniforms?.()?.bottomRadius?.() ?? NaN
- );
- const usedBottomRadius = Number.isFinite(atmBottomRadius)
- ? atmBottomRadius
- : (Number(this.params.bottomRadius) || 0);
- const corr = this._getAltitudeCorrectionOffset(usedBottomRadius);
- const correctedPos = Cesium.Cartesian3.add(
- viewer.camera.positionWC,
- corr,
- new Cesium.Cartesian3()
- );
- const correctedHeight = Math.max(
- 0,
- Cesium.Cartesian3.magnitude(correctedPos) - usedBottomRadius
- );
- }));
- this._setupGUI();
- console.log("[Pipeline] ready: Cloud -> Atmosphere -> Aerial");
- })();
- return this._ready;
- }
- // ── Destroy ────────────────────────────────────────────────────────────
- destroy() {
- for (const remove of this._listeners) if (typeof remove === "function") remove();
- this._listeners = [];
- const stages = this.viewer?.scene?.postProcessStages;
- if (stages && this.cloudStage) { try { stages.remove(this.cloudStage); } catch {} }
- this.cloudStage = null;
- try { this.aerial?.destroy(); } catch {} this.aerial = null;
- try { this.atmosphere?.destroy(); } catch {} this.atmosphere = null;
- try { this._bsm.pass?.destroy(); } catch {} this._bsm.pass = null;
- try { this._bsm.resolve?.destroy(); } catch {} this._bsm.resolve = null;
- const gl = this.viewer?.scene?.context?._gl;
- if (gl) {
- // 清理 BSM 资源
- if (this._bsm.blitFbo) gl.deleteFramebuffer(this._bsm.blitFbo);
- if (this._bsm.blitProg) gl.deleteProgram(this._bsm.blitProg);
- if (this._bsm.blitVbo) gl.deleteBuffer(this._bsm.blitVbo);
- // 清理 TAA 资源
- if (this._taa.texA) gl.deleteTexture(this._taa.texA);
- if (this._taa.texB) gl.deleteTexture(this._taa.texB);
- if (this._taa.pbo) gl.deleteBuffer(this._taa.pbo);
- // 清理原始 WebGL2 云渲染资源
- if (this._raw.prog) gl.deleteProgram(this._raw.prog);
- if (this._raw.vao) gl.deleteVertexArray(this._raw.vao);
- if (this._raw.vbo) gl.deleteBuffer(this._raw.vbo);
- if (this._raw.sceneColor) gl.deleteTexture(this._raw.sceneColor);
- if (this._raw.depthTex) gl.deleteTexture(this._raw.depthTex);
- if (this._raw.depthFbo) gl.deleteFramebuffer(this._raw.depthFbo);
- }
- this._bsm = { pass: null, resolve: null, blitFbo: null, blitProg: null, blitVbo: null };
- this._taa = { texA: null, texB: null, current: 0, pbo: null, pboReady: false, w: 0, h: 0, frameCount: 0, prevVP: null, curVP: null };
- this._raw = { prog: null, vao: null, vbo: null, sceneColor: null, depthTex: null, depthFbo: null, depthW: 0, depthH: 0, texUnits: {} };
- if (this.textures) { for (const k in this.textures) { try { this.textures[k]?.destroy?.(); } catch {} } this.textures = null; }
- if (this._gui) { this._gui.destroy(); this._gui = null; }
- this._ready = null;
- }
- }
|