ThreeGeospatialPipeline_temp.mjs 102 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071
  1. /**
  2. * ThreeGeospatialPipeline - 精炼版体积云 + Bruneton 大气 + 空中透视一体化管线。
  3. *
  4. * 渲染顺序(与 three-geospatial 对齐):
  5. * 1. PostProcessStage: 体积云 raymarch(含 BSM 采样、shadowLength、haze)
  6. * 2. PostProcessStage: AtmospherePostProcess 天空
  7. * 3. PostProcessStage: AerialPerspectiveEffect 几何透视 + tonemap
  8. *
  9. * BSM(Beer Shadow Map)和 TAA 通过原生 WebGL 在 preRender/postRender 执行。
  10. * BSM 数据通过 setCloudShadow 同步到大气和 Aerial 两侧,实现丁达尔与地面云影。
  11. */
  12. import * as dat from "dat.gui";
  13. import { AtmosphereParameters, PRECOMPUTE_CONSTANTS, getPrecomputeDefines, flattenAtmosphereUniform } from "./AtmosphereFromThreeGeospatial/AtmosphereParameters.js";
  14. import { AtmospherePostProcess } from "./AtmosphereFromThreeGeospatial/AtmospherePostProcess.js";
  15. import { AerialPerspectiveEffect } from "./AtmosphereFromThreeGeospatial/AerialPerspectiveEffect.js";
  16. import { loadBinThreeGeospatial, bindData3DTextureToCesiumContext } from "./loadBinThreeGeospatial.js";
  17. import { loadShaderSource } from "./shaderLoader.js";
  18. import {
  19. DEFAULT_CLOUDS_ASSETS_BASE,
  20. DEFAULT_BRUNETON_SHADER_BASE,
  21. DEFAULT_BLUE_NOISE_URL,
  22. DEFAULT_ATMOSPHERE_ASSETS_BASE,
  23. DEFAULT_ATMOSPHERE_SHADER_BASE,
  24. } from "./assetPaths.js";
  25. const SHADOW_MAP_SIZE = 1024;
  26. const SHADOW_CASCADE_COUNT = 4;
  27. const SHADOW_RAY_FAR = 500000.0;
  28. const BSM_BLIT_SIZE = 1024;
  29. // ─── Cloud fragment shader (Bruneton integrated, no debug branches) ────────
  30. function getCloudFragmentShader() {
  31. return /* glsl */ `
  32. const float RECIPROCAL_PI4 = 0.07957747154594767;
  33. const float EVOLUTION_SCALE = 2e4;
  34. uniform sampler2D colorTexture;
  35. uniform sampler2D depthTexture;
  36. uniform sampler3D u_shapeTexture;
  37. uniform sampler3D u_shapeDetailTexture;
  38. uniform sampler3D u_stbnTexture;
  39. uniform sampler2D u_weatherTexture;
  40. uniform sampler2D u_turbulenceTexture;
  41. uniform sampler2D u_blueNoise;
  42. uniform float u_blueNoiseScale;
  43. uniform float u_jitterStrength;
  44. uniform vec3 u_cameraPosition;
  45. uniform vec3 u_altitudeCorrection;
  46. uniform float u_cameraHeight;
  47. uniform float u_bottomRadius;
  48. uniform float u_minHeight;
  49. uniform float u_maxHeight;
  50. uniform vec4 u_minLayerHeights;
  51. uniform vec4 u_maxLayerHeights;
  52. uniform vec4 u_densityScales;
  53. uniform vec4 u_shapeAmounts;
  54. uniform vec4 u_shapeDetailAmounts;
  55. uniform vec4 u_weatherExponents;
  56. uniform vec4 u_shapeAlteringBiases;
  57. uniform vec4 u_coverageFilterWidths;
  58. uniform float u_maxSteps;
  59. uniform float u_maxStepsToSun;
  60. uniform float u_minStepSize;
  61. uniform float u_maxStepSize;
  62. uniform float u_maxRayDistance;
  63. uniform float u_cameraNear;
  64. uniform float u_shadowTopHeight;
  65. uniform int u_shadowLengthEnabled;
  66. uniform int u_hazeEnabled;
  67. uniform int u_maxShadowLengthIterationCount;
  68. uniform float u_minShadowLengthStepSize;
  69. uniform float u_maxShadowLengthRayDistance;
  70. uniform float u_hazeDensityScale;
  71. uniform float u_hazeExponent;
  72. uniform float u_hazeScatteringCoefficient;
  73. uniform float u_hazeAbsorptionCoefficient;
  74. uniform sampler2D u_shadowBuffer;
  75. uniform vec2 u_shadowTexelSize;
  76. uniform vec2 u_shadowIntervals[4];
  77. uniform mat4 u_shadowMatrices[4];
  78. uniform float u_shadowFar;
  79. uniform float u_maxShadowFilterRadius;
  80. uniform int u_useShadowBuffer;
  81. uniform float u_skyLightScale;
  82. uniform float u_weatherRepeat;
  83. uniform vec2 u_localWeatherOffset;
  84. uniform float u_shapeRepeat;
  85. uniform vec3 u_shapeOffset;
  86. uniform float u_shapeDetailRepeat;
  87. uniform vec3 u_shapeDetailOffset;
  88. uniform float u_turbulenceRepeat;
  89. uniform float u_turbulenceDisplacement;
  90. uniform vec4 u_coverages;
  91. uniform float u_coverageHaze;
  92. uniform float u_scatteringCoefficient;
  93. uniform float u_absorptionCoefficient;
  94. uniform float u_scatterG1;
  95. uniform float u_scatterG2;
  96. uniform float u_scatterMix;
  97. uniform float u_sunIntensity;
  98. uniform float u_skyToSunRatio;
  99. uniform float u_powderScale;
  100. uniform float u_powderExponent;
  101. uniform float u_aerialPerspectiveScale;
  102. uniform float u_cloudExposure;
  103. uniform float u_magentaFixStrength;
  104. uniform float u_edgeAlphaCutoff;
  105. uniform vec2 u_resolution;
  106. uniform float u_mipLevelScale;
  107. uniform float u_perspectiveStepScale;
  108. uniform float u_minDensity;
  109. uniform float u_minExtinction;
  110. uniform float u_minTransmittance;
  111. // 远处云密度距离衰减:从 u_distFadeStart(米)开始线性降到0,到 u_distFadeEnd 完全消失
  112. // 消除天际线附近云"堆在一起"的视觉拥挤
  113. uniform float u_distFadeStart;
  114. uniform float u_distFadeEnd;
  115. uniform float u_minSecondaryStepSize;
  116. uniform float u_secondaryStepScale;
  117. uniform int u_multiScatteringOctaves;
  118. uniform float u_lowLayerDensityBoost;
  119. uniform vec4 u_densityProfileExpTerms;
  120. uniform vec4 u_densityProfileExponents;
  121. uniform vec4 u_densityProfileLinearTerms;
  122. uniform vec4 u_densityProfileConstantTerms;
  123. uniform vec3 u_minIntervalHeights;
  124. uniform vec3 u_maxIntervalHeights;
  125. uniform sampler2D u_historyTexture;
  126. uniform mat4 u_prevViewProjection;
  127. uniform float u_temporalAlpha;
  128. uniform int u_temporalEnabled;
  129. uniform int u_frame;
  130. in vec2 v_textureCoordinates;
  131. vec3 ACESFilmic(vec3 x) {
  132. float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;
  133. return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
  134. }
  135. float saturate(float x) { return clamp(x, 0.0, 1.0); }
  136. vec4 saturate(vec4 x) { return clamp(x, 0.0, 1.0); }
  137. float remap(float v, float a, float b, float c, float d) { return c + (v - a) * (d - c) / (b - a); }
  138. 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)); }
  139. float remapClamped(float v, float a, float b) { return clamp((v - a) / (b - a), 0.0, 1.0); }
  140. vec4 remap(vec4 v, vec4 a, vec4 b, vec4 c, vec4 d) { return c + (v - a) * (d - c) / (b - a); }
  141. 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)); }
  142. vec4 remapClamped(vec4 v, vec4 a, vec4 b) { return clamp((v - a) / (b - a), 0.0, 1.0); }
  143. vec3 reduceMagenta(vec3 color, float strength) {
  144. float magenta = max(0.0, min(color.r, color.b) - color.g);
  145. float fix = clamp(magenta * 5.0 * max(strength, 0.0), 0.0, 1.0);
  146. float target = color.g;
  147. color.r = mix(color.r, target, fix);
  148. color.b = mix(color.b, target, fix);
  149. return color;
  150. }
  151. vec2 raySphereIntersect(vec3 ro, vec3 rd, float radius) {
  152. float b = dot(ro, rd);
  153. float c = dot(ro, ro) - radius * radius;
  154. float h = b * b - c;
  155. if (h < 0.0) return vec2(-1.0);
  156. h = sqrt(h);
  157. return vec2(-b - h, -b + h);
  158. }
  159. void reconstructRay(out vec3 ro, out vec3 rd) {
  160. ro = u_cameraPosition + u_altitudeCorrection;
  161. vec2 uv = v_textureCoordinates * 2.0 - 1.0;
  162. vec4 clipPos = vec4(uv, 1.0, 1.0);
  163. vec4 viewPos = czm_inverseProjection * clipPos;
  164. viewPos /= viewPos.w;
  165. vec4 worldPos4 = czm_inverseView * viewPos;
  166. vec3 worldPos = worldPos4.xyz + u_altitudeCorrection;
  167. rd = normalize(worldPos - ro);
  168. }
  169. float getSTBN() {
  170. // 与 three-geospatial 一致:按帧在 3D STBN 的 z 维切片轮换
  171. ivec3 size = textureSize(u_stbnTexture, 0);
  172. vec3 scale = 1.0 / vec3(size);
  173. return texture(
  174. u_stbnTexture,
  175. vec3(gl_FragCoord.xy, float(u_frame % size.z)) * scale
  176. ).r;
  177. }
  178. vec2 getCubeSphereUv(vec3 position) {
  179. vec3 n = normalize(position);
  180. vec3 f = abs(n);
  181. vec3 c = n / max(f.x, max(f.y, f.z));
  182. vec2 m;
  183. if (f.y >= f.x && f.y >= f.z) { m = c.y > 0.0 ? vec2(-n.x, n.z) : n.xz; }
  184. else if (f.x >= f.y && f.x >= f.z) { m = c.x > 0.0 ? n.yz : vec2(-n.y, n.z); }
  185. else { m = c.z > 0.0 ? n.xy : vec2(n.x, -n.y); }
  186. vec2 m2 = m * m;
  187. float q = dot(m2.xy, vec2(-2.0, 2.0)) - 3.0;
  188. float q2 = q * q;
  189. vec2 uv;
  190. 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);
  191. uv.y = sqrt(6.0 / max(0.001, 3.0 - uv.x * uv.x)) * m.y;
  192. return uv * 0.5 + 0.5;
  193. }
  194. vec2 getGlobeUv(vec3 position) { return getCubeSphereUv(position); }
  195. float getMipLevel(vec2 uv) {
  196. vec2 coord = uv * u_resolution;
  197. vec2 ddx_v = dFdx(coord);
  198. vec2 ddy_v = dFdy(coord);
  199. float deltaMaxSqr = max(dot(ddx_v, ddx_v), dot(ddy_v, ddy_v)) * 0.1;
  200. return max(0.0, 0.5 * log2(max(1.0, deltaMaxSqr)));
  201. }
  202. bool inEmptySpace(float height) {
  203. bvec3 gt = greaterThan(vec3(height), u_minIntervalHeights);
  204. bvec3 lt = lessThan(vec3(height), u_maxIntervalHeights);
  205. return gt.x && lt.x || gt.y && lt.y || gt.z && lt.z;
  206. }
  207. vec4 getLayerDensity(vec4 hf) {
  208. return u_densityProfileExpTerms * exp(u_densityProfileExponents * hf) + u_densityProfileLinearTerms * hf + u_densityProfileConstantTerms;
  209. }
  210. vec4 getHeightFractions(float height) {
  211. vec4 range = u_maxLayerHeights - u_minLayerHeights;
  212. return clamp((vec4(height) - u_minLayerHeights) / max(range, vec4(0.0001)), 0.0, 1.0);
  213. }
  214. struct WeatherSample { vec4 heightFraction; vec4 density; };
  215. struct MediaSample { float density; vec4 weight; float scattering; float extinction; };
  216. vec4 shapeAlteringFunction(vec4 hf, vec4 bias) {
  217. vec4 biased = pow(hf, bias);
  218. vec4 x = clamp(biased * 2.0 - 1.0, -1.0, 1.0);
  219. return 1.0 - x * x;
  220. }
  221. WeatherSample sampleWeather(vec2 uv, float height, float mipLevel) {
  222. WeatherSample w;
  223. w.heightFraction = getHeightFractions(height);
  224. vec2 wUv = uv * u_weatherRepeat + u_localWeatherOffset;
  225. vec4 localW = pow(textureLod(u_weatherTexture, wUv, mipLevel).rgba, u_weatherExponents);
  226. vec4 hs = shapeAlteringFunction(w.heightFraction, u_shapeAlteringBiases);
  227. vec4 factor = 1.0 - u_coverages * hs;
  228. w.density = remapClamped(mix(localW, vec4(1.0), u_coverageFilterWidths), factor, factor + u_coverageFilterWidths);
  229. return w;
  230. }
  231. MediaSample sampleMedia(WeatherSample weather, vec3 position, vec2 uv, float mipLevel, float jitter) {
  232. vec4 density = weather.density;
  233. vec3 sn = normalize(position);
  234. vec3 evolution = -sn * length(u_localWeatherOffset) * EVOLUTION_SCALE;
  235. vec2 tUv = uv * u_weatherRepeat * u_turbulenceRepeat;
  236. vec3 turb = u_turbulenceDisplacement * (texture(u_turbulenceTexture, tUv).rgb * 2.0 - 1.0)
  237. * dot(density, remapClamped(weather.heightFraction, vec4(0.3), vec4(0.0)));
  238. vec3 sp = (position + evolution + turb) * u_shapeRepeat + u_shapeOffset;
  239. float shapeTex = texture(u_shapeTexture, fract(sp)).r;
  240. density = remapClamped(density, vec4(1.0 - shapeTex) * u_shapeAmounts, vec4(1.0));
  241. if (mipLevel * 0.5 + (jitter - 0.5) * 0.5 < 0.5) {
  242. vec3 dp = (position + turb) * u_shapeDetailRepeat + u_shapeDetailOffset;
  243. float detail = texture(u_shapeDetailTexture, dp).r;
  244. vec4 modifier = mix(vec4(pow(detail, 6.0)), vec4(1.0 - detail),
  245. remapClamped(weather.heightFraction, vec4(0.2), vec4(0.4), vec4(0.0), vec4(1.0)));
  246. modifier = mix(vec4(0.0), modifier, u_shapeDetailAmounts);
  247. density = remapClamped(density * 2.0, vec4(modifier * 0.5), vec4(1.0));
  248. }
  249. density = saturate(density * u_densityScales * getLayerDensity(weather.heightFraction));
  250. float ds = density.x + density.y + density.z + density.w;
  251. MediaSample m;
  252. m.density = ds;
  253. m.weight = density / max(ds, 1e-7);
  254. m.scattering = ds * u_scatteringCoefficient;
  255. m.extinction = ds * u_absorptionCoefficient + m.scattering;
  256. return m;
  257. }
  258. float henyeyGreenstein(float g, float cosTheta) {
  259. float g2 = g * g;
  260. return RECIPROCAL_PI4 * (1.0 - g2) / pow(1.0 + g2 - 2.0 * g * cosTheta, 1.5);
  261. }
  262. float phaseFunction(float cosTheta, float attenuation) {
  263. return mix(henyeyGreenstein(u_scatterG1 * attenuation, cosTheta),
  264. henyeyGreenstein(u_scatterG2 * attenuation, cosTheta), u_scatterMix);
  265. }
  266. float approximateMultipleScattering(float opticalDepth, float cosTheta) {
  267. vec3 coeffs = vec3(1.0);
  268. const vec3 attenuation = vec3(0.5);
  269. float scattering = 0.0;
  270. for (int i = 0; i < 12; i++) {
  271. if (i >= u_multiScatteringOctaves) break;
  272. scattering += coeffs.x * exp(-opticalDepth * coeffs.y) * phaseFunction(cosTheta, coeffs.z);
  273. coeffs *= attenuation;
  274. }
  275. return scattering;
  276. }
  277. float marchOpticalDepthToSun(vec3 rayOrigin, vec3 rayDirection, float mipLevel, float jitter, out float sunRayDist) {
  278. float iterCount = max(0.0, remap(mipLevel, 0.0, 1.0, float(u_maxStepsToSun) + 1.0, 1.0) - jitter);
  279. int ic = int(iterCount);
  280. if (ic == 0) return 0.5;
  281. float stepSize = u_minSecondaryStepSize / iterCount;
  282. float nextDist = stepSize * jitter;
  283. float od = 0.0;
  284. sunRayDist = 0.0;
  285. for (int i = 0; i < 8; i++) {
  286. if (i >= ic) break;
  287. sunRayDist = nextDist;
  288. vec3 pos = rayDirection * nextDist + rayOrigin;
  289. vec2 uv = getGlobeUv(pos);
  290. float h = length(pos) - u_bottomRadius;
  291. WeatherSample ws = sampleWeather(uv, h, mipLevel);
  292. MediaSample ms = sampleMedia(ws, pos, uv, mipLevel, jitter);
  293. od += ms.extinction * stepSize;
  294. nextDist += stepSize;
  295. stepSize *= u_secondaryStepScale;
  296. }
  297. return od;
  298. }
  299. bool rayIntersectsGround(vec3 camPos, vec3 rd) {
  300. float r = length(camPos);
  301. float mu = dot(camPos, rd) / r;
  302. return mu < 0.0 && r * r * (mu * mu - 1.0) + u_bottomRadius * u_bottomRadius >= 0.0;
  303. }
  304. void raySphereIntersections(vec3 origin, vec3 direction, vec4 radius, out vec4 i1, out vec4 i2) {
  305. float b = 2.0 * dot(direction, origin);
  306. vec4 c = dot(origin, origin) - radius * radius;
  307. vec4 disc = b * b - 4.0 * c;
  308. vec4 mask = step(disc, vec4(0.0));
  309. vec4 Q = sqrt(max(vec4(0.0), disc));
  310. i1 = mix((-b - Q) * 0.5, vec4(-1.0), mask);
  311. i2 = mix((-b + Q) * 0.5, vec4(-1.0), mask);
  312. }
  313. void getIntersections(vec3 camPos, vec3 rd, out bool ground, out vec4 first, out vec4 second) {
  314. ground = rayIntersectsGround(camPos, rd);
  315. vec4 radii = u_bottomRadius + vec4(0.0, u_minHeight, u_maxHeight, u_shadowTopHeight);
  316. raySphereIntersections(camPos, rd, radii, first, second);
  317. }
  318. vec2 getRayNearFar(bool ground, vec4 first, vec4 second) {
  319. vec2 nearFar;
  320. if (u_cameraHeight < u_minHeight) {
  321. if (ground) {
  322. nearFar = vec2(-1.0);
  323. } else {
  324. nearFar = vec2(second.y, second.z);
  325. nearFar.y = min(nearFar.y, u_maxRayDistance);
  326. }
  327. } else if (u_cameraHeight < u_maxHeight) {
  328. if (ground) {
  329. // 地面相交时,采样从相机近裁面到云层下边界
  330. nearFar = vec2(u_cameraNear, first.y);
  331. if (nearFar.y <= nearFar.x) nearFar = vec2(-1.0);
  332. } else {
  333. float farExit = max(max(first.y, second.y), max(first.z, second.z));
  334. if (farExit <= 0.0) {
  335. // 无有效远边界时,强制采样到最大射线距离
  336. farExit = u_maxRayDistance;
  337. }
  338. farExit = min(farExit, u_maxRayDistance);
  339. farExit = max(farExit, u_cameraNear + u_minStepSize * 0.5);
  340. nearFar = vec2(u_cameraNear, farExit);
  341. }
  342. } else {
  343. float farExit = max(max(first.y, second.y), max(first.z, second.z));
  344. if (farExit > 0.0) {
  345. farExit = min(farExit, u_maxRayDistance);
  346. farExit = max(farExit, u_cameraNear + u_minStepSize * 0.5);
  347. nearFar = vec2(u_cameraNear, farExit);
  348. }
  349. }
  350. return nearFar;
  351. }
  352. vec2 getShadowRayNearFar(bool ground, vec4 first, vec4 second) {
  353. vec2 nf;
  354. if (u_cameraHeight < u_shadowTopHeight) {
  355. nf = ground ? vec2(u_cameraNear, first.x) : vec2(u_cameraNear, second.w);
  356. } else {
  357. nf = vec2(first.w, second.w);
  358. if (ground) nf.y = first.x;
  359. }
  360. nf.y = min(nf.y, u_maxShadowLengthRayDistance);
  361. return nf;
  362. }
  363. vec2 getHazeRayNearFar(bool ground, vec4 first, vec4 second) {
  364. vec2 nf;
  365. if (u_cameraHeight < u_maxHeight) {
  366. nf = ground ? vec2(u_cameraNear, first.x) : vec2(u_cameraNear, second.z);
  367. } else {
  368. nf = vec2(u_cameraNear, second.z);
  369. if (ground) nf.y = first.x;
  370. }
  371. return nf;
  372. }
  373. // ── BSM sampling ──
  374. float sampleShadowOpticalDepth(vec3 rayPosition, float distanceOffset, float radius, float jitter);
  375. float getDistanceToShadowTop(vec3 rayPos) {
  376. vec3 rd = czm_sunDirectionWC;
  377. float R = u_bottomRadius + u_shadowTopHeight;
  378. float b = dot(rayPos, rd);
  379. float c = dot(rayPos, rayPos) - R * R;
  380. float h = b * b - c;
  381. if (h < 0.0) return -1.0;
  382. return -b + sqrt(h);
  383. }
  384. // three.js / CloudShadowPass intervals=(d-near)/(far-near) 一致
  385. float viewZToOrthographicDepth(float viewZ, float near, float far) {
  386. return (viewZ + near) / (near - far);
  387. }
  388. int getFadedCascadeIndex(mat4 viewMat, vec3 worldPos, vec2 intervals[4], float near, float far, float jitter) {
  389. vec4 vp = viewMat * vec4(worldPos, 1.0);
  390. float depth = viewZToOrthographicDepth(vp.z, near, far);
  391. int nextIndex = -1, prevIndex = -1;
  392. float alpha = 1.0;
  393. for (int i = 0; i < 4; ++i) {
  394. vec2 interval = intervals[i];
  395. float intervalCenter = (interval.x + interval.y) * 0.5;
  396. float closestEdge = depth < intervalCenter ? interval.x : interval.y;
  397. float margin = closestEdge * closestEdge * 0.5;
  398. interval += margin * vec2(-0.5, 0.5);
  399. if (i < 3) {
  400. if (depth >= interval.x && depth < interval.y) { prevIndex = nextIndex; nextIndex = i; alpha = saturate(min(depth - interval.x, interval.y - depth) / max(margin, 1e-6)); }
  401. } else {
  402. if (depth >= interval.x) { prevIndex = nextIndex; nextIndex = i; alpha = saturate((depth - interval.x) / max(margin, 1e-6)); }
  403. }
  404. }
  405. return jitter <= alpha ? nextIndex : prevIndex;
  406. }
  407. 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; }
  408. vec2 getShadowAtlasOffset(int ci) { return vec2(mod(float(ci), 2.0) * 0.5, (ci < 2) ? 0.5 : 0.0); }
  409. float readShadowOpticalDepth(vec2 uv, int ci, float distToTop, float distOff) {
  410. if (u_useShadowBuffer == 0) return 0.0;
  411. vec2 atlasUv = getShadowAtlasOffset(ci) + uv * 0.5;
  412. vec4 shadow = texture(u_shadowBuffer, atlasUv);
  413. float distToFront = max(0.0, distToTop - distOff - shadow.r);
  414. return min(shadow.b + shadow.a, shadow.g * distToFront);
  415. }
  416. float interleavedGradientNoise(vec2 coord) {
  417. const vec3 magic = vec3(0.06711056, 0.00583715, 52.9829189);
  418. return fract(magic.z * fract(dot(coord, magic.xy)));
  419. }
  420. vec2 vogelDisk(int index, int count, float phi) {
  421. const float goldenAngle = 2.39996322972865332;
  422. float r = sqrt(float(index) + 0.5) / sqrt(float(count));
  423. float theta = float(index) * goldenAngle + phi;
  424. return r * vec2(cos(theta), sin(theta));
  425. }
  426. float sampleShadowOpticalDepthPCF(vec3 worldPos, float distToTop, float distOff, float radius, int ci) {
  427. vec2 uv = getShadowUv(worldPos, ci);
  428. if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) return 0.0;
  429. if (radius < 0.1) return readShadowOpticalDepth(uv, ci, distToTop, distOff);
  430. float sum = 0.0;
  431. float phi = interleavedGradientNoise(gl_FragCoord.xy) * 3.14159265 * 2.0;
  432. for (int i = 0; i < 16; ++i) sum += readShadowOpticalDepth(uv + vogelDisk(i, 16, phi) * radius * u_shadowTexelSize, ci, distToTop, distOff);
  433. return sum / 16.0;
  434. }
  435. float sampleShadowOpticalDepth(vec3 rayPos, float distOff, float radius, float jitter) {
  436. float distToTop = getDistanceToShadowTop(rayPos);
  437. if (distToTop <= 0.0) return 0.0;
  438. int ci = getFadedCascadeIndex(czm_view, rayPos, u_shadowIntervals, u_cameraNear, u_shadowFar, jitter);
  439. return ci >= 0 ? sampleShadowOpticalDepthPCF(rayPos, distToTop, distOff, radius, ci) : 0.0;
  440. }
  441. float marchShadowLength(vec3 rayOrigin, vec3 rayDir, vec2 rayNearFar, float jitter) {
  442. float shadowLen = 0.0;
  443. float maxDist = rayNearFar.y - rayNearFar.x;
  444. float stepSize = u_minShadowLengthStepSize;
  445. float rayDist = stepSize * jitter;
  446. for (int i = 0; i < 512; i++) {
  447. if (float(i) >= float(u_maxShadowLengthIterationCount)) break;
  448. if (rayDist > maxDist) break;
  449. vec3 pos = rayDir * rayDist + rayOrigin;
  450. float od = sampleShadowOpticalDepth(pos, 0.0, 0.0, jitter);
  451. shadowLen += (1.0 - exp(-od)) * stepSize;
  452. stepSize *= u_perspectiveStepScale;
  453. rayDist += stepSize;
  454. }
  455. return shadowLen;
  456. }
  457. #ifdef USE_ATMOSPHERE_IRRADIANCE
  458. void applyAerialPerspective(vec3 camPos, vec3 frontPos, float dist, float shadowLen, inout vec4 color) {
  459. vec3 transmittance;
  460. vec3 inscatter = GetSkyRadianceToPoint(camPos * METER_TO_LENGTH_UNIT, frontPos * METER_TO_LENGTH_UNIT, shadowLen * METER_TO_LENGTH_UNIT, sunDirection, transmittance);
  461. float horizonBias = smoothstep(20.0, 80.0, dist * METER_TO_LENGTH_UNIT);
  462. // 【新增】根据太阳高度计算可见度,晚上太阳沉下地平线时 sunVis 为 0
  463. float sunVis = smoothstep(-0.02, 0.05, dot(normalize(camPos), sunDirection));
  464. vec3 fakeHorizonColor = vec3(0.5, 0.6, 0.8) * 0.2 * sunVis;
  465. inscatter = mix(inscatter, inscatter * 0.4 + fakeHorizonColor, horizonBias);
  466. color.rgb = color.rgb * transmittance + inscatter * color.a * u_aerialPerspectiveScale;
  467. }
  468. #else
  469. void applyAerialPerspective(vec3 camPos, vec3 frontPos, float dist, float shadowLen, inout vec4 color) {
  470. vec3 rayleigh = vec3(0.005802, 0.013558, 0.033100) * 0.001;
  471. float h = length(frontPos) - u_bottomRadius;
  472. float density = exp(-h / 8000.0);
  473. vec3 transmittance = exp(-dist * rayleigh * density * u_aerialPerspectiveScale);
  474. // 【新增】太阳可见度衰减
  475. float sunVis = smoothstep(-0.05, 0.1, dot(normalize(camPos), sunDirection));
  476. vec3 skyColor = vec3(0.4, 0.6, 1.0) * u_sunIntensity * 0.02 * sunVis;
  477. color.rgb = color.rgb * transmittance + skyColor * (1.0 - transmittance) * color.a;
  478. }
  479. #endif
  480. vec4 approximateHaze(vec3 ro, vec3 rd, float maxDist, float cosTheta, float shadowLen) {
  481. float modulation = remapClamped(u_coverageHaze, 0.2, 0.4);
  482. if (u_cameraHeight * modulation < 0.0) return vec4(0.0);
  483. float density = modulation * u_hazeDensityScale * exp(-u_cameraHeight * u_hazeExponent);
  484. if (density < 1e-7) return vec4(0.0);
  485. vec3 nOrigin = normalize(ro);
  486. float sunHeight = dot(nOrigin, sunDirection);
  487. float sunVis = smoothstep(-0.02, 0.05, sunHeight);
  488. float viewZenith = abs(rd.y);
  489. float horizonTaming = smoothstep(0.0, 0.15, viewZenith);
  490. // 即使在天际线,也要保留一点点基础亮度,但不能是 1.0
  491. horizonTaming = mix(0.3, 1.0, horizonTaming);
  492. vec3 nHoriz = (ro - dot(ro, rd) * rd) / u_bottomRadius;
  493. float alpha = remapClamped(dot(nOrigin, nHoriz), 0.9, 1.0);
  494. vec3 normal = mix(nOrigin, nHoriz, alpha);
  495. float angle = max(dot(normal, rd), 1e-5);
  496. float exponent = angle * u_hazeExponent;
  497. float linearTerm = density / u_hazeExponent / angle;
  498. float expTerm = 1.0 - exp(-maxDist * exponent);
  499. float shadowExpTerm = 1.0 - exp(-min(maxDist, shadowLen) * exponent);
  500. float opticalDepth = expTerm * linearTerm;
  501. float shadowOD = max((expTerm - shadowExpTerm) * linearTerm, 0.0);
  502. float transmittance = saturate(1.0 - exp(-opticalDepth));
  503. float shadowTransmittance = saturate(1.0 - exp(-shadowOD));
  504. // 【修改】将硬编码的光源强度乘以太阳可见度
  505. vec3 skyIrradiance = vec3(0.4, 0.6, 1.0) * u_sunIntensity * 0.04 * sunVis * horizonTaming;
  506. vec3 sunIrradiance = vec3(1.0, 0.95, 0.9) * u_sunIntensity * sunVis;
  507. float ph = henyeyGreenstein(u_scatterG1, cosTheta) * (1.0 - u_scatterMix) + henyeyGreenstein(u_scatterG2, cosTheta) * u_scatterMix;
  508. vec3 inscatter = sunIrradiance * ph * shadowTransmittance + skyIrradiance * RECIPROCAL_PI4 * u_skyLightScale * transmittance;
  509. inscatter *= u_hazeScatteringCoefficient / (u_hazeAbsorptionCoefficient + u_hazeScatteringCoefficient);
  510. return vec4(inscatter, transmittance);
  511. }
  512. // ── Main raymarch ──
  513. vec4 marchClouds(vec3 rayOrigin, vec3 rd, vec2 rayNearFar, float cosTheta, float jitter, float rayStartTexels, out float frontDepth) {
  514. float maxDist = min(rayNearFar.y - rayNearFar.x, u_maxRayDistance);
  515. vec3 radInt = vec3(0.0);
  516. float transInt = 1.0, wdSum = 0.0, tSum = 0.0;
  517. float perspDist = min(rayNearFar.x, 3000.0);
  518. float stepSize = u_minStepSize + (u_perspectiveStepScale - 1.0) * perspDist;
  519. float rayDist = stepSize * jitter * 2.0;
  520. #ifdef USE_ATMOSPHERE_IRRADIANCE
  521. float refRadius = u_bottomRadius;
  522. #else
  523. vec3 sunColorBase = vec3(1.0, 0.95, 0.9) * u_sunIntensity;
  524. vec3 skyColorBase = vec3(0.4, 0.6, 1.0) * u_sunIntensity * u_skyToSunRatio;
  525. float refRadius = u_bottomRadius;
  526. vec3 sunDirection = czm_sunDirectionWC;
  527. #endif
  528. for (int i = 0; i < 512; i++) {
  529. if (float(i) >= u_maxSteps) break;
  530. if (rayDist > maxDist) break;
  531. if (transInt <= u_minTransmittance) break;
  532. vec3 position = rayOrigin + rd * rayDist;
  533. float height = length(position) - refRadius;
  534. float mipLevel = log2(max(1.0, rayStartTexels + rayDist * 1e-5));
  535. if (inEmptySpace(height)) { stepSize *= u_perspectiveStepScale; rayDist += mix(stepSize, u_maxStepSize, min(1.0, mipLevel)); continue; }
  536. vec2 uv = getGlobeUv(position);
  537. WeatherSample weather = sampleWeather(uv, height, mipLevel);
  538. if (!any(greaterThan(weather.density, vec4(u_minDensity)))) { stepSize *= u_perspectiveStepScale; rayDist += mix(stepSize, u_maxStepSize, min(1.0, mipLevel)); continue; }
  539. weather.density.xy *= u_lowLayerDensityBoost;
  540. MediaSample media = sampleMedia(weather, position, uv, mipLevel, jitter);
  541. if (media.extinction > u_minExtinction) {
  542. #ifdef USE_ATMOSPHERE_IRRADIANCE
  543. vec3 skyIrradiance;
  544. vec3 sunIrradiance = GetSunAndSkyScalarIrradiance(position * METER_TO_LENGTH_UNIT, sunDirection, skyIrradiance);
  545. float skyGradient = dot(weather.heightFraction * 0.5 + 0.5, media.weight);
  546. vec3 sunColor = sunIrradiance * u_sunIntensity;
  547. vec3 skyColor = skyIrradiance * u_sunIntensity * u_skyToSunRatio;
  548. #else
  549. float heightAlpha = clamp((height - u_minHeight) / max(u_maxHeight - u_minHeight, 1.0), 0.0, 1.0);
  550. vec3 sunColor = mix(sunColorBase * 0.85, sunColorBase, heightAlpha);
  551. vec3 skyColor = mix(skyColorBase * 0.6, skyColorBase, heightAlpha);
  552. float skyGradient = dot(weather.heightFraction * 0.5 + 0.5, media.weight);
  553. #endif
  554. float sunRayDist;
  555. float opticalDepth = marchOpticalDepthToSun(position, sunDirection, mipLevel, jitter, sunRayDist);
  556. if (length(position) - refRadius < u_shadowTopHeight && u_useShadowBuffer == 1) {
  557. vec3 sn = normalize(position);
  558. float r = u_maxShadowFilterRadius * remapClamped(dot(sunDirection, sn), 0.1, 0.0);
  559. opticalDepth += sampleShadowOpticalDepth(position, sunRayDist, r, jitter);
  560. }
  561. vec3 radiance = sunColor * approximateMultipleScattering(opticalDepth, cosTheta);
  562. radiance += skyColor * RECIPROCAL_PI4 * skyGradient * u_skyLightScale;
  563. radiance *= media.scattering * (1.0 - u_powderScale * exp(-media.extinction * u_powderExponent));
  564. float transmittance = exp(-media.extinction * stepSize);
  565. vec3 scatInt = (radiance - radiance * transmittance) / max(media.extinction, 1e-7);
  566. radInt += transInt * scatInt;
  567. transInt *= transmittance;
  568. wdSum += rayDist * transInt;
  569. tSum += transInt;
  570. }
  571. stepSize *= u_perspectiveStepScale;
  572. rayDist += stepSize;
  573. }
  574. frontDepth = tSum > 0.0 ? wdSum / tSum : -1.0;
  575. float alpha = saturate(remapClamped(transInt, 1.0, u_minTransmittance));
  576. return vec4(radInt, alpha);
  577. }
  578. void main() {
  579. vec4 sceneColor = texture(colorTexture, v_textureCoordinates);
  580. float depth = czm_readDepth(depthTexture, v_textureCoordinates);
  581. vec3 ro, rd;
  582. reconstructRay(ro, rd);
  583. #ifndef USE_ATMOSPHERE_IRRADIANCE
  584. vec3 sunDirection = czm_sunDirectionWC;
  585. #endif
  586. float jitter = getSTBN();
  587. bool ground; vec4 first, second;
  588. getIntersections(ro, rd, ground, first, second);
  589. vec2 rayNearFar = getRayNearFar(ground, first, second);
  590. vec2 shadowNF = vec2(-1.0), hazeNF = vec2(-1.0);
  591. if (u_shadowLengthEnabled == 1) shadowNF = getShadowRayNearFar(ground, first, second);
  592. if (u_hazeEnabled == 1) hazeNF = getHazeRayNearFar(ground, first, second);
  593. // depthTestAgainstTerrain 只影响 Globe/贴地物体与地形网格的深度关系,不能替后处理修正「沿像素射线」的距离。
  594. // 此处必须用 inverseView 还原命中点,再沿 rd 求距离;用 -viewZ/dot(rd, forward) 在离轴像素上会偏大 → 云压在地形前。
  595. float rayDistToScene = 0.0;
  596. if (depth < 1.0 - 1e-7) {
  597. vec4 eyePos = czm_windowToEyeCoordinates(vec4(gl_FragCoord.xy, depth, 1.0));
  598. if (abs(eyePos.w) > 1e-6) {
  599. eyePos /= eyePos.w;
  600. if (eyePos.z < 0.0) {
  601. vec4 worldPos4 = czm_inverseView * eyePos;
  602. vec3 worldHit = worldPos4.xyz + u_altitudeCorrection;
  603. rayDistToScene = max(0.0, dot(worldHit - ro, rd));
  604. }
  605. }
  606. }
  607. float tMax = rayNearFar.y;
  608. // 原逻辑:低于云层且 !ground 时跳过深度钳位 —— 平视/看山体时 ground 常为 false,会整屏不钳位 → 云盖住地形。
  609. // 仅当该像素无场景深度(天空)时才允许跳过;有地形/几何时必须用 rayDistToScene 截断射线。
  610. const float DEPTH_SKY = 1.0 - 1e-7;
  611. bool skipDepthClamp =
  612. (depth >= DEPTH_SKY) && (u_cameraHeight < u_minHeight) && (!ground);
  613. if (rayDistToScene > 0.0 && !skipDepthClamp) {
  614. tMax = min(tMax, rayDistToScene);
  615. if (u_shadowLengthEnabled == 1 && shadowNF.y > 0.0) shadowNF.y = min(shadowNF.y, rayDistToScene);
  616. if (u_hazeEnabled == 1 && hazeNF.y > 0.0) hazeNF.y = min(hazeNF.y, rayDistToScene);
  617. }
  618. if (rayNearFar.x >= tMax) { gl_FragColor = sceneColor; return; }
  619. float frontDepth;
  620. float cosTheta = dot(rd, sunDirection);
  621. vec2 globeUv = getGlobeUv(ro + rd * rayNearFar.x);
  622. float mipLevel = getMipLevel(globeUv * u_weatherRepeat) * u_mipLevelScale;
  623. mipLevel = mix(0.0, mipLevel, min(1.0, 0.2 * u_cameraHeight / max(u_maxHeight, 1.0)));
  624. vec4 cloudColor = marchClouds(ro + rd * rayNearFar.x, rd, vec2(rayNearFar.x, tMax), cosTheta, jitter, pow(2.0, mipLevel), frontDepth);
  625. // 远处云透明度距离衰减:用"相机到云层入口距离"(rayNearFar.x)衰减 alpha,
  626. // 而非云内穿行距离。天顶云入口近不衰减,天际线云入口远衰减——只压远处透明度,不影响各层密度。
  627. // 这解决斜射时云层路径长导致 alpha 堆积的问题,且不误伤高空稀疏层(层2 近处不衰减)。
  628. float entryFade = 1.0 - smoothstep(u_distFadeStart, u_distFadeEnd, rayNearFar.x);
  629. cloudColor.a *= entryFade;
  630. cloudColor.rgb *= entryFade;
  631. float shadowLen = 0.0;
  632. bool hitClouds = frontDepth > 0.0 && cloudColor.a > max(u_edgeAlphaCutoff, 0.02);
  633. float rayFrontT = rayNearFar.x + frontDepth;
  634. if (hitClouds) {
  635. if (u_shadowLengthEnabled == 1 && all(greaterThanEqual(shadowNF, vec2(0.0)))) {
  636. shadowNF.y = mix(shadowNF.y, min(rayFrontT, shadowNF.y), cloudColor.a);
  637. shadowLen = marchShadowLength(ro + rd * shadowNF.x, rd, shadowNF, jitter);
  638. }
  639. if (u_hazeEnabled == 1 && all(greaterThanEqual(hazeNF, vec2(0.0))))
  640. hazeNF.y = mix(hazeNF.y, min(rayFrontT, hazeNF.y), cloudColor.a);
  641. applyAerialPerspective(ro, ro + rd * rayFrontT, rayFrontT, shadowLen, cloudColor);
  642. } else if (u_shadowLengthEnabled == 1 && all(greaterThanEqual(shadowNF, vec2(0.0)))) {
  643. shadowLen = marchShadowLength(ro + rd * shadowNF.x, rd, shadowNF, jitter);
  644. }
  645. if (u_hazeEnabled == 1) {
  646. float hazeDist = all(greaterThanEqual(hazeNF, vec2(0.0))) ? (hazeNF.y - hazeNF.x) : 0.0;
  647. vec4 haze = approximateHaze(ro, rd, hazeDist, cosTheta, shadowLen);
  648. cloudColor.rgb = mix(cloudColor.rgb, haze.rgb, haze.a);
  649. cloudColor.a = cloudColor.a * (1.0 - haze.a) + haze.a;
  650. }
  651. // 边缘裁剪:低 alpha 区域直接清零,避免云边缘细碎噪点与闪烁
  652. if (cloudColor.a < u_edgeAlphaCutoff) {
  653. cloudColor = vec4(0.0);
  654. }
  655. // 边缘裁剪后再判一次:防止“已被裁掉的薄云像素”仍进入 TAA,导致底层模型抖动
  656. hitClouds = hitClouds && (cloudColor.a > max(u_edgeAlphaCutoff, 0.02));
  657. // 边缘稳噪:低 alpha 处直接除以 alpha 会把随机误差放大成亮点/闪点
  658. float edgeSafeAlpha = max(cloudColor.a, 0.08);
  659. vec3 cloudActual = cloudColor.rgb / edgeSafeAlpha;
  660. cloudActual = ACESFilmic(cloudActual * u_cloudExposure);
  661. cloudActual = pow(cloudActual, vec3(1.0 / 2.2));
  662. vec4 composited = vec4(
  663. sceneColor.rgb * (1.0 - cloudColor.a) + cloudActual * cloudColor.a,
  664. // 让 history.a 表示“云覆盖度”,用于后续 TAA 历史有效性判定
  665. cloudColor.a
  666. );
  667. // 在最终云合成色上去品红,按云覆盖度加权,确保无云区域不受影响
  668. vec3 compositedNoMagenta = reduceMagenta(composited.rgb, u_magentaFixStrength);
  669. float cloudW = smoothstep(0.02, 0.3, cloudColor.a);
  670. composited.rgb = mix(composited.rgb, compositedNoMagenta, cloudW);
  671. if (u_temporalEnabled > 0 && hitClouds) {
  672. vec3 worldPos = ro + rd * rayFrontT - u_altitudeCorrection;
  673. vec4 prevClip = u_prevViewProjection * vec4(worldPos, 1.0);
  674. vec2 prevUv = (prevClip.xy / prevClip.w) * 0.5 + 0.5;
  675. if (prevUv.x >= 0.0 && prevUv.x <= 1.0 && prevUv.y >= 0.0 && prevUv.y <= 1.0) {
  676. vec4 history = texture(u_historyTexture, prevUv);
  677. // TAA 仅对“云增量”做融合,底层模型保持当前帧,减少模型虚影
  678. vec3 deltaNow = composited.rgb - sceneColor.rgb;
  679. vec3 deltaHist = history.rgb - sceneColor.rgb;
  680. float maxDiff = max(abs(deltaHist.r - deltaNow.r), max(abs(deltaHist.g - deltaNow.g), abs(deltaHist.b - deltaNow.b)));
  681. float reject = max(
  682. smoothstep(0.35, 0.75, maxDiff),
  683. smoothstep(0.004, 0.03, length(prevUv - v_textureCoordinates))
  684. );
  685. // 让低透明边缘也参与历史融合,抑制云边缘噪点“跳闪”
  686. float opacityW = smoothstep(0.015, 0.25, cloudColor.a);
  687. float a = mix(1.0, mix(u_temporalAlpha, 1.0, reject), opacityW);
  688. // 仅当“当前与历史”都存在足够云覆盖时才使用历史,避免把模型底色抖动带入
  689. float historyCloudW = smoothstep(0.02, 0.12, history.a);
  690. float currentCloudW = smoothstep(0.02, 0.12, cloudColor.a);
  691. float cloudHistoryValidity = min(historyCloudW, currentCloudW);
  692. a = mix(1.0, a, cloudHistoryValidity);
  693. vec3 deltaFiltered = mix(deltaHist, deltaNow, a);
  694. composited.rgb = sceneColor.rgb + deltaFiltered;
  695. composited.a = cloudColor.a;
  696. }
  697. }
  698. gl_FragColor = composited;
  699. }
  700. `;
  701. }
  702. // ─── Helper: compile & link GL program ─────────────────────────────────────
  703. function createGLProgram(gl, vsSource, fsSource, label) {
  704. const vs = gl.createShader(gl.VERTEX_SHADER);
  705. gl.shaderSource(vs, vsSource);
  706. gl.compileShader(vs);
  707. if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) { console.error(`[${label}] VS:`, gl.getShaderInfoLog(vs)); gl.deleteShader(vs); return null; }
  708. const fs = gl.createShader(gl.FRAGMENT_SHADER);
  709. gl.shaderSource(fs, fsSource);
  710. gl.compileShader(fs);
  711. if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) { console.error(`[${label}] FS:`, gl.getShaderInfoLog(fs)); gl.deleteShader(vs); gl.deleteShader(fs); return null; }
  712. const prog = gl.createProgram();
  713. gl.attachShader(prog, vs); gl.attachShader(prog, fs); gl.linkProgram(prog);
  714. gl.deleteShader(vs); gl.deleteShader(fs);
  715. if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { console.error(`[${label}] link:`, gl.getProgramInfoLog(prog)); gl.deleteProgram(prog); return null; }
  716. return prog;
  717. }
  718. // ─── Main pipeline class ──────────────────────────────────────────────────
  719. export class ThreeGeospatialPipeline {
  720. constructor(viewer, options = {}) {
  721. this.viewer = viewer;
  722. this.atmosphereParams = options.atmosphereParams ?? new AtmosphereParameters();
  723. this._frameCount = 0;
  724. this._gui = null;
  725. // 可配置的资源/shader 根路径(均带默认值,便于在任意部署路径下使用)
  726. this.assetsBase = options.cloudsAssetsBase ?? DEFAULT_CLOUDS_ASSETS_BASE;
  727. this.brunetonShaderBase = options.brunetonShaderBase ?? DEFAULT_BRUNETON_SHADER_BASE;
  728. this.blueNoiseUrl = options.blueNoiseUrl ?? DEFAULT_BLUE_NOISE_URL;
  729. this.atmosphereAssetsBase = options.atmosphereAssetsBase ?? DEFAULT_ATMOSPHERE_ASSETS_BASE;
  730. this.atmosphereShaderBase = options.atmosphereShaderBase ?? DEFAULT_ATMOSPHERE_SHADER_BASE;
  731. this.params = {
  732. cloudsVisible: true,
  733. bottomRadius: 6371860,
  734. layers: [
  735. { 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 } },
  736. { 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 } },
  737. { 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 } },
  738. { channel: 'a' }
  739. ],
  740. maxSteps: 500, maxStepsToSun: 8, minStepSize: 20.0, maxStepSize: 1000.0, maxRayDistance: 200000.0,
  741. perspectiveStepScale: 1.005, minDensity: 1e-5, minExtinction: 1e-5, minTransmittance: 0.01,
  742. // 远处云距离衰减(米):天际线附近射线斜穿云层累积过密,从 distFadeStart 起线性衰减到 distFadeEnd 完全消失
  743. distFadeStart: 11000.0, distFadeEnd: 51000.0,
  744. minSecondaryStepSize: 100.0, secondaryStepScale: 2.0, multiScatteringOctaves: 8, lowLayerDensityBoost: 1.0,
  745. shadowLengthEnabled: true, useShadowBuffer: true, hazeEnabled: false,
  746. maxShadowLengthIterationCount: 500, minShadowLengthStepSize: 50.0, maxShadowLengthRayDistance: 200000.0,
  747. hazeDensityScale: 3e-5, hazeExponent: 1e-3, hazeScatteringCoefficient: 0.9, hazeAbsorptionCoefficient: 0.5,
  748. weatherRepeat: 100.0, shapeRepeat: 4.1, shapeDetailRepeat: 0.0005,
  749. turbulenceRepeat: 2.0, turbulenceDisplacement: 400.0,
  750. scatteringCoefficient: 1.0, absorptionCoefficient: 0.0,
  751. scatterG1: 0.7, scatterG2: -0.2, scatterMix: 0.5,
  752. sunIntensity: 20.0, skyLightScale: 1.0, skyToSunRatio: 0.28,
  753. powderScale: 0.8, powderExponent: 150.0,
  754. aerialPerspectiveScale: 1.3, cloudExposure: 3.0, magentaFixStrength: 2.0, edgeAlphaCutoff: 0.0, mipLevelScale: 0.35,
  755. windSpeed: 0.0, evolutionSpeed: 0.005,
  756. temporalEnabled: false, temporalAlpha: 0.1,
  757. blueNoiseScale: 1.0, jitterStrength: 1.0,
  758. // BSM cascade 几何:shadowFar 控制覆盖最远距离,splitLambda 控制近处分配,fadeScale 扩大 ortho radius 防切割
  759. // fadeScale 提高以扩大 ortho 覆盖,避免 cascade 矩形外硬切;不再依赖 UV edgeFade
  760. shadowFar: 40000, shadowSplitLambda: 1.0, shadowFadeScale: 5.0,
  761. };
  762. this.atmosphere = null;
  763. this.aerial = null;
  764. this.cloudStage = null;
  765. this.textures = null;
  766. this._ready = null;
  767. // BSM state
  768. this._bsm = { pass: null, resolve: null, blitFbo: null, blitProg: null, blitVbo: null };
  769. // TAA state
  770. this._taa = { texA: null, texB: null, current: 0, pbo: null, pboReady: false, w: 0, h: 0, frameCount: 0, prevVP: null, curVP: null };
  771. // Wind offsets
  772. this._weatherOffsetX = 0; this._weatherOffsetY = 0;
  773. this._shapeOffsetX = 0; this._shapeOffsetY = 0; this._shapeOffsetZ = 0;
  774. this._shapeDetailOffsetX = 0; this._shapeDetailOffsetY = 0; this._shapeDetailOffsetZ = 0;
  775. this._lastFrameTime = undefined;
  776. this._listeners = [];
  777. // 原始 WebGL2 云渲染状态
  778. this._raw = {
  779. prog: null, // 云渲染着色器程序
  780. vao: null, // 全屏四边形 VAO
  781. vbo: null, // 全屏四边形 VBO
  782. sceneColor: null, // 场景颜色拷贝纹理
  783. depthTex: null, // 场景深度拷贝纹理
  784. depthFbo: null, // 深度拷贝用 FBO
  785. depthW: 0,
  786. depthH: 0,
  787. texUnits: {}, // 纹理 uniform 到纹理单元映射
  788. };
  789. }
  790. // ── Raw WebGL2 云渲染片段着色器(czm_* 替换为原始 uniform)────────────
  791. /**
  792. * 返回一个自包含的 GLSL #version 100 片段着色器。
  793. * 将 Cesium PostProcessStage 中的 czm_* 内置变量替换为显式 uniform,
  794. * 移除了 USE_ATMOSPHERE_IRRADIANCE(改用简化 Rayleigh 天光模型)。
  795. * 输出使用 gl_FragColor。
  796. */
  797. _getRawCloudFragmentShader() {
  798. let src = getCloudFragmentShader();
  799. // #version 100 兼容:in → varying
  800. src = src.replace('in vec2 v_textureCoordinates;', 'varying vec2 v_textureCoordinates;');
  801. // 在文件头部插入 extension 声明 + 新增 uniform
  802. const header = `#extension GL_OES_texture_3D : enable
  803. precision highp float;
  804. precision highp sampler2D;
  805. precision highp sampler3D;
  806. uniform mat4 u_inverseProjection;
  807. uniform mat4 u_inverseView;
  808. uniform mat4 u_viewMatrix;
  809. uniform vec3 u_sunDirection;
  810. `;
  811. // 找到第一个非注释行(跳过开头的 const 声明、main 之前的代码)
  812. // 直接在所有代码之前插入 header
  813. // getCloudFragmentShader() 以模板字面量 \` 开始,内容以 const float 开头
  814. // 我们先移除 precision 和 extension 行(如果有的话),再插入
  815. // 但原始 shader 在 _buildCloudFragmentShader 里才加 precision,这里裸的 getCloudFragmentShader 没有 precision 行
  816. // 直接在开头插入即可
  817. // 在第一个非空白、非注释行之前插入 header
  818. // getCloudFragmentShader() 第一行是 `\nconst float RECIPROCAL_PI4...`
  819. // 我们把 header 插在 const float RECIPROCAL_PI4 之前
  820. src = header + src;
  821. // #define USE_ATMOSPHERE_IRRADIANCE 相关代码不再需要,但 shader 默认没有 #define USE_ATMOSPHERE_IRRADIANCE
  822. // (它仅在 _buildCloudFragmentShader 中被定义),所以 else 分支会走简化 Rayleigh 模型。
  823. // 不需要额外操作。
  824. // 替换 czm_* 为 uniform
  825. // 注意替换顺序:先长后短,避免部分匹配
  826. src = src.replace(/czm_windowToEyeCoordinates\(vec4\(gl_FragCoord\.xy,\s*depth,\s*1\.0\)\)/g,
  827. '(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))');
  828. src = src.replace(/czm_inverseProjection/g, 'u_inverseProjection');
  829. src = src.replace(/czm_inverseView/g, 'u_inverseView');
  830. src = src.replace(/czm_sunDirectionWC/g, 'u_sunDirection');
  831. src = src.replace(/czm_view/g, 'u_viewMatrix');
  832. src = src.replace(/czm_readDepth\(depthTexture,\s*v_textureCoordinates\)/g,
  833. 'texture2D(depthTexture, v_textureCoordinates).r');
  834. return src;
  835. }
  836. // ── Texture loading ────────────────────────────────────────────────────
  837. async _load3DTexture(url, size) {
  838. const data3D = await loadBinThreeGeospatial(url, size);
  839. return bindData3DTextureToCesiumContext(this.viewer, data3D, Cesium);
  840. }
  841. async _load3DTextureWHD(url, width, height, depth) {
  842. const arrayBuffer = await Cesium.Resource.fetchArrayBuffer(url);
  843. const raw = new Uint8Array(arrayBuffer);
  844. const context = this.viewer.scene.context;
  845. const gl = context._gl;
  846. try {
  847. return new Cesium.Texture3D({
  848. context,
  849. width,
  850. height,
  851. depth,
  852. pixelFormat: Cesium.PixelFormat.RED,
  853. pixelDatatype: Cesium.PixelDatatype.UNSIGNED_BYTE,
  854. source: {
  855. arrayBufferView: raw,
  856. width,
  857. height,
  858. depth,
  859. },
  860. sampler: new Cesium.Sampler({
  861. minificationFilter: Cesium.TextureMinificationFilter.LINEAR,
  862. magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR,
  863. wrapS: Cesium.TextureWrap.REPEAT,
  864. wrapT: Cesium.TextureWrap.REPEAT,
  865. wrapR: Cesium.TextureWrap.REPEAT,
  866. }),
  867. });
  868. } catch (e) {
  869. console.warn('[Pipeline] Cesium.Texture3D 失败,使用原生 WebGL2 回退:', e.message);
  870. const tex = gl.createTexture();
  871. gl.bindTexture(gl.TEXTURE_3D, tex);
  872. gl.texImage3D(gl.TEXTURE_3D, 0, gl.R8, width, height, depth, 0, gl.RED, gl.UNSIGNED_BYTE, raw);
  873. gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  874. gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  875. gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_S, gl.REPEAT);
  876. gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_T, gl.REPEAT);
  877. gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_WRAP_R, gl.REPEAT);
  878. gl.bindTexture(gl.TEXTURE_3D, null);
  879. return {
  880. _texture: tex,
  881. _textureTarget: gl.TEXTURE_3D,
  882. _target: gl.TEXTURE_3D,
  883. width, height, depth,
  884. bind: function(textureUnit) {
  885. gl.activeTexture(gl.TEXTURE0 + textureUnit);
  886. gl.bindTexture(gl.TEXTURE_3D, tex);
  887. },
  888. destroy: function() { gl.deleteTexture(tex); }
  889. };
  890. }
  891. }
  892. async _load2DTexture(url) {
  893. const img = await Cesium.Resource.fetchImage(url);
  894. if (!img || img.width <= 2 || img.height <= 2) throw new Error(`Invalid image: ${url}`);
  895. return new Cesium.Texture({
  896. context: this.viewer.scene.context, source: img,
  897. sampler: new Cesium.Sampler({ minificationFilter: Cesium.TextureMinificationFilter.LINEAR, magnificationFilter: Cesium.TextureMagnificationFilter.LINEAR, wrapS: Cesium.TextureWrap.REPEAT, wrapT: Cesium.TextureWrap.REPEAT })
  898. });
  899. }
  900. async _loadTextures() {
  901. const bp = this.assetsBase;
  902. const [shape, detail, stbn, weather, turb, noise] = await Promise.all([
  903. this._load3DTexture(bp + "shape.bin", 128).catch(() => null),
  904. this._load3DTexture(bp + "shape_detail.bin", 32).catch(() => null),
  905. this._load3DTextureWHD(bp + "stbn.bin", 128, 128, 64).catch(() => null),
  906. this._load2DTexture(bp + "local_weather.png").catch(() => null),
  907. this._load2DTexture(bp + "turbulence.png").catch(() => null),
  908. this._load2DTexture(this.blueNoiseUrl).catch(() => null),
  909. ]);
  910. this.textures = { shape, shapeDetail: detail, stbn, weather, turbulence: turb, blueNoise: noise };
  911. console.log("[Pipeline] textures:", Object.fromEntries(Object.entries(this.textures).map(([k, v]) => [k, !!v])));
  912. }
  913. // ── Shader loading for Bruneton prefix ─────────────────────────────────
  914. async _loadShader(name) {
  915. return loadShaderSource(name, { shaderBaseUrl: this.brunetonShaderBase });
  916. }
  917. async _buildCloudFragmentShader() {
  918. const provider = this.atmosphere.getAtmosphereForClouds();
  919. const [definitions, common, runtime] = await Promise.all([
  920. this._loadShader("definitions.glsl"),
  921. this._loadShader("common.glsl"),
  922. this._loadShader("runtime.glsl"),
  923. ]);
  924. const defines = "precision highp float;\nprecision highp sampler2D;\nprecision highp sampler3D;\n"
  925. + provider.constants.getShaderDefines()
  926. + "\n#define METER_TO_LENGTH_UNIT 0.001\n#define USE_ATMOSPHERE_IRRADIANCE\n";
  927. const globalU = `
  928. uniform vec3 sunDirection;
  929. uniform AtmosphereParameters ATMOSPHERE;
  930. uniform vec3 SUN_SPECTRAL_RADIANCE_TO_LUMINANCE;
  931. uniform vec3 SKY_SPECTRAL_RADIANCE_TO_LUMINANCE;
  932. uniform sampler2D transmittance_texture;
  933. uniform sampler3D scattering_texture;
  934. uniform sampler3D single_mie_scattering_texture;
  935. uniform sampler2D irradiance_texture;
  936. `;
  937. return defines + definitions + "\n" + common + "\n" + globalU + "\n" + runtime + "\n" + getCloudFragmentShader();
  938. }
  939. // ── Wind animation ─────────────────────────────────────────────────────
  940. _advanceOffsets() {
  941. const now = performance.now() / 1000;
  942. if (this._lastFrameTime !== undefined) {
  943. const dt = now - this._lastFrameTime;
  944. this._weatherOffsetX += (this.params.windSpeed || 0) * dt;
  945. this._shapeOffsetX += (this.params.evolutionSpeed || 0) * dt;
  946. this._shapeDetailOffsetX += (this.params.evolutionSpeed || 0) * 2 * dt;
  947. }
  948. this._lastFrameTime = now;
  949. }
  950. // ── Helpers ────────────────────────────────────────────────────────────
  951. _getDensityProfileVec4(key) {
  952. const ls = this.params.layers, def = k => k === "linearTerm" ? 0.75 : k === "constantTerm" ? 0.25 : 0;
  953. return new Cesium.Cartesian4(...[0,1,2,3].map(i => {
  954. const val = ls[i]?.densityProfile?.[key];
  955. return val !== undefined ? Number(val) : def(key);
  956. }));
  957. }
  958. _getIntervalHeights() {
  959. const ls = this.params.layers, entries = [];
  960. 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 }); }
  961. entries.sort((a, b) => a.v !== b.v ? a.v - b.v : a.flag - b.flag);
  962. const intervals = [{ min: 0, max: 0 }, { min: 0, max: 0 }, { min: 0, max: 0 }];
  963. let idx = 0, balance = 0;
  964. 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; }
  965. 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) };
  966. }
  967. _getLayerVec4(key, fallback = 0) {
  968. const ls = this.params.layers;
  969. return new Cesium.Cartesian4(...[0,1,2,3].map(i => {
  970. const val = ls[i]?.[key];
  971. return val !== undefined ? Number(val) : fallback;
  972. }));
  973. }
  974. _getAltitudeCorrectionOffset(bottomRadius) {
  975. const ellipsoid = this.viewer?.scene?.globe?.ellipsoid;
  976. const cameraPos = this.viewer?.camera?.positionWC;
  977. if (!ellipsoid || !cameraPos) return Cesium.Cartesian3.ZERO.clone();
  978. const carto = Cesium.Cartographic.fromCartesian(cameraPos, ellipsoid);
  979. if (!carto) return Cesium.Cartesian3.ZERO.clone();
  980. const surface = Cesium.Cartesian3.fromRadians(
  981. carto.longitude,
  982. carto.latitude,
  983. 0.0,
  984. ellipsoid
  985. );
  986. const normal = ellipsoid.geodeticSurfaceNormal(surface, new Cesium.Cartesian3());
  987. const center = Cesium.Cartesian3.subtract(
  988. surface,
  989. Cesium.Cartesian3.multiplyByScalar(normal, Number(bottomRadius) || 0, new Cesium.Cartesian3()),
  990. new Cesium.Cartesian3()
  991. );
  992. return Cesium.Cartesian3.negate(center, new Cesium.Cartesian3());
  993. }
  994. _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; }
  995. _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; }
  996. // ── Cloud PostProcessStage uniform map ─────────────────────────────────
  997. _buildCloudUniforms() {
  998. const self = this, p = () => self.params, tex = () => self.textures;
  999. const provider = this.atmosphere.getAtmosphereForClouds();
  1000. const atm = provider.getUniforms();
  1001. const u = {
  1002. u_shapeTexture: () => tex()?.shape,
  1003. u_shapeDetailTexture: () => tex()?.shapeDetail,
  1004. u_stbnTexture: () => tex()?.stbn || tex()?.shape,
  1005. u_weatherTexture: () => tex()?.weather,
  1006. u_turbulenceTexture: () => tex()?.turbulence,
  1007. u_blueNoise: () => tex()?.blueNoise,
  1008. u_blueNoiseScale: () => p().blueNoiseScale ?? 1.0,
  1009. u_jitterStrength: () => p().jitterStrength ?? 1.0,
  1010. u_cameraPosition: () => self.viewer.camera.positionWC,
  1011. u_altitudeCorrection: () => {
  1012. const br = Number(atm.bottomRadius()) || Number(p().bottomRadius) || 0;
  1013. return self._getAltitudeCorrectionOffset(br);
  1014. },
  1015. u_cameraHeight: () => {
  1016. const corr = u.u_altitudeCorrection();
  1017. const pos = Cesium.Cartesian3.add(self.viewer.camera.positionWC, corr, new Cesium.Cartesian3());
  1018. const br = Number(atm.bottomRadius()) || Number(p().bottomRadius) || 0;
  1019. return Math.max(0, Cesium.Cartesian3.magnitude(pos) - br);
  1020. },
  1021. u_bottomRadius: () => Number(p().bottomRadius),
  1022. u_minHeight: () => self._getMinHeight(),
  1023. u_maxHeight: () => self._getMaxHeight(),
  1024. u_minLayerHeights: () => self._getLayerVec4("altitude", 0),
  1025. 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))); },
  1026. u_densityScales: () => self._getLayerVec4("densityScale", 0),
  1027. u_shapeAmounts: () => self._getLayerVec4("shapeAmount", 0),
  1028. u_shapeDetailAmounts: () => self._getLayerVec4("shapeDetailAmount", 0),
  1029. u_weatherExponents: () => self._getLayerVec4("weatherExponent", 1),
  1030. u_shapeAlteringBiases: () => self._getLayerVec4("shapeAlteringBias", 0.35),
  1031. u_coverageFilterWidths: () => self._getLayerVec4("coverageFilterWidth", 0.6),
  1032. u_maxSteps: () => p().maxSteps, u_maxStepsToSun: () => p().maxStepsToSun,
  1033. u_minStepSize: () => p().minStepSize, u_maxStepSize: () => p().maxStepSize,
  1034. u_maxRayDistance: () => p().maxRayDistance,
  1035. u_distFadeStart: () => Number(p().distFadeStart) || 30000.0,
  1036. u_distFadeEnd: () => Number(p().distFadeEnd) || 150000.0,
  1037. u_cameraNear: () => Number(self.viewer.camera.frustum?.near) || 0,
  1038. u_shadowTopHeight: () => self._getMaxHeight(),
  1039. u_shadowLengthEnabled: () => p().shadowLengthEnabled ? 1 : 0,
  1040. u_hazeEnabled: () => p().hazeEnabled ? 1 : 0,
  1041. u_maxShadowLengthIterationCount: () => p().maxShadowLengthIterationCount,
  1042. u_minShadowLengthStepSize: () => p().minShadowLengthStepSize,
  1043. u_maxShadowLengthRayDistance: () => p().maxShadowLengthRayDistance,
  1044. u_hazeDensityScale: () => p().hazeDensityScale, u_hazeExponent: () => p().hazeExponent,
  1045. u_hazeScatteringCoefficient: () => p().hazeScatteringCoefficient,
  1046. u_hazeAbsorptionCoefficient: () => p().hazeAbsorptionCoefficient,
  1047. u_shadowBuffer: () => {
  1048. if (p().useShadowBuffer && self._bsm.resolve) {
  1049. const t = self._bsmResolveGetTexture(); // 直接调用对象的原生方法
  1050. if (t) return t;
  1051. }
  1052. return tex()?.weather;
  1053. },
  1054. u_shadowTexelSize: () => { const tile = self._bsm.pass ? Math.floor(SHADOW_MAP_SIZE / 2) : 512; return new Cesium.Cartesian2(1 / tile, 1 / tile); },
  1055. u_shadowIntervals: () => { if (p().useShadowBuffer && self._bsm.pass) {
  1056. const iv = self._bsm.pass.getShadowIntervals(); // 使用 Getter
  1057. return iv.map(a => new Cesium.Cartesian2(a[0], a[1]));
  1058. }
  1059. return Array(4).fill(null).map(() => new Cesium.Cartesian2(0, 0)); },
  1060. 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()); },
  1061. u_shadowFar: () => self._bsm.pass ? self._bsm.pass._shadowFar : p().maxShadowLengthRayDistance,
  1062. u_maxShadowFilterRadius: () => 2.0,
  1063. u_useShadowBuffer: () => p().useShadowBuffer ? 1 : 0,
  1064. u_skyLightScale: () => p().skyLightScale,
  1065. u_weatherRepeat: () => p().weatherRepeat,
  1066. u_localWeatherOffset: () => { self._advanceOffsets(); return new Cesium.Cartesian2(self._weatherOffsetX, self._weatherOffsetY); },
  1067. u_shapeRepeat: () => (Number(p().shapeRepeat) || 3) / 1e4,
  1068. u_shapeOffset: () => { self._advanceOffsets(); return new Cesium.Cartesian3(self._shapeOffsetX, self._shapeOffsetY, self._shapeOffsetZ); },
  1069. u_shapeDetailRepeat: () => p().shapeDetailRepeat,
  1070. u_shapeDetailOffset: () => { self._advanceOffsets(); return new Cesium.Cartesian3(self._shapeDetailOffsetX, self._shapeDetailOffsetY, self._shapeDetailOffsetZ); },
  1071. u_turbulenceRepeat: () => p().turbulenceRepeat, u_turbulenceDisplacement: () => p().turbulenceDisplacement,
  1072. u_coverages: () => self._getLayerVec4("coverage", 0.3),
  1073. 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); },
  1074. u_scatteringCoefficient: () => p().scatteringCoefficient, u_absorptionCoefficient: () => p().absorptionCoefficient,
  1075. u_scatterG1: () => p().scatterG1, u_scatterG2: () => p().scatterG2, u_scatterMix: () => p().scatterMix,
  1076. u_sunIntensity: () => p().sunIntensity, u_skyToSunRatio: () => p().skyToSunRatio,
  1077. u_powderScale: () => p().powderScale, u_powderExponent: () => p().powderExponent,
  1078. u_aerialPerspectiveScale: () => p().aerialPerspectiveScale, u_cloudExposure: () => p().cloudExposure,
  1079. u_magentaFixStrength: () => p().magentaFixStrength ?? 0.8,
  1080. u_edgeAlphaCutoff: () => p().edgeAlphaCutoff ?? 0.03,
  1081. u_resolution: () => { const ctx = self.viewer.scene.context; return new Cesium.Cartesian2(ctx.drawingBufferWidth || 1, ctx.drawingBufferHeight || 1); },
  1082. u_mipLevelScale: () => Number(p().mipLevelScale) || 1.0,
  1083. u_perspectiveStepScale: () => p().perspectiveStepScale ?? 1.01,
  1084. u_minDensity: () => p().minDensity ?? 1e-5, u_minExtinction: () => p().minExtinction ?? 1e-5,
  1085. u_minTransmittance: () => p().minTransmittance ?? 0.01,
  1086. u_minSecondaryStepSize: () => p().minSecondaryStepSize ?? 100, u_secondaryStepScale: () => p().secondaryStepScale ?? 2,
  1087. u_multiScatteringOctaves: () => Math.min(12, Math.max(1, p().multiScatteringOctaves ?? 8)),
  1088. u_lowLayerDensityBoost: () => p().lowLayerDensityBoost ?? 1.0,
  1089. u_densityProfileExpTerms: () => self._getDensityProfileVec4("expTerm"),
  1090. u_densityProfileExponents: () => self._getDensityProfileVec4("exponent"),
  1091. u_densityProfileLinearTerms: () => self._getDensityProfileVec4("linearTerm"),
  1092. u_densityProfileConstantTerms: () => self._getDensityProfileVec4("constantTerm"),
  1093. u_minIntervalHeights: () => self._getIntervalHeights().min,
  1094. u_maxIntervalHeights: () => self._getIntervalHeights().max,
  1095. u_historyTexture: () => { const t = self._taaGetHistoryTexture(); return t || tex()?.blueNoise; },
  1096. u_prevViewProjection: () => self._taa.prevVP || Cesium.Matrix4.IDENTITY,
  1097. u_temporalAlpha: () => p().temporalAlpha ?? 0.1,
  1098. u_temporalEnabled: () => (p().temporalEnabled && self._taa.frameCount > 2 && self._taa.prevVP) ? 1 : 0,
  1099. u_frame: () => self._frameCount || 0,
  1100. };
  1101. Object.assign(u, atm);
  1102. u.u_cameraPosition = atm.cameraPosition;
  1103. u.u_bottomRadius = atm.bottomRadius;
  1104. return u;
  1105. }
  1106. // ── 原始 WebGL2 云渲染初始化 ────────────────────────────────────────────
  1107. /**
  1108. * 初始化原始 WebGL2 全屏四边形渲染管线。
  1109. * @param {WebGL2RenderingContext} gl
  1110. * @returns {boolean} 是否成功
  1111. */
  1112. _initRawWebGL(gl) {
  1113. // 顶点着色器(#version 100,兼容 gl_FragColor)
  1114. const vsSrc = `
  1115. attribute vec2 a_position;
  1116. varying vec2 v_textureCoordinates;
  1117. void main() {
  1118. v_textureCoordinates = a_position * 0.5 + 0.5;
  1119. gl_Position = vec4(a_position, 0.0, 1.0);
  1120. }`;
  1121. const fsSrc = this._getRawCloudFragmentShader();
  1122. this._raw.prog = createGLProgram(gl, vsSrc, fsSrc, 'RawCloud');
  1123. if (!this._raw.prog) {
  1124. console.error('[Pipeline] 原始 WebGL 云渲染程序编译失败');
  1125. return false;
  1126. }
  1127. // 全屏四边形 VAO
  1128. this._raw.vao = gl.createVertexArray();
  1129. gl.bindVertexArray(this._raw.vao);
  1130. this._raw.vbo = gl.createBuffer();
  1131. gl.bindBuffer(gl.ARRAY_BUFFER, this._raw.vbo);
  1132. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
  1133. const posLoc = gl.getAttribLocation(this._raw.prog, 'a_position');
  1134. if (posLoc >= 0) {
  1135. gl.enableVertexAttribArray(posLoc);
  1136. gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
  1137. }
  1138. gl.bindVertexArray(null);
  1139. gl.bindBuffer(gl.ARRAY_BUFFER, null);
  1140. // 场景颜色拷贝纹理
  1141. this._raw.sceneColor = gl.createTexture();
  1142. gl.bindTexture(gl.TEXTURE_2D, this._raw.sceneColor);
  1143. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  1144. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  1145. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  1146. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  1147. gl.bindTexture(gl.TEXTURE_2D, null);
  1148. // 深度拷贝纹理 + FBO
  1149. this._raw.depthTex = gl.createTexture();
  1150. gl.bindTexture(gl.TEXTURE_2D, this._raw.depthTex);
  1151. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
  1152. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
  1153. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  1154. gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  1155. gl.bindTexture(gl.TEXTURE_2D, null);
  1156. this._raw.depthFbo = gl.createFramebuffer();
  1157. // 建立纹理 uniform → 纹理单元映射表
  1158. this._raw.texUnits = {
  1159. colorTexture: 0,
  1160. depthTexture: 1,
  1161. u_shapeTexture: 2,
  1162. u_shapeDetailTexture: 3,
  1163. u_stbnTexture: 4,
  1164. u_weatherTexture: 5,
  1165. u_turbulenceTexture: 6,
  1166. u_blueNoise: 7,
  1167. u_shadowBuffer: 8,
  1168. u_historyTexture: 9,
  1169. };
  1170. return true;
  1171. }
  1172. /**
  1173. * 为原始 WebGL 云渲染设置所有 uniform。
  1174. * 使用 _buildCloudUniforms() 获取值与 Cesium 一致的参数值。
  1175. */
  1176. _setRawCloudUniforms(gl, w, h) {
  1177. const prog = this._raw.prog;
  1178. if (!prog) return;
  1179. const u = this._buildCloudUniforms();
  1180. // 工具:尝试获取 uniform 位置,不存在时返回 -1
  1181. const uloc = (name) => gl.getUniformLocation(prog, name);
  1182. // ── 纹理绑定 ──
  1183. const texUnits = this._raw.texUnits;
  1184. // 场景颜色(从拷贝纹理读取)
  1185. gl.activeTexture(gl.TEXTURE0 + texUnits.colorTexture);
  1186. gl.bindTexture(gl.TEXTURE_2D, this._raw.sceneColor);
  1187. const locColor = uloc('colorTexture');
  1188. if (locColor) gl.uniform1i(locColor, texUnits.colorTexture);
  1189. // 场景深度(从拷贝纹理读取)
  1190. gl.activeTexture(gl.TEXTURE0 + texUnits.depthTexture);
  1191. gl.bindTexture(gl.TEXTURE_2D, this._raw.depthTex);
  1192. const locDepth = uloc('depthTexture');
  1193. if (locDepth) gl.uniform1i(locDepth, texUnits.depthTexture);
  1194. // 云 3D 纹理 + 2D 纹理
  1195. const texBindings = [
  1196. { name: 'u_shapeTexture', getter: u.u_shapeTexture, unit: texUnits.u_shapeTexture },
  1197. { name: 'u_shapeDetailTexture', getter: u.u_shapeDetailTexture, unit: texUnits.u_shapeDetailTexture },
  1198. { name: 'u_stbnTexture', getter: u.u_stbnTexture, unit: texUnits.u_stbnTexture },
  1199. { name: 'u_weatherTexture', getter: u.u_weatherTexture, unit: texUnits.u_weatherTexture },
  1200. { name: 'u_turbulenceTexture', getter: u.u_turbulenceTexture, unit: texUnits.u_turbulenceTexture },
  1201. { name: 'u_blueNoise', getter: u.u_blueNoise, unit: texUnits.u_blueNoise },
  1202. { name: 'u_shadowBuffer', getter: u.u_shadowBuffer, unit: texUnits.u_shadowBuffer },
  1203. { name: 'u_historyTexture', getter: u.u_historyTexture, unit: texUnits.u_historyTexture },
  1204. ];
  1205. for (const tb of texBindings) {
  1206. const texVal = tb.getter();
  1207. const loc = uloc(tb.name);
  1208. if (loc && texVal) {
  1209. gl.activeTexture(gl.TEXTURE0 + tb.unit);
  1210. if (texVal.bind) {
  1211. texVal.bind(tb.unit);
  1212. } else if (texVal._texture) {
  1213. const target = texVal._target || gl.TEXTURE_2D;
  1214. gl.bindTexture(target, texVal._texture);
  1215. }
  1216. gl.uniform1i(loc, tb.unit);
  1217. }
  1218. }
  1219. // ── 数值/向量 uniform ──
  1220. const uniformSetters = [
  1221. { name: 'u_cameraPosition', getter: u.u_cameraPosition },
  1222. { name: 'u_altitudeCorrection', getter: u.u_altitudeCorrection },
  1223. { name: 'u_cameraHeight', getter: u.u_cameraHeight },
  1224. { name: 'u_bottomRadius', getter: u.u_bottomRadius },
  1225. { name: 'u_minHeight', getter: u.u_minHeight },
  1226. { name: 'u_maxHeight', getter: u.u_maxHeight },
  1227. { name: 'u_minLayerHeights', getter: u.u_minLayerHeights },
  1228. { name: 'u_maxLayerHeights', getter: u.u_maxLayerHeights },
  1229. { name: 'u_densityScales', getter: u.u_densityScales },
  1230. { name: 'u_shapeAmounts', getter: u.u_shapeAmounts },
  1231. { name: 'u_shapeDetailAmounts', getter: u.u_shapeDetailAmounts },
  1232. { name: 'u_weatherExponents', getter: u.u_weatherExponents },
  1233. { name: 'u_shapeAlteringBiases', getter: u.u_shapeAlteringBiases },
  1234. { name: 'u_coverageFilterWidths', getter: u.u_coverageFilterWidths },
  1235. { name: 'u_maxSteps', getter: u.u_maxSteps },
  1236. { name: 'u_maxStepsToSun', getter: u.u_maxStepsToSun },
  1237. { name: 'u_minStepSize', getter: u.u_minStepSize },
  1238. { name: 'u_maxStepSize', getter: u.u_maxStepSize },
  1239. { name: 'u_maxRayDistance', getter: u.u_maxRayDistance },
  1240. { name: 'u_distFadeStart', getter: u.u_distFadeStart },
  1241. { name: 'u_distFadeEnd', getter: u.u_distFadeEnd },
  1242. { name: 'u_cameraNear', getter: u.u_cameraNear },
  1243. { name: 'u_shadowTopHeight', getter: u.u_shadowTopHeight },
  1244. { name: 'u_shadowLengthEnabled', getter: u.u_shadowLengthEnabled },
  1245. { name: 'u_hazeEnabled', getter: u.u_hazeEnabled },
  1246. { name: 'u_maxShadowLengthIterationCount', getter: u.u_maxShadowLengthIterationCount },
  1247. { name: 'u_minShadowLengthStepSize', getter: u.u_minShadowLengthStepSize },
  1248. { name: 'u_maxShadowLengthRayDistance', getter: u.u_maxShadowLengthRayDistance },
  1249. { name: 'u_hazeDensityScale', getter: u.u_hazeDensityScale },
  1250. { name: 'u_hazeExponent', getter: u.u_hazeExponent },
  1251. { name: 'u_hazeScatteringCoefficient', getter: u.u_hazeScatteringCoefficient },
  1252. { name: 'u_hazeAbsorptionCoefficient', getter: u.u_hazeAbsorptionCoefficient },
  1253. { name: 'u_shadowTexelSize', getter: u.u_shadowTexelSize },
  1254. { name: 'u_shadowFar', getter: u.u_shadowFar },
  1255. { name: 'u_maxShadowFilterRadius', getter: () => 2.0 },
  1256. { name: 'u_useShadowBuffer', getter: u.u_useShadowBuffer },
  1257. { name: 'u_skyLightScale', getter: u.u_skyLightScale },
  1258. { name: 'u_weatherRepeat', getter: u.u_weatherRepeat },
  1259. { name: 'u_localWeatherOffset', getter: u.u_localWeatherOffset },
  1260. { name: 'u_shapeRepeat', getter: u.u_shapeRepeat },
  1261. { name: 'u_shapeOffset', getter: u.u_shapeOffset },
  1262. { name: 'u_shapeDetailRepeat', getter: u.u_shapeDetailRepeat },
  1263. { name: 'u_shapeDetailOffset', getter: u.u_shapeDetailOffset },
  1264. { name: 'u_turbulenceRepeat', getter: u.u_turbulenceRepeat },
  1265. { name: 'u_turbulenceDisplacement', getter: u.u_turbulenceDisplacement },
  1266. { name: 'u_coverages', getter: u.u_coverages },
  1267. { name: 'u_coverageHaze', getter: u.u_coverageHaze },
  1268. { name: 'u_scatteringCoefficient', getter: u.u_scatteringCoefficient },
  1269. { name: 'u_absorptionCoefficient', getter: u.u_absorptionCoefficient },
  1270. { name: 'u_scatterG1', getter: u.u_scatterG1 },
  1271. { name: 'u_scatterG2', getter: u.u_scatterG2 },
  1272. { name: 'u_scatterMix', getter: u.u_scatterMix },
  1273. { name: 'u_sunIntensity', getter: u.u_sunIntensity },
  1274. { name: 'u_skyToSunRatio', getter: u.u_skyToSunRatio },
  1275. { name: 'u_powderScale', getter: u.u_powderScale },
  1276. { name: 'u_powderExponent', getter: u.u_powderExponent },
  1277. { name: 'u_aerialPerspectiveScale', getter: u.u_aerialPerspectiveScale },
  1278. { name: 'u_cloudExposure', getter: u.u_cloudExposure },
  1279. { name: 'u_magentaFixStrength', getter: u.u_magentaFixStrength },
  1280. { name: 'u_edgeAlphaCutoff', getter: u.u_edgeAlphaCutoff },
  1281. { name: 'u_resolution', getter: () => new Cesium.Cartesian2(w, h) },
  1282. { name: 'u_mipLevelScale', getter: u.u_mipLevelScale },
  1283. { name: 'u_perspectiveStepScale', getter: u.u_perspectiveStepScale },
  1284. { name: 'u_minDensity', getter: u.u_minDensity },
  1285. { name: 'u_minExtinction', getter: u.u_minExtinction },
  1286. { name: 'u_minTransmittance', getter: u.u_minTransmittance },
  1287. { name: 'u_minSecondaryStepSize', getter: u.u_minSecondaryStepSize },
  1288. { name: 'u_secondaryStepScale', getter: u.u_secondaryStepScale },
  1289. { name: 'u_multiScatteringOctaves', getter: u.u_multiScatteringOctaves },
  1290. { name: 'u_lowLayerDensityBoost', getter: u.u_lowLayerDensityBoost },
  1291. { name: 'u_densityProfileExpTerms', getter: u.u_densityProfileExpTerms },
  1292. { name: 'u_densityProfileExponents', getter: u.u_densityProfileExponents },
  1293. { name: 'u_densityProfileLinearTerms', getter: u.u_densityProfileLinearTerms },
  1294. { name: 'u_densityProfileConstantTerms', getter: u.u_densityProfileConstantTerms },
  1295. { name: 'u_minIntervalHeights', getter: u.u_minIntervalHeights },
  1296. { name: 'u_maxIntervalHeights', getter: u.u_maxIntervalHeights },
  1297. { name: 'u_prevViewProjection', getter: u.u_prevViewProjection },
  1298. { name: 'u_temporalAlpha', getter: u.u_temporalAlpha },
  1299. { name: 'u_temporalEnabled', getter: u.u_temporalEnabled },
  1300. { name: 'u_frame', getter: u.u_frame },
  1301. { name: 'u_blueNoiseScale', getter: u.u_blueNoiseScale },
  1302. { name: 'u_jitterStrength', getter: u.u_jitterStrength },
  1303. // 新增原始 uniform(替代 czm_* 内置变量)
  1304. { name: 'u_inverseProjection', getter: () => {
  1305. const p = this.viewer.camera.frustum;
  1306. const m = p.infiniteProjectionMatrix || p.projectionMatrix;
  1307. const inv = new Cesium.Matrix4();
  1308. return Cesium.Matrix4.inverse(m, inv) || Cesium.Matrix4.IDENTITY;
  1309. }},
  1310. { name: 'u_inverseView', getter: () => this.viewer.camera.inverseViewMatrix },
  1311. { name: 'u_viewMatrix', getter: () => this.viewer.camera.viewMatrix },
  1312. { name: 'u_sunDirection', getter: () => {
  1313. // czm_sunDirectionWC 等价于从相机到太阳的归一化方向
  1314. const sunPos = this.viewer.scene.sunPosition;
  1315. if (!sunPos) return new Cesium.Cartesian3(0.577, 0.577, 0.577);
  1316. return Cesium.Cartesian3.normalize(sunPos, new Cesium.Cartesian3());
  1317. }},
  1318. ];
  1319. // 处理 shadowIntervals(数组)和 shadowMatrices(数组)
  1320. // 特别注意 czm_view 和 czm_sunDirectionWC 已替换为 u_viewMatrix / u_sunDirection
  1321. for (const s of uniformSetters) {
  1322. const loc = uloc(s.name);
  1323. if (!loc) continue;
  1324. let val;
  1325. try {
  1326. val = s.getter();
  1327. } catch (e) {
  1328. // uniform getter 可能依赖未就绪的状态(如 TAA history),跳过
  1329. continue;
  1330. }
  1331. if (val === null || val === undefined) continue;
  1332. this._setRawGLUniform(gl, loc, val);
  1333. }
  1334. // ── 数组 uniform ──
  1335. // u_shadowIntervals: uniform vec2[4]
  1336. const locSI = uloc('u_shadowIntervals');
  1337. if (locSI) {
  1338. const intervals = u.u_shadowIntervals();
  1339. if (intervals && intervals.length === 4) {
  1340. const flat = [];
  1341. for (let i = 0; i < 4; i++) {
  1342. const iv = intervals[i];
  1343. if (iv && iv.x !== undefined) { flat.push(iv.x, iv.y); }
  1344. else { flat.push(0, 0); }
  1345. }
  1346. gl.uniform2fv(locSI, flat);
  1347. }
  1348. }
  1349. // u_shadowMatrices: uniform mat4[4]
  1350. const locSM = uloc('u_shadowMatrices');
  1351. if (locSM) {
  1352. const mats = u.u_shadowMatrices();
  1353. if (mats && mats.length === 4) {
  1354. for (let i = 0; i < 4; i++) {
  1355. const m = mats[i];
  1356. if (m && m.values) {
  1357. gl.uniformMatrix4fv(uloc(`u_shadowMatrices[${i}]`) || locSM, false, m.values);
  1358. }
  1359. }
  1360. }
  1361. }
  1362. }
  1363. /**
  1364. * 将 Cesium 类型值转换为原始 WebGL uniform 调用。
  1365. */
  1366. _setRawGLUniform(gl, loc, val) {
  1367. if (val === null || val === undefined) return;
  1368. // Cesium 类型判断
  1369. const isCesiumObj = val.constructor && typeof val.constructor.name === 'string';
  1370. const typeName = val.constructor?.name;
  1371. if (typeof val === 'number') {
  1372. gl.uniform1f(loc, val);
  1373. } else if (typeof val === 'boolean') {
  1374. gl.uniform1i(loc, val ? 1 : 0);
  1375. } else if (typeName === 'Cartesian2' || (val.x !== undefined && val.y !== undefined && val.z === undefined && val.w === undefined)) {
  1376. gl.uniform2f(loc, Number(val.x), Number(val.y));
  1377. } else if (typeName === 'Cartesian3' || (val.x !== undefined && val.y !== undefined && val.z !== undefined && val.w === undefined)) {
  1378. gl.uniform3f(loc, Number(val.x), Number(val.y), Number(val.z));
  1379. } else if (typeName === 'Cartesian4' || (val.x !== undefined && val.y !== undefined && val.z !== undefined && val.w !== undefined)) {
  1380. gl.uniform4f(loc, Number(val.x), Number(val.y), Number(val.z), Number(val.w));
  1381. } else if (typeName === 'Matrix4') {
  1382. gl.uniformMatrix4fv(loc, false, val.values);
  1383. } else if (typeName === 'Matrix3') {
  1384. gl.uniformMatrix3fv(loc, false, val.values);
  1385. } else if (typeName === 'Matrix2') {
  1386. gl.uniformMatrix2fv(loc, false, val.values);
  1387. } else if (Array.isArray(val)) {
  1388. if (val.length === 2) gl.uniform2fv(loc, val);
  1389. else if (val.length === 3) gl.uniform3fv(loc, val);
  1390. else if (val.length === 4) gl.uniform4fv(loc, val);
  1391. else if (val.length === 9) gl.uniformMatrix3fv(loc, false, val);
  1392. else if (val.length === 16) gl.uniformMatrix4fv(loc, false, val);
  1393. else if (val.length === 1) gl.uniform1f(loc, val[0]);
  1394. } else if (val._texture) {
  1395. // 纹理 uniform 已在 _setRawCloudUniforms 中单独处理
  1396. } else if (typeof val === 'object' && val !== null) {
  1397. // 尝试作为 Cartesian 兼容对象处理
  1398. const keys = Object.keys(val).filter(k => !isNaN(Number(k)));
  1399. if (keys.length > 0) {
  1400. const arr = keys.map(k => Number(val[k]));
  1401. if (arr.length <= 4) {
  1402. if (arr.length === 1) gl.uniform1f(loc, arr[0]);
  1403. else if (arr.length === 2) gl.uniform2fv(loc, arr);
  1404. else if (arr.length === 3) gl.uniform3fv(loc, arr);
  1405. else if (arr.length === 4) gl.uniform4fv(loc, arr);
  1406. }
  1407. }
  1408. }
  1409. }
  1410. /**
  1411. * 在 postRender 中执行原始 WebGL2 云渲染。
  1412. * 拷贝当前帧缓冲区的颜色和深度,然后渲染全屏四边形。
  1413. */
  1414. _renderRawClouds(gl) {
  1415. if (!this._raw.prog || !this._raw.vao) return;
  1416. const canvas = this.viewer.scene.canvas;
  1417. const w = canvas.width;
  1418. const h = canvas.height;
  1419. if (w < 2 || h < 2) return;
  1420. // 如果画布尺寸变化,重新分配纹理
  1421. if (this._raw.depthW !== w || this._raw.depthH !== h) {
  1422. gl.bindTexture(gl.TEXTURE_2D, this._raw.depthTex);
  1423. gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, w, h, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
  1424. gl.bindTexture(gl.TEXTURE_2D, null);
  1425. gl.bindTexture(gl.TEXTURE_2D, this._raw.sceneColor);
  1426. gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  1427. gl.bindTexture(gl.TEXTURE_2D, null);
  1428. gl.bindFramebuffer(gl.FRAMEBUFFER, this._raw.depthFbo);
  1429. gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, this._raw.depthTex, 0);
  1430. // 深度拷贝 FBO 仅有深度附着,无颜色附着——需设置 NONE 以避免 FRAMEBUFFER_INCOMPLETE
  1431. gl.drawBuffers([gl.NONE]);
  1432. gl.readBuffer(gl.NONE);
  1433. gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  1434. this._raw.depthW = w;
  1435. this._raw.depthH = h;
  1436. }
  1437. // 保存 GL 状态
  1438. const prevFbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
  1439. const prevVp = gl.getParameter(gl.VIEWPORT);
  1440. const prevBlend = gl.getParameter(gl.BLEND);
  1441. const prevDepthTest = gl.getParameter(gl.DEPTH_TEST);
  1442. const prevCullFace = gl.getParameter(gl.CULL_FACE);
  1443. const prevProg = gl.getParameter(gl.CURRENT_PROGRAM);
  1444. const prevVAO = gl.getParameter(gl.VERTEX_ARRAY_BINDING);
  1445. // 1. 拷贝场景颜色到纹理
  1446. gl.bindTexture(gl.TEXTURE_2D, this._raw.sceneColor);
  1447. gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, w, h);
  1448. // 2. 拷贝场景深度到纹理
  1449. gl.bindFramebuffer(gl.READ_FRAMEBUFFER, prevFbo ? prevFbo : null);
  1450. gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this._raw.depthFbo);
  1451. try {
  1452. gl.blitFramebuffer(0, 0, w, h, 0, 0, w, h, gl.DEPTH_BUFFER_BIT, gl.NEAREST);
  1453. } catch (e) {
  1454. // 深度拷贝可能因 FBO 不兼容而失败,静默忽略
  1455. }
  1456. // 3. 渲染云层到当前帧缓冲
  1457. gl.bindFramebuffer(gl.FRAMEBUFFER, prevFbo ? prevFbo : null);
  1458. gl.viewport(0, 0, w, h);
  1459. gl.disable(gl.BLEND);
  1460. gl.disable(gl.DEPTH_TEST);
  1461. gl.disable(gl.CULL_FACE);
  1462. gl.useProgram(this._raw.prog);
  1463. gl.bindVertexArray(this._raw.vao);
  1464. this._setRawCloudUniforms(gl, w, h);
  1465. gl.drawArrays(gl.TRIANGLES, 0, 3);
  1466. // 4. 恢复状态
  1467. gl.bindVertexArray(prevVAO);
  1468. gl.useProgram(prevProg);
  1469. if (prevBlend) gl.enable(gl.BLEND); else gl.disable(gl.BLEND);
  1470. if (prevDepthTest) gl.enable(gl.DEPTH_TEST); else gl.disable(gl.DEPTH_TEST);
  1471. if (prevCullFace) gl.enable(gl.CULL_FACE); else gl.disable(gl.CULL_FACE);
  1472. gl.viewport(prevVp[0], prevVp[1], prevVp[2], prevVp[3]);
  1473. }
  1474. // ── BSM helpers ─────────────────────────────────────────────────────────
  1475. _bsmResolveGetTexture() {
  1476. const r = this._bsm.resolve;
  1477. const tex = r ? r._historyTex : (this._bsm.pass ? this._bsm.pass._colorTexture : null);
  1478. if (!tex) return null;
  1479. const gl = this.viewer.scene.context._gl;
  1480. return {
  1481. _texture: tex,
  1482. _textureTarget: gl.TEXTURE_2D,
  1483. _target: gl.TEXTURE_2D,
  1484. // 【关键注入】Cesium 必须调用此方法才能把纹理挂载到 GPU
  1485. bind: function(textureUnit) {
  1486. gl.activeTexture(gl.TEXTURE0 + textureUnit);
  1487. gl.bindTexture(gl.TEXTURE_2D, this._texture);
  1488. }
  1489. };
  1490. }
  1491. _taaGetHistoryTexture() {
  1492. const gl = this.viewer.scene.context?._gl;
  1493. if (!gl) return null;
  1494. const tex = this._taa.current === 0 ? this._taa.texA : this._taa.texB;
  1495. if (!tex) return null;
  1496. return { _texture: tex, _textureTarget: gl.TEXTURE_2D, _target: gl.TEXTURE_2D };
  1497. }
  1498. // ── BSM blit to Cesium.Texture ─────────────────────────────────────────
  1499. _blitBSM(sourceTex, targetCesiumTex, scale) {
  1500. const gl = this.viewer.scene.context?._gl;
  1501. if (!gl || !sourceTex?._texture || !targetCesiumTex?._texture) return;
  1502. if (!this._bsm.blitFbo) {
  1503. this._bsm.blitFbo = gl.createFramebuffer();
  1504. this._bsm.blitProg = createGLProgram(gl,
  1505. `#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);}`,
  1506. `#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);}`,
  1507. "BSMBlit");
  1508. const vbo = gl.createBuffer();
  1509. gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
  1510. gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 3,-1, -1,3]), gl.STATIC_DRAW);
  1511. this._bsm.blitVbo = vbo;
  1512. }
  1513. const prevFbo = gl.getParameter(gl.FRAMEBUFFER_BINDING), prevVp = gl.getParameter(gl.VIEWPORT);
  1514. gl.bindFramebuffer(gl.FRAMEBUFFER, this._bsm.blitFbo);
  1515. gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetCesiumTex._texture, 0);
  1516. if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { gl.bindFramebuffer(gl.FRAMEBUFFER, prevFbo); gl.viewport(...prevVp); return; }
  1517. gl.viewport(0, 0, BSM_BLIT_SIZE, BSM_BLIT_SIZE);
  1518. gl.useProgram(this._bsm.blitProg);
  1519. gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, sourceTex._texture);
  1520. gl.uniform1i(gl.getUniformLocation(this._bsm.blitProg, "u_src"), 0);
  1521. gl.uniform1f(gl.getUniformLocation(this._bsm.blitProg, "u_scale"), scale);
  1522. gl.bindBuffer(gl.ARRAY_BUFFER, this._bsm.blitVbo);
  1523. const aloc = gl.getAttribLocation(this._bsm.blitProg, "a_pos");
  1524. if (aloc >= 0) { gl.enableVertexAttribArray(aloc); gl.vertexAttribPointer(aloc, 2, gl.FLOAT, false, 0, 0); }
  1525. gl.drawArrays(gl.TRIANGLES, 0, 3);
  1526. if (aloc >= 0) gl.disableVertexAttribArray(aloc);
  1527. gl.bindFramebuffer(gl.FRAMEBUFFER, prevFbo); gl.viewport(prevVp[0], prevVp[1], prevVp[2], prevVp[3]);
  1528. }
  1529. // ── BSM sync to Atmosphere + Aerial ────────────────────────────────────
  1530. _syncBSM() {
  1531. const sp = this._bsm.pass;
  1532. if (!sp || !this.params.useShadowBuffer) {
  1533. this.atmosphere?.setCloudShadow?.({ enabled: false });
  1534. this.aerial?.setCloudShadow?.({ enabled: false });
  1535. return;
  1536. }
  1537. sp.updateDynamicParams({
  1538. localWeatherOffset: [this._weatherOffsetX || 0, this._weatherOffsetY || 0],
  1539. shapeOffset: [this._shapeOffsetX || 0, this._shapeOffsetY || 0, this._shapeOffsetZ || 0],
  1540. shapeDetailOffset: [this._shapeDetailOffsetX || 0, this._shapeDetailOffsetY || 0, this._shapeDetailOffsetZ || 0],
  1541. bottomRadius: this.params.bottomRadius,
  1542. // 每帧同步 shadow cascade far,限制到云层相关距离(避免 Cesium frustum.far~8e8 导致矩阵 NaN)
  1543. shadowFar: Number(this.params.shadowFar) || Number(this.params.maxShadowLengthRayDistance) || 200000.0,
  1544. maxShadowLengthRayDistance: Number(this.params.maxShadowLengthRayDistance) || 200000.0,
  1545. shadowSplitLambda: Number(this.params.shadowSplitLambda) || 0.5,
  1546. shadowFadeScale: Number(this.params.shadowFadeScale) || 1.0,
  1547. // 同步 layer 参数(coverage/densityScale 等),否则 GUI 调 coverage 只影响主云,BSM 阴影不变
  1548. // 用普通数组(非 Cartesian4),因为 BSM 的 set4f 走原生 gl.uniform4fv 只接受数组/Float32Array
  1549. coverages: [0,1,2,3].map(i => { const v = this.params.layers[i]?.coverage; return v !== undefined ? Number(v) : 0.3; }),
  1550. densityScales: [0,1,2,3].map(i => { const v = this.params.layers[i]?.densityScale; return v !== undefined ? Number(v) : 0; }),
  1551. shapeAmounts: [0,1,2,3].map(i => { const v = this.params.layers[i]?.shapeAmount; return v !== undefined ? Number(v) : 0; }),
  1552. shapeDetailAmounts: [0,1,2,3].map(i => { const v = this.params.layers[i]?.shapeDetailAmount; return v !== undefined ? Number(v) : 0; }),
  1553. weatherExponents: [0,1,2,3].map(i => { const v = this.params.layers[i]?.weatherExponent; return v !== undefined ? Number(v) : 1; }),
  1554. shapeAlteringBiases: [0,1,2,3].map(i => { const v = this.params.layers[i]?.shapeAlteringBias; return v !== undefined ? Number(v) : 0.35; }),
  1555. coverageFilterWidths: [0,1,2,3].map(i => { const v = this.params.layers[i]?.coverageFilterWidth; return v !== undefined ? Number(v) : 0.6; }),
  1556. scatteringCoefficient: Number(this.params.scatteringCoefficient) ?? 0.9,
  1557. absorptionCoefficient: Number(this.params.absorptionCoefficient) ?? 1.0,
  1558. });
  1559. let tex = this._bsm.resolve ? this._bsmResolveGetTexture() : null;
  1560. if (!tex) tex = sp.getTexture();
  1561. if (!tex) { this.atmosphere?.setCloudShadow?.({ enabled: false }); this.aerial?.setCloudShadow?.({ enabled: false }); return; }
  1562. const provider = this.atmosphere?.getAtmosphereForClouds?.();
  1563. const targetTex = provider?.getCloudShadowTargetTexture?.();
  1564. const clamp01 = provider?.getCloudShadowClamp01?.() ?? true;
  1565. const scaleToPass = clamp01 ? 200.0 : 1.0;
  1566. let textureToPass = tex;
  1567. if (targetTex && tex._texture) { this._blitBSM(tex, targetTex, scaleToPass); textureToPass = targetTex; }
  1568. const intervals = sp.getShadowIntervals();
  1569. const mats = sp.getShadowMatrices();
  1570. const tile = sp.getTileSize?.() || Math.floor(SHADOW_MAP_SIZE / 2);
  1571. // 远距几何误差修正:相机越高/越远,越把 BSM 采样点拉向稳定球面(对齐 three-geospatial correctGeometricError)
  1572. const camH = this.viewer.camera.positionCartographic?.height ?? 0;
  1573. const geoAmt = Math.min(1, Math.max(0, (camH - 2000) / 25000));
  1574. const opts = {
  1575. enabled: true, texture: textureToPass, scale: scaleToPass,
  1576. decode: { x: 1, y: 1, z: 1, w: 1 },
  1577. near: sp.getShadowNear?.() ?? (Number(this.viewer.camera.frustum?.near) || 0.1),
  1578. far: sp.getShadowFar(),
  1579. topHeight: this._getMaxHeight(), bottomRadius: Number(this.params.bottomRadius) || 6371000,
  1580. intervals: intervals.map(a => new Cesium.Cartesian2(a[0], a[1])),
  1581. matrices: mats.map(m => Cesium.Matrix4.fromArray(m)),
  1582. texelSize: { x: 1 / tile, y: 1 / tile },
  1583. geometricErrorCorrectionAmount: geoAmt,
  1584. };
  1585. this.atmosphere?.setCloudShadow?.(opts);
  1586. this.aerial?.setCloudShadow?.(opts);
  1587. }
  1588. // ── TAA (inline CloudsResolvePass) ─────────────────────────────────────
  1589. _taaCapture() {
  1590. const gl = this.viewer.scene.context?._gl;
  1591. if (!gl) return;
  1592. const canvas = this.viewer.scene.canvas;
  1593. const w = canvas.width, h = canvas.height;
  1594. if (w !== this._taa.w || h !== this._taa.h) {
  1595. if (this._taa.texA) gl.deleteTexture(this._taa.texA);
  1596. if (this._taa.texB) gl.deleteTexture(this._taa.texB);
  1597. if (this._taa.pbo) gl.deleteBuffer(this._taa.pbo);
  1598. 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; };
  1599. this._taa.texA = mkTex(); this._taa.texB = mkTex();
  1600. 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);
  1601. this._taa.w = w; this._taa.h = h; this._taa.frameCount = 0; this._taa.pboReady = false;
  1602. }
  1603. const writeTex = this._taa.current === 0 ? this._taa.texB : this._taa.texA;
  1604. if (this._taa.pboReady) {
  1605. const prevTex = gl.getParameter(gl.TEXTURE_BINDING_2D);
  1606. const flipY = gl.getParameter(gl.UNPACK_FLIP_Y_WEBGL), premul = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL);
  1607. if (flipY) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  1608. if (premul) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
  1609. gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, this._taa.pbo);
  1610. gl.bindTexture(gl.TEXTURE_2D, writeTex);
  1611. gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, 0);
  1612. gl.bindTexture(gl.TEXTURE_2D, prevTex);
  1613. gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, null);
  1614. if (flipY) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
  1615. if (premul) gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
  1616. this._taa.current = 1 - this._taa.current;
  1617. this._taa.frameCount++;
  1618. }
  1619. const prevFbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
  1620. gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  1621. gl.bindBuffer(gl.PIXEL_PACK_BUFFER, this._taa.pbo);
  1622. gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, 0);
  1623. gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
  1624. if (prevFbo) gl.bindFramebuffer(gl.FRAMEBUFFER, prevFbo);
  1625. this._taa.pboReady = true;
  1626. }
  1627. _taaUpdateVP() {
  1628. this._taa.prevVP = this._taa.curVP;
  1629. const cam = this.viewer.camera;
  1630. this._taa.curVP = Cesium.Matrix4.multiply(cam.frustum.projectionMatrix, cam.viewMatrix, new Cesium.Matrix4());
  1631. }
  1632. // ── GUI ────────────────────────────────────────────────────────────────
  1633. _setupGUI() {
  1634. if (this._gui) return;
  1635. this._gui = new dat.GUI({ name: "体积云管线" });
  1636. const p = this.params, ls = p.layers;
  1637. const f = this._gui.addFolder("云层");
  1638. f.add(ls[0], "altitude", 0, 20000, 50).name("层0底高(m)");
  1639. f.add(ls[0], "height", 0, 10000, 50).name("层0厚度(m)");
  1640. f.add(ls[0], "coverage", 0, 1, 0.01).name("层0覆盖度");
  1641. f.add(ls[1], "altitude", 0, 20000, 50).name("层1底高(m)");
  1642. f.add(ls[1], "height", 0, 10000, 50).name("层1厚度(m)");
  1643. f.add(ls[1], "coverage", 0, 1, 0.01).name("层1覆盖度");
  1644. f.add(ls[2], "altitude", 0, 20000, 50).name("层2底高(m)");
  1645. f.add(ls[2], "height", 0, 10000, 50).name("层2厚度(m)");
  1646. f.add(ls[2], "coverage", 0, 1, 0.01).name("层2覆盖度");
  1647. // 降高频相关参数(用于抑制边缘噪点/闪烁)
  1648. f.add(p, "shapeRepeat", 1.0, 8.0, 0.1).name("主体噪声频率");
  1649. f.add(p, "shapeDetailRepeat", 0.0005, 0.02, 0.0001).name("细节噪声频率");
  1650. f.add(ls[0], "shapeDetailAmount", 0.0, 1.5, 0.01).name("层0细节权重");
  1651. f.add(ls[1], "shapeDetailAmount", 0.0, 1.5, 0.01).name("层1细节权重");
  1652. f.add(ls[2], "shapeDetailAmount", 0.0, 1.5, 0.01).name("层2细节权重");
  1653. f.add(ls[0], "weatherExponent", 0.2, 2.0, 0.01).name("层0天气指数");
  1654. f.add(ls[1], "weatherExponent", 0.2, 2.0, 0.01).name("层1天气指数");
  1655. f.add(ls[2], "weatherExponent", 0.2, 2.0, 0.01).name("层2天气指数");
  1656. f.add(ls[0], "coverageFilterWidth", 0.1, 1.0, 0.01).name("层0覆盖过滤宽度");
  1657. f.add(ls[1], "coverageFilterWidth", 0.1, 1.0, 0.01).name("层1覆盖过滤宽度");
  1658. f.add(ls[2], "coverageFilterWidth", 0.1, 1.0, 0.01).name("层2覆盖过滤宽度");
  1659. f.open();
  1660. const l = this._gui.addFolder("光照");
  1661. l.add(p, "sunIntensity", 0, 150, 5).name("太阳强度");
  1662. l.add(p, "skyToSunRatio", 0.05, 0.6, 0.01).name("天空/太阳比");
  1663. l.add(p, "cloudExposure", 0.1, 5.0, 0.1).name("云曝光");
  1664. l.add(p, "magentaFixStrength", 0.0, 2.0, 0.05).name("落日去品红强度");
  1665. l.add(p, "edgeAlphaCutoff", 0.0, 0.2, 0.005).name("边缘Alpha裁剪");
  1666. l.add(p, "aerialPerspectiveScale", 0, 3, 0.1).name("大气透视");
  1667. const s = this._gui.addFolder("散射");
  1668. s.add(p, "scatterG1", 0, 0.99, 0.01).name("前向散射G");
  1669. s.add(p, "scatterG2", -0.99, 0, 0.01).name("后向散射G");
  1670. s.add(p, "multiScatteringOctaves", 1, 12, 1).name("多散射阶数");
  1671. const a = this._gui.addFolder("动画");
  1672. a.add(p, "windSpeed", 0, 1, 0.0001).name("风速");
  1673. a.add(p, "evolutionSpeed", 0, 0.0001, 0.000001).name("演化速度");
  1674. // 远处云距离衰减:调小 distFadeStart 让衰减更早开始(远处更疏),调大 distFadeEnd 让衰减更平缓
  1675. a.add(p, "distFadeStart", 5000, 100000, 1000).name("远处衰减起点(m)");
  1676. a.add(p, "distFadeEnd", 20000, 200000, 1000).name("远处衰减终点(m)");
  1677. const o = this._gui.addFolder("开关");
  1678. o.add(p, "cloudsVisible").name("显示云").onChange((v) => {
  1679. if (this.cloudStage) this.cloudStage.enabled = v;
  1680. });
  1681. o.add(p, "useShadowBuffer").name("BSM(云阴影)");
  1682. o.add(p, "shadowLengthEnabled").name("阴影长度(丁达尔)");
  1683. o.add(p, "hazeEnabled").name("雾效(HAZE)");
  1684. o.add(p, "temporalEnabled").name("TAA");
  1685. o.add(p, "maxSteps", 64, 1200, 1).name("主采样步数");
  1686. o.add(p, "minStepSize", 5.0, 200.0, 1.0).name("最小步长");
  1687. o.add(p, "blueNoiseScale", 0.25, 4.0, 0.05).name("噪声采样缩放");
  1688. o.add(p, "jitterStrength", 0.0, 1.0, 0.01).name("抖动强度");
  1689. // BSM OD 缩放联动:地面阴影实际在 AerialPerspectiveEffect stage 渲染,
  1690. // 但原 GUI 只绑了 AtmospherePostProcess 实例,调不动。这里统一驱动两侧。
  1691. const bsm = this._gui.addFolder("BSM 缩放");
  1692. const syncBsmScale = (key, val) => {
  1693. if (this.atmosphere) this.atmosphere[`_${key}`] = val;
  1694. if (this.aerial) this.aerial[`_${key}`] = val;
  1695. };
  1696. // 用 params 上的占位属性承载 GUI 值,初始与两侧默认(1.0)对齐
  1697. p._bsmGroundScale = 0.3; p._bsmTyndallScale = 1.0;
  1698. bsm.add(p, "_bsmGroundScale", 0.1, 20.0, 0.1).name("OD缩放(地面)").onChange((v) => syncBsmScale("bsmGroundOpticalDepthScale", v));
  1699. bsm.add(p, "_bsmTyndallScale", 0.1, 20.0, 0.1).name("OD缩放(光柱)").onChange((v) => syncBsmScale("bsmTyndallOpticalDepthScale", v));
  1700. // cascade 几何:调这三个解决"近处阴影被切割"。shadowFar=覆盖最远距离,splitLambda=近处分配(越大近处越多),fadeScale=ortho扩展
  1701. bsm.add(p, "shadowFar", 20000, 500000, 5000).name("阴影覆盖距离");
  1702. bsm.add(p, "shadowSplitLambda", 0.0, 1.0, 0.05).name("近处分配");
  1703. bsm.add(p, "shadowFadeScale", 0.0, 5.0, 0.1).name("边缘扩展");
  1704. // 关键:占位初始值不会自动触发 onChange,这里手动同步一次,否则启动时两侧 scale 仍是构造默认(1.0)
  1705. syncBsmScale("bsmGroundOpticalDepthScale", p._bsmGroundScale);
  1706. syncBsmScale("bsmTyndallOpticalDepthScale", p._bsmTyndallScale);
  1707. }
  1708. // ── BSM ShadowPass params (for CloudShadowPass) ────────────────────────
  1709. _getShadowPassParams() {
  1710. const ls = this.params.layers;
  1711. const minLayerHeights = [], maxLayerHeights = [], densityProfileLinear = [], densityProfileConstant = [];
  1712. const densityScales = [], shapeAmounts = [], shapeDetailAmounts = [], weatherExponents = [];
  1713. const shapeAlteringBiases = [], coverageFilterWidths = [], coverages = [];
  1714. let minAlt = 1e9, maxAltH = 0;
  1715. for (let i = 0; i < 4; i++) {
  1716. const a = Number(ls[i]?.altitude) || 0, h = Number(ls[i]?.height) || 0;
  1717. if (a + h > 0) { minAlt = Math.min(minAlt, a); maxAltH = Math.max(maxAltH, a + h); }
  1718. minLayerHeights[i] = a; maxLayerHeights[i] = a + h;
  1719. densityProfileLinear[i] = Number(ls[i]?.densityProfile?.linearTerm) ?? 0.75;
  1720. densityProfileConstant[i] = Number(ls[i]?.densityProfile?.constantTerm) ?? 0.25;
  1721. densityScales[i] = Number(ls[i]?.densityScale) || 0;
  1722. shapeAmounts[i] = Number(ls[i]?.shapeAmount) ?? 1;
  1723. shapeDetailAmounts[i] = Number(ls[i]?.shapeDetailAmount) ?? 1;
  1724. weatherExponents[i] = Number(ls[i]?.weatherExponent) ?? 1;
  1725. shapeAlteringBiases[i] = Number(ls[i]?.shapeAlteringBias) ?? 0.35;
  1726. coverageFilterWidths[i] = Number(ls[i]?.coverageFilterWidth) ?? 0.6;
  1727. coverages[i] = Number(ls[i]?.coverage) ?? 0.3;
  1728. }
  1729. const iv = this._getIntervalHeights();
  1730. const cBottom = Number.isFinite(minAlt) ? minAlt : 750;
  1731. return {
  1732. bottomRadius: Number(this.params.bottomRadius) || 6378137,
  1733. cloudBottomHeight: cBottom, cloudTopHeight: Math.max(0, maxAltH - cBottom) || 1500,
  1734. shadowBottomHeight: cBottom, shadowTopHeight: maxAltH || (cBottom + 1500),
  1735. // shadow cascade far 必须限制到云层相关距离,否则会取 Cesium 相机 frustum.far(~8e8),
  1736. // 导致 ortho proj radius 爆炸、数值精度崩坏产生 NaN、矩阵不可逆、BSM 全失效。
  1737. shadowFar: Number(this.params.shadowFar) || Number(this.params.maxShadowLengthRayDistance) || 200000.0,
  1738. maxShadowLengthRayDistance: Number(this.params.maxShadowLengthRayDistance) || 200000.0,
  1739. shadowSplitLambda: Number(this.params.shadowSplitLambda) || 0.5,
  1740. shadowFadeScale: Number(this.params.shadowFadeScale) || 1.0,
  1741. weatherRepeat: Number(this.params.weatherRepeat) || 100, windSpeed: Number(this.params.windSpeed) || 0,
  1742. shapeRepeat: (Number(this.params.shapeRepeat) || 3) / 1e4,
  1743. shapeDetailRepeat: Number(this.params.shapeDetailRepeat) || 0.006,
  1744. turbulenceRepeat: Number(this.params.turbulenceRepeat) || 2,
  1745. turbulenceDisplacement: Number(this.params.turbulenceDisplacement) || 400,
  1746. coverage: Math.max(...coverages), densityScale: Math.max(...densityScales),
  1747. scatteringCoefficient: Number(this.params.scatteringCoefficient) ?? 0.9,
  1748. absorptionCoefficient: Number(this.params.absorptionCoefficient) ?? 1.0,
  1749. startTime: performance.now() / 1000, evolutionSpeed: Number(this.params.evolutionSpeed) || 0.005,
  1750. maxSteps: this.params.maxSteps, minStepSize: this.params.minStepSize,
  1751. minDensity: this.params.minDensity ?? 1e-5, minExtinction: this.params.minExtinction ?? 1e-5,
  1752. minTransmittance: this.params.minTransmittance ?? 0.01, opticalDepthTailScale: 1.0,
  1753. minLayerHeights, maxLayerHeights, densityProfileLinear, densityProfileConstant,
  1754. densityProfileExpTerms: [0,0,0,0], densityProfileExponents: [0,0,0,0],
  1755. densityScales, shapeAmounts, shapeDetailAmounts, weatherExponents,
  1756. shapeAlteringBiases, coverageFilterWidths, coverages,
  1757. minIntervalHeights: [iv.min.x, iv.min.y, iv.min.z],
  1758. maxIntervalHeights: [iv.max.x, iv.max.y, iv.max.z],
  1759. localWeatherOffset: [0, 0], shapeOffset: [0, 0, 0], shapeDetailOffset: [0, 0, 0],
  1760. };
  1761. }
  1762. // ── Init ───────────────────────────────────────────────────────────────
  1763. async init() {
  1764. if (this._ready) return this._ready;
  1765. this._ready = (async () => {
  1766. const viewer = this.viewer;
  1767. viewer.scene.globe.depthTestAgainstTerrain = true;
  1768. // 1. Atmosphere
  1769. this.atmosphere = new AtmospherePostProcess(viewer, {
  1770. atmosphereParams: this.atmosphereParams, renderSky: true,
  1771. applyGroundAtmosphere: false, autoAddStage: false,
  1772. assetsBaseUrl: this.atmosphereAssetsBase, shaderBaseUrl: this.atmosphereShaderBase,
  1773. });
  1774. await this.atmosphere.init();
  1775. // 2. Aerial
  1776. this.aerial = new AerialPerspectiveEffect(viewer, {
  1777. atmosphereParams: this.atmosphereParams, autoAddStage: false,
  1778. assetsBaseUrl: this.atmosphereAssetsBase, shaderBaseUrl: this.atmosphereShaderBase,
  1779. });
  1780. // 3. Load cloud textures + build shader
  1781. await this._loadTextures();
  1782. const fragmentShader = await this._buildCloudFragmentShader();
  1783. // 4. BSM passes (import dynamically to avoid circular deps)
  1784. const { CloudShadowPass } = await import("./CloudShadowPass.js");
  1785. const { ShadowResolvePass } = await import("./ShadowResolvePass.js");
  1786. if (this.params.useShadowBuffer && this.textures) {
  1787. this._bsm.pass = new CloudShadowPass(viewer, { textures: this.textures, params: this._getShadowPassParams() });
  1788. this._bsm.pass.init();
  1789. // 静止 temporalAlpha 对齐 three-geospatial≈0.01;运动时 ShadowResolvePass 内会抬高 alpha
  1790. this._bsm.resolve = new ShadowResolvePass(viewer, { size: SHADOW_MAP_SIZE, temporalAlpha: 0.01 });
  1791. this._bsm.resolve.setInputTextures(this._bsm.pass.getTexture(), this._bsm.pass.getDepthVelocityTexture());
  1792. this._bsm.resolve.init();
  1793. }
  1794. // 5. Aerial init
  1795. await this.aerial.init();
  1796. // 6. 初始化原始 WebGL2 云渲染管线
  1797. // 注意:大气/空中透视仍使用 Cesium PostProcessStage(提供天空背景),
  1798. // 但它们的 sampler3D uniform 在 Cesium binding 中可能失败。
  1799. // 因此这里不注册任何 PostProcessStage,只渲染云层。
  1800. const gl = viewer.scene.context._gl;
  1801. const rawOk = gl ? this._initRawWebGL(gl) : false;
  1802. if (!rawOk) {
  1803. console.warn('[Pipeline] 原始 WebGL2 云渲染初始化失败,回退使用 Cesium PostProcessStage');
  1804. // 回退:使用原来的 PostProcessStage(但 sampler3D 可能失败)
  1805. const uniforms = this._buildCloudUniforms();
  1806. this.cloudStage = new Cesium.PostProcessStage({
  1807. name: "GeospatialVolumetricClouds", fragmentShader, uniforms,
  1808. });
  1809. this.cloudStage.enabled = this.params.cloudsVisible;
  1810. const stages = viewer.scene.postProcessStages;
  1811. if (this.atmosphere.stage) stages.add(this.atmosphere.stage);
  1812. if (this.aerial.stage) stages.add(this.aerial.stage);
  1813. stages.add(this.cloudStage);
  1814. } else {
  1815. console.log('[Pipeline] 使用原始 WebGL2 渲染云层(跳过 PostProcessStage)');
  1816. // 仅用 Cesium 的默认 skyBox 作为背景
  1817. }
  1818. // 7. preRender: BSM sync
  1819. this._listeners.push(viewer.scene.preRender.addEventListener(() => this._syncBSM()));
  1820. // 8. postRender: TAA capture + frame count + 原始 WebGL2 云渲染
  1821. this._listeners.push(viewer.scene.postRender.addEventListener(() => {
  1822. this._taaUpdateVP();
  1823. if (this.params.temporalEnabled) this._taaCapture();
  1824. this._frameCount++;
  1825. // 如果使用原始 WebGL2 渲染,在 postRender 中执行
  1826. if (rawOk && this.params.cloudsVisible) {
  1827. try { this._renderRawClouds(gl); } catch (e) {
  1828. console.warn('[Pipeline] 原始 WebGL2 云渲染失败:', e.message);
  1829. }
  1830. }
  1831. }));
  1832. this._listeners.push(viewer.camera.changed.addEventListener(() => {
  1833. const c = Cesium.Cartographic.fromCartesian(
  1834. viewer.camera.positionWC,
  1835. viewer.scene.globe.ellipsoid
  1836. );
  1837. const ellipsoidHeight = Number(c?.height) || 0;
  1838. const atmBottomRadius = Number(
  1839. this.atmosphere?.getAtmosphereForClouds?.()?.getUniforms?.()?.bottomRadius?.() ?? NaN
  1840. );
  1841. const usedBottomRadius = Number.isFinite(atmBottomRadius)
  1842. ? atmBottomRadius
  1843. : (Number(this.params.bottomRadius) || 0);
  1844. const corr = this._getAltitudeCorrectionOffset(usedBottomRadius);
  1845. const correctedPos = Cesium.Cartesian3.add(
  1846. viewer.camera.positionWC,
  1847. corr,
  1848. new Cesium.Cartesian3()
  1849. );
  1850. const correctedHeight = Math.max(
  1851. 0,
  1852. Cesium.Cartesian3.magnitude(correctedPos) - usedBottomRadius
  1853. );
  1854. }));
  1855. this._setupGUI();
  1856. console.log("[Pipeline] ready: Cloud -> Atmosphere -> Aerial");
  1857. })();
  1858. return this._ready;
  1859. }
  1860. // ── Destroy ────────────────────────────────────────────────────────────
  1861. destroy() {
  1862. for (const remove of this._listeners) if (typeof remove === "function") remove();
  1863. this._listeners = [];
  1864. const stages = this.viewer?.scene?.postProcessStages;
  1865. if (stages && this.cloudStage) { try { stages.remove(this.cloudStage); } catch {} }
  1866. this.cloudStage = null;
  1867. try { this.aerial?.destroy(); } catch {} this.aerial = null;
  1868. try { this.atmosphere?.destroy(); } catch {} this.atmosphere = null;
  1869. try { this._bsm.pass?.destroy(); } catch {} this._bsm.pass = null;
  1870. try { this._bsm.resolve?.destroy(); } catch {} this._bsm.resolve = null;
  1871. const gl = this.viewer?.scene?.context?._gl;
  1872. if (gl) {
  1873. // 清理 BSM 资源
  1874. if (this._bsm.blitFbo) gl.deleteFramebuffer(this._bsm.blitFbo);
  1875. if (this._bsm.blitProg) gl.deleteProgram(this._bsm.blitProg);
  1876. if (this._bsm.blitVbo) gl.deleteBuffer(this._bsm.blitVbo);
  1877. // 清理 TAA 资源
  1878. if (this._taa.texA) gl.deleteTexture(this._taa.texA);
  1879. if (this._taa.texB) gl.deleteTexture(this._taa.texB);
  1880. if (this._taa.pbo) gl.deleteBuffer(this._taa.pbo);
  1881. // 清理原始 WebGL2 云渲染资源
  1882. if (this._raw.prog) gl.deleteProgram(this._raw.prog);
  1883. if (this._raw.vao) gl.deleteVertexArray(this._raw.vao);
  1884. if (this._raw.vbo) gl.deleteBuffer(this._raw.vbo);
  1885. if (this._raw.sceneColor) gl.deleteTexture(this._raw.sceneColor);
  1886. if (this._raw.depthTex) gl.deleteTexture(this._raw.depthTex);
  1887. if (this._raw.depthFbo) gl.deleteFramebuffer(this._raw.depthFbo);
  1888. }
  1889. this._bsm = { pass: null, resolve: null, blitFbo: null, blitProg: null, blitVbo: null };
  1890. this._taa = { texA: null, texB: null, current: 0, pbo: null, pboReady: false, w: 0, h: 0, frameCount: 0, prevVP: null, curVP: null };
  1891. this._raw = { prog: null, vao: null, vbo: null, sceneColor: null, depthTex: null, depthFbo: null, depthW: 0, depthH: 0, texUnits: {} };
  1892. if (this.textures) { for (const k in this.textures) { try { this.textures[k]?.destroy?.(); } catch {} } this.textures = null; }
  1893. if (this._gui) { this._gui.destroy(); this._gui = null; }
  1894. this._ready = null;
  1895. }
  1896. }