@navaramap/three 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +8 -0
- package/dist/assets/atmosphere/README.md +1 -0
- package/dist/assets/atmosphere/higher_order_scattering.bin +0 -0
- package/dist/assets/atmosphere/higher_order_scattering.exr +0 -0
- package/dist/assets/atmosphere/irradiance.bin +0 -0
- package/dist/assets/atmosphere/irradiance.exr +0 -0
- package/dist/assets/atmosphere/scattering.bin +0 -0
- package/dist/assets/atmosphere/scattering.exr +0 -0
- package/dist/assets/atmosphere/single_mie_scattering.bin +0 -0
- package/dist/assets/atmosphere/single_mie_scattering.exr +0 -0
- package/dist/assets/atmosphere/stars.bin +0 -0
- package/dist/assets/atmosphere/transmittance.bin +0 -0
- package/dist/assets/atmosphere/transmittance.exr +0 -0
- package/dist/assets/cloud/README.md +1 -0
- package/dist/assets/cloud/local_weather.png +0 -0
- package/dist/assets/cloud/shape.bin +1 -0
- package/dist/assets/cloud/shape_detail.bin +1 -0
- package/dist/assets/cloud/turbulence.png +0 -0
- package/dist/assets/fontWorker-CEJEfRpb.js +2 -0
- package/dist/assets/fontWorker-CEJEfRpb.js.map +1 -0
- package/dist/assets/index-BUB-ijMO.js +12 -0
- package/dist/assets/index-BUB-ijMO.js.map +1 -0
- package/dist/assets/navara_wasm_font_worker_bg-665OLxjy.wasm +0 -0
- package/dist/assets/navara_wasm_worker_bg-BXhKC5bE.wasm +0 -0
- package/dist/assets/noise/README.md +1 -0
- package/dist/assets/noise/stbn.bin +0 -0
- package/dist/assets/water/README.md +3 -0
- package/dist/assets/water/waternormals.jpg +0 -0
- package/dist/index.d.ts +10114 -0
- package/dist/index.js +34481 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.cjs +6345 -0
- package/dist/index.umd.cjs.map +1 -0
- package/package.json +95 -0
- package/src/Color.ts +111 -0
- package/src/ShadowMapViewers.ts +49 -0
- package/src/atmosphere.test.ts +76 -0
- package/src/atmosphere.ts +364 -0
- package/src/bufferView.ts +90 -0
- package/src/camera.ts +182 -0
- package/src/concurrency.ts +15 -0
- package/src/constants/assets.ts +30 -0
- package/src/constants/decoder.ts +7 -0
- package/src/constants/index.ts +2 -0
- package/src/core/BaseDesc.ts +142 -0
- package/src/core/BaseHandle.ts +126 -0
- package/src/core/DescRegistry.ts +88 -0
- package/src/core/EffectDesc.ts +343 -0
- package/src/core/EffectDescRegistry.ts +31 -0
- package/src/core/InstancedMeshDesc.ts +413 -0
- package/src/core/LightDesc.ts +216 -0
- package/src/core/LightDescRegistry.ts +32 -0
- package/src/core/MeshDesc.ts +457 -0
- package/src/core/MeshDescRegistry.ts +32 -0
- package/src/core/MeshDescWithSelectiveEffect.ts +129 -0
- package/src/core/Registries.ts +59 -0
- package/src/core/SelectiveEffectRegistry.ts +90 -0
- package/src/core/ViewContext.ts +235 -0
- package/src/core/errors.ts +16 -0
- package/src/core/index.ts +50 -0
- package/src/device.ts +414 -0
- package/src/effects/CustomEffectPass.ts +28 -0
- package/src/effects/copy.ts +10 -0
- package/src/effects/effect.ts +87 -0
- package/src/effects/index.ts +4 -0
- package/src/effects/render.ts +10 -0
- package/src/evaluations/FeatureEvaluator.ts +529 -0
- package/src/evaluations/index.ts +1 -0
- package/src/event/HillshadeContext.test.ts +232 -0
- package/src/event/HillshadeContext.ts +299 -0
- package/src/event/context.ts +279 -0
- package/src/event/feature.ts +357 -0
- package/src/event/featureEvent.ts +81 -0
- package/src/event/features/billboard.ts +30 -0
- package/src/event/features/model.ts +192 -0
- package/src/event/features/modelGpuBytes.test.ts +69 -0
- package/src/event/features/modelGpuBytes.ts +87 -0
- package/src/event/features/point.ts +25 -0
- package/src/event/features/polygon.ts +23 -0
- package/src/event/features/polygonOutline.ts +19 -0
- package/src/event/features/polyline.ts +19 -0
- package/src/event/features/text.ts +71 -0
- package/src/event/hillshade.test.ts +842 -0
- package/src/event/hillshade.ts +588 -0
- package/src/event/index.ts +700 -0
- package/src/event/loaders.ts +53 -0
- package/src/event/tile.ts +19 -0
- package/src/event/worker.ts +999 -0
- package/src/event/workerTaskSettlement.test.ts +243 -0
- package/src/event/workerTaskSettlement.ts +128 -0
- package/src/inMemoryBufferStore.test.ts +60 -0
- package/src/inMemoryBufferStore.ts +67 -0
- package/src/index.iife.ts +11 -0
- package/src/index.ts +2556 -0
- package/src/input.ts +144 -0
- package/src/layer.ts +183 -0
- package/src/layers/effect/FinalCopyEffectDesc.ts +29 -0
- package/src/layers/effect/MRTPassEffectDesc.ts +105 -0
- package/src/layers/effect/README.md +9 -0
- package/src/layers/effect/SelectiveEffectDesc.ts +113 -0
- package/src/layers/effect/SkyEnvMapEffectDesc.ts +58 -0
- package/src/layers/effect/TransparentPassEffectDesc.ts +62 -0
- package/src/layers/effect/index.ts +5 -0
- package/src/layers/index.ts +1 -0
- package/src/layersManager.ts +63 -0
- package/src/light.ts +15 -0
- package/src/loaders/AbortableImageLoader.ts +203 -0
- package/src/loaders/AbortableTextureLoader.ts +74 -0
- package/src/loaders/index.ts +5 -0
- package/src/material/enhancer/DESIGN.md +300 -0
- package/src/material/enhancer/MaterialEnhancer.ts +112 -0
- package/src/material/enhancer/ShaderReplacer.test.ts +114 -0
- package/src/material/enhancer/ShaderReplacer.ts +97 -0
- package/src/material/enhancer/batchTexture.ts +8 -0
- package/src/material/enhancer/index.ts +46 -0
- package/src/material/enhancer/instancedSprite/index.ts +7 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/index.test.ts +214 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/index.ts +134 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/markers.ts +21 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/material.ts +32 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.test.ts +277 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/mutates.ts +151 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/shader.test.ts +114 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/shader.ts +49 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/state.test.ts +82 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/state.ts +80 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteBaseEnhancer/types.ts +146 -0
- package/src/material/enhancer/instancedSprite/instancedSpriteMaterialEnhancer.ts +105 -0
- package/src/material/enhancer/model/index.ts +25 -0
- package/src/material/enhancer/model/modelBaseEnhancer/index.test.ts +74 -0
- package/src/material/enhancer/model/modelBaseEnhancer/index.ts +110 -0
- package/src/material/enhancer/model/modelBaseEnhancer/markers.ts +27 -0
- package/src/material/enhancer/model/modelBaseEnhancer/material.test.ts +66 -0
- package/src/material/enhancer/model/modelBaseEnhancer/material.ts +44 -0
- package/src/material/enhancer/model/modelBaseEnhancer/mutates.test.ts +84 -0
- package/src/material/enhancer/model/modelBaseEnhancer/mutates.ts +47 -0
- package/src/material/enhancer/model/modelBaseEnhancer/shader.test.ts +24 -0
- package/src/material/enhancer/model/modelBaseEnhancer/shader.ts +162 -0
- package/src/material/enhancer/model/modelBaseEnhancer/state.test.ts +103 -0
- package/src/material/enhancer/model/modelBaseEnhancer/state.ts +52 -0
- package/src/material/enhancer/model/modelBaseEnhancer/types.ts +89 -0
- package/src/material/enhancer/model/modelMaterialEnhancer.ts +91 -0
- package/src/material/enhancer/model/modelWaterEnhancer/index.test.ts +71 -0
- package/src/material/enhancer/model/modelWaterEnhancer/index.ts +175 -0
- package/src/material/enhancer/model/modelWaterEnhancer/material.test.ts +48 -0
- package/src/material/enhancer/model/modelWaterEnhancer/material.ts +25 -0
- package/src/material/enhancer/model/modelWaterEnhancer/mutates.test.ts +89 -0
- package/src/material/enhancer/model/modelWaterEnhancer/mutates.ts +89 -0
- package/src/material/enhancer/model/modelWaterEnhancer/shader.test.ts +34 -0
- package/src/material/enhancer/model/modelWaterEnhancer/shader.ts +111 -0
- package/src/material/enhancer/model/modelWaterEnhancer/state.test.ts +105 -0
- package/src/material/enhancer/model/modelWaterEnhancer/state.ts +59 -0
- package/src/material/enhancer/model/modelWaterEnhancer/types.ts +141 -0
- package/src/material/enhancer/model/pntsEnhancer/index.test.ts +96 -0
- package/src/material/enhancer/model/pntsEnhancer/index.ts +82 -0
- package/src/material/enhancer/model/pntsEnhancer/markers.ts +6 -0
- package/src/material/enhancer/model/pntsEnhancer/material.test.ts +37 -0
- package/src/material/enhancer/model/pntsEnhancer/material.ts +21 -0
- package/src/material/enhancer/model/pntsEnhancer/mutates.test.ts +80 -0
- package/src/material/enhancer/model/pntsEnhancer/mutates.ts +29 -0
- package/src/material/enhancer/model/pntsEnhancer/shader.test.ts +32 -0
- package/src/material/enhancer/model/pntsEnhancer/shader.ts +56 -0
- package/src/material/enhancer/model/pntsEnhancer/state.test.ts +87 -0
- package/src/material/enhancer/model/pntsEnhancer/state.ts +29 -0
- package/src/material/enhancer/model/pntsEnhancer/types.ts +38 -0
- package/src/material/enhancer/polygon/index.ts +22 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/index.test.ts +101 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/index.ts +130 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/markers.ts +23 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/material.test.ts +19 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/material.ts +38 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.test.ts +75 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/mutates.ts +122 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.test.ts +25 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/shader.ts +249 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/state.test.ts +102 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/state.ts +92 -0
- package/src/material/enhancer/polygon/polygonBaseEnhancer/types.ts +150 -0
- package/src/material/enhancer/polygon/polygonMaterialEnhancer.ts +97 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/index.test.ts +72 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/index.ts +164 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/material.test.ts +48 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/material.ts +35 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/mutates.ts +61 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/shader.test.ts +34 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/shader.ts +96 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/state.test.ts +98 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/state.ts +57 -0
- package/src/material/enhancer/polygon/polygonWaterEnhancer/types.ts +115 -0
- package/src/material/enhancer/polyline/index.ts +7 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/index.test.ts +157 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/index.ts +154 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/markers.ts +19 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/material.test.ts +42 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/material.ts +38 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.test.ts +120 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/mutates.ts +169 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/shader.test.ts +67 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/shader.ts +75 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/state.test.ts +55 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/state.ts +91 -0
- package/src/material/enhancer/polyline/polylineBaseEnhancer/types.ts +170 -0
- package/src/material/enhancer/polyline/polylineMaterialEnhancer.ts +102 -0
- package/src/material/enhancer/sdfText/index.ts +7 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/index.ts +125 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/markers.ts +19 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/material.test.ts +35 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/material.ts +32 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.test.ts +381 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/mutates.ts +232 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/shader.ts +36 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.test.ts +118 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/state.ts +107 -0
- package/src/material/enhancer/sdfText/sdfTextBaseEnhancer/types.ts +184 -0
- package/src/material/enhancer/sdfText/sdfTextMaterialEnhancer.ts +100 -0
- package/src/material/enhancer/shadowMap/index.ts +7 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/index.ts +97 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/markers.ts +10 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/material.ts +31 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/mutates.ts +8 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/shader.test.ts +17 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/shader.ts +53 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/state.ts +17 -0
- package/src/material/enhancer/shadowMap/shadowMapDepthEnhancer/types.ts +26 -0
- package/src/material/enhancer/test-utils/shaderCompatibility.ts +124 -0
- package/src/material/enhancer/tileComposite/compose.test.ts +95 -0
- package/src/material/enhancer/tileComposite/compose.ts +112 -0
- package/src/material/enhancer/tileComposite/index.ts +23 -0
- package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/index.ts +11 -0
- package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/mutates.ts +132 -0
- package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/shader.ts +245 -0
- package/src/material/enhancer/tileComposite/tileCompositeBaseEnhancer/types.ts +43 -0
- package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/index.ts +29 -0
- package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/mutates.ts +68 -0
- package/src/material/enhancer/tileComposite/tileElevationHeatmapEnhancer/shader.ts +24 -0
- package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/index.ts +29 -0
- package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/mutates.ts +34 -0
- package/src/material/enhancer/tileComposite/tileHillshadeEnhancer/shader.ts +36 -0
- package/src/material/enhancer/tileComposite/tileWaterEnhancer/index.ts +23 -0
- package/src/material/enhancer/tileComposite/tileWaterEnhancer/mutates.ts +23 -0
- package/src/material/enhancer/tileComposite/tileWaterEnhancer/shader.ts +8 -0
- package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/index.ts +23 -0
- package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/mutates.ts +34 -0
- package/src/material/enhancer/tileComposite/tileWatermaskEnhancer/shader.ts +13 -0
- package/src/material/enhancer/tileComposite/types.ts +58 -0
- package/src/material/index.ts +3 -0
- package/src/material/macro/index.ts +1 -0
- package/src/material/macro/tileShader.test.ts +136 -0
- package/src/material/macro/tileShader.ts +287 -0
- package/src/material/overrideMaterialsForMRT.ts +439 -0
- package/src/material/selectiveEffectMaterialSetup.test.ts +52 -0
- package/src/material/selectiveEffectMaterialSetup.ts +53 -0
- package/src/material/types/index.ts +1 -0
- package/src/material/types/uniform.ts +1 -0
- package/src/mesh/DrapedMesh.ts +101 -0
- package/src/mesh/batchTexture.test.ts +646 -0
- package/src/mesh/batchTexture.ts +437 -0
- package/src/mesh/batchedFeature.ts +186 -0
- package/src/mesh/batchedSdfText.ts +484 -0
- package/src/mesh/featureMesh.ts +43 -0
- package/src/mesh/index.ts +16 -0
- package/src/mesh/instanced.ts +117 -0
- package/src/mesh/model.ts +483 -0
- package/src/mesh/pickableMesh.ts +55 -0
- package/src/mesh/pickableMeshWrapper.ts +529 -0
- package/src/mesh/polygon.ts +699 -0
- package/src/mesh/polygonOutline.ts +393 -0
- package/src/mesh/polyline.ts +523 -0
- package/src/mesh/releaseGeometryArrays.ts +57 -0
- package/src/mesh/rtcRteHelper.ts +211 -0
- package/src/mesh/sdfText.test.ts +200 -0
- package/src/mesh/sdfText.ts +1019 -0
- package/src/mesh/sprite/billboardAtlas.test.ts +177 -0
- package/src/mesh/sprite/billboardAtlas.ts +210 -0
- package/src/mesh/sprite/billboardAtlasImageLoader.ts +29 -0
- package/src/mesh/sprite/index.ts +1 -0
- package/src/mesh/sprite/instancedSprite.ts +694 -0
- package/src/mesh/tile.test.ts +393 -0
- package/src/mesh/tile.ts +1790 -0
- package/src/object3DEvent.ts +6 -0
- package/src/orchestrators/RenderPassOrchestrator.ts +147 -0
- package/src/orchestrators/index.ts +1 -0
- package/src/passes/AllDepthCopyPass.ts +165 -0
- package/src/passes/CustomRenderPass.ts +279 -0
- package/src/passes/NormalCopyPass.ts +125 -0
- package/src/passes/RenderTargetCopyPass.ts +91 -0
- package/src/passes/SkyEnvMapPass.ts +58 -0
- package/src/passes/index.ts +5 -0
- package/src/pick/pickHelper.ts +273 -0
- package/src/pick/pickTerrain.ts +254 -0
- package/src/plugins/AttributionPlugin/AttributionPlugin.test.ts +210 -0
- package/src/plugins/AttributionPlugin/attribution.test.ts +225 -0
- package/src/plugins/AttributionPlugin/attribution.ts +355 -0
- package/src/plugins/AttributionPlugin/attributionStyles.ts +163 -0
- package/src/plugins/AttributionPlugin/index.ts +702 -0
- package/src/plugins/index.ts +1 -0
- package/src/quality.ts +1 -0
- package/src/renderOrder.ts +3 -0
- package/src/scene.test.ts +164 -0
- package/src/scene.ts +127 -0
- package/src/shaders/index.ts +1 -0
- package/src/solar.test.ts +81 -0
- package/src/solar.ts +101 -0
- package/src/source.ts +80 -0
- package/src/stats.ts +102 -0
- package/src/tasks/computeVertexNormalsAsync.ts +15 -0
- package/src/tasks/constructPolygonBatchedFeature.ts +34 -0
- package/src/tasks/constructPolylineBatchedFeature.ts +27 -0
- package/src/tasks/constructQuantizedMeshTerrainMesh.ts +24 -0
- package/src/tasks/constructTerrainMesh.ts +25 -0
- package/src/tasks/getImageDataFromBlob.ts +13 -0
- package/src/tasks/getImageDataFromImageBitmap.ts +12 -0
- package/src/tasks/parseMvtTile.ts +22 -0
- package/src/tasks/queueTask.ts +19 -0
- package/src/tasks/toCreasedNormalsAsync.ts +18 -0
- package/src/tasks/upsampleQuantizedMeshTerrainMesh.ts +44 -0
- package/src/tasks/upsampleTerrainMesh.ts +39 -0
- package/src/texture.ts +19 -0
- package/src/textures.ts +13 -0
- package/src/tileTexture/CompositeShader.snapshot.test.ts +59 -0
- package/src/tileTexture/CompositeShader.test.ts +168 -0
- package/src/tileTexture/CompositeShader.ts +53 -0
- package/src/tileTexture/SlotPlanner.test.ts +105 -0
- package/src/tileTexture/SlotPlanner.ts +102 -0
- package/src/tileTexture/TileTextureCache.test.ts +227 -0
- package/src/tileTexture/TileTextureCache.ts +192 -0
- package/src/tileTexture/TileTextureCompositor.test.ts +260 -0
- package/src/tileTexture/TileTextureCompositor.ts +421 -0
- package/src/tileTexture/__snapshots__/CompositeShader.snapshot.test.ts.snap +2649 -0
- package/src/tileTexture/index.ts +8 -0
- package/src/tileTexture/types.ts +150 -0
- package/src/type/index.ts +261 -0
- package/src/uniforms.ts +20 -0
- package/src/utils/arraysEqual.ts +9 -0
- package/src/utils/blendModes.ts +80 -0
- package/src/utils/geometry.ts +53 -0
- package/src/utils/hillshadeNormalMapGenerator.ts +193 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/object.test.ts +14 -0
- package/src/utils/object.ts +7 -0
- package/src/utils/replacer.test.ts +42 -0
- package/src/utils/replacer.ts +89 -0
- package/src/utils/screen.ts +29 -0
- package/src/utils/textureFragmentIndex.test.ts +224 -0
- package/src/utils/textureFragmentIndex.ts +97 -0
- package/src/utils/worker.ts +3 -0
- package/src/vite-env.d.ts +2 -0
- package/src/worker/index.ts +13 -0
- package/src/worker/tasks/computeVertexNormals.ts +15 -0
- package/src/worker/tasks/toCreasedNormals.ts +22 -0
|
@@ -0,0 +1,2649 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`generateCompositeFragmentShader snapshots > all features on, mixed raster+vector 1`] = `
|
|
4
|
+
"
|
|
5
|
+
precision highp float;
|
|
6
|
+
precision highp int;
|
|
7
|
+
|
|
8
|
+
#include <common>
|
|
9
|
+
|
|
10
|
+
varying vec2 vUv;
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
uniform int uShows[4];
|
|
15
|
+
uniform vec3 uColors[4];
|
|
16
|
+
uniform float uOpacities[4];
|
|
17
|
+
uniform sampler2D uTextures[4];
|
|
18
|
+
uniform vec2 uLayerUvOffset[4];
|
|
19
|
+
uniform vec2 uLayerUvScale[4];
|
|
20
|
+
uniform int uReproject[4];
|
|
21
|
+
uniform vec2 uReprojectTerrainLat[4];
|
|
22
|
+
// CPU-precomputed per-slot reprojection constants (see mutates.ts):
|
|
23
|
+
// .xy = (mRs, mDen) — source tile's Mercator band start + span,
|
|
24
|
+
// .z = clamp-to-top-edge flag, .w = clamp-to-bottom-edge flag (polar cap).
|
|
25
|
+
uniform vec4 uReprojectMerc[4];
|
|
26
|
+
uniform bool uIsHillshades[4];
|
|
27
|
+
uniform float uHillshadeExaggeration;
|
|
28
|
+
uniform bool uIsElevationHeatmaps[4];
|
|
29
|
+
uniform bool uWaters[4];
|
|
30
|
+
|
|
31
|
+
uniform sampler2D uWatermask;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
#if USE_ELEVATION_HEATMAP
|
|
35
|
+
float nvr_branchFreeTernary(bool comparison, float a, float b) {
|
|
36
|
+
float useA = float(comparison);
|
|
37
|
+
return a * useA + b * (1.0 - useA);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Branchless ternary operator to be used when it's inexpensive to explicitly
|
|
42
|
+
* evaluate both possibilities for a vec2 expression.
|
|
43
|
+
*
|
|
44
|
+
* @name nvr_branchFreeTernary
|
|
45
|
+
* @glslFunction
|
|
46
|
+
*
|
|
47
|
+
* @param {bool} comparison A comparison statement
|
|
48
|
+
* @param {vec2} a Value to return if the comparison is true.
|
|
49
|
+
* @param {vec2} b Value to return if the comparison is false.
|
|
50
|
+
*
|
|
51
|
+
* @returns {vec2} equivalent of comparison ? a : b
|
|
52
|
+
*/
|
|
53
|
+
vec2 nvr_branchFreeTernary(bool comparison, vec2 a, vec2 b) {
|
|
54
|
+
float useA = float(comparison);
|
|
55
|
+
return a * useA + b * (1.0 - useA);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Branchless ternary operator to be used when it's inexpensive to explicitly
|
|
60
|
+
* evaluate both possibilities for a vec3 expression.
|
|
61
|
+
*
|
|
62
|
+
* @name nvr_branchFreeTernary
|
|
63
|
+
* @glslFunction
|
|
64
|
+
*
|
|
65
|
+
* @param {bool} comparison A comparison statement
|
|
66
|
+
* @param {vec3} a Value to return if the comparison is true.
|
|
67
|
+
* @param {vec3} b Value to return if the comparison is false.
|
|
68
|
+
*
|
|
69
|
+
* @returns {vec3} equivalent of comparison ? a : b
|
|
70
|
+
*/
|
|
71
|
+
vec3 nvr_branchFreeTernary(bool comparison, vec3 a, vec3 b) {
|
|
72
|
+
float useA = float(comparison);
|
|
73
|
+
return a * useA + b * (1.0 - useA);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Branchless ternary operator to be used when it's inexpensive to explicitly
|
|
78
|
+
* evaluate both possibilities for a vec4 expression.
|
|
79
|
+
*
|
|
80
|
+
* @name nvr_branchFreeTernary
|
|
81
|
+
* @glslFunction
|
|
82
|
+
*
|
|
83
|
+
* @param {bool} comparison A comparison statement
|
|
84
|
+
* @param {vec3} a Value to return if the comparison is true.
|
|
85
|
+
* @param {vec3} b Value to return if the comparison is false.
|
|
86
|
+
*
|
|
87
|
+
* @returns {vec3} equivalent of comparison ? a : b
|
|
88
|
+
*/
|
|
89
|
+
vec4 nvr_branchFreeTernary(bool comparison, vec4 a, vec4 b) {
|
|
90
|
+
float useA = float(comparison);
|
|
91
|
+
return a * useA + b * (1.0 - useA);
|
|
92
|
+
}
|
|
93
|
+
#ifndef DEM_UTIL_GLSL
|
|
94
|
+
#define DEM_UTIL_GLSL
|
|
95
|
+
|
|
96
|
+
bool isPowerOfTwo(int n) {
|
|
97
|
+
return n > 0 && ((n & (n - 1)) == 0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const float invalidHeight = -999999.0;
|
|
101
|
+
bool isValidHeight(float h) {
|
|
102
|
+
return h > -999998.0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
struct DEMBilinearData {
|
|
106
|
+
ivec2 p00, p10, p01, p11;
|
|
107
|
+
vec2 frac;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
DEMBilinearData prepareDEMBilinear(ivec2 actualTexSize, vec2 uv) {
|
|
111
|
+
vec2 paddedTexSize = vec2(actualTexSize);
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
bool hasPadding = !isPowerOfTwo(actualTexSize.x);
|
|
117
|
+
|
|
118
|
+
vec2 contentSize = hasPadding ? paddedTexSize - vec2(2.0) : paddedTexSize;
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
vec2 pixelCoord = hasPadding
|
|
129
|
+
? uv * (contentSize - vec2(1.0)) + vec2(1.0)
|
|
130
|
+
: uv * (contentSize - vec2(1.0));
|
|
131
|
+
|
|
132
|
+
vec2 pixelFloor = floor(pixelCoord);
|
|
133
|
+
vec2 pixelFrac = fract(pixelCoord);
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
ivec2 maxCoord = ivec2(paddedTexSize) - ivec2(1);
|
|
138
|
+
|
|
139
|
+
DEMBilinearData data;
|
|
140
|
+
data.p00 = clamp(ivec2(pixelFloor), ivec2(0), maxCoord);
|
|
141
|
+
data.p10 = clamp(ivec2(pixelFloor + vec2(1.0, 0.0)), ivec2(0), maxCoord);
|
|
142
|
+
data.p01 = clamp(ivec2(pixelFloor + vec2(0.0, 1.0)), ivec2(0), maxCoord);
|
|
143
|
+
data.p11 = clamp(ivec2(pixelFloor + vec2(1.0, 1.0)), ivec2(0), maxCoord);
|
|
144
|
+
data.frac = pixelFrac;
|
|
145
|
+
|
|
146
|
+
return data;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
float decodeDEMHeight(vec4 color, vec3 rgbScaler, float boundary, float minOffset, float maxOffset, float epsilon, float offset) {
|
|
150
|
+
vec3 rgb = color.rgb * 255.0;
|
|
151
|
+
float x = dot(rgb, rgbScaler);
|
|
152
|
+
|
|
153
|
+
float h;
|
|
154
|
+
float epsilon_cmp = 1.0;
|
|
155
|
+
if (abs(x - boundary) > epsilon_cmp) {
|
|
156
|
+
if (x > boundary) {
|
|
157
|
+
h = x + maxOffset;
|
|
158
|
+
} else {
|
|
159
|
+
h = x + minOffset;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
h = h * epsilon + offset;
|
|
163
|
+
} else {
|
|
164
|
+
h = invalidHeight;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return h;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
float interpolateDEMHeights(float h00, float h10, float h01, float h11, vec2 frac) {
|
|
171
|
+
|
|
172
|
+
if (!isValidHeight(h00)) return invalidHeight;
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
float maxReasonableDiff = 1000.0;
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if (isValidHeight(h10) && abs(h10 - h00) > maxReasonableDiff) h10 = h00;
|
|
180
|
+
if (isValidHeight(h01) && abs(h01 - h00) > maxReasonableDiff) h01 = h00;
|
|
181
|
+
if (isValidHeight(h11) && abs(h11 - h00) > maxReasonableDiff) h11 = h00;
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
if (!isValidHeight(h10)) h10 = h00;
|
|
185
|
+
if (!isValidHeight(h01)) h01 = h00;
|
|
186
|
+
if (!isValidHeight(h11)) h11 = h00;
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
float h0 = mix(h00, h10, frac.x);
|
|
190
|
+
float h1 = mix(h01, h11, frac.x);
|
|
191
|
+
return mix(h0, h1, frac.y);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
#endif
|
|
195
|
+
|
|
196
|
+
uniform sampler2D uColorMapTexture;
|
|
197
|
+
uniform vec3 uElevationRGBScaler;
|
|
198
|
+
uniform vec3 uElevationMinMaxHeightAndBoundary;
|
|
199
|
+
uniform vec4 uElevationMinMaxOffsetAndEpsilonAndOffset;
|
|
200
|
+
uniform bool uLogarithmic;
|
|
201
|
+
uniform float uLogBase;
|
|
202
|
+
uniform float uLogBoundary;
|
|
203
|
+
|
|
204
|
+
float pseudoLog(float value) {
|
|
205
|
+
return value > uLogBoundary ? log(value) / uLogBase : value < -uLogBoundary ? -log(-value) / uLogBase : value / uLogBoundary;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
float decodeRawHeightForElevation(vec4 color) {
|
|
210
|
+
return decodeDEMHeight(
|
|
211
|
+
color,
|
|
212
|
+
uElevationRGBScaler,
|
|
213
|
+
uElevationMinMaxHeightAndBoundary.z,
|
|
214
|
+
uElevationMinMaxOffsetAndEpsilonAndOffset.x,
|
|
215
|
+
uElevationMinMaxOffsetAndEpsilonAndOffset.y,
|
|
216
|
+
uElevationMinMaxOffsetAndEpsilonAndOffset.z,
|
|
217
|
+
uElevationMinMaxOffsetAndEpsilonAndOffset.w
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
float normalizeElevationHeight(float h) {
|
|
223
|
+
|
|
224
|
+
float minHeight = nvr_branchFreeTernary(uLogarithmic, pseudoLog(uElevationMinMaxHeightAndBoundary.x), uElevationMinMaxHeightAndBoundary.x);
|
|
225
|
+
float maxHeight = nvr_branchFreeTernary(uLogarithmic, pseudoLog(uElevationMinMaxHeightAndBoundary.y), uElevationMinMaxHeightAndBoundary.y);
|
|
226
|
+
float value = nvr_branchFreeTernary(uLogarithmic, pseudoLog(h), h);
|
|
227
|
+
|
|
228
|
+
return clamp(
|
|
229
|
+
(value - minHeight) / (maxHeight - minHeight),
|
|
230
|
+
0.0,
|
|
231
|
+
1.0
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
float sampleElevationBilinear(sampler2D demTexture, vec2 uv, ivec2 texSize) {
|
|
238
|
+
|
|
239
|
+
DEMBilinearData data = prepareDEMBilinear(texSize, uv);
|
|
240
|
+
|
|
241
|
+
float h = invalidHeight;
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
float h00 = decodeRawHeightForElevation(texelFetch(demTexture, data.p00, 0));
|
|
245
|
+
if(isValidHeight(h00)) {
|
|
246
|
+
float h10 = decodeRawHeightForElevation(texelFetch(demTexture, data.p10, 0));
|
|
247
|
+
float h01 = decodeRawHeightForElevation(texelFetch(demTexture, data.p01, 0));
|
|
248
|
+
float h11 = decodeRawHeightForElevation(texelFetch(demTexture, data.p11, 0));
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
h = interpolateDEMHeights(h00, h10, h01, h11, data.frac);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!isValidHeight(h)) {
|
|
255
|
+
return 0.0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
return normalizeElevationHeight(h);
|
|
260
|
+
}
|
|
261
|
+
#endif
|
|
262
|
+
|
|
263
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
264
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
265
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
266
|
+
|
|
267
|
+
void main() {
|
|
268
|
+
vec4 compositedColor = vec4(0.0);
|
|
269
|
+
int winningSlot = -1;
|
|
270
|
+
float isWater = 0.0;
|
|
271
|
+
float isTexturized = 0.0;
|
|
272
|
+
|
|
273
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
274
|
+
bool hasHillshadeNormal = false;
|
|
275
|
+
|
|
276
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
277
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
278
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
279
|
+
float gReprojMLat = 0.0;
|
|
280
|
+
bool gReprojMLatReady = false;
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
// ---- compact slot 0 (absolute slot 0, raster) ----
|
|
284
|
+
if (uShows[0] == 1) {
|
|
285
|
+
vec2 texUv0 = vUv * uLayerUvScale[0] + uLayerUvOffset[0];
|
|
286
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
287
|
+
float inBounds0 = 1.0;
|
|
288
|
+
|
|
289
|
+
if (uReproject[0] == 1) {
|
|
290
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
291
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
292
|
+
//
|
|
293
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
294
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
295
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
296
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
297
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
298
|
+
if (!gReprojMLatReady) {
|
|
299
|
+
float lat = uReprojectTerrainLat[0].x
|
|
300
|
+
+ vUv.y * (uReprojectTerrainLat[0].y - uReprojectTerrainLat[0].x);
|
|
301
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
302
|
+
gReprojMLatReady = true;
|
|
303
|
+
}
|
|
304
|
+
float mRs = uReprojectMerc[0].x;
|
|
305
|
+
float mDen = uReprojectMerc[0].y;
|
|
306
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
307
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
308
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
309
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
310
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
311
|
+
if (abs(mDen) > 1e-3) {
|
|
312
|
+
texUv0.y = (gReprojMLat - mRs) / mDen;
|
|
313
|
+
}
|
|
314
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
315
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
316
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
317
|
+
// that edge texel so the last available source row stretches across the cap
|
|
318
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
319
|
+
// the normal drop.
|
|
320
|
+
if (uReprojectMerc[0].z > 0.5) texUv0.y = min(texUv0.y, 1.0);
|
|
321
|
+
if (uReprojectMerc[0].w > 0.5) texUv0.y = max(texUv0.y, 0.0);
|
|
322
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
323
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
324
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
325
|
+
inBounds0 = step(0.0, texUv0.x) * step(texUv0.x, 1.0)
|
|
326
|
+
* step(0.0, texUv0.y) * step(texUv0.y, 1.0);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
vec4 texColor0;
|
|
330
|
+
if (uIsElevationHeatmaps[0]) {
|
|
331
|
+
ivec2 demTexSize = textureSize(uTextures[0], 0);
|
|
332
|
+
float normalized_h = sampleElevationBilinear(uTextures[0], texUv0, demTexSize);
|
|
333
|
+
texColor0 = vec4(texture2D(uColorMapTexture, vec2(normalized_h, 0.5)).rgb, 1.0);
|
|
334
|
+
} else {
|
|
335
|
+
texColor0 = texture2D(uTextures[0], texUv0) * vec4(uColors[0], 1.0);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (uIsHillshades[0]) {
|
|
339
|
+
texColor0 = vec4(0.0);
|
|
340
|
+
}
|
|
341
|
+
float alpha0 = texColor0.a * uOpacities[0] * inBounds0;
|
|
342
|
+
if (alpha0 > 0.01) {
|
|
343
|
+
compositedColor = mix(compositedColor, vec4(texColor0.rgb, 1.0), alpha0);
|
|
344
|
+
winningSlot = 0;
|
|
345
|
+
isTexturized = 0.0;
|
|
346
|
+
|
|
347
|
+
isWater = uWaters[0] ? 1.0 : 0.0;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// ---- compact slot 1 (absolute slot 1, raster) ----
|
|
352
|
+
if (uShows[1] == 1) {
|
|
353
|
+
vec2 texUv1 = vUv * uLayerUvScale[1] + uLayerUvOffset[1];
|
|
354
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
355
|
+
float inBounds1 = 1.0;
|
|
356
|
+
|
|
357
|
+
if (uReproject[1] == 1) {
|
|
358
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
359
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
360
|
+
//
|
|
361
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
362
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
363
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
364
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
365
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
366
|
+
if (!gReprojMLatReady) {
|
|
367
|
+
float lat = uReprojectTerrainLat[1].x
|
|
368
|
+
+ vUv.y * (uReprojectTerrainLat[1].y - uReprojectTerrainLat[1].x);
|
|
369
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
370
|
+
gReprojMLatReady = true;
|
|
371
|
+
}
|
|
372
|
+
float mRs = uReprojectMerc[1].x;
|
|
373
|
+
float mDen = uReprojectMerc[1].y;
|
|
374
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
375
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
376
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
377
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
378
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
379
|
+
if (abs(mDen) > 1e-3) {
|
|
380
|
+
texUv1.y = (gReprojMLat - mRs) / mDen;
|
|
381
|
+
}
|
|
382
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
383
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
384
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
385
|
+
// that edge texel so the last available source row stretches across the cap
|
|
386
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
387
|
+
// the normal drop.
|
|
388
|
+
if (uReprojectMerc[1].z > 0.5) texUv1.y = min(texUv1.y, 1.0);
|
|
389
|
+
if (uReprojectMerc[1].w > 0.5) texUv1.y = max(texUv1.y, 0.0);
|
|
390
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
391
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
392
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
393
|
+
inBounds1 = step(0.0, texUv1.x) * step(texUv1.x, 1.0)
|
|
394
|
+
* step(0.0, texUv1.y) * step(texUv1.y, 1.0);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
vec4 texColor1;
|
|
398
|
+
if (uIsElevationHeatmaps[1]) {
|
|
399
|
+
ivec2 demTexSize = textureSize(uTextures[1], 0);
|
|
400
|
+
float normalized_h = sampleElevationBilinear(uTextures[1], texUv1, demTexSize);
|
|
401
|
+
texColor1 = vec4(texture2D(uColorMapTexture, vec2(normalized_h, 0.5)).rgb, 1.0);
|
|
402
|
+
} else {
|
|
403
|
+
texColor1 = texture2D(uTextures[1], texUv1) * vec4(uColors[1], 1.0);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (uIsHillshades[1]) {
|
|
407
|
+
texColor1 = vec4(0.0);
|
|
408
|
+
}
|
|
409
|
+
float alpha1 = texColor1.a * uOpacities[1] * inBounds1;
|
|
410
|
+
if (alpha1 > 0.01) {
|
|
411
|
+
compositedColor = mix(compositedColor, vec4(texColor1.rgb, 1.0), alpha1);
|
|
412
|
+
winningSlot = 1;
|
|
413
|
+
isTexturized = 0.0;
|
|
414
|
+
|
|
415
|
+
isWater = uWaters[1] ? 1.0 : 0.0;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ---- compact slot 2 (absolute slot 2, vector) ----
|
|
420
|
+
if (uShows[2] == 1) {
|
|
421
|
+
vec2 texUv2 = vUv * uLayerUvScale[2] + uLayerUvOffset[2];
|
|
422
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
423
|
+
float inBounds2 = 1.0;
|
|
424
|
+
|
|
425
|
+
if (uReproject[2] == 1) {
|
|
426
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
427
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
428
|
+
//
|
|
429
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
430
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
431
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
432
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
433
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
434
|
+
if (!gReprojMLatReady) {
|
|
435
|
+
float lat = uReprojectTerrainLat[2].x
|
|
436
|
+
+ vUv.y * (uReprojectTerrainLat[2].y - uReprojectTerrainLat[2].x);
|
|
437
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
438
|
+
gReprojMLatReady = true;
|
|
439
|
+
}
|
|
440
|
+
float mRs = uReprojectMerc[2].x;
|
|
441
|
+
float mDen = uReprojectMerc[2].y;
|
|
442
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
443
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
444
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
445
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
446
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
447
|
+
if (abs(mDen) > 1e-3) {
|
|
448
|
+
texUv2.y = (gReprojMLat - mRs) / mDen;
|
|
449
|
+
}
|
|
450
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
451
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
452
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
453
|
+
// that edge texel so the last available source row stretches across the cap
|
|
454
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
455
|
+
// the normal drop.
|
|
456
|
+
if (uReprojectMerc[2].z > 0.5) texUv2.y = min(texUv2.y, 1.0);
|
|
457
|
+
if (uReprojectMerc[2].w > 0.5) texUv2.y = max(texUv2.y, 0.0);
|
|
458
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
459
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
460
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
461
|
+
inBounds2 = step(0.0, texUv2.x) * step(texUv2.x, 1.0)
|
|
462
|
+
* step(0.0, texUv2.y) * step(texUv2.y, 1.0);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
vec4 texColor2;
|
|
466
|
+
if (uIsElevationHeatmaps[2]) {
|
|
467
|
+
ivec2 demTexSize = textureSize(uTextures[2], 0);
|
|
468
|
+
float normalized_h = sampleElevationBilinear(uTextures[2], texUv2, demTexSize);
|
|
469
|
+
texColor2 = vec4(texture2D(uColorMapTexture, vec2(normalized_h, 0.5)).rgb, 1.0);
|
|
470
|
+
} else {
|
|
471
|
+
texColor2 = texture2D(uTextures[2], texUv2) * vec4(uColors[2], 1.0);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (uIsHillshades[2]) {
|
|
475
|
+
texColor2 = vec4(0.0);
|
|
476
|
+
}
|
|
477
|
+
float alpha2 = texColor2.a * uOpacities[2] * inBounds2;
|
|
478
|
+
if (alpha2 > 0.01) {
|
|
479
|
+
compositedColor = mix(compositedColor, vec4(texColor2.rgb, 1.0), alpha2);
|
|
480
|
+
winningSlot = 2;
|
|
481
|
+
isTexturized = 1.0;
|
|
482
|
+
|
|
483
|
+
isWater = uWaters[2] ? 1.0 : 0.0;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ---- compact slot 3 (absolute slot 3, vector) ----
|
|
488
|
+
if (uShows[3] == 1) {
|
|
489
|
+
vec2 texUv3 = vUv * uLayerUvScale[3] + uLayerUvOffset[3];
|
|
490
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
491
|
+
float inBounds3 = 1.0;
|
|
492
|
+
|
|
493
|
+
if (uReproject[3] == 1) {
|
|
494
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
495
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
496
|
+
//
|
|
497
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
498
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
499
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
500
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
501
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
502
|
+
if (!gReprojMLatReady) {
|
|
503
|
+
float lat = uReprojectTerrainLat[3].x
|
|
504
|
+
+ vUv.y * (uReprojectTerrainLat[3].y - uReprojectTerrainLat[3].x);
|
|
505
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
506
|
+
gReprojMLatReady = true;
|
|
507
|
+
}
|
|
508
|
+
float mRs = uReprojectMerc[3].x;
|
|
509
|
+
float mDen = uReprojectMerc[3].y;
|
|
510
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
511
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
512
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
513
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
514
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
515
|
+
if (abs(mDen) > 1e-3) {
|
|
516
|
+
texUv3.y = (gReprojMLat - mRs) / mDen;
|
|
517
|
+
}
|
|
518
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
519
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
520
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
521
|
+
// that edge texel so the last available source row stretches across the cap
|
|
522
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
523
|
+
// the normal drop.
|
|
524
|
+
if (uReprojectMerc[3].z > 0.5) texUv3.y = min(texUv3.y, 1.0);
|
|
525
|
+
if (uReprojectMerc[3].w > 0.5) texUv3.y = max(texUv3.y, 0.0);
|
|
526
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
527
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
528
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
529
|
+
inBounds3 = step(0.0, texUv3.x) * step(texUv3.x, 1.0)
|
|
530
|
+
* step(0.0, texUv3.y) * step(texUv3.y, 1.0);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
vec4 texColor3;
|
|
534
|
+
if (uIsElevationHeatmaps[3]) {
|
|
535
|
+
ivec2 demTexSize = textureSize(uTextures[3], 0);
|
|
536
|
+
float normalized_h = sampleElevationBilinear(uTextures[3], texUv3, demTexSize);
|
|
537
|
+
texColor3 = vec4(texture2D(uColorMapTexture, vec2(normalized_h, 0.5)).rgb, 1.0);
|
|
538
|
+
} else {
|
|
539
|
+
texColor3 = texture2D(uTextures[3], texUv3) * vec4(uColors[3], 1.0);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (uIsHillshades[3]) {
|
|
543
|
+
texColor3 = vec4(0.0);
|
|
544
|
+
}
|
|
545
|
+
float alpha3 = texColor3.a * uOpacities[3] * inBounds3;
|
|
546
|
+
if (alpha3 > 0.01) {
|
|
547
|
+
compositedColor = mix(compositedColor, vec4(texColor3.rgb, 1.0), alpha3);
|
|
548
|
+
winningSlot = 3;
|
|
549
|
+
isTexturized = 1.0;
|
|
550
|
+
|
|
551
|
+
isWater = uWaters[3] ? 1.0 : 0.0;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
if (uIsHillshades[0]) {
|
|
557
|
+
ivec2 normalMapSize0 = textureSize(uTextures[0], 0);
|
|
558
|
+
if (normalMapSize0.x > 1 && normalMapSize0.y > 1) {
|
|
559
|
+
vec2 hillshadeUv0 = clamp(vUv * uLayerUvScale[0] + uLayerUvOffset[0], vec2(0.0), vec2(1.0));
|
|
560
|
+
vec3 n0 = texture2D(uTextures[0], hillshadeUv0).rgb * 2.0 - 1.0;
|
|
561
|
+
n0 = normalize(n0);
|
|
562
|
+
n0.xy *= uHillshadeExaggeration;
|
|
563
|
+
n0 = normalize(n0);
|
|
564
|
+
hillshadeNormal = n0 * 0.5 + 0.5; // store in [0,1]
|
|
565
|
+
hasHillshadeNormal = true;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if (uIsHillshades[1]) {
|
|
570
|
+
ivec2 normalMapSize1 = textureSize(uTextures[1], 0);
|
|
571
|
+
if (normalMapSize1.x > 1 && normalMapSize1.y > 1) {
|
|
572
|
+
vec2 hillshadeUv1 = clamp(vUv * uLayerUvScale[1] + uLayerUvOffset[1], vec2(0.0), vec2(1.0));
|
|
573
|
+
vec3 n1 = texture2D(uTextures[1], hillshadeUv1).rgb * 2.0 - 1.0;
|
|
574
|
+
n1 = normalize(n1);
|
|
575
|
+
n1.xy *= uHillshadeExaggeration;
|
|
576
|
+
n1 = normalize(n1);
|
|
577
|
+
hillshadeNormal = n1 * 0.5 + 0.5; // store in [0,1]
|
|
578
|
+
hasHillshadeNormal = true;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
if (uIsHillshades[2]) {
|
|
583
|
+
ivec2 normalMapSize2 = textureSize(uTextures[2], 0);
|
|
584
|
+
if (normalMapSize2.x > 1 && normalMapSize2.y > 1) {
|
|
585
|
+
vec2 hillshadeUv2 = clamp(vUv * uLayerUvScale[2] + uLayerUvOffset[2], vec2(0.0), vec2(1.0));
|
|
586
|
+
vec3 n2 = texture2D(uTextures[2], hillshadeUv2).rgb * 2.0 - 1.0;
|
|
587
|
+
n2 = normalize(n2);
|
|
588
|
+
n2.xy *= uHillshadeExaggeration;
|
|
589
|
+
n2 = normalize(n2);
|
|
590
|
+
hillshadeNormal = n2 * 0.5 + 0.5; // store in [0,1]
|
|
591
|
+
hasHillshadeNormal = true;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (uIsHillshades[3]) {
|
|
596
|
+
ivec2 normalMapSize3 = textureSize(uTextures[3], 0);
|
|
597
|
+
if (normalMapSize3.x > 1 && normalMapSize3.y > 1) {
|
|
598
|
+
vec2 hillshadeUv3 = clamp(vUv * uLayerUvScale[3] + uLayerUvOffset[3], vec2(0.0), vec2(1.0));
|
|
599
|
+
vec3 n3 = texture2D(uTextures[3], hillshadeUv3).rgb * 2.0 - 1.0;
|
|
600
|
+
n3 = normalize(n3);
|
|
601
|
+
n3.xy *= uHillshadeExaggeration;
|
|
602
|
+
n3 = normalize(n3);
|
|
603
|
+
hillshadeNormal = n3 * 0.5 + 0.5; // store in [0,1]
|
|
604
|
+
hasHillshadeNormal = true;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Quantized-mesh watermask: 0 = land, 255 = water (per spec). Takes priority
|
|
609
|
+
// over per-slot uWaters — even if no winning slot flagged water, watermask
|
|
610
|
+
// can still mark this pixel as water. Threshold at 0.5 (byte > 127).
|
|
611
|
+
isWater = max(isWater, step(0.5, texture(uWatermask, vUv).r));
|
|
612
|
+
|
|
613
|
+
colorBuffer = compositedColor;
|
|
614
|
+
|
|
615
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
616
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
617
|
+
attrBuffer = vec4(
|
|
618
|
+
isWater,
|
|
619
|
+
isTexturized,
|
|
620
|
+
0.0,
|
|
621
|
+
float(winningSlot + 1) / 255.0
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
625
|
+
}
|
|
626
|
+
"
|
|
627
|
+
`;
|
|
628
|
+
|
|
629
|
+
exports[`generateCompositeFragmentShader snapshots > elevation heatmap only 1`] = `
|
|
630
|
+
"
|
|
631
|
+
precision highp float;
|
|
632
|
+
precision highp int;
|
|
633
|
+
|
|
634
|
+
#include <common>
|
|
635
|
+
|
|
636
|
+
varying vec2 vUv;
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
uniform int uShows[2];
|
|
641
|
+
uniform vec3 uColors[2];
|
|
642
|
+
uniform float uOpacities[2];
|
|
643
|
+
uniform sampler2D uTextures[2];
|
|
644
|
+
uniform vec2 uLayerUvOffset[2];
|
|
645
|
+
uniform vec2 uLayerUvScale[2];
|
|
646
|
+
uniform int uReproject[2];
|
|
647
|
+
uniform vec2 uReprojectTerrainLat[2];
|
|
648
|
+
// CPU-precomputed per-slot reprojection constants (see mutates.ts):
|
|
649
|
+
// .xy = (mRs, mDen) — source tile's Mercator band start + span,
|
|
650
|
+
// .z = clamp-to-top-edge flag, .w = clamp-to-bottom-edge flag (polar cap).
|
|
651
|
+
uniform vec4 uReprojectMerc[2];
|
|
652
|
+
uniform bool uIsElevationHeatmaps[2];
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
#if USE_ELEVATION_HEATMAP
|
|
658
|
+
float nvr_branchFreeTernary(bool comparison, float a, float b) {
|
|
659
|
+
float useA = float(comparison);
|
|
660
|
+
return a * useA + b * (1.0 - useA);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Branchless ternary operator to be used when it's inexpensive to explicitly
|
|
665
|
+
* evaluate both possibilities for a vec2 expression.
|
|
666
|
+
*
|
|
667
|
+
* @name nvr_branchFreeTernary
|
|
668
|
+
* @glslFunction
|
|
669
|
+
*
|
|
670
|
+
* @param {bool} comparison A comparison statement
|
|
671
|
+
* @param {vec2} a Value to return if the comparison is true.
|
|
672
|
+
* @param {vec2} b Value to return if the comparison is false.
|
|
673
|
+
*
|
|
674
|
+
* @returns {vec2} equivalent of comparison ? a : b
|
|
675
|
+
*/
|
|
676
|
+
vec2 nvr_branchFreeTernary(bool comparison, vec2 a, vec2 b) {
|
|
677
|
+
float useA = float(comparison);
|
|
678
|
+
return a * useA + b * (1.0 - useA);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Branchless ternary operator to be used when it's inexpensive to explicitly
|
|
683
|
+
* evaluate both possibilities for a vec3 expression.
|
|
684
|
+
*
|
|
685
|
+
* @name nvr_branchFreeTernary
|
|
686
|
+
* @glslFunction
|
|
687
|
+
*
|
|
688
|
+
* @param {bool} comparison A comparison statement
|
|
689
|
+
* @param {vec3} a Value to return if the comparison is true.
|
|
690
|
+
* @param {vec3} b Value to return if the comparison is false.
|
|
691
|
+
*
|
|
692
|
+
* @returns {vec3} equivalent of comparison ? a : b
|
|
693
|
+
*/
|
|
694
|
+
vec3 nvr_branchFreeTernary(bool comparison, vec3 a, vec3 b) {
|
|
695
|
+
float useA = float(comparison);
|
|
696
|
+
return a * useA + b * (1.0 - useA);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Branchless ternary operator to be used when it's inexpensive to explicitly
|
|
701
|
+
* evaluate both possibilities for a vec4 expression.
|
|
702
|
+
*
|
|
703
|
+
* @name nvr_branchFreeTernary
|
|
704
|
+
* @glslFunction
|
|
705
|
+
*
|
|
706
|
+
* @param {bool} comparison A comparison statement
|
|
707
|
+
* @param {vec3} a Value to return if the comparison is true.
|
|
708
|
+
* @param {vec3} b Value to return if the comparison is false.
|
|
709
|
+
*
|
|
710
|
+
* @returns {vec3} equivalent of comparison ? a : b
|
|
711
|
+
*/
|
|
712
|
+
vec4 nvr_branchFreeTernary(bool comparison, vec4 a, vec4 b) {
|
|
713
|
+
float useA = float(comparison);
|
|
714
|
+
return a * useA + b * (1.0 - useA);
|
|
715
|
+
}
|
|
716
|
+
#ifndef DEM_UTIL_GLSL
|
|
717
|
+
#define DEM_UTIL_GLSL
|
|
718
|
+
|
|
719
|
+
bool isPowerOfTwo(int n) {
|
|
720
|
+
return n > 0 && ((n & (n - 1)) == 0);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const float invalidHeight = -999999.0;
|
|
724
|
+
bool isValidHeight(float h) {
|
|
725
|
+
return h > -999998.0;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
struct DEMBilinearData {
|
|
729
|
+
ivec2 p00, p10, p01, p11;
|
|
730
|
+
vec2 frac;
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
DEMBilinearData prepareDEMBilinear(ivec2 actualTexSize, vec2 uv) {
|
|
734
|
+
vec2 paddedTexSize = vec2(actualTexSize);
|
|
735
|
+
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
bool hasPadding = !isPowerOfTwo(actualTexSize.x);
|
|
740
|
+
|
|
741
|
+
vec2 contentSize = hasPadding ? paddedTexSize - vec2(2.0) : paddedTexSize;
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
vec2 pixelCoord = hasPadding
|
|
752
|
+
? uv * (contentSize - vec2(1.0)) + vec2(1.0)
|
|
753
|
+
: uv * (contentSize - vec2(1.0));
|
|
754
|
+
|
|
755
|
+
vec2 pixelFloor = floor(pixelCoord);
|
|
756
|
+
vec2 pixelFrac = fract(pixelCoord);
|
|
757
|
+
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
ivec2 maxCoord = ivec2(paddedTexSize) - ivec2(1);
|
|
761
|
+
|
|
762
|
+
DEMBilinearData data;
|
|
763
|
+
data.p00 = clamp(ivec2(pixelFloor), ivec2(0), maxCoord);
|
|
764
|
+
data.p10 = clamp(ivec2(pixelFloor + vec2(1.0, 0.0)), ivec2(0), maxCoord);
|
|
765
|
+
data.p01 = clamp(ivec2(pixelFloor + vec2(0.0, 1.0)), ivec2(0), maxCoord);
|
|
766
|
+
data.p11 = clamp(ivec2(pixelFloor + vec2(1.0, 1.0)), ivec2(0), maxCoord);
|
|
767
|
+
data.frac = pixelFrac;
|
|
768
|
+
|
|
769
|
+
return data;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
float decodeDEMHeight(vec4 color, vec3 rgbScaler, float boundary, float minOffset, float maxOffset, float epsilon, float offset) {
|
|
773
|
+
vec3 rgb = color.rgb * 255.0;
|
|
774
|
+
float x = dot(rgb, rgbScaler);
|
|
775
|
+
|
|
776
|
+
float h;
|
|
777
|
+
float epsilon_cmp = 1.0;
|
|
778
|
+
if (abs(x - boundary) > epsilon_cmp) {
|
|
779
|
+
if (x > boundary) {
|
|
780
|
+
h = x + maxOffset;
|
|
781
|
+
} else {
|
|
782
|
+
h = x + minOffset;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
h = h * epsilon + offset;
|
|
786
|
+
} else {
|
|
787
|
+
h = invalidHeight;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
return h;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
float interpolateDEMHeights(float h00, float h10, float h01, float h11, vec2 frac) {
|
|
794
|
+
|
|
795
|
+
if (!isValidHeight(h00)) return invalidHeight;
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
float maxReasonableDiff = 1000.0;
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
if (isValidHeight(h10) && abs(h10 - h00) > maxReasonableDiff) h10 = h00;
|
|
803
|
+
if (isValidHeight(h01) && abs(h01 - h00) > maxReasonableDiff) h01 = h00;
|
|
804
|
+
if (isValidHeight(h11) && abs(h11 - h00) > maxReasonableDiff) h11 = h00;
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
if (!isValidHeight(h10)) h10 = h00;
|
|
808
|
+
if (!isValidHeight(h01)) h01 = h00;
|
|
809
|
+
if (!isValidHeight(h11)) h11 = h00;
|
|
810
|
+
|
|
811
|
+
|
|
812
|
+
float h0 = mix(h00, h10, frac.x);
|
|
813
|
+
float h1 = mix(h01, h11, frac.x);
|
|
814
|
+
return mix(h0, h1, frac.y);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
#endif
|
|
818
|
+
|
|
819
|
+
uniform sampler2D uColorMapTexture;
|
|
820
|
+
uniform vec3 uElevationRGBScaler;
|
|
821
|
+
uniform vec3 uElevationMinMaxHeightAndBoundary;
|
|
822
|
+
uniform vec4 uElevationMinMaxOffsetAndEpsilonAndOffset;
|
|
823
|
+
uniform bool uLogarithmic;
|
|
824
|
+
uniform float uLogBase;
|
|
825
|
+
uniform float uLogBoundary;
|
|
826
|
+
|
|
827
|
+
float pseudoLog(float value) {
|
|
828
|
+
return value > uLogBoundary ? log(value) / uLogBase : value < -uLogBoundary ? -log(-value) / uLogBase : value / uLogBoundary;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
float decodeRawHeightForElevation(vec4 color) {
|
|
833
|
+
return decodeDEMHeight(
|
|
834
|
+
color,
|
|
835
|
+
uElevationRGBScaler,
|
|
836
|
+
uElevationMinMaxHeightAndBoundary.z,
|
|
837
|
+
uElevationMinMaxOffsetAndEpsilonAndOffset.x,
|
|
838
|
+
uElevationMinMaxOffsetAndEpsilonAndOffset.y,
|
|
839
|
+
uElevationMinMaxOffsetAndEpsilonAndOffset.z,
|
|
840
|
+
uElevationMinMaxOffsetAndEpsilonAndOffset.w
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
|
|
845
|
+
float normalizeElevationHeight(float h) {
|
|
846
|
+
|
|
847
|
+
float minHeight = nvr_branchFreeTernary(uLogarithmic, pseudoLog(uElevationMinMaxHeightAndBoundary.x), uElevationMinMaxHeightAndBoundary.x);
|
|
848
|
+
float maxHeight = nvr_branchFreeTernary(uLogarithmic, pseudoLog(uElevationMinMaxHeightAndBoundary.y), uElevationMinMaxHeightAndBoundary.y);
|
|
849
|
+
float value = nvr_branchFreeTernary(uLogarithmic, pseudoLog(h), h);
|
|
850
|
+
|
|
851
|
+
return clamp(
|
|
852
|
+
(value - minHeight) / (maxHeight - minHeight),
|
|
853
|
+
0.0,
|
|
854
|
+
1.0
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
float sampleElevationBilinear(sampler2D demTexture, vec2 uv, ivec2 texSize) {
|
|
861
|
+
|
|
862
|
+
DEMBilinearData data = prepareDEMBilinear(texSize, uv);
|
|
863
|
+
|
|
864
|
+
float h = invalidHeight;
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
float h00 = decodeRawHeightForElevation(texelFetch(demTexture, data.p00, 0));
|
|
868
|
+
if(isValidHeight(h00)) {
|
|
869
|
+
float h10 = decodeRawHeightForElevation(texelFetch(demTexture, data.p10, 0));
|
|
870
|
+
float h01 = decodeRawHeightForElevation(texelFetch(demTexture, data.p01, 0));
|
|
871
|
+
float h11 = decodeRawHeightForElevation(texelFetch(demTexture, data.p11, 0));
|
|
872
|
+
|
|
873
|
+
|
|
874
|
+
h = interpolateDEMHeights(h00, h10, h01, h11, data.frac);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (!isValidHeight(h)) {
|
|
878
|
+
return 0.0;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
|
|
882
|
+
return normalizeElevationHeight(h);
|
|
883
|
+
}
|
|
884
|
+
#endif
|
|
885
|
+
|
|
886
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
887
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
888
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
889
|
+
|
|
890
|
+
void main() {
|
|
891
|
+
vec4 compositedColor = vec4(0.0);
|
|
892
|
+
int winningSlot = -1;
|
|
893
|
+
float isWater = 0.0;
|
|
894
|
+
float isTexturized = 0.0;
|
|
895
|
+
|
|
896
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
897
|
+
bool hasHillshadeNormal = false;
|
|
898
|
+
|
|
899
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
900
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
901
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
902
|
+
float gReprojMLat = 0.0;
|
|
903
|
+
bool gReprojMLatReady = false;
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
// ---- compact slot 0 (absolute slot 0, raster) ----
|
|
907
|
+
if (uShows[0] == 1) {
|
|
908
|
+
vec2 texUv0 = vUv * uLayerUvScale[0] + uLayerUvOffset[0];
|
|
909
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
910
|
+
float inBounds0 = 1.0;
|
|
911
|
+
|
|
912
|
+
if (uReproject[0] == 1) {
|
|
913
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
914
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
915
|
+
//
|
|
916
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
917
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
918
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
919
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
920
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
921
|
+
if (!gReprojMLatReady) {
|
|
922
|
+
float lat = uReprojectTerrainLat[0].x
|
|
923
|
+
+ vUv.y * (uReprojectTerrainLat[0].y - uReprojectTerrainLat[0].x);
|
|
924
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
925
|
+
gReprojMLatReady = true;
|
|
926
|
+
}
|
|
927
|
+
float mRs = uReprojectMerc[0].x;
|
|
928
|
+
float mDen = uReprojectMerc[0].y;
|
|
929
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
930
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
931
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
932
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
933
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
934
|
+
if (abs(mDen) > 1e-3) {
|
|
935
|
+
texUv0.y = (gReprojMLat - mRs) / mDen;
|
|
936
|
+
}
|
|
937
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
938
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
939
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
940
|
+
// that edge texel so the last available source row stretches across the cap
|
|
941
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
942
|
+
// the normal drop.
|
|
943
|
+
if (uReprojectMerc[0].z > 0.5) texUv0.y = min(texUv0.y, 1.0);
|
|
944
|
+
if (uReprojectMerc[0].w > 0.5) texUv0.y = max(texUv0.y, 0.0);
|
|
945
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
946
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
947
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
948
|
+
inBounds0 = step(0.0, texUv0.x) * step(texUv0.x, 1.0)
|
|
949
|
+
* step(0.0, texUv0.y) * step(texUv0.y, 1.0);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
vec4 texColor0;
|
|
953
|
+
if (uIsElevationHeatmaps[0]) {
|
|
954
|
+
ivec2 demTexSize = textureSize(uTextures[0], 0);
|
|
955
|
+
float normalized_h = sampleElevationBilinear(uTextures[0], texUv0, demTexSize);
|
|
956
|
+
texColor0 = vec4(texture2D(uColorMapTexture, vec2(normalized_h, 0.5)).rgb, 1.0);
|
|
957
|
+
} else {
|
|
958
|
+
texColor0 = texture2D(uTextures[0], texUv0) * vec4(uColors[0], 1.0);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
float alpha0 = texColor0.a * uOpacities[0] * inBounds0;
|
|
962
|
+
if (alpha0 > 0.01) {
|
|
963
|
+
compositedColor = mix(compositedColor, vec4(texColor0.rgb, 1.0), alpha0);
|
|
964
|
+
winningSlot = 0;
|
|
965
|
+
isTexturized = 0.0;
|
|
966
|
+
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// ---- compact slot 1 (absolute slot 1, raster) ----
|
|
971
|
+
if (uShows[1] == 1) {
|
|
972
|
+
vec2 texUv1 = vUv * uLayerUvScale[1] + uLayerUvOffset[1];
|
|
973
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
974
|
+
float inBounds1 = 1.0;
|
|
975
|
+
|
|
976
|
+
if (uReproject[1] == 1) {
|
|
977
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
978
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
979
|
+
//
|
|
980
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
981
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
982
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
983
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
984
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
985
|
+
if (!gReprojMLatReady) {
|
|
986
|
+
float lat = uReprojectTerrainLat[1].x
|
|
987
|
+
+ vUv.y * (uReprojectTerrainLat[1].y - uReprojectTerrainLat[1].x);
|
|
988
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
989
|
+
gReprojMLatReady = true;
|
|
990
|
+
}
|
|
991
|
+
float mRs = uReprojectMerc[1].x;
|
|
992
|
+
float mDen = uReprojectMerc[1].y;
|
|
993
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
994
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
995
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
996
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
997
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
998
|
+
if (abs(mDen) > 1e-3) {
|
|
999
|
+
texUv1.y = (gReprojMLat - mRs) / mDen;
|
|
1000
|
+
}
|
|
1001
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1002
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1003
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1004
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1005
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1006
|
+
// the normal drop.
|
|
1007
|
+
if (uReprojectMerc[1].z > 0.5) texUv1.y = min(texUv1.y, 1.0);
|
|
1008
|
+
if (uReprojectMerc[1].w > 0.5) texUv1.y = max(texUv1.y, 0.0);
|
|
1009
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1010
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1011
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1012
|
+
inBounds1 = step(0.0, texUv1.x) * step(texUv1.x, 1.0)
|
|
1013
|
+
* step(0.0, texUv1.y) * step(texUv1.y, 1.0);
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
vec4 texColor1;
|
|
1017
|
+
if (uIsElevationHeatmaps[1]) {
|
|
1018
|
+
ivec2 demTexSize = textureSize(uTextures[1], 0);
|
|
1019
|
+
float normalized_h = sampleElevationBilinear(uTextures[1], texUv1, demTexSize);
|
|
1020
|
+
texColor1 = vec4(texture2D(uColorMapTexture, vec2(normalized_h, 0.5)).rgb, 1.0);
|
|
1021
|
+
} else {
|
|
1022
|
+
texColor1 = texture2D(uTextures[1], texUv1) * vec4(uColors[1], 1.0);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
float alpha1 = texColor1.a * uOpacities[1] * inBounds1;
|
|
1026
|
+
if (alpha1 > 0.01) {
|
|
1027
|
+
compositedColor = mix(compositedColor, vec4(texColor1.rgb, 1.0), alpha1);
|
|
1028
|
+
winningSlot = 1;
|
|
1029
|
+
isTexturized = 0.0;
|
|
1030
|
+
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
colorBuffer = compositedColor;
|
|
1037
|
+
|
|
1038
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
1039
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
1040
|
+
attrBuffer = vec4(
|
|
1041
|
+
isWater,
|
|
1042
|
+
isTexturized,
|
|
1043
|
+
0.0,
|
|
1044
|
+
float(winningSlot + 1) / 255.0
|
|
1045
|
+
);
|
|
1046
|
+
|
|
1047
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
1048
|
+
}
|
|
1049
|
+
"
|
|
1050
|
+
`;
|
|
1051
|
+
|
|
1052
|
+
exports[`generateCompositeFragmentShader snapshots > hillshade only 1`] = `
|
|
1053
|
+
"
|
|
1054
|
+
precision highp float;
|
|
1055
|
+
precision highp int;
|
|
1056
|
+
|
|
1057
|
+
#include <common>
|
|
1058
|
+
|
|
1059
|
+
varying vec2 vUv;
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
uniform int uShows[2];
|
|
1064
|
+
uniform vec3 uColors[2];
|
|
1065
|
+
uniform float uOpacities[2];
|
|
1066
|
+
uniform sampler2D uTextures[2];
|
|
1067
|
+
uniform vec2 uLayerUvOffset[2];
|
|
1068
|
+
uniform vec2 uLayerUvScale[2];
|
|
1069
|
+
uniform int uReproject[2];
|
|
1070
|
+
uniform vec2 uReprojectTerrainLat[2];
|
|
1071
|
+
// CPU-precomputed per-slot reprojection constants (see mutates.ts):
|
|
1072
|
+
// .xy = (mRs, mDen) — source tile's Mercator band start + span,
|
|
1073
|
+
// .z = clamp-to-top-edge flag, .w = clamp-to-bottom-edge flag (polar cap).
|
|
1074
|
+
uniform vec4 uReprojectMerc[2];
|
|
1075
|
+
uniform bool uIsHillshades[2];
|
|
1076
|
+
uniform float uHillshadeExaggeration;
|
|
1077
|
+
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
1084
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
1085
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
1086
|
+
|
|
1087
|
+
void main() {
|
|
1088
|
+
vec4 compositedColor = vec4(0.0);
|
|
1089
|
+
int winningSlot = -1;
|
|
1090
|
+
float isWater = 0.0;
|
|
1091
|
+
float isTexturized = 0.0;
|
|
1092
|
+
|
|
1093
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
1094
|
+
bool hasHillshadeNormal = false;
|
|
1095
|
+
|
|
1096
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
1097
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
1098
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
1099
|
+
float gReprojMLat = 0.0;
|
|
1100
|
+
bool gReprojMLatReady = false;
|
|
1101
|
+
|
|
1102
|
+
|
|
1103
|
+
// ---- compact slot 0 (absolute slot 0, raster) ----
|
|
1104
|
+
if (uShows[0] == 1) {
|
|
1105
|
+
vec2 texUv0 = vUv * uLayerUvScale[0] + uLayerUvOffset[0];
|
|
1106
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1107
|
+
float inBounds0 = 1.0;
|
|
1108
|
+
|
|
1109
|
+
if (uReproject[0] == 1) {
|
|
1110
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1111
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1112
|
+
//
|
|
1113
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1114
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1115
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1116
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1117
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1118
|
+
if (!gReprojMLatReady) {
|
|
1119
|
+
float lat = uReprojectTerrainLat[0].x
|
|
1120
|
+
+ vUv.y * (uReprojectTerrainLat[0].y - uReprojectTerrainLat[0].x);
|
|
1121
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1122
|
+
gReprojMLatReady = true;
|
|
1123
|
+
}
|
|
1124
|
+
float mRs = uReprojectMerc[0].x;
|
|
1125
|
+
float mDen = uReprojectMerc[0].y;
|
|
1126
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1127
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1128
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1129
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1130
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1131
|
+
if (abs(mDen) > 1e-3) {
|
|
1132
|
+
texUv0.y = (gReprojMLat - mRs) / mDen;
|
|
1133
|
+
}
|
|
1134
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1135
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1136
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1137
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1138
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1139
|
+
// the normal drop.
|
|
1140
|
+
if (uReprojectMerc[0].z > 0.5) texUv0.y = min(texUv0.y, 1.0);
|
|
1141
|
+
if (uReprojectMerc[0].w > 0.5) texUv0.y = max(texUv0.y, 0.0);
|
|
1142
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1143
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1144
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1145
|
+
inBounds0 = step(0.0, texUv0.x) * step(texUv0.x, 1.0)
|
|
1146
|
+
* step(0.0, texUv0.y) * step(texUv0.y, 1.0);
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
vec4 texColor0 = texture2D(uTextures[0], texUv0) * vec4(uColors[0], 1.0);
|
|
1150
|
+
|
|
1151
|
+
if (uIsHillshades[0]) {
|
|
1152
|
+
texColor0 = vec4(0.0);
|
|
1153
|
+
}
|
|
1154
|
+
float alpha0 = texColor0.a * uOpacities[0] * inBounds0;
|
|
1155
|
+
if (alpha0 > 0.01) {
|
|
1156
|
+
compositedColor = mix(compositedColor, vec4(texColor0.rgb, 1.0), alpha0);
|
|
1157
|
+
winningSlot = 0;
|
|
1158
|
+
isTexturized = 0.0;
|
|
1159
|
+
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// ---- compact slot 1 (absolute slot 1, raster) ----
|
|
1164
|
+
if (uShows[1] == 1) {
|
|
1165
|
+
vec2 texUv1 = vUv * uLayerUvScale[1] + uLayerUvOffset[1];
|
|
1166
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1167
|
+
float inBounds1 = 1.0;
|
|
1168
|
+
|
|
1169
|
+
if (uReproject[1] == 1) {
|
|
1170
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1171
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1172
|
+
//
|
|
1173
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1174
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1175
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1176
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1177
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1178
|
+
if (!gReprojMLatReady) {
|
|
1179
|
+
float lat = uReprojectTerrainLat[1].x
|
|
1180
|
+
+ vUv.y * (uReprojectTerrainLat[1].y - uReprojectTerrainLat[1].x);
|
|
1181
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1182
|
+
gReprojMLatReady = true;
|
|
1183
|
+
}
|
|
1184
|
+
float mRs = uReprojectMerc[1].x;
|
|
1185
|
+
float mDen = uReprojectMerc[1].y;
|
|
1186
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1187
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1188
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1189
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1190
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1191
|
+
if (abs(mDen) > 1e-3) {
|
|
1192
|
+
texUv1.y = (gReprojMLat - mRs) / mDen;
|
|
1193
|
+
}
|
|
1194
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1195
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1196
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1197
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1198
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1199
|
+
// the normal drop.
|
|
1200
|
+
if (uReprojectMerc[1].z > 0.5) texUv1.y = min(texUv1.y, 1.0);
|
|
1201
|
+
if (uReprojectMerc[1].w > 0.5) texUv1.y = max(texUv1.y, 0.0);
|
|
1202
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1203
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1204
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1205
|
+
inBounds1 = step(0.0, texUv1.x) * step(texUv1.x, 1.0)
|
|
1206
|
+
* step(0.0, texUv1.y) * step(texUv1.y, 1.0);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
vec4 texColor1 = texture2D(uTextures[1], texUv1) * vec4(uColors[1], 1.0);
|
|
1210
|
+
|
|
1211
|
+
if (uIsHillshades[1]) {
|
|
1212
|
+
texColor1 = vec4(0.0);
|
|
1213
|
+
}
|
|
1214
|
+
float alpha1 = texColor1.a * uOpacities[1] * inBounds1;
|
|
1215
|
+
if (alpha1 > 0.01) {
|
|
1216
|
+
compositedColor = mix(compositedColor, vec4(texColor1.rgb, 1.0), alpha1);
|
|
1217
|
+
winningSlot = 1;
|
|
1218
|
+
isTexturized = 0.0;
|
|
1219
|
+
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
if (uIsHillshades[0]) {
|
|
1225
|
+
ivec2 normalMapSize0 = textureSize(uTextures[0], 0);
|
|
1226
|
+
if (normalMapSize0.x > 1 && normalMapSize0.y > 1) {
|
|
1227
|
+
vec2 hillshadeUv0 = clamp(vUv * uLayerUvScale[0] + uLayerUvOffset[0], vec2(0.0), vec2(1.0));
|
|
1228
|
+
vec3 n0 = texture2D(uTextures[0], hillshadeUv0).rgb * 2.0 - 1.0;
|
|
1229
|
+
n0 = normalize(n0);
|
|
1230
|
+
n0.xy *= uHillshadeExaggeration;
|
|
1231
|
+
n0 = normalize(n0);
|
|
1232
|
+
hillshadeNormal = n0 * 0.5 + 0.5; // store in [0,1]
|
|
1233
|
+
hasHillshadeNormal = true;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
if (uIsHillshades[1]) {
|
|
1238
|
+
ivec2 normalMapSize1 = textureSize(uTextures[1], 0);
|
|
1239
|
+
if (normalMapSize1.x > 1 && normalMapSize1.y > 1) {
|
|
1240
|
+
vec2 hillshadeUv1 = clamp(vUv * uLayerUvScale[1] + uLayerUvOffset[1], vec2(0.0), vec2(1.0));
|
|
1241
|
+
vec3 n1 = texture2D(uTextures[1], hillshadeUv1).rgb * 2.0 - 1.0;
|
|
1242
|
+
n1 = normalize(n1);
|
|
1243
|
+
n1.xy *= uHillshadeExaggeration;
|
|
1244
|
+
n1 = normalize(n1);
|
|
1245
|
+
hillshadeNormal = n1 * 0.5 + 0.5; // store in [0,1]
|
|
1246
|
+
hasHillshadeNormal = true;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
colorBuffer = compositedColor;
|
|
1251
|
+
|
|
1252
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
1253
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
1254
|
+
attrBuffer = vec4(
|
|
1255
|
+
isWater,
|
|
1256
|
+
isTexturized,
|
|
1257
|
+
0.0,
|
|
1258
|
+
float(winningSlot + 1) / 255.0
|
|
1259
|
+
);
|
|
1260
|
+
|
|
1261
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
1262
|
+
}
|
|
1263
|
+
"
|
|
1264
|
+
`;
|
|
1265
|
+
|
|
1266
|
+
exports[`generateCompositeFragmentShader snapshots > raster+single vector, boundary 24 1`] = `
|
|
1267
|
+
"
|
|
1268
|
+
precision highp float;
|
|
1269
|
+
precision highp int;
|
|
1270
|
+
|
|
1271
|
+
#include <common>
|
|
1272
|
+
|
|
1273
|
+
varying vec2 vUv;
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
uniform int uShows[3];
|
|
1278
|
+
uniform vec3 uColors[3];
|
|
1279
|
+
uniform float uOpacities[3];
|
|
1280
|
+
uniform sampler2D uTextures[3];
|
|
1281
|
+
uniform vec2 uLayerUvOffset[3];
|
|
1282
|
+
uniform vec2 uLayerUvScale[3];
|
|
1283
|
+
uniform int uReproject[3];
|
|
1284
|
+
uniform vec2 uReprojectTerrainLat[3];
|
|
1285
|
+
// CPU-precomputed per-slot reprojection constants (see mutates.ts):
|
|
1286
|
+
// .xy = (mRs, mDen) — source tile's Mercator band start + span,
|
|
1287
|
+
// .z = clamp-to-top-edge flag, .w = clamp-to-bottom-edge flag (polar cap).
|
|
1288
|
+
uniform vec4 uReprojectMerc[3];
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
|
|
1293
|
+
|
|
1294
|
+
|
|
1295
|
+
|
|
1296
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
1297
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
1298
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
1299
|
+
|
|
1300
|
+
void main() {
|
|
1301
|
+
vec4 compositedColor = vec4(0.0);
|
|
1302
|
+
int winningSlot = -1;
|
|
1303
|
+
float isWater = 0.0;
|
|
1304
|
+
float isTexturized = 0.0;
|
|
1305
|
+
|
|
1306
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
1307
|
+
bool hasHillshadeNormal = false;
|
|
1308
|
+
|
|
1309
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
1310
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
1311
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
1312
|
+
float gReprojMLat = 0.0;
|
|
1313
|
+
bool gReprojMLatReady = false;
|
|
1314
|
+
|
|
1315
|
+
|
|
1316
|
+
// ---- compact slot 0 (absolute slot 0, raster) ----
|
|
1317
|
+
if (uShows[0] == 1) {
|
|
1318
|
+
vec2 texUv0 = vUv * uLayerUvScale[0] + uLayerUvOffset[0];
|
|
1319
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1320
|
+
float inBounds0 = 1.0;
|
|
1321
|
+
|
|
1322
|
+
if (uReproject[0] == 1) {
|
|
1323
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1324
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1325
|
+
//
|
|
1326
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1327
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1328
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1329
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1330
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1331
|
+
if (!gReprojMLatReady) {
|
|
1332
|
+
float lat = uReprojectTerrainLat[0].x
|
|
1333
|
+
+ vUv.y * (uReprojectTerrainLat[0].y - uReprojectTerrainLat[0].x);
|
|
1334
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1335
|
+
gReprojMLatReady = true;
|
|
1336
|
+
}
|
|
1337
|
+
float mRs = uReprojectMerc[0].x;
|
|
1338
|
+
float mDen = uReprojectMerc[0].y;
|
|
1339
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1340
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1341
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1342
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1343
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1344
|
+
if (abs(mDen) > 1e-3) {
|
|
1345
|
+
texUv0.y = (gReprojMLat - mRs) / mDen;
|
|
1346
|
+
}
|
|
1347
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1348
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1349
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1350
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1351
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1352
|
+
// the normal drop.
|
|
1353
|
+
if (uReprojectMerc[0].z > 0.5) texUv0.y = min(texUv0.y, 1.0);
|
|
1354
|
+
if (uReprojectMerc[0].w > 0.5) texUv0.y = max(texUv0.y, 0.0);
|
|
1355
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1356
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1357
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1358
|
+
inBounds0 = step(0.0, texUv0.x) * step(texUv0.x, 1.0)
|
|
1359
|
+
* step(0.0, texUv0.y) * step(texUv0.y, 1.0);
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
vec4 texColor0 = texture2D(uTextures[0], texUv0) * vec4(uColors[0], 1.0);
|
|
1363
|
+
|
|
1364
|
+
float alpha0 = texColor0.a * uOpacities[0] * inBounds0;
|
|
1365
|
+
if (alpha0 > 0.01) {
|
|
1366
|
+
compositedColor = mix(compositedColor, vec4(texColor0.rgb, 1.0), alpha0);
|
|
1367
|
+
winningSlot = 0;
|
|
1368
|
+
isTexturized = 0.0;
|
|
1369
|
+
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// ---- compact slot 1 (absolute slot 1, raster) ----
|
|
1374
|
+
if (uShows[1] == 1) {
|
|
1375
|
+
vec2 texUv1 = vUv * uLayerUvScale[1] + uLayerUvOffset[1];
|
|
1376
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1377
|
+
float inBounds1 = 1.0;
|
|
1378
|
+
|
|
1379
|
+
if (uReproject[1] == 1) {
|
|
1380
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1381
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1382
|
+
//
|
|
1383
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1384
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1385
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1386
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1387
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1388
|
+
if (!gReprojMLatReady) {
|
|
1389
|
+
float lat = uReprojectTerrainLat[1].x
|
|
1390
|
+
+ vUv.y * (uReprojectTerrainLat[1].y - uReprojectTerrainLat[1].x);
|
|
1391
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1392
|
+
gReprojMLatReady = true;
|
|
1393
|
+
}
|
|
1394
|
+
float mRs = uReprojectMerc[1].x;
|
|
1395
|
+
float mDen = uReprojectMerc[1].y;
|
|
1396
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1397
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1398
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1399
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1400
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1401
|
+
if (abs(mDen) > 1e-3) {
|
|
1402
|
+
texUv1.y = (gReprojMLat - mRs) / mDen;
|
|
1403
|
+
}
|
|
1404
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1405
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1406
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1407
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1408
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1409
|
+
// the normal drop.
|
|
1410
|
+
if (uReprojectMerc[1].z > 0.5) texUv1.y = min(texUv1.y, 1.0);
|
|
1411
|
+
if (uReprojectMerc[1].w > 0.5) texUv1.y = max(texUv1.y, 0.0);
|
|
1412
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1413
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1414
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1415
|
+
inBounds1 = step(0.0, texUv1.x) * step(texUv1.x, 1.0)
|
|
1416
|
+
* step(0.0, texUv1.y) * step(texUv1.y, 1.0);
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
vec4 texColor1 = texture2D(uTextures[1], texUv1) * vec4(uColors[1], 1.0);
|
|
1420
|
+
|
|
1421
|
+
float alpha1 = texColor1.a * uOpacities[1] * inBounds1;
|
|
1422
|
+
if (alpha1 > 0.01) {
|
|
1423
|
+
compositedColor = mix(compositedColor, vec4(texColor1.rgb, 1.0), alpha1);
|
|
1424
|
+
winningSlot = 1;
|
|
1425
|
+
isTexturized = 0.0;
|
|
1426
|
+
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
// ---- compact slot 2 (absolute slot 24, vector) ----
|
|
1431
|
+
if (uShows[2] == 1) {
|
|
1432
|
+
vec2 texUv2 = vUv * uLayerUvScale[2] + uLayerUvOffset[2];
|
|
1433
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1434
|
+
float inBounds2 = 1.0;
|
|
1435
|
+
|
|
1436
|
+
if (uReproject[2] == 1) {
|
|
1437
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1438
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1439
|
+
//
|
|
1440
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1441
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1442
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1443
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1444
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1445
|
+
if (!gReprojMLatReady) {
|
|
1446
|
+
float lat = uReprojectTerrainLat[2].x
|
|
1447
|
+
+ vUv.y * (uReprojectTerrainLat[2].y - uReprojectTerrainLat[2].x);
|
|
1448
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1449
|
+
gReprojMLatReady = true;
|
|
1450
|
+
}
|
|
1451
|
+
float mRs = uReprojectMerc[2].x;
|
|
1452
|
+
float mDen = uReprojectMerc[2].y;
|
|
1453
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1454
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1455
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1456
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1457
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1458
|
+
if (abs(mDen) > 1e-3) {
|
|
1459
|
+
texUv2.y = (gReprojMLat - mRs) / mDen;
|
|
1460
|
+
}
|
|
1461
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1462
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1463
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1464
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1465
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1466
|
+
// the normal drop.
|
|
1467
|
+
if (uReprojectMerc[2].z > 0.5) texUv2.y = min(texUv2.y, 1.0);
|
|
1468
|
+
if (uReprojectMerc[2].w > 0.5) texUv2.y = max(texUv2.y, 0.0);
|
|
1469
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1470
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1471
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1472
|
+
inBounds2 = step(0.0, texUv2.x) * step(texUv2.x, 1.0)
|
|
1473
|
+
* step(0.0, texUv2.y) * step(texUv2.y, 1.0);
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
vec4 texColor2 = texture2D(uTextures[2], texUv2) * vec4(uColors[2], 1.0);
|
|
1477
|
+
|
|
1478
|
+
float alpha2 = texColor2.a * uOpacities[2] * inBounds2;
|
|
1479
|
+
if (alpha2 > 0.01) {
|
|
1480
|
+
compositedColor = mix(compositedColor, vec4(texColor2.rgb, 1.0), alpha2);
|
|
1481
|
+
winningSlot = 24;
|
|
1482
|
+
isTexturized = 1.0;
|
|
1483
|
+
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
colorBuffer = compositedColor;
|
|
1490
|
+
|
|
1491
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
1492
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
1493
|
+
attrBuffer = vec4(
|
|
1494
|
+
isWater,
|
|
1495
|
+
isTexturized,
|
|
1496
|
+
0.0,
|
|
1497
|
+
float(winningSlot + 1) / 255.0
|
|
1498
|
+
);
|
|
1499
|
+
|
|
1500
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
1501
|
+
}
|
|
1502
|
+
"
|
|
1503
|
+
`;
|
|
1504
|
+
|
|
1505
|
+
exports[`generateCompositeFragmentShader snapshots > raster+vector, all features off 1`] = `
|
|
1506
|
+
"
|
|
1507
|
+
precision highp float;
|
|
1508
|
+
precision highp int;
|
|
1509
|
+
|
|
1510
|
+
#include <common>
|
|
1511
|
+
|
|
1512
|
+
varying vec2 vUv;
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
|
|
1516
|
+
uniform int uShows[4];
|
|
1517
|
+
uniform vec3 uColors[4];
|
|
1518
|
+
uniform float uOpacities[4];
|
|
1519
|
+
uniform sampler2D uTextures[4];
|
|
1520
|
+
uniform vec2 uLayerUvOffset[4];
|
|
1521
|
+
uniform vec2 uLayerUvScale[4];
|
|
1522
|
+
uniform int uReproject[4];
|
|
1523
|
+
uniform vec2 uReprojectTerrainLat[4];
|
|
1524
|
+
// CPU-precomputed per-slot reprojection constants (see mutates.ts):
|
|
1525
|
+
// .xy = (mRs, mDen) — source tile's Mercator band start + span,
|
|
1526
|
+
// .z = clamp-to-top-edge flag, .w = clamp-to-bottom-edge flag (polar cap).
|
|
1527
|
+
uniform vec4 uReprojectMerc[4];
|
|
1528
|
+
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
|
|
1533
|
+
|
|
1534
|
+
|
|
1535
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
1536
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
1537
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
1538
|
+
|
|
1539
|
+
void main() {
|
|
1540
|
+
vec4 compositedColor = vec4(0.0);
|
|
1541
|
+
int winningSlot = -1;
|
|
1542
|
+
float isWater = 0.0;
|
|
1543
|
+
float isTexturized = 0.0;
|
|
1544
|
+
|
|
1545
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
1546
|
+
bool hasHillshadeNormal = false;
|
|
1547
|
+
|
|
1548
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
1549
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
1550
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
1551
|
+
float gReprojMLat = 0.0;
|
|
1552
|
+
bool gReprojMLatReady = false;
|
|
1553
|
+
|
|
1554
|
+
|
|
1555
|
+
// ---- compact slot 0 (absolute slot 0, raster) ----
|
|
1556
|
+
if (uShows[0] == 1) {
|
|
1557
|
+
vec2 texUv0 = vUv * uLayerUvScale[0] + uLayerUvOffset[0];
|
|
1558
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1559
|
+
float inBounds0 = 1.0;
|
|
1560
|
+
|
|
1561
|
+
if (uReproject[0] == 1) {
|
|
1562
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1563
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1564
|
+
//
|
|
1565
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1566
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1567
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1568
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1569
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1570
|
+
if (!gReprojMLatReady) {
|
|
1571
|
+
float lat = uReprojectTerrainLat[0].x
|
|
1572
|
+
+ vUv.y * (uReprojectTerrainLat[0].y - uReprojectTerrainLat[0].x);
|
|
1573
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1574
|
+
gReprojMLatReady = true;
|
|
1575
|
+
}
|
|
1576
|
+
float mRs = uReprojectMerc[0].x;
|
|
1577
|
+
float mDen = uReprojectMerc[0].y;
|
|
1578
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1579
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1580
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1581
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1582
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1583
|
+
if (abs(mDen) > 1e-3) {
|
|
1584
|
+
texUv0.y = (gReprojMLat - mRs) / mDen;
|
|
1585
|
+
}
|
|
1586
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1587
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1588
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1589
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1590
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1591
|
+
// the normal drop.
|
|
1592
|
+
if (uReprojectMerc[0].z > 0.5) texUv0.y = min(texUv0.y, 1.0);
|
|
1593
|
+
if (uReprojectMerc[0].w > 0.5) texUv0.y = max(texUv0.y, 0.0);
|
|
1594
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1595
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1596
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1597
|
+
inBounds0 = step(0.0, texUv0.x) * step(texUv0.x, 1.0)
|
|
1598
|
+
* step(0.0, texUv0.y) * step(texUv0.y, 1.0);
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
vec4 texColor0 = texture2D(uTextures[0], texUv0) * vec4(uColors[0], 1.0);
|
|
1602
|
+
|
|
1603
|
+
float alpha0 = texColor0.a * uOpacities[0] * inBounds0;
|
|
1604
|
+
if (alpha0 > 0.01) {
|
|
1605
|
+
compositedColor = mix(compositedColor, vec4(texColor0.rgb, 1.0), alpha0);
|
|
1606
|
+
winningSlot = 0;
|
|
1607
|
+
isTexturized = 0.0;
|
|
1608
|
+
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// ---- compact slot 1 (absolute slot 1, raster) ----
|
|
1613
|
+
if (uShows[1] == 1) {
|
|
1614
|
+
vec2 texUv1 = vUv * uLayerUvScale[1] + uLayerUvOffset[1];
|
|
1615
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1616
|
+
float inBounds1 = 1.0;
|
|
1617
|
+
|
|
1618
|
+
if (uReproject[1] == 1) {
|
|
1619
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1620
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1621
|
+
//
|
|
1622
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1623
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1624
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1625
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1626
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1627
|
+
if (!gReprojMLatReady) {
|
|
1628
|
+
float lat = uReprojectTerrainLat[1].x
|
|
1629
|
+
+ vUv.y * (uReprojectTerrainLat[1].y - uReprojectTerrainLat[1].x);
|
|
1630
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1631
|
+
gReprojMLatReady = true;
|
|
1632
|
+
}
|
|
1633
|
+
float mRs = uReprojectMerc[1].x;
|
|
1634
|
+
float mDen = uReprojectMerc[1].y;
|
|
1635
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1636
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1637
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1638
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1639
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1640
|
+
if (abs(mDen) > 1e-3) {
|
|
1641
|
+
texUv1.y = (gReprojMLat - mRs) / mDen;
|
|
1642
|
+
}
|
|
1643
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1644
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1645
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1646
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1647
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1648
|
+
// the normal drop.
|
|
1649
|
+
if (uReprojectMerc[1].z > 0.5) texUv1.y = min(texUv1.y, 1.0);
|
|
1650
|
+
if (uReprojectMerc[1].w > 0.5) texUv1.y = max(texUv1.y, 0.0);
|
|
1651
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1652
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1653
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1654
|
+
inBounds1 = step(0.0, texUv1.x) * step(texUv1.x, 1.0)
|
|
1655
|
+
* step(0.0, texUv1.y) * step(texUv1.y, 1.0);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
vec4 texColor1 = texture2D(uTextures[1], texUv1) * vec4(uColors[1], 1.0);
|
|
1659
|
+
|
|
1660
|
+
float alpha1 = texColor1.a * uOpacities[1] * inBounds1;
|
|
1661
|
+
if (alpha1 > 0.01) {
|
|
1662
|
+
compositedColor = mix(compositedColor, vec4(texColor1.rgb, 1.0), alpha1);
|
|
1663
|
+
winningSlot = 1;
|
|
1664
|
+
isTexturized = 0.0;
|
|
1665
|
+
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
// ---- compact slot 2 (absolute slot 2, vector) ----
|
|
1670
|
+
if (uShows[2] == 1) {
|
|
1671
|
+
vec2 texUv2 = vUv * uLayerUvScale[2] + uLayerUvOffset[2];
|
|
1672
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1673
|
+
float inBounds2 = 1.0;
|
|
1674
|
+
|
|
1675
|
+
if (uReproject[2] == 1) {
|
|
1676
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1677
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1678
|
+
//
|
|
1679
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1680
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1681
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1682
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1683
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1684
|
+
if (!gReprojMLatReady) {
|
|
1685
|
+
float lat = uReprojectTerrainLat[2].x
|
|
1686
|
+
+ vUv.y * (uReprojectTerrainLat[2].y - uReprojectTerrainLat[2].x);
|
|
1687
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1688
|
+
gReprojMLatReady = true;
|
|
1689
|
+
}
|
|
1690
|
+
float mRs = uReprojectMerc[2].x;
|
|
1691
|
+
float mDen = uReprojectMerc[2].y;
|
|
1692
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1693
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1694
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1695
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1696
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1697
|
+
if (abs(mDen) > 1e-3) {
|
|
1698
|
+
texUv2.y = (gReprojMLat - mRs) / mDen;
|
|
1699
|
+
}
|
|
1700
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1701
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1702
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1703
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1704
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1705
|
+
// the normal drop.
|
|
1706
|
+
if (uReprojectMerc[2].z > 0.5) texUv2.y = min(texUv2.y, 1.0);
|
|
1707
|
+
if (uReprojectMerc[2].w > 0.5) texUv2.y = max(texUv2.y, 0.0);
|
|
1708
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1709
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1710
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1711
|
+
inBounds2 = step(0.0, texUv2.x) * step(texUv2.x, 1.0)
|
|
1712
|
+
* step(0.0, texUv2.y) * step(texUv2.y, 1.0);
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
vec4 texColor2 = texture2D(uTextures[2], texUv2) * vec4(uColors[2], 1.0);
|
|
1716
|
+
|
|
1717
|
+
float alpha2 = texColor2.a * uOpacities[2] * inBounds2;
|
|
1718
|
+
if (alpha2 > 0.01) {
|
|
1719
|
+
compositedColor = mix(compositedColor, vec4(texColor2.rgb, 1.0), alpha2);
|
|
1720
|
+
winningSlot = 2;
|
|
1721
|
+
isTexturized = 1.0;
|
|
1722
|
+
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// ---- compact slot 3 (absolute slot 3, vector) ----
|
|
1727
|
+
if (uShows[3] == 1) {
|
|
1728
|
+
vec2 texUv3 = vUv * uLayerUvScale[3] + uLayerUvOffset[3];
|
|
1729
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1730
|
+
float inBounds3 = 1.0;
|
|
1731
|
+
|
|
1732
|
+
if (uReproject[3] == 1) {
|
|
1733
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1734
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1735
|
+
//
|
|
1736
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1737
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1738
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1739
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1740
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1741
|
+
if (!gReprojMLatReady) {
|
|
1742
|
+
float lat = uReprojectTerrainLat[3].x
|
|
1743
|
+
+ vUv.y * (uReprojectTerrainLat[3].y - uReprojectTerrainLat[3].x);
|
|
1744
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1745
|
+
gReprojMLatReady = true;
|
|
1746
|
+
}
|
|
1747
|
+
float mRs = uReprojectMerc[3].x;
|
|
1748
|
+
float mDen = uReprojectMerc[3].y;
|
|
1749
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1750
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1751
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1752
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1753
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1754
|
+
if (abs(mDen) > 1e-3) {
|
|
1755
|
+
texUv3.y = (gReprojMLat - mRs) / mDen;
|
|
1756
|
+
}
|
|
1757
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1758
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1759
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1760
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1761
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1762
|
+
// the normal drop.
|
|
1763
|
+
if (uReprojectMerc[3].z > 0.5) texUv3.y = min(texUv3.y, 1.0);
|
|
1764
|
+
if (uReprojectMerc[3].w > 0.5) texUv3.y = max(texUv3.y, 0.0);
|
|
1765
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1766
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1767
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1768
|
+
inBounds3 = step(0.0, texUv3.x) * step(texUv3.x, 1.0)
|
|
1769
|
+
* step(0.0, texUv3.y) * step(texUv3.y, 1.0);
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
vec4 texColor3 = texture2D(uTextures[3], texUv3) * vec4(uColors[3], 1.0);
|
|
1773
|
+
|
|
1774
|
+
float alpha3 = texColor3.a * uOpacities[3] * inBounds3;
|
|
1775
|
+
if (alpha3 > 0.01) {
|
|
1776
|
+
compositedColor = mix(compositedColor, vec4(texColor3.rgb, 1.0), alpha3);
|
|
1777
|
+
winningSlot = 3;
|
|
1778
|
+
isTexturized = 1.0;
|
|
1779
|
+
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
colorBuffer = compositedColor;
|
|
1786
|
+
|
|
1787
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
1788
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
1789
|
+
attrBuffer = vec4(
|
|
1790
|
+
isWater,
|
|
1791
|
+
isTexturized,
|
|
1792
|
+
0.0,
|
|
1793
|
+
float(winningSlot + 1) / 255.0
|
|
1794
|
+
);
|
|
1795
|
+
|
|
1796
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
1797
|
+
}
|
|
1798
|
+
"
|
|
1799
|
+
`;
|
|
1800
|
+
|
|
1801
|
+
exports[`generateCompositeFragmentShader snapshots > single raster+2 vector, boundary 24 1`] = `
|
|
1802
|
+
"
|
|
1803
|
+
precision highp float;
|
|
1804
|
+
precision highp int;
|
|
1805
|
+
|
|
1806
|
+
#include <common>
|
|
1807
|
+
|
|
1808
|
+
varying vec2 vUv;
|
|
1809
|
+
|
|
1810
|
+
|
|
1811
|
+
|
|
1812
|
+
uniform int uShows[3];
|
|
1813
|
+
uniform vec3 uColors[3];
|
|
1814
|
+
uniform float uOpacities[3];
|
|
1815
|
+
uniform sampler2D uTextures[3];
|
|
1816
|
+
uniform vec2 uLayerUvOffset[3];
|
|
1817
|
+
uniform vec2 uLayerUvScale[3];
|
|
1818
|
+
uniform int uReproject[3];
|
|
1819
|
+
uniform vec2 uReprojectTerrainLat[3];
|
|
1820
|
+
// CPU-precomputed per-slot reprojection constants (see mutates.ts):
|
|
1821
|
+
// .xy = (mRs, mDen) — source tile's Mercator band start + span,
|
|
1822
|
+
// .z = clamp-to-top-edge flag, .w = clamp-to-bottom-edge flag (polar cap).
|
|
1823
|
+
uniform vec4 uReprojectMerc[3];
|
|
1824
|
+
|
|
1825
|
+
|
|
1826
|
+
|
|
1827
|
+
|
|
1828
|
+
|
|
1829
|
+
|
|
1830
|
+
|
|
1831
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
1832
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
1833
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
1834
|
+
|
|
1835
|
+
void main() {
|
|
1836
|
+
vec4 compositedColor = vec4(0.0);
|
|
1837
|
+
int winningSlot = -1;
|
|
1838
|
+
float isWater = 0.0;
|
|
1839
|
+
float isTexturized = 0.0;
|
|
1840
|
+
|
|
1841
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
1842
|
+
bool hasHillshadeNormal = false;
|
|
1843
|
+
|
|
1844
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
1845
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
1846
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
1847
|
+
float gReprojMLat = 0.0;
|
|
1848
|
+
bool gReprojMLatReady = false;
|
|
1849
|
+
|
|
1850
|
+
|
|
1851
|
+
// ---- compact slot 0 (absolute slot 0, raster) ----
|
|
1852
|
+
if (uShows[0] == 1) {
|
|
1853
|
+
vec2 texUv0 = vUv * uLayerUvScale[0] + uLayerUvOffset[0];
|
|
1854
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1855
|
+
float inBounds0 = 1.0;
|
|
1856
|
+
|
|
1857
|
+
if (uReproject[0] == 1) {
|
|
1858
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1859
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1860
|
+
//
|
|
1861
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1862
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1863
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1864
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1865
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1866
|
+
if (!gReprojMLatReady) {
|
|
1867
|
+
float lat = uReprojectTerrainLat[0].x
|
|
1868
|
+
+ vUv.y * (uReprojectTerrainLat[0].y - uReprojectTerrainLat[0].x);
|
|
1869
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1870
|
+
gReprojMLatReady = true;
|
|
1871
|
+
}
|
|
1872
|
+
float mRs = uReprojectMerc[0].x;
|
|
1873
|
+
float mDen = uReprojectMerc[0].y;
|
|
1874
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1875
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1876
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1877
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1878
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1879
|
+
if (abs(mDen) > 1e-3) {
|
|
1880
|
+
texUv0.y = (gReprojMLat - mRs) / mDen;
|
|
1881
|
+
}
|
|
1882
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1883
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1884
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1885
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1886
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1887
|
+
// the normal drop.
|
|
1888
|
+
if (uReprojectMerc[0].z > 0.5) texUv0.y = min(texUv0.y, 1.0);
|
|
1889
|
+
if (uReprojectMerc[0].w > 0.5) texUv0.y = max(texUv0.y, 0.0);
|
|
1890
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1891
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1892
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1893
|
+
inBounds0 = step(0.0, texUv0.x) * step(texUv0.x, 1.0)
|
|
1894
|
+
* step(0.0, texUv0.y) * step(texUv0.y, 1.0);
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
vec4 texColor0 = texture2D(uTextures[0], texUv0) * vec4(uColors[0], 1.0);
|
|
1898
|
+
|
|
1899
|
+
float alpha0 = texColor0.a * uOpacities[0] * inBounds0;
|
|
1900
|
+
if (alpha0 > 0.01) {
|
|
1901
|
+
compositedColor = mix(compositedColor, vec4(texColor0.rgb, 1.0), alpha0);
|
|
1902
|
+
winningSlot = 0;
|
|
1903
|
+
isTexturized = 0.0;
|
|
1904
|
+
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// ---- compact slot 1 (absolute slot 24, vector) ----
|
|
1909
|
+
if (uShows[1] == 1) {
|
|
1910
|
+
vec2 texUv1 = vUv * uLayerUvScale[1] + uLayerUvOffset[1];
|
|
1911
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1912
|
+
float inBounds1 = 1.0;
|
|
1913
|
+
|
|
1914
|
+
if (uReproject[1] == 1) {
|
|
1915
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1916
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1917
|
+
//
|
|
1918
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1919
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1920
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1921
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1922
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1923
|
+
if (!gReprojMLatReady) {
|
|
1924
|
+
float lat = uReprojectTerrainLat[1].x
|
|
1925
|
+
+ vUv.y * (uReprojectTerrainLat[1].y - uReprojectTerrainLat[1].x);
|
|
1926
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1927
|
+
gReprojMLatReady = true;
|
|
1928
|
+
}
|
|
1929
|
+
float mRs = uReprojectMerc[1].x;
|
|
1930
|
+
float mDen = uReprojectMerc[1].y;
|
|
1931
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1932
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1933
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1934
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1935
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1936
|
+
if (abs(mDen) > 1e-3) {
|
|
1937
|
+
texUv1.y = (gReprojMLat - mRs) / mDen;
|
|
1938
|
+
}
|
|
1939
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1940
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1941
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1942
|
+
// that edge texel so the last available source row stretches across the cap
|
|
1943
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
1944
|
+
// the normal drop.
|
|
1945
|
+
if (uReprojectMerc[1].z > 0.5) texUv1.y = min(texUv1.y, 1.0);
|
|
1946
|
+
if (uReprojectMerc[1].w > 0.5) texUv1.y = max(texUv1.y, 0.0);
|
|
1947
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
1948
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
1949
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
1950
|
+
inBounds1 = step(0.0, texUv1.x) * step(texUv1.x, 1.0)
|
|
1951
|
+
* step(0.0, texUv1.y) * step(texUv1.y, 1.0);
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
vec4 texColor1 = texture2D(uTextures[1], texUv1) * vec4(uColors[1], 1.0);
|
|
1955
|
+
|
|
1956
|
+
float alpha1 = texColor1.a * uOpacities[1] * inBounds1;
|
|
1957
|
+
if (alpha1 > 0.01) {
|
|
1958
|
+
compositedColor = mix(compositedColor, vec4(texColor1.rgb, 1.0), alpha1);
|
|
1959
|
+
winningSlot = 24;
|
|
1960
|
+
isTexturized = 1.0;
|
|
1961
|
+
|
|
1962
|
+
}
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// ---- compact slot 2 (absolute slot 25, vector) ----
|
|
1966
|
+
if (uShows[2] == 1) {
|
|
1967
|
+
vec2 texUv2 = vUv * uLayerUvScale[2] + uLayerUvOffset[2];
|
|
1968
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
1969
|
+
float inBounds2 = 1.0;
|
|
1970
|
+
|
|
1971
|
+
if (uReproject[2] == 1) {
|
|
1972
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
1973
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
1974
|
+
//
|
|
1975
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
1976
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
1977
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
1978
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
1979
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
1980
|
+
if (!gReprojMLatReady) {
|
|
1981
|
+
float lat = uReprojectTerrainLat[2].x
|
|
1982
|
+
+ vUv.y * (uReprojectTerrainLat[2].y - uReprojectTerrainLat[2].x);
|
|
1983
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
1984
|
+
gReprojMLatReady = true;
|
|
1985
|
+
}
|
|
1986
|
+
float mRs = uReprojectMerc[2].x;
|
|
1987
|
+
float mDen = uReprojectMerc[2].y;
|
|
1988
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
1989
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
1990
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
1991
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
1992
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
1993
|
+
if (abs(mDen) > 1e-3) {
|
|
1994
|
+
texUv2.y = (gReprojMLat - mRs) / mDen;
|
|
1995
|
+
}
|
|
1996
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
1997
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
1998
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
1999
|
+
// that edge texel so the last available source row stretches across the cap
|
|
2000
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
2001
|
+
// the normal drop.
|
|
2002
|
+
if (uReprojectMerc[2].z > 0.5) texUv2.y = min(texUv2.y, 1.0);
|
|
2003
|
+
if (uReprojectMerc[2].w > 0.5) texUv2.y = max(texUv2.y, 0.0);
|
|
2004
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
2005
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
2006
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
2007
|
+
inBounds2 = step(0.0, texUv2.x) * step(texUv2.x, 1.0)
|
|
2008
|
+
* step(0.0, texUv2.y) * step(texUv2.y, 1.0);
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
vec4 texColor2 = texture2D(uTextures[2], texUv2) * vec4(uColors[2], 1.0);
|
|
2012
|
+
|
|
2013
|
+
float alpha2 = texColor2.a * uOpacities[2] * inBounds2;
|
|
2014
|
+
if (alpha2 > 0.01) {
|
|
2015
|
+
compositedColor = mix(compositedColor, vec4(texColor2.rgb, 1.0), alpha2);
|
|
2016
|
+
winningSlot = 25;
|
|
2017
|
+
isTexturized = 1.0;
|
|
2018
|
+
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
|
|
2023
|
+
|
|
2024
|
+
colorBuffer = compositedColor;
|
|
2025
|
+
|
|
2026
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
2027
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
2028
|
+
attrBuffer = vec4(
|
|
2029
|
+
isWater,
|
|
2030
|
+
isTexturized,
|
|
2031
|
+
0.0,
|
|
2032
|
+
float(winningSlot + 1) / 255.0
|
|
2033
|
+
);
|
|
2034
|
+
|
|
2035
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
2036
|
+
}
|
|
2037
|
+
"
|
|
2038
|
+
`;
|
|
2039
|
+
|
|
2040
|
+
exports[`generateCompositeFragmentShader snapshots > vector-only (rasterCount 0) 1`] = `
|
|
2041
|
+
"
|
|
2042
|
+
precision highp float;
|
|
2043
|
+
precision highp int;
|
|
2044
|
+
|
|
2045
|
+
#include <common>
|
|
2046
|
+
|
|
2047
|
+
varying vec2 vUv;
|
|
2048
|
+
|
|
2049
|
+
|
|
2050
|
+
|
|
2051
|
+
uniform int uShows[2];
|
|
2052
|
+
uniform vec3 uColors[2];
|
|
2053
|
+
uniform float uOpacities[2];
|
|
2054
|
+
uniform sampler2D uTextures[2];
|
|
2055
|
+
uniform vec2 uLayerUvOffset[2];
|
|
2056
|
+
uniform vec2 uLayerUvScale[2];
|
|
2057
|
+
uniform int uReproject[2];
|
|
2058
|
+
uniform vec2 uReprojectTerrainLat[2];
|
|
2059
|
+
// CPU-precomputed per-slot reprojection constants (see mutates.ts):
|
|
2060
|
+
// .xy = (mRs, mDen) — source tile's Mercator band start + span,
|
|
2061
|
+
// .z = clamp-to-top-edge flag, .w = clamp-to-bottom-edge flag (polar cap).
|
|
2062
|
+
uniform vec4 uReprojectMerc[2];
|
|
2063
|
+
|
|
2064
|
+
|
|
2065
|
+
|
|
2066
|
+
|
|
2067
|
+
|
|
2068
|
+
|
|
2069
|
+
|
|
2070
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
2071
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
2072
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
2073
|
+
|
|
2074
|
+
void main() {
|
|
2075
|
+
vec4 compositedColor = vec4(0.0);
|
|
2076
|
+
int winningSlot = -1;
|
|
2077
|
+
float isWater = 0.0;
|
|
2078
|
+
float isTexturized = 0.0;
|
|
2079
|
+
|
|
2080
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
2081
|
+
bool hasHillshadeNormal = false;
|
|
2082
|
+
|
|
2083
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
2084
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
2085
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
2086
|
+
float gReprojMLat = 0.0;
|
|
2087
|
+
bool gReprojMLatReady = false;
|
|
2088
|
+
|
|
2089
|
+
|
|
2090
|
+
// ---- compact slot 0 (absolute slot 24, vector) ----
|
|
2091
|
+
if (uShows[0] == 1) {
|
|
2092
|
+
vec2 texUv0 = vUv * uLayerUvScale[0] + uLayerUvOffset[0];
|
|
2093
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
2094
|
+
float inBounds0 = 1.0;
|
|
2095
|
+
|
|
2096
|
+
if (uReproject[0] == 1) {
|
|
2097
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
2098
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
2099
|
+
//
|
|
2100
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
2101
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
2102
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
2103
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
2104
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
2105
|
+
if (!gReprojMLatReady) {
|
|
2106
|
+
float lat = uReprojectTerrainLat[0].x
|
|
2107
|
+
+ vUv.y * (uReprojectTerrainLat[0].y - uReprojectTerrainLat[0].x);
|
|
2108
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
2109
|
+
gReprojMLatReady = true;
|
|
2110
|
+
}
|
|
2111
|
+
float mRs = uReprojectMerc[0].x;
|
|
2112
|
+
float mDen = uReprojectMerc[0].y;
|
|
2113
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
2114
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
2115
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
2116
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
2117
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
2118
|
+
if (abs(mDen) > 1e-3) {
|
|
2119
|
+
texUv0.y = (gReprojMLat - mRs) / mDen;
|
|
2120
|
+
}
|
|
2121
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
2122
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
2123
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
2124
|
+
// that edge texel so the last available source row stretches across the cap
|
|
2125
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
2126
|
+
// the normal drop.
|
|
2127
|
+
if (uReprojectMerc[0].z > 0.5) texUv0.y = min(texUv0.y, 1.0);
|
|
2128
|
+
if (uReprojectMerc[0].w > 0.5) texUv0.y = max(texUv0.y, 0.0);
|
|
2129
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
2130
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
2131
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
2132
|
+
inBounds0 = step(0.0, texUv0.x) * step(texUv0.x, 1.0)
|
|
2133
|
+
* step(0.0, texUv0.y) * step(texUv0.y, 1.0);
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
vec4 texColor0 = texture2D(uTextures[0], texUv0) * vec4(uColors[0], 1.0);
|
|
2137
|
+
|
|
2138
|
+
float alpha0 = texColor0.a * uOpacities[0] * inBounds0;
|
|
2139
|
+
if (alpha0 > 0.01) {
|
|
2140
|
+
compositedColor = mix(compositedColor, vec4(texColor0.rgb, 1.0), alpha0);
|
|
2141
|
+
winningSlot = 24;
|
|
2142
|
+
isTexturized = 1.0;
|
|
2143
|
+
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
// ---- compact slot 1 (absolute slot 25, vector) ----
|
|
2148
|
+
if (uShows[1] == 1) {
|
|
2149
|
+
vec2 texUv1 = vUv * uLayerUvScale[1] + uLayerUvOffset[1];
|
|
2150
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
2151
|
+
float inBounds1 = 1.0;
|
|
2152
|
+
|
|
2153
|
+
if (uReproject[1] == 1) {
|
|
2154
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
2155
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
2156
|
+
//
|
|
2157
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
2158
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
2159
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
2160
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
2161
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
2162
|
+
if (!gReprojMLatReady) {
|
|
2163
|
+
float lat = uReprojectTerrainLat[1].x
|
|
2164
|
+
+ vUv.y * (uReprojectTerrainLat[1].y - uReprojectTerrainLat[1].x);
|
|
2165
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
2166
|
+
gReprojMLatReady = true;
|
|
2167
|
+
}
|
|
2168
|
+
float mRs = uReprojectMerc[1].x;
|
|
2169
|
+
float mDen = uReprojectMerc[1].y;
|
|
2170
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
2171
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
2172
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
2173
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
2174
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
2175
|
+
if (abs(mDen) > 1e-3) {
|
|
2176
|
+
texUv1.y = (gReprojMLat - mRs) / mDen;
|
|
2177
|
+
}
|
|
2178
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
2179
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
2180
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
2181
|
+
// that edge texel so the last available source row stretches across the cap
|
|
2182
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
2183
|
+
// the normal drop.
|
|
2184
|
+
if (uReprojectMerc[1].z > 0.5) texUv1.y = min(texUv1.y, 1.0);
|
|
2185
|
+
if (uReprojectMerc[1].w > 0.5) texUv1.y = max(texUv1.y, 0.0);
|
|
2186
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
2187
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
2188
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
2189
|
+
inBounds1 = step(0.0, texUv1.x) * step(texUv1.x, 1.0)
|
|
2190
|
+
* step(0.0, texUv1.y) * step(texUv1.y, 1.0);
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
vec4 texColor1 = texture2D(uTextures[1], texUv1) * vec4(uColors[1], 1.0);
|
|
2194
|
+
|
|
2195
|
+
float alpha1 = texColor1.a * uOpacities[1] * inBounds1;
|
|
2196
|
+
if (alpha1 > 0.01) {
|
|
2197
|
+
compositedColor = mix(compositedColor, vec4(texColor1.rgb, 1.0), alpha1);
|
|
2198
|
+
winningSlot = 25;
|
|
2199
|
+
isTexturized = 1.0;
|
|
2200
|
+
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
|
|
2205
|
+
|
|
2206
|
+
colorBuffer = compositedColor;
|
|
2207
|
+
|
|
2208
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
2209
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
2210
|
+
attrBuffer = vec4(
|
|
2211
|
+
isWater,
|
|
2212
|
+
isTexturized,
|
|
2213
|
+
0.0,
|
|
2214
|
+
float(winningSlot + 1) / 255.0
|
|
2215
|
+
);
|
|
2216
|
+
|
|
2217
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
2218
|
+
}
|
|
2219
|
+
"
|
|
2220
|
+
`;
|
|
2221
|
+
|
|
2222
|
+
exports[`generateCompositeFragmentShader snapshots > water only 1`] = `
|
|
2223
|
+
"
|
|
2224
|
+
precision highp float;
|
|
2225
|
+
precision highp int;
|
|
2226
|
+
|
|
2227
|
+
#include <common>
|
|
2228
|
+
|
|
2229
|
+
varying vec2 vUv;
|
|
2230
|
+
|
|
2231
|
+
|
|
2232
|
+
|
|
2233
|
+
uniform int uShows[2];
|
|
2234
|
+
uniform vec3 uColors[2];
|
|
2235
|
+
uniform float uOpacities[2];
|
|
2236
|
+
uniform sampler2D uTextures[2];
|
|
2237
|
+
uniform vec2 uLayerUvOffset[2];
|
|
2238
|
+
uniform vec2 uLayerUvScale[2];
|
|
2239
|
+
uniform int uReproject[2];
|
|
2240
|
+
uniform vec2 uReprojectTerrainLat[2];
|
|
2241
|
+
// CPU-precomputed per-slot reprojection constants (see mutates.ts):
|
|
2242
|
+
// .xy = (mRs, mDen) — source tile's Mercator band start + span,
|
|
2243
|
+
// .z = clamp-to-top-edge flag, .w = clamp-to-bottom-edge flag (polar cap).
|
|
2244
|
+
uniform vec4 uReprojectMerc[2];
|
|
2245
|
+
uniform bool uWaters[2];
|
|
2246
|
+
|
|
2247
|
+
|
|
2248
|
+
|
|
2249
|
+
|
|
2250
|
+
|
|
2251
|
+
|
|
2252
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
2253
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
2254
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
2255
|
+
|
|
2256
|
+
void main() {
|
|
2257
|
+
vec4 compositedColor = vec4(0.0);
|
|
2258
|
+
int winningSlot = -1;
|
|
2259
|
+
float isWater = 0.0;
|
|
2260
|
+
float isTexturized = 0.0;
|
|
2261
|
+
|
|
2262
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
2263
|
+
bool hasHillshadeNormal = false;
|
|
2264
|
+
|
|
2265
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
2266
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
2267
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
2268
|
+
float gReprojMLat = 0.0;
|
|
2269
|
+
bool gReprojMLatReady = false;
|
|
2270
|
+
|
|
2271
|
+
|
|
2272
|
+
// ---- compact slot 0 (absolute slot 0, raster) ----
|
|
2273
|
+
if (uShows[0] == 1) {
|
|
2274
|
+
vec2 texUv0 = vUv * uLayerUvScale[0] + uLayerUvOffset[0];
|
|
2275
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
2276
|
+
float inBounds0 = 1.0;
|
|
2277
|
+
|
|
2278
|
+
if (uReproject[0] == 1) {
|
|
2279
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
2280
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
2281
|
+
//
|
|
2282
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
2283
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
2284
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
2285
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
2286
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
2287
|
+
if (!gReprojMLatReady) {
|
|
2288
|
+
float lat = uReprojectTerrainLat[0].x
|
|
2289
|
+
+ vUv.y * (uReprojectTerrainLat[0].y - uReprojectTerrainLat[0].x);
|
|
2290
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
2291
|
+
gReprojMLatReady = true;
|
|
2292
|
+
}
|
|
2293
|
+
float mRs = uReprojectMerc[0].x;
|
|
2294
|
+
float mDen = uReprojectMerc[0].y;
|
|
2295
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
2296
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
2297
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
2298
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
2299
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
2300
|
+
if (abs(mDen) > 1e-3) {
|
|
2301
|
+
texUv0.y = (gReprojMLat - mRs) / mDen;
|
|
2302
|
+
}
|
|
2303
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
2304
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
2305
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
2306
|
+
// that edge texel so the last available source row stretches across the cap
|
|
2307
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
2308
|
+
// the normal drop.
|
|
2309
|
+
if (uReprojectMerc[0].z > 0.5) texUv0.y = min(texUv0.y, 1.0);
|
|
2310
|
+
if (uReprojectMerc[0].w > 0.5) texUv0.y = max(texUv0.y, 0.0);
|
|
2311
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
2312
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
2313
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
2314
|
+
inBounds0 = step(0.0, texUv0.x) * step(texUv0.x, 1.0)
|
|
2315
|
+
* step(0.0, texUv0.y) * step(texUv0.y, 1.0);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
vec4 texColor0 = texture2D(uTextures[0], texUv0) * vec4(uColors[0], 1.0);
|
|
2319
|
+
|
|
2320
|
+
float alpha0 = texColor0.a * uOpacities[0] * inBounds0;
|
|
2321
|
+
if (alpha0 > 0.01) {
|
|
2322
|
+
compositedColor = mix(compositedColor, vec4(texColor0.rgb, 1.0), alpha0);
|
|
2323
|
+
winningSlot = 0;
|
|
2324
|
+
isTexturized = 0.0;
|
|
2325
|
+
|
|
2326
|
+
isWater = uWaters[0] ? 1.0 : 0.0;
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
// ---- compact slot 1 (absolute slot 1, raster) ----
|
|
2331
|
+
if (uShows[1] == 1) {
|
|
2332
|
+
vec2 texUv1 = vUv * uLayerUvScale[1] + uLayerUvOffset[1];
|
|
2333
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
2334
|
+
float inBounds1 = 1.0;
|
|
2335
|
+
|
|
2336
|
+
if (uReproject[1] == 1) {
|
|
2337
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
2338
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
2339
|
+
//
|
|
2340
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
2341
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
2342
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
2343
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
2344
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
2345
|
+
if (!gReprojMLatReady) {
|
|
2346
|
+
float lat = uReprojectTerrainLat[1].x
|
|
2347
|
+
+ vUv.y * (uReprojectTerrainLat[1].y - uReprojectTerrainLat[1].x);
|
|
2348
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
2349
|
+
gReprojMLatReady = true;
|
|
2350
|
+
}
|
|
2351
|
+
float mRs = uReprojectMerc[1].x;
|
|
2352
|
+
float mDen = uReprojectMerc[1].y;
|
|
2353
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
2354
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
2355
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
2356
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
2357
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
2358
|
+
if (abs(mDen) > 1e-3) {
|
|
2359
|
+
texUv1.y = (gReprojMLat - mRs) / mDen;
|
|
2360
|
+
}
|
|
2361
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
2362
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
2363
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
2364
|
+
// that edge texel so the last available source row stretches across the cap
|
|
2365
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
2366
|
+
// the normal drop.
|
|
2367
|
+
if (uReprojectMerc[1].z > 0.5) texUv1.y = min(texUv1.y, 1.0);
|
|
2368
|
+
if (uReprojectMerc[1].w > 0.5) texUv1.y = max(texUv1.y, 0.0);
|
|
2369
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
2370
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
2371
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
2372
|
+
inBounds1 = step(0.0, texUv1.x) * step(texUv1.x, 1.0)
|
|
2373
|
+
* step(0.0, texUv1.y) * step(texUv1.y, 1.0);
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
vec4 texColor1 = texture2D(uTextures[1], texUv1) * vec4(uColors[1], 1.0);
|
|
2377
|
+
|
|
2378
|
+
float alpha1 = texColor1.a * uOpacities[1] * inBounds1;
|
|
2379
|
+
if (alpha1 > 0.01) {
|
|
2380
|
+
compositedColor = mix(compositedColor, vec4(texColor1.rgb, 1.0), alpha1);
|
|
2381
|
+
winningSlot = 1;
|
|
2382
|
+
isTexturized = 0.0;
|
|
2383
|
+
|
|
2384
|
+
isWater = uWaters[1] ? 1.0 : 0.0;
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
|
|
2388
|
+
|
|
2389
|
+
|
|
2390
|
+
colorBuffer = compositedColor;
|
|
2391
|
+
|
|
2392
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
2393
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
2394
|
+
attrBuffer = vec4(
|
|
2395
|
+
isWater,
|
|
2396
|
+
isTexturized,
|
|
2397
|
+
0.0,
|
|
2398
|
+
float(winningSlot + 1) / 255.0
|
|
2399
|
+
);
|
|
2400
|
+
|
|
2401
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
2402
|
+
}
|
|
2403
|
+
"
|
|
2404
|
+
`;
|
|
2405
|
+
|
|
2406
|
+
exports[`generateCompositeFragmentShader snapshots > watermask only feature, with slots 1`] = `
|
|
2407
|
+
"
|
|
2408
|
+
precision highp float;
|
|
2409
|
+
precision highp int;
|
|
2410
|
+
|
|
2411
|
+
#include <common>
|
|
2412
|
+
|
|
2413
|
+
varying vec2 vUv;
|
|
2414
|
+
|
|
2415
|
+
|
|
2416
|
+
|
|
2417
|
+
uniform int uShows[2];
|
|
2418
|
+
uniform vec3 uColors[2];
|
|
2419
|
+
uniform float uOpacities[2];
|
|
2420
|
+
uniform sampler2D uTextures[2];
|
|
2421
|
+
uniform vec2 uLayerUvOffset[2];
|
|
2422
|
+
uniform vec2 uLayerUvScale[2];
|
|
2423
|
+
uniform int uReproject[2];
|
|
2424
|
+
uniform vec2 uReprojectTerrainLat[2];
|
|
2425
|
+
// CPU-precomputed per-slot reprojection constants (see mutates.ts):
|
|
2426
|
+
// .xy = (mRs, mDen) — source tile's Mercator band start + span,
|
|
2427
|
+
// .z = clamp-to-top-edge flag, .w = clamp-to-bottom-edge flag (polar cap).
|
|
2428
|
+
uniform vec4 uReprojectMerc[2];
|
|
2429
|
+
|
|
2430
|
+
|
|
2431
|
+
uniform sampler2D uWatermask;
|
|
2432
|
+
|
|
2433
|
+
|
|
2434
|
+
|
|
2435
|
+
|
|
2436
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
2437
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
2438
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
2439
|
+
|
|
2440
|
+
void main() {
|
|
2441
|
+
vec4 compositedColor = vec4(0.0);
|
|
2442
|
+
int winningSlot = -1;
|
|
2443
|
+
float isWater = 0.0;
|
|
2444
|
+
float isTexturized = 0.0;
|
|
2445
|
+
|
|
2446
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
2447
|
+
bool hasHillshadeNormal = false;
|
|
2448
|
+
|
|
2449
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
2450
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
2451
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
2452
|
+
float gReprojMLat = 0.0;
|
|
2453
|
+
bool gReprojMLatReady = false;
|
|
2454
|
+
|
|
2455
|
+
|
|
2456
|
+
// ---- compact slot 0 (absolute slot 0, raster) ----
|
|
2457
|
+
if (uShows[0] == 1) {
|
|
2458
|
+
vec2 texUv0 = vUv * uLayerUvScale[0] + uLayerUvOffset[0];
|
|
2459
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
2460
|
+
float inBounds0 = 1.0;
|
|
2461
|
+
|
|
2462
|
+
if (uReproject[0] == 1) {
|
|
2463
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
2464
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
2465
|
+
//
|
|
2466
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
2467
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
2468
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
2469
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
2470
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
2471
|
+
if (!gReprojMLatReady) {
|
|
2472
|
+
float lat = uReprojectTerrainLat[0].x
|
|
2473
|
+
+ vUv.y * (uReprojectTerrainLat[0].y - uReprojectTerrainLat[0].x);
|
|
2474
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
2475
|
+
gReprojMLatReady = true;
|
|
2476
|
+
}
|
|
2477
|
+
float mRs = uReprojectMerc[0].x;
|
|
2478
|
+
float mDen = uReprojectMerc[0].y;
|
|
2479
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
2480
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
2481
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
2482
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
2483
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
2484
|
+
if (abs(mDen) > 1e-3) {
|
|
2485
|
+
texUv0.y = (gReprojMLat - mRs) / mDen;
|
|
2486
|
+
}
|
|
2487
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
2488
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
2489
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
2490
|
+
// that edge texel so the last available source row stretches across the cap
|
|
2491
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
2492
|
+
// the normal drop.
|
|
2493
|
+
if (uReprojectMerc[0].z > 0.5) texUv0.y = min(texUv0.y, 1.0);
|
|
2494
|
+
if (uReprojectMerc[0].w > 0.5) texUv0.y = max(texUv0.y, 0.0);
|
|
2495
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
2496
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
2497
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
2498
|
+
inBounds0 = step(0.0, texUv0.x) * step(texUv0.x, 1.0)
|
|
2499
|
+
* step(0.0, texUv0.y) * step(texUv0.y, 1.0);
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
vec4 texColor0 = texture2D(uTextures[0], texUv0) * vec4(uColors[0], 1.0);
|
|
2503
|
+
|
|
2504
|
+
float alpha0 = texColor0.a * uOpacities[0] * inBounds0;
|
|
2505
|
+
if (alpha0 > 0.01) {
|
|
2506
|
+
compositedColor = mix(compositedColor, vec4(texColor0.rgb, 1.0), alpha0);
|
|
2507
|
+
winningSlot = 0;
|
|
2508
|
+
isTexturized = 0.0;
|
|
2509
|
+
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
|
|
2513
|
+
// ---- compact slot 1 (absolute slot 1, raster) ----
|
|
2514
|
+
if (uShows[1] == 1) {
|
|
2515
|
+
vec2 texUv1 = vUv * uLayerUvScale[1] + uLayerUvOffset[1];
|
|
2516
|
+
// 1.0 unless the reproject branch below confines this slot to a sub-rect.
|
|
2517
|
+
float inBounds1 = 1.0;
|
|
2518
|
+
|
|
2519
|
+
if (uReproject[1] == 1) {
|
|
2520
|
+
// Affine UV is correct in longitude but stretches latitude. Remap this
|
|
2521
|
+
// fragment's latitude through Mercator so it samples the source correctly.
|
|
2522
|
+
//
|
|
2523
|
+
// The only per-fragment transcendental left is mLat: it depends solely on
|
|
2524
|
+
// vUv.y and the (tile-wide, slot-invariant) terrain lat range, so it is
|
|
2525
|
+
// identical for every reprojecting slot — computed once and reused. The
|
|
2526
|
+
// per-slot source band (mRs, mDen) and polar-cap flags are constant per slot
|
|
2527
|
+
// and precomputed on the CPU (see mutates.ts), so no log/tan runs per slot.
|
|
2528
|
+
if (!gReprojMLatReady) {
|
|
2529
|
+
float lat = uReprojectTerrainLat[1].x
|
|
2530
|
+
+ vUv.y * (uReprojectTerrainLat[1].y - uReprojectTerrainLat[1].x);
|
|
2531
|
+
gReprojMLat = log(tan(PI * 0.25 + lat * 0.5));
|
|
2532
|
+
gReprojMLatReady = true;
|
|
2533
|
+
}
|
|
2534
|
+
float mRs = uReprojectMerc[1].x;
|
|
2535
|
+
float mDen = uReprojectMerc[1].y;
|
|
2536
|
+
// Only reproject when the source's Mercator span is wide enough to be
|
|
2537
|
+
// numerically safe. For deep/upsampled tiles the span shrinks to ~1e-6 while
|
|
2538
|
+
// mLat/mRs are ~O(1), so (mLat-mRs)/(mRn-mRs) catastrophically cancels in
|
|
2539
|
+
// float32 and produces vertical-stretch noise. There the Mercator correction
|
|
2540
|
+
// is sub-pixel anyway, so the affine texUv.y is both correct and stable.
|
|
2541
|
+
if (abs(mDen) > 1e-3) {
|
|
2542
|
+
texUv1.y = (gReprojMLat - mRs) / mDen;
|
|
2543
|
+
}
|
|
2544
|
+
// Polar cap fill: WebMercator content stops at ±~85.05° but Geographic
|
|
2545
|
+
// terrain reaches ±90°. When this slot is the band-edge source (its
|
|
2546
|
+
// north/south edge sits on the WM limit), clamp the polar overshoot onto
|
|
2547
|
+
// that edge texel so the last available source row stretches across the cap
|
|
2548
|
+
// instead of being dropped by the in-bounds test below. Non-edge tiles keep
|
|
2549
|
+
// the normal drop.
|
|
2550
|
+
if (uReprojectMerc[1].z > 0.5) texUv1.y = min(texUv1.y, 1.0);
|
|
2551
|
+
if (uReprojectMerc[1].w > 0.5) texUv1.y = max(texUv1.y, 0.0);
|
|
2552
|
+
// Confine the slot to the sub-rect it actually covers. With N:M draping each
|
|
2553
|
+
// source tile covers only part of the terrain tile; outside its [0,1] range
|
|
2554
|
+
// the sampler would smear the edge texel across the rest, so drop it there.
|
|
2555
|
+
inBounds1 = step(0.0, texUv1.x) * step(texUv1.x, 1.0)
|
|
2556
|
+
* step(0.0, texUv1.y) * step(texUv1.y, 1.0);
|
|
2557
|
+
}
|
|
2558
|
+
|
|
2559
|
+
vec4 texColor1 = texture2D(uTextures[1], texUv1) * vec4(uColors[1], 1.0);
|
|
2560
|
+
|
|
2561
|
+
float alpha1 = texColor1.a * uOpacities[1] * inBounds1;
|
|
2562
|
+
if (alpha1 > 0.01) {
|
|
2563
|
+
compositedColor = mix(compositedColor, vec4(texColor1.rgb, 1.0), alpha1);
|
|
2564
|
+
winningSlot = 1;
|
|
2565
|
+
isTexturized = 0.0;
|
|
2566
|
+
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
|
|
2571
|
+
// Quantized-mesh watermask: 0 = land, 255 = water (per spec). Takes priority
|
|
2572
|
+
// over per-slot uWaters — even if no winning slot flagged water, watermask
|
|
2573
|
+
// can still mark this pixel as water. Threshold at 0.5 (byte > 127).
|
|
2574
|
+
isWater = max(isWater, step(0.5, texture(uWatermask, vUv).r));
|
|
2575
|
+
|
|
2576
|
+
colorBuffer = compositedColor;
|
|
2577
|
+
|
|
2578
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
2579
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
2580
|
+
attrBuffer = vec4(
|
|
2581
|
+
isWater,
|
|
2582
|
+
isTexturized,
|
|
2583
|
+
0.0,
|
|
2584
|
+
float(winningSlot + 1) / 255.0
|
|
2585
|
+
);
|
|
2586
|
+
|
|
2587
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
2588
|
+
}
|
|
2589
|
+
"
|
|
2590
|
+
`;
|
|
2591
|
+
|
|
2592
|
+
exports[`generateCompositeFragmentShader snapshots > watermask-only shader (no slots) 1`] = `
|
|
2593
|
+
"
|
|
2594
|
+
precision highp float;
|
|
2595
|
+
precision highp int;
|
|
2596
|
+
|
|
2597
|
+
#include <common>
|
|
2598
|
+
|
|
2599
|
+
varying vec2 vUv;
|
|
2600
|
+
|
|
2601
|
+
|
|
2602
|
+
|
|
2603
|
+
uniform sampler2D uWatermask;
|
|
2604
|
+
|
|
2605
|
+
|
|
2606
|
+
|
|
2607
|
+
|
|
2608
|
+
layout(location = 0) out vec4 colorBuffer;
|
|
2609
|
+
layout(location = 1) out vec4 attrBuffer;
|
|
2610
|
+
layout(location = 2) out vec4 normalBuffer;
|
|
2611
|
+
|
|
2612
|
+
void main() {
|
|
2613
|
+
vec4 compositedColor = vec4(0.0);
|
|
2614
|
+
int winningSlot = -1;
|
|
2615
|
+
float isWater = 0.0;
|
|
2616
|
+
float isTexturized = 0.0;
|
|
2617
|
+
|
|
2618
|
+
vec3 hillshadeNormal = vec3(0.5, 0.5, 1.0); // neutral upward
|
|
2619
|
+
bool hasHillshadeNormal = false;
|
|
2620
|
+
|
|
2621
|
+
// Shared WebMercator reprojection latitude for this fragment. The terrain lat
|
|
2622
|
+
// range is tile-wide, so mLat is identical across every reprojecting slot —
|
|
2623
|
+
// computed lazily on first use and reused by the rest (see reproject block).
|
|
2624
|
+
float gReprojMLat = 0.0;
|
|
2625
|
+
bool gReprojMLatReady = false;
|
|
2626
|
+
|
|
2627
|
+
|
|
2628
|
+
|
|
2629
|
+
|
|
2630
|
+
// Quantized-mesh watermask: 0 = land, 255 = water (per spec). Takes priority
|
|
2631
|
+
// over per-slot uWaters — even if no winning slot flagged water, watermask
|
|
2632
|
+
// can still mark this pixel as water. Threshold at 0.5 (byte > 127).
|
|
2633
|
+
isWater = max(isWater, step(0.5, texture(uWatermask, vUv).r));
|
|
2634
|
+
|
|
2635
|
+
colorBuffer = compositedColor;
|
|
2636
|
+
|
|
2637
|
+
// Attr.a packs the winning slot index as (idx+1)/255 — 0 means no winner.
|
|
2638
|
+
// Main shader: int winIdx = int(round(attr.a * 255.0)) - 1.
|
|
2639
|
+
attrBuffer = vec4(
|
|
2640
|
+
isWater,
|
|
2641
|
+
isTexturized,
|
|
2642
|
+
0.0,
|
|
2643
|
+
float(winningSlot + 1) / 255.0
|
|
2644
|
+
);
|
|
2645
|
+
|
|
2646
|
+
normalBuffer = vec4(hillshadeNormal, hasHillshadeNormal ? 1.0 : 0.0);
|
|
2647
|
+
}
|
|
2648
|
+
"
|
|
2649
|
+
`;
|