UNPKG

@giro3d/giro3d

Version:

A JS/WebGL framework for 3D geospatial data visualization

57 lines (55 loc) 40.8 kB
/* * Copyright (c) 2015-2018, IGN France. * Copyright (c) 2018-2026, Giro3D team. * SPDX-License-Identifier: MIT */ import { ShaderChunk } from 'three'; // We use non camel-case file names to be as consistent as possible with three.js naming scheme /* babel-plugin-inline-import './giro3d_colormap_pars_fragment.glsl' */ const giro3d_colormap_pars_fragment = "uniform sampler2D colorMapAtlas; // The color maps atlas\n"; /* babel-plugin-inline-import './giro3d_common.glsl' */ const giro3d_common = "#define M_PI 3.1415926535897932384626433832795\n\n// Pixel formats\nconst int PIXEL_FORMAT_RGBA = 1023;\nconst int PIXEL_FORMAT_RG = 1030;\n\n// Texture types\nconst int TEXTURE_TYPE_FLOAT = 1015;\nconst int TEXTURE_TYPE_UINT8 = 1009;\n\n// Converts a RG / Float color into a RGBA / Unsigned byte color\nvec4 convert_RG_Float_RGBA_UnsignedByte(const in vec4 color, const in float _precision, const in float offset) {\n float f = (color.r + offset) / _precision;\n\n vec4 result;\n\n // https://stackoverflow.com/a/12553149/2704779\n result.b = floor(f / 256.0 / 256.0);\n result.g = floor((f - result.b * 256.0 * 256.0) / 256.0);\n result.r = floor(f - result.b * 256.0 * 256.0 - result.g * 256.0);\n // now we have a vec3 with the 3 components in range [0..255]. Let's normalize it!\n result /= 255.0;\n result.a = color.g;\n\n return result;\n}\n\nconst int BLENDING_MODE_NONE = 0;\nconst int BLENDING_MODE_NORMAL = 1;\nconst int BLENDING_MODE_ADDITIVE = 2;\nconst int BLENDING_MODE_MULTIPLICATIVE = 3;\n\n/**\n * Describe a color layer's attributes.\n */\nstruct LayerInfo {\n vec4 offsetScale; // The offset/scale tuple.\n vec4 color; // Includes opacity/visible as alpha component\n vec2 textureSize; // The size, in pixels, of the atlas section mapping to this layer.\n int mode; // The layer mode (normal, mask)\n #if defined(ENABLE_ELEVATION_RANGE)\n vec2 elevationRange; // Optional elevation range for the layer. Any fragment above or below this range will be ignored.\n #endif\n vec3 brightnessContrastSaturation;\n int blendingMode;\n};\n\nstruct NoDataOptions {\n float replacementAlpha;\n float radius;\n bool enabled;\n};\n\nfloat linearTransfer(float v) {\n return (v < 0.04045) ? v * 0.0773993808 : pow(v * 0.9478672986 + 0.0521327014, 2.4);\n}\n\nvec4 sRGBToLinear( in vec4 srgb ) {\n float r = linearTransfer(srgb.r);\n float g = linearTransfer(srgb.g);\n float b = linearTransfer(srgb.b);\n return vec4(r, g, b, srgb.a);\n}\n\nfloat getElevationAlpha(vec4 c) {\n // Elevation textures are in the RG Format, so the transparency/no-data\n // information is actually in the green channel rather than the alpha channel.\n return c.g;\n}\n\n/**\n * Returns the elevation value at the specified coordinate, or the default value if the pixel is transparent (no-data).\n */\nfloat getElevationOrDefault(sampler2D tex, vec2 uv, float defaultValue) {\n vec4 c = texture2D(tex, uv);\n float alpha = getElevationAlpha(c);\n if (alpha == 0.0) {\n return defaultValue;\n }\n return c.r;\n}\n\nbool isNoData(sampler2D tex, vec2 uv) {\n float alpha = getElevationAlpha(texture2D(tex, uv));\n if (abs(alpha) < 0.001) {\n return true;\n }\n\n return false;\n}\n\nfloat getElevation(sampler2D tex, vec2 uv) {\n vec4 c = texture2D(tex, uv);\n return c.r;\n}\n\nvec4 blend(vec4 fore, vec4 back) {\n if (fore.a == 0. && back.a == 0.) {\n return vec4(0);\n }\n float alpha = fore.a + back.a * (1.0 - fore.a);\n vec3 color = (fore.rgb * fore.a) + back.rgb * (back.a * (1.0 - fore.a)) / alpha;\n\n return vec4(color, alpha);\n}\n\nvec4 applyBlending(vec4 fore, vec4 back, int blendingMode) {\n if (blendingMode == BLENDING_MODE_NORMAL) {\n return blend(fore, back);\n } else if (blendingMode == BLENDING_MODE_ADDITIVE) {\n vec3 rgb = clamp((fore.rgb * fore.a) + (back.rgb * back.a), 0.0, 1.0);\n return vec4(rgb, 1.0);\n } else if (blendingMode == BLENDING_MODE_MULTIPLICATIVE) {\n vec3 rgb = clamp(fore.rgb * back.rgb, 0.0, 1.0);\n return vec4(rgb, 1.0);\n } else {\n return fore;\n }\n}\n\nvec3 desaturate(vec3 color, float factor) {\n vec3 lum = vec3(0.299, 0.587, 0.114);\n vec3 gray = vec3(dot(lum, color));\n return mix(color, gray, factor);\n}\n\n// This version of atan is numerically stable around zero\n// See https://stackoverflow.com/a/27228836\n// This is used to circumvent a bug on Mac devices where this computation would produce visual artifacts.\nfloat atan2(in float y, in float x) {\n return x == 0.0 ? sign(y) * M_PI / 2. : atan(y, x);\n}\n\nvec2 computeElevationDerivatives(vec2 dimensions, vec2 uv, sampler2D tex, float elevationFactor, vec4 offsetScale) {\n ivec2 texSize = textureSize(tex, 0);\n // Compute pixel dimensions, in normalized coordinates.\n // Since textures are not necessarily square, we must compute both width and height separately.\n float width = 1.0 / float(texSize.x);\n float height = 1.0 / float(texSize.y);\n\n // Now compute the elevations for the 8 neigbouring pixels\n // +---+---+---+\n // | a | b | c |\n // +---+---+---+\n // | d | e | f |\n // +---+---+---+\n // | g | h | i |\n // +---+---+---+\n // Note: 'e' is the center of the sample. We don't use it for derivative computation.\n float a = elevationFactor * getElevation(tex, uv + vec2(-width, height));\n float b = elevationFactor * getElevation(tex, uv + vec2( 0.0, height));\n float c = elevationFactor * getElevation(tex, uv + vec2( width, height));\n float d = elevationFactor * getElevation(tex, uv + vec2(-width, 0.0));\n float f = elevationFactor * getElevation(tex, uv + vec2( width, 0.0));\n float g = elevationFactor * getElevation(tex, uv + vec2(-width, -height));\n float h = elevationFactor * getElevation(tex, uv + vec2( 0.0, -height));\n float i = elevationFactor * getElevation(tex, uv + vec2( width, -height));\n\n float cellWidth = dimensions.x / (offsetScale.z * float(texSize.x));\n float cellHeight = dimensions.y / (offsetScale.w * float(texSize.y));\n float dzdx = ((c + 2.0 * f + i) - (a + 2.0 * d + g)) / (8.0 * cellWidth);\n float dzdy = ((g + 2.0 * h + i) - (a + 2.0 * b + c)) / (8.0 * cellHeight);\n\n return vec2(dzdx, dzdy);\n}\n\n/**\n * Returns the slope given the derivatives (X and Y derivatives)\n */\nfloat calcSlope( vec2 derivatives ) {\n // https://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/how-slope-works.htm\n return atan(sqrt(derivatives.x * derivatives.x + derivatives.y * derivatives.y)); // In radians\n}\n\n/**\n * Returns the aspect (azimuth from the light source)\n */\nfloat calcAspect ( vec2 derivatives ) {\n // https://desktop.arcgis.com/en/arcmap/10.3/tools/spatial-analyst-toolbox/how-aspect-works.htm\n float aspect = atan2(derivatives.y, -derivatives.x);\n if(aspect < 0.0){\n aspect = M_PI * 0.5 - aspect;\n } else if (aspect > M_PI * 0.5) {\n aspect = 2.0 * M_PI - aspect + M_PI * 0.5;\n } else {\n aspect = M_PI * 0.5 - aspect;\n }\n return aspect; // In radians\n}\n\n/**\n * Linear map between [min1, max1] and [min2, max2]\n */\nfloat map(float value, float min1, float max1, float min2, float max2) {\n return min2 + (value - min1) * (max2 - min2) / (max1 - min1);\n}\n\nvec3 getNormalFromDerivatives(float dx, float dy) {\n vec3 direction = normalize(vec3(-dx, dy, 1.0));\n float magnitude = sqrt(pow(direction.x, 2.0) + pow(direction.y, 2.0) + pow(direction.z, 2.0));\n return direction / magnitude;\n}\n\nvec2 clamp01(vec2 uv) {\n return vec2(\n clamp(uv.x, 0., 1.),\n clamp(uv.y, 0., 1.));\n}\n\nvec2 computeUv(vec2 uv, vec2 offset, vec2 scale) {\n return vec2(\n uv.x * scale.x + offset.x,\n uv.y * scale.y + offset.y);\n}\n\nfloat squaredDistance(vec2 a, vec2 b) {\n vec2 c = a - b;\n return dot(c, c);\n}\n\n/**\n * Returns the value of the valid pixel closest to uv.\n */\nvec4 getNearestPixel(sampler2D tex, vec2 uv, int alphaChannel, float radius, float alpha) {\n const int SAMPLES = 64;\n const float fSAMPLES = float(SAMPLES);\n\n vec4 result = vec4(0, 0, 0, 0);\n float nearest = 9999.;\n float sqRadius = radius * radius;\n\n // This brute force approach produces very good visual results, but is quite costly.\n // Collect all the samples, then use only the closest valid sample to the requested position.\n for(int x = 0; x < SAMPLES; ++x) {\n for(int y = 0; y < SAMPLES; ++y) {\n float u = float(x) / fSAMPLES;\n float v = float(y) / fSAMPLES;\n\n vec2 samplePosition = vec2(u, v);\n\n vec4 color = texture2D(tex, samplePosition);\n\n // Is it a valid sample ?\n if(color[alphaChannel] == 1.) {\n // We don't need the absolute distance, since we are only interested\n // in the closest point: we avoid a costly square root computation.\n float dist = squaredDistance(samplePosition, uv);\n\n if (dist < nearest && dist <= sqRadius) {\n nearest = dist;\n result.rgb = color.rgb;\n result[alphaChannel] = alpha;\n }\n }\n }\n }\n\n return result;\n}\n\n/*\n * Sample the texture, filling no-data (transparent) pixels with neighbouring\n * valid pixels.\n * Note: a pixel is considered no-data if its alpha channel is less than 1.\n * This way, if a bilinear interpolation touches a no-data pixel, it's also considered no-data.\n */\nvec4 texture2DFillNodata(sampler2D tex, vec2 uv, NoDataOptions options, int alphaChannel) {\n vec4 value = texture2D(tex, uv);\n\n // Due to how no-data is determined here, we don't support non 1-bit alpha.\n if(value[alphaChannel] == 1.) {\n return value;\n }\n\n return getNearestPixel(tex, uv, alphaChannel, options.radius, options.replacementAlpha);\n}\n\nconst int INTERPRETATION_RAW = 0;\nconst int INTERPRETATION_SCALED = 2;\nconst int INTERPRETATION_COMPRESS_TO_8BIT = 3;\n\nstruct Interpretation {\n int mode;\n bool negateValues;\n float min; // only for INTERPRETATION_SCALED\n float max; // only for INTERPRETATION_SCALED\n};\n\nvec4 grayscaleToRGB(vec4 c, Interpretation interpretation) {\n if (interpretation.mode == INTERPRETATION_RAW) {\n return c.rrrg;\n }\n\n return c.rrra;\n}\n\nconst int OUTPUT_MODE_COLOR = 0;\nconst int OUTPUT_MODE_ELEVATION = 1;\n\n/**\n * Decodes the raw color according to the specified interpretation.\n */\nvec4 decodeInterpretation(vec4 raw, Interpretation interpretation) {\n vec4 result = raw;\n if (interpretation.mode == INTERPRETATION_SCALED) {\n float min = interpretation.min;\n float max = interpretation.max;\n float scale = max - min;\n result = vec4(\n min + raw.r * scale,\n min + raw.g * scale,\n min + raw.b * scale,\n raw.a);\n } else if (interpretation.mode == INTERPRETATION_COMPRESS_TO_8BIT) {\n float min = interpretation.min;\n float max = interpretation.max;\n float scale = 1.0 / (max - min);\n vec3 srgb = vec3(\n (raw.r - min) * scale,\n (raw.g - min) * scale,\n (raw.b - min) * scale);\n\n result = sRGBToLinear(vec4(srgb, raw.a));\n }\n\n if (interpretation.negateValues) {\n // Note that we don't flip the alpha channel\n return vec4(-result.r, -result.g, -result.b, result.a);\n }\n\n return result;\n}\n\n/**\n * Describes a color map.\n * Color maps are a way to change the color of a texture by\n * mapping the pixel's grayscale color into a value of the lookup table (LUT).\n * The pixel color acts like a UV value, that is then scaled with the min/max values\n * and mapped to the LUT texture.\n * Note: due to limitations in GLSL, the actual LUT texture must be in a separate uniform.\n */\nstruct ColorMap {\n int mode;\n float min;\n float max;\n float offset; // The V offset in the color map atlas texture.\n};\n\nconst int COLORMAP_MODE_DISABLED = 0;\nconst int COLORMAP_MODE_ELEVATION = 1;\nconst int COLORMAP_MODE_SLOPE = 2;\nconst int COLORMAP_MODE_ASPECT = 3;\n\nvec4 sampleColorMap(in float t, in float min, in float max, in sampler2D lut, in float v) {\n t = clamp(t, min, max);\n t = map(t, min, max, 0., 1.);\n return texture2D(lut, vec2(t, v));\n}\n\nvec4 computeColorMap(\n vec2 tileDimensions,\n LayerInfo layer,\n sampler2D sampledTexture,\n ColorMap colorMap,\n sampler2D lut,\n vec2 rawUv\n) {\n float value;\n\n vec2 uv = computeUv(rawUv, layer.offsetScale.xy, layer.offsetScale.zw);\n\n if (colorMap.mode == COLORMAP_MODE_ELEVATION) {\n value = getElevation(sampledTexture, uv);\n } else {\n vec2 derivatives = computeElevationDerivatives(tileDimensions, uv, sampledTexture, 1.0, layer.offsetScale);\n if (colorMap.mode == COLORMAP_MODE_SLOPE) {\n value = calcSlope(derivatives);\n } else if (colorMap.mode == COLORMAP_MODE_ASPECT) {\n value = calcAspect(derivatives);\n }\n value *= 180.0 / M_PI; // Convert radians to degrees\n }\n\n vec4 rgba = sampleColorMap(value, colorMap.min, colorMap.max, lut, colorMap.offset);\n float a = texture2D(sampledTexture, uv).a;\n return vec4(rgba.rgb, rgba.a * a);\n}\n\nvec3 adjustBrightnessContrastSaturation(\n vec3 rgb,\n vec3 brightnessContrastSaturation\n) {\n rgb = (rgb - 0.5) * brightnessContrastSaturation.y + 0.5;\n rgb += brightnessContrastSaturation.x;\n\n float luminance = dot(rgb, vec3(0.2126, 0.7152, 0.0722));\n return mix(vec3(luminance), rgb, brightnessContrastSaturation.z);\n}\n"; /* babel-plugin-inline-import './giro3d_compose_layers_pars_fragment.glsl' */ const giro3d_compose_layers_pars_fragment = "#if defined(ENABLE_LAYER_MASKS)\nconst int LAYER_MODE_NORMAL = 0;\nconst int LAYER_MODE_MASK = 1;\nconst int LAYER_MODE_MASK_INVERTED = 2;\n#endif\n\n#if VISIBLE_COLOR_LAYER_COUNT\n#if defined(USE_ATLAS_TEXTURE)\nuniform sampler2D atlasTexture; // Atlas texture shared among color layers\n#else\nuniform sampler2D colorTextures[VISIBLE_COLOR_LAYER_COUNT]; // Individual textures for each color layer\n#endif\nuniform LayerInfo layers[VISIBLE_COLOR_LAYER_COUNT]; // The color layers' infos\nuniform ColorMap layersColorMaps[VISIBLE_COLOR_LAYER_COUNT]; // The color layers' color maps\n#endif\n\nvec4 computeColor(vec2 rawUv, vec4 offsetScale, sampler2D tex) {\n vec2 uv = computeUv(rawUv, offsetScale.xy, offsetScale.zw);\n return texture2D(tex, uv);\n}\n\nvec4 computeColorLayer(\n vec2 tileDimensions,\n sampler2D texture,\n sampler2D lut,\n LayerInfo layer,\n ColorMap colorMap,\n vec2 uv\n) {\n if (layer.offsetScale.zw != vec2(0.0)) {\n vec4 color;\n if (colorMap.mode != COLORMAP_MODE_DISABLED) {\n color = computeColorMap(tileDimensions, layer, texture, colorMap, lut, uv);\n } else {\n color = computeColor(uv, layer.offsetScale, texture);\n }\n vec3 rgb = color.rgb * layer.color.rgb;\n\n float a = color.a * layer.color.a;\n return vec4(adjustBrightnessContrastSaturation(rgb, layer.brightnessContrastSaturation), a);\n }\n\n return vec4(0);\n}"; /* babel-plugin-inline-import './giro3d_contour_line_fragment.glsl' */ const giro3d_contour_line_fragment = "#if defined(ENABLE_CONTOUR_LINES)\n // Code inspired from https://github.com/NASA-AMMOS/3DTilesRendererJS/blob/master/example/customMaterial.js\n // Note: we use the 'height' variable rather than vWorldPosition.z because we want\n // this feature to work event when terrain deformation is disabled, and height\n // is always available.\n\n // thickness scale\n float upwardness = dot( vWorldNormal, vec3( 0.0, 1.0, 0.0 ) );\n float yInv = clamp( 1.0 - abs( upwardness ), 0.0, 1.0 );\n float thicknessScale = pow( yInv, 0.4 );\n thicknessScale *= 0.25 + 0.5 * ( vViewPosition.z + 1.0 ) / 2.0;\n\n // thickness\n float thickness = 0.01 * thicknessScale;\n\n float finalThickness = thickness * contourLines.thickness * 0.15;\n\n float contourLineAlpha = contourLines.color.a * 1.0;\n\n drawContourLine(height, contourLines.primaryInterval, finalThickness, vec4(contourLines.color.rgb, contourLineAlpha));\n\n drawContourLine(height, contourLines.secondaryInterval, finalThickness, vec4(contourLines.color.rgb, contourLineAlpha * 0.4));\n#endif"; /* babel-plugin-inline-import './giro3d_contour_line_pars_fragment.glsl' */ const giro3d_contour_line_pars_fragment = "#if defined(ENABLE_CONTOUR_LINES)\nstruct ContourLine {\n float thickness; // 1 = default\n float primaryInterval; // A zero interval disables the line\n float secondaryInterval; // A zero interval disables the line\n vec4 color; // Stores both the color and opacity\n};\n\nuniform ContourLine contourLines; // 1 = default\n#endif\n\nvoid drawContourLine(float height, float interval, float thickness, vec4 color) {\n if (interval > 0.) {\n float dist = mod(height, interval);\n\n if (dist <= thickness) {\n gl_FragColor = blend(color, gl_FragColor);\n }\n }\n}"; /* babel-plugin-inline-import './giro3d_fragment_shader_header.glsl' */ const giro3d_fragment_shader_header = "layout(location = 0) out highp vec4 pc_fragColor;\n// GLSL version 3 does not define the built-in gl_FragColor, so we alias it\n#define gl_FragColor pc_fragColor"; /* babel-plugin-inline-import './giro3d_graticule_fragment.glsl' */ const giro3d_graticule_fragment = "#if defined(ENABLE_GRATICULE)\n vec2 graticuleCoordinates = vec2(extent[0] + vUv.x * extent[2], extent[1] + vUv.y * extent[3]);\n drawGraticule(graticuleCoordinates, graticule);\n#endif"; /* babel-plugin-inline-import './giro3d_graticule_pars_fragment.glsl' */ const giro3d_graticule_pars_fragment = "#if defined(ENABLE_GRATICULE)\nstruct Graticule {\n float thickness; // 1 = default\n // xOffset, yOffset, xStep, yStep\n vec4 position;\n vec4 color; // Stores both the color and opacity\n};\n\nuniform Graticule graticule;\n\n\nfloat getGraticuleOpacity(float coordinate, float offset, float step, float thickness) {\n float dist = mod(coordinate + offset, step);\n\n float halfThickness = graticule.thickness / 2.0;\n float falloffWidth = graticule.thickness / 10.0;\n float fallofStart = halfThickness - falloffWidth;\n\n if (dist <= halfThickness) {\n float opacity = 1.0;\n if (dist > fallofStart) {\n float normalizedBorderDistance = 1.0 - ((dist - fallofStart) / falloffWidth);\n opacity *= normalizedBorderDistance;\n } else if (dist <= falloffWidth) {\n float normalizedBorderDistance = 1.0 - ((falloffWidth - dist) / falloffWidth);\n opacity *= normalizedBorderDistance;\n }\n\n return opacity;\n }\n\n return 0.0;\n}\n\nvoid drawGraticule(vec2 coordinate, Graticule graticule) {\n vec4 pos = graticule.position;\n float xOffset = pos[0];\n float yOffset = pos[1];\n float xStep = pos[2];\n float yStep = pos[3];\n\n if (xStep > 0. && yStep > 0.) {\n float xOpacity = getGraticuleOpacity(coordinate.x, xOffset, xStep, graticule.thickness);\n float yOpacity = getGraticuleOpacity(coordinate.y, yOffset, yStep, graticule.thickness);\n\n float opacity = graticule.color.a * max(xOpacity, yOpacity);\n\n vec4 finalColor = vec4(graticule.color.rgb, opacity);\n gl_FragColor = blend(finalColor, gl_FragColor);\n }\n}\n#endif"; /* babel-plugin-inline-import './giro3d_hillshading_pars_fragment.glsl' */ const giro3d_hillshading_pars_fragment = "const int HILLSHADE_DISABLED = 0;\nconst int HILLSHADE_SIMPLE = 1;\nconst int HILLSHADE_PHYSICAL = 2;\n\nstruct Hillshading {\n int mode; // One of HILLSHADE_DISABLED, HILLSHADE_SIMPLE, HILLSHADE_PHYSICAL\n\n float zFactor; // The factor to apply to slopes.\n\n // HILLSHADE_SIMPLE specific parameters\n float intensity; // The global lighting intensity\n float zenith; // Zenith of sunlight, in degrees (0 - 90)\n float azimuth; // Azimuth on sunlight, in degrees (0 - 360)\n};\n\nuniform Hillshading hillshading;\n\nvec3 hillshade(in vec2 derivatives, in float zenith, in float azimuth, float intensity) {\n float slope = calcSlope(derivatives);\n float aspect = calcAspect(derivatives);\n float zenith_rad = hillshading.zenith * M_PI / 180.0; // in radians\n float azimuth_rad = hillshading.azimuth * M_PI / 180.0; // in radians\n float diffuse = ((cos(zenith_rad) * cos(slope)) + (sin(zenith_rad) * sin(slope) * cos(azimuth_rad - aspect)));\n\n diffuse = clamp(diffuse, 0., 1.);\n diffuse = mix(1., diffuse, intensity);\n\n return vec3(diffuse, diffuse, diffuse);\n}"; /* babel-plugin-inline-import './giro3d_intersecting_volume_pars.glsl' */ const giro3d_intersecting_volume_pars = "#ifdef INTERSECTING_VOLUMES_SUPPORT\nstruct IntersectingVolume {\n mat4 viewToBoxNc;\n vec3 color;\n};\n\nstruct IntersectingVolumes {\n int count;\n IntersectingVolume volumes[MAX_INTERSECTING_VOLUMES_COUNT];\n};\n\nuniform IntersectingVolumes intersectingVolumes;\n\nvoid applyIntersectingVolumes(const vec4 viewPosition, inout vec4 color) {\n for (int i = 0; i < intersectingVolumes.count; i++) {\n vec4 volumeNc = intersectingVolumes.volumes[i].viewToBoxNc * viewPosition;\n volumeNc.xyz /= volumeNc.w;\n volumeNc.xyz = abs(volumeNc.xyz);\n if (volumeNc.x < 1.0 && volumeNc.y < 1.0 && volumeNc.z < 1.0) {\n color = vec4(intersectingVolumes.volumes[i].color, 1);\n return;\n }\n }\n}\n#endif"; /* babel-plugin-inline-import './giro3d_outline_fragment.glsl' */ const giro3d_outline_fragment = "#if defined(ENABLE_OUTLINES)\nif (vUv.x < (OUTLINE_THICKNESS / baseTextureSize.x)) { // WEST\n gl_FragColor.rgb = tileOutlineColor;\n} else if (vUv.x > 1.0 - (OUTLINE_THICKNESS / baseTextureSize.x)) { // EAST\n gl_FragColor.rgb = tileOutlineColor;\n} else if (vUv.y < (OUTLINE_THICKNESS / baseTextureSize.y)) { // NORTH\n gl_FragColor.rgb = tileOutlineColor;\n} else if (vUv.y > 1.0 - (OUTLINE_THICKNESS / baseTextureSize.y)) { // SOUTH\n gl_FragColor.rgb = tileOutlineColor;\n}\n#endif"; /* babel-plugin-inline-import './giro3d_outline_pars_fragment.glsl' */ const giro3d_outline_pars_fragment = "#if defined(ENABLE_OUTLINES)\nconst float OUTLINE_THICKNESS = 1.0;\n\nuniform vec3 tileOutlineColor;\n#endif"; /* babel-plugin-inline-import './giro3d_precision_qualifiers.glsl' */ const giro3d_precision_qualifiers = "precision highp float;\nprecision highp int;"; /* babel-plugin-inline-import './giro3d_terrain_pars_vertex.glsl' */ const giro3d_terrain_pars_vertex = " uniform float segments;\n uniform vec2 tileDimensions;\n uniform sampler2D elevationTexture;\n uniform LayerInfo elevationLayer;\n\n uniform float elevationScaling;\n\n#if defined (ENABLE_SKIRTS)\n // The vertex indices range that corresponds to the skirt bottom vertices\n uniform vec2 skirtVertexRange;\n#endif\n\n#if defined(STITCHING)\n struct Neighbour {\n vec4 offsetScale;\n float diffLevel;\n };\n\n uniform Neighbour neighbours[8];\n uniform sampler2D neighbourTextures[8];\n\n const int NULL = -1;\n const int NO_CORNER_NEIGHBOUR = 0;\n const int ALL_NEIGHBOURS_ARE_SAME_SIZE = 1;\n const int SOME_NEIGHBOURS_ARE_BIGGER = 2;\n const float NO_NEIGHBOUR = -99.;\n const int INNER_VERTEX = -1;\n\n const int TOP = 0;\n const int TOP_RIGHT = 1;\n const int RIGHT = 2;\n const int BOTTOM_RIGHT = 3;\n const int BOTTOM = 4;\n const int BOTTOM_LEFT = 5;\n const int LEFT = 6;\n const int TOP_LEFT = 7;\n\n struct CornerNeighbour {\n int location;\n float diffLevel;\n };\n\n bool isEdge(int location) {\n return mod(float(location), 2.) == 0.;\n }\n\n float readNeighbourElevation(vec2 uv, int neighbour, float defaultElevation) {\n // We don't want UV outside the unit square\n vec2 vv = clamp01(uv);\n\n vec4 offsetScale = neighbours[neighbour].offsetScale;\n vec2 neighbourUv = computeUv(vv, offsetScale.xy, offsetScale.zw);\n\n // Why can't we simply do neighbourTextures[neighbour] ?\n // It's because of a limitation of GLSL ES : texture arrays cannot be indexed dynamically.\n // They must be indexed by a constant expression (a literal or a constant).\n // See https://stackoverflow.com/a/60110986/2704779\n if (neighbour == TOP)\n return getElevationOrDefault(neighbourTextures[TOP], neighbourUv, defaultElevation);\n if (neighbour == TOP_RIGHT)\n return getElevationOrDefault(neighbourTextures[TOP_RIGHT], neighbourUv, defaultElevation);\n if (neighbour == RIGHT)\n return getElevationOrDefault(neighbourTextures[RIGHT], neighbourUv, defaultElevation);\n if (neighbour == BOTTOM_RIGHT)\n return getElevationOrDefault(neighbourTextures[BOTTOM_RIGHT], neighbourUv, defaultElevation);\n if (neighbour == BOTTOM)\n return getElevationOrDefault(neighbourTextures[BOTTOM], neighbourUv, defaultElevation);\n if (neighbour == BOTTOM_LEFT)\n return getElevationOrDefault(neighbourTextures[BOTTOM_LEFT], neighbourUv, defaultElevation);\n if (neighbour == LEFT)\n return getElevationOrDefault(neighbourTextures[LEFT], neighbourUv, defaultElevation);\n if (neighbour == TOP_LEFT)\n return getElevationOrDefault(neighbourTextures[TOP_LEFT], neighbourUv, defaultElevation);\n }\n\n // Returns the seam or corner that this UV belongs to.\n // If this UV does not belong to a seam nor a corner, returns INNER_VERTEX\n int locateVertex(vec2 uv) {\n const float ONE = 1.;\n const float ZERO = 0.;\n\n uv = clamp01(uv);\n\n float x = uv.x;\n float y = uv.y;\n\n if (y == ONE) {\n if (x == ZERO) {\n return TOP_LEFT;\n } else if (x == ONE) {\n return TOP_RIGHT;\n } else {\n return TOP;\n }\n } else if (y == ZERO) {\n if (x == ZERO) {\n return BOTTOM_LEFT;\n } else if (x == ONE) {\n return BOTTOM_RIGHT;\n } else {\n return BOTTOM;\n }\n } else if (x == ONE) {\n return RIGHT;\n } else if (x == ZERO) {\n return LEFT;\n } else {\n return INNER_VERTEX;\n }\n }\n\n /**\n * Computes the offsets of vertex position and UV coordinate to apply to this vertex\n * in order to fuse it with a neighbouring vertex.\n */\n bool computeXYStitchingOffsets(\n // the UV of the vertex\n vec2 uv,\n // the location of the vertex (seam, corner, or inner)\n int location,\n // the resulting offset to apply to the vertex local space position\n out vec3 vertexOffset,\n // the resulting offset to apply to the vertex UV\n out vec2 uvOffset) {\n\n vec3 factor;\n float axis;\n\n const vec2 NO_UV_OFFSET = vec2(0, 0);\n const vec3 NO_POS_OFFSET = vec3(0, 0, 0);\n\n if (location == RIGHT || location == LEFT) {\n factor = vec3(0, 1, 0);\n axis = uv.y;\n } else if (location == TOP || location == BOTTOM) {\n factor = vec3(1, 0, 0);\n axis = uv.x;\n } else {\n // we only move vertices that do belong to seams and nothing else.\n vertexOffset = NO_POS_OFFSET;\n uvOffset = NO_UV_OFFSET;\n return false;\n }\n\n float diffLevel = neighbours[location].diffLevel;\n if (diffLevel == NO_NEIGHBOUR) {\n vertexOffset = NO_POS_OFFSET;\n uvOffset = NO_UV_OFFSET;\n return false;\n }\n\n // XY-stitching only concerns tiles smaller than their neighbour.\n if (diffLevel < 0.) {\n float neighbourFactor = pow(2.0, abs(diffLevel));\n float modulo = neighbourFactor / segments;\n float offset = fract(axis / modulo) * modulo;\n uvOffset = offset * factor.xy;\n vertexOffset = offset * factor * vec3(tileDimensions, 0);\n return true;\n } else {\n vertexOffset = NO_POS_OFFSET;\n uvOffset = NO_UV_OFFSET;\n return false;\n }\n }\n\n CornerNeighbour getNeighbour(int location) {\n float diffLevel = neighbours[location].diffLevel;\n CornerNeighbour result;\n\n if (diffLevel != NO_NEIGHBOUR) {\n result.location = location;\n result.diffLevel = diffLevel;\n } else {\n result.location = NULL;\n result.diffLevel = NO_NEIGHBOUR;\n }\n\n return result;\n }\n\n /**\n * Returns the locations of the three possible neighbours of this corner location.\n * If a neighbour is not present, its value is NULL.\n * If a neighbour is bigger than us, short-circuit and return only this neighbour.\n * Returns true if at least one corner neighbour exists.\n */\n ivec4 getCornerNeighbours(int location) {\n int result = ALL_NEIGHBOURS_ARE_SAME_SIZE;\n\n int n0 = NULL;\n int n1 = NULL;\n int n2 = NULL;\n\n CornerNeighbour cn0;\n CornerNeighbour cn1;\n CornerNeighbour cn2;\n\n float biggerDiffLevel = 0.;\n\n bool atLeastOne = false;\n\n float floc = float(location);\n\n // one of the neighbour is the location itself of course\n cn0 = getNeighbour(location);\n if (cn0.diffLevel != NO_NEIGHBOUR) {\n biggerDiffLevel = min(biggerDiffLevel, cn0.diffLevel);\n atLeastOne = true;\n }\n\n int next = int(mod(floc + 1., 8.));\n cn1 = getNeighbour(next);\n if (cn1.diffLevel != NO_NEIGHBOUR) {\n biggerDiffLevel = min(biggerDiffLevel, cn1.diffLevel);\n atLeastOne = true;\n }\n\n int prev = int(mod(floc - 1., 8.));\n cn2 = getNeighbour(prev);\n if (cn2.diffLevel != NO_NEIGHBOUR) {\n biggerDiffLevel = min(biggerDiffLevel, cn2.diffLevel);\n atLeastOne = true;\n }\n\n if (atLeastOne) {\n // Eliminate corners that are smaller than the others\n if (cn0.location != NULL && cn0.diffLevel != biggerDiffLevel) {\n cn0.location = NULL;\n result = SOME_NEIGHBOURS_ARE_BIGGER;\n }\n if (cn1.location != NULL && cn1.diffLevel != biggerDiffLevel) {\n cn1.location = NULL;\n result = SOME_NEIGHBOURS_ARE_BIGGER;\n }\n if (cn2.location != NULL && cn2.diffLevel != biggerDiffLevel) {\n cn2.location = NULL;\n result = SOME_NEIGHBOURS_ARE_BIGGER;\n }\n\n n0 = cn0.location;\n n1 = cn1.location;\n n2 = cn2.location;\n\n return ivec4(result, n0, n1, n2);\n }\n\n return ivec4(NO_CORNER_NEIGHBOUR, NULL, NULL, NULL);\n }\n\n float computeZStitchedElevation(vec2 uv, int location, float currentElevation) {\n // First case : the vertex is on an edge\n if (isEdge(location)) {\n float diffLevel = neighbours[location].diffLevel;\n\n // We don't have any neighbour at this location\n if (diffLevel == NO_NEIGHBOUR) {\n return currentElevation;\n }\n\n // If our neighbour has the same level (hence size), we average the two elevations\n // This neighbour will do the same in its own vertex shader with our elevation, and\n // the two vertices will have the same height.\n float neighbourElevation = readNeighbourElevation(uv, location, currentElevation);\n if (diffLevel == 0.) {\n return mix(currentElevation, neighbourElevation, 0.5);\n } else if (diffLevel < 0.) {\n // If our neighbour is bigger than us, we don't average. Instead, we take its elevation.\n // The reason for this behaviour is that it's not possible for the bigger neighbour to\n // average with our elevation, as the bigger neighbour can have more than one neighbour\n // for the same edge, making the computation really impractical.\n return neighbourElevation;\n }\n } else {\n // Corner case (pun intended). This case is more complicated as we can have up to 3 neighbours,\n // and the rule differ whether one neighbour is bigger than us.\n // If all the neighbours of this corner have the same depth, we average, otherwise we take the\n // elevation of the biggest neighbour.\n\n // First, we need to collect the theoretical neighbours, then eliminate the absent ones.\n ivec4 corners = getCornerNeighbours(location);\n\n int cornerSituation = corners[0];\n\n // First, check that we have at least one corner neighbour.\n if (cornerSituation != NO_CORNER_NEIGHBOUR) {\n int n0, n1, n2;\n\n n0 = corners[1];\n n1 = corners[2];\n n2 = corners[3];\n\n float sum;\n float weight;\n\n if (cornerSituation == ALL_NEIGHBOURS_ARE_SAME_SIZE) {\n // Now compute the weighted average between existing (same level) neighbours.\n sum = currentElevation;\n weight = 1.;\n } else {\n // If the neighbour(s) are bigger, we don't average with our own elevation, but\n // we only consider the neighbours' elevation.\n sum = 0.;\n weight = 0.;\n }\n\n if (n0 != NULL) {\n sum += readNeighbourElevation(uv, n0, currentElevation);\n weight += 1.;\n }\n if (n1 != NULL) {\n sum += readNeighbourElevation(uv, n1, currentElevation);\n weight += 1.;\n }\n if (n2 != NULL) {\n sum += readNeighbourElevation(uv, n2, currentElevation);\n weight += 1.;\n }\n\n return sum / weight;\n }\n }\n\n return currentElevation;\n }\n#endif\n"; /* babel-plugin-inline-import './giro3d_terrain_vertex.glsl' */ const giro3d_terrain_vertex = "// The elevation offset to apply to vertices\nfloat elevation = 0.0;\n\n#if defined(GLOBE)\n // Nothing to do\n#else\n // In flat mode, the vertex shader does the terrain deformation on the Z-axis,\n // since this axis is the same as the local up vector.\n transformed.z = 0.0;\n#endif\n\n#if defined(TERRAIN_DEFORMATION)\n#if defined(ELEVATION_LAYER)\nif(elevationLayer.offsetScale.z > 0.) {\n vec2 vVv = computeUv(vUv, elevationLayer.offsetScale.xy, elevationLayer.offsetScale.zw);\n\n elevation = getElevation(elevationTexture, vVv);\n\n#if defined(GLOBE)\n // Disabled: stitching does not work well we curved surfaces (globes)\n#elif defined(STITCHING)\n /*\n Stitching aims to eliminate visible cracks between neighbouring tiles, that are caused\n by slight discrepancies in elevation and a different level of detail (LOD).\n\n This process contains 2 steps : XY-stitching and Z-stitching.\n\n XY-stitching\n ============\n\n XY-stitching works on the horizontal plane and is used to weld seams for neighbour tiles\n that have a different levels.\n\n The smallest tile (with the highest level) has a higher vertex density along the seam.\n Meaning that some vertices will not have an equivalent vertex in the neighbour, leading\n to visible cracks.\n\n In this figure, XY-stitching moves vertex A along the seam to the position of B.\n A and B have now exactly the same position in space, and the crack is removed.\n\n +------B------+------+ +------A+B----+------+\n | | | | / | |\n | | | | / | |\n +------A + => + | |\n | | | | | |\n | | | | | |\n +------+------+------+ +------+------+------+\n\n Note : XY-stitching only moves intermediate vertices of the seams, not corner vertices.\n\n Z-stitching\n ============\n\n Z-stitching is used to reconcile the variations in elevation (on the Z-axis) between the\n neighbouring seams, due to the fact that elevation pixels may have slightly different\n values on each side of the seam.\n */\n\n // Locate the vertex (is it on a seam, on a corner, or an inner vertex ?)\n int location = locateVertex(uv);\n\n // Don't perform stitching on vertices that are not on borders\n if (location != INNER_VERTEX) {\n vec3 vertexOffset;\n vec2 uvOffset;\n\n // Is there XY-stiching ?\n if (computeXYStitchingOffsets(\n vUv,\n location,\n vertexOffset,\n uvOffset)) {\n\n // move the UV and the vertex to perform XY-stitching\n vUv -= uvOffset;\n transformed -= vertexOffset;\n\n // sanitize the UV to fight off potential rounding errors (we don't want the UV to\n // be outside the unit square)\n vUv = clamp01(vUv);\n\n // The vertex has moved, maybe now it location has changed (from seam to corner)\n location = locateVertex(vUv);\n }\n\n // Get the elevation of our vertex in our texture\n vec2 elevUv = computeUv(vUv, elevationLayer.offsetScale.xy, elevationLayer.offsetScale.zw);\n float currentElevation = getElevation(elevationTexture, elevUv);\n\n // Then apply Z-stitching\n elevation = computeZStitchedElevation(vUv, location, currentElevation);\n }\n#endif // STITCHING\n}\n#endif // ELEVATION_LAYER\n\n#if defined(ENABLE_SKIRTS)\n bool isBottomVertex = vUv.x <= -999.0 && vUv.y <= -999.0;\n // Skirt bottom vertices must not be affected by terrain\n // deformation since they have a pre-defined height.\n if (isBottomVertex) {\n elevation = skirtElevation;\n }\n#endif\n\n#if defined(GLOBE)\n vec3 upVector = objectNormal;\n#else\n vec3 upVector = vec3(0, 0, 1);\n#endif\n transformed.xyz += upVector * elevation * elevationScaling;\n#endif // TERRAIN_DEFORMATION\n"; export default function registerChunks() { const Giro3dShaderChunk = ShaderChunk; Giro3dShaderChunk.giro3d_precision_qualifiers = giro3d_precision_qualifiers; Giro3dShaderChunk.giro3d_common = giro3d_common; Giro3dShaderChunk.giro3d_outline_pars_fragment = giro3d_outline_pars_fragment; Giro3dShaderChunk.giro3d_outline_fragment = giro3d_outline_fragment; Giro3dShaderChunk.giro3d_compose_layers_pars_fragment = giro3d_compose_layers_pars_fragment; Giro3dShaderChunk.giro3d_colormap_pars_fragment = giro3d_colormap_pars_fragment; Giro3dShaderChunk.giro3d_contour_line_pars_fragment = giro3d_contour_line_pars_fragment; Giro3dShaderChunk.giro3d_contour_line_fragment = giro3d_contour_line_fragment; Giro3dShaderChunk.giro3d_fragment_shader_header = giro3d_fragment_shader_header; Giro3dShaderChunk.giro3d_graticule_fragment = giro3d_graticule_fragment; Giro3dShaderChunk.giro3d_graticule_pars_fragment = giro3d_graticule_pars_fragment; Giro3dShaderChunk.giro3d_hillshading_pars_fragment = giro3d_hillshading_pars_fragment; Giro3dShaderChunk.giro3d_intersecting_volume_pars = giro3d_intersecting_volume_pars; Giro3dShaderChunk.giro3d_terrain_pars_vertex = giro3d_terrain_pars_vertex; Giro3dShaderChunk.giro3d_terrain_vertex = giro3d_terrain_vertex; }