diff --git a/Apps/Sandcastle/gallery/Terrain.html b/Apps/Sandcastle/gallery/Terrain.html index a24491646484..62c2722fb4f5 100644 --- a/Apps/Sandcastle/gallery/Terrain.html +++ b/Apps/Sandcastle/gallery/Terrain.html @@ -27,6 +27,12 @@
+ + + + + +
Enable fog
+ + + + + +
+

Loading...

+
+ + + + + + + + + + + + + +
enabled
density
sse increase factor
+
+
+ + + diff --git a/CHANGES.md b/CHANGES.md index 60da000e89a1..c7fbdffa3b9a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ Change Log * `GeoJsonDataSource.load` now takes an optional `describeProperty` function for generating feature description properties. [#3140](https://github.com/AnalyticalGraphicsInc/cesium/pull/3140) * Fixed a bug which caused `Entity` polyline graphics to be incorrect when a scene's ellipsoid was not WGS84. [#3174](https://github.com/AnalyticalGraphicsInc/cesium/pull/3174) * Added `ImageryProvider.readyPromise` and `TerrainProvider.readyPromise` and implemented it in all terrain and imagery providers. This is a promise which resolves when `ready` becomes true and rejected if there is an error during initialization. [#3175](https://github.com/AnalyticalGraphicsInc/cesium/pull/3175) +* Added support for fog near the horizon, which improves performance by rendering less terrain tiles and reduces terrain tile requests. This is enabled by default. See `Scene.fog` for options. [#3154](https://github.com/AnalyticalGraphicsInc/cesium/pull/3154) ### 1.15 - 2015-11-02 diff --git a/Source/Core/Math.js b/Source/Core/Math.js index 5e0638236751..35582d19aa01 100644 --- a/Source/Core/Math.js +++ b/Source/Core/Math.js @@ -780,5 +780,13 @@ define([ return 2.0 * radius * Math.sin(angle * 0.5); }; + /** + * @private + */ + CesiumMath.fog = function(distanceToCamera, density) { + var scalar = distanceToCamera * density; + return 1.0 - Math.exp(-(scalar * scalar)); + }; + return CesiumMath; }); diff --git a/Source/Renderer/AutomaticUniforms.js b/Source/Renderer/AutomaticUniforms.js index 420a3939dc0e..f7b0bc34ecf7 100644 --- a/Source/Renderer/AutomaticUniforms.js +++ b/Source/Renderer/AutomaticUniforms.js @@ -1396,6 +1396,22 @@ define([ getValue : function(uniformState) { return uniformState.resolutionScale; } + }), + + /** + * An automatic GLSL uniform scalar used to mix a color with the fog color based on the distance to the camera. + * + * @alias czm_fogDensity + * @glslUniform + * + * @see czm_fog + */ + czm_fogDensity : new AutomaticUniform({ + size : 1, + datatype : WebGLConstants.FLOAT, + getValue : function(uniformState) { + return uniformState.fogDensity; + } }) }; diff --git a/Source/Renderer/UniformState.js b/Source/Renderer/UniformState.js index 6e450c44dd96..49523552309d 100644 --- a/Source/Renderer/UniformState.js +++ b/Source/Renderer/UniformState.js @@ -146,6 +146,8 @@ define([ this._frustum2DWidth = 0.0; this._eyeHeight2D = new Cartesian2(); this._resolutionScale = 1.0; + + this._fogDensity = undefined; }; defineProperties(UniformState.prototype, { @@ -764,6 +766,17 @@ define([ get : function() { return this._resolutionScale; } + }, + + /** + * A scalar used to mix a color with the fog color based on the distance to the camera. + * @memberof UniformState.prototype + * @type {Number} + */ + fogDensity : { + get : function() { + return this._fogDensity; + } } }); @@ -904,6 +917,8 @@ define([ this._entireFrustum.y = camera.frustum.far; this.updateFrustum(camera.frustum); + this._fogDensity = frameState.fog.density; + this._frameState = frameState; this._temeToPseudoFixed = Transforms.computeTemeToPseudoFixedMatrix(frameState.time, this._temeToPseudoFixed); }; diff --git a/Source/Scene/Fog.js b/Source/Scene/Fog.js new file mode 100644 index 000000000000..68354b8ac157 --- /dev/null +++ b/Source/Scene/Fog.js @@ -0,0 +1,142 @@ +/*global define*/ +define([ + '../Core/Cartesian3', + '../Core/defined', + '../Core/Math', + './SceneMode' + ], function( + Cartesian3, + defined, + CesiumMath, + SceneMode) { + "use strict"; + + /** + * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional + * performance improvements by rendering less geometry and dispatching less terrain requests. + * + * @alias Fog + * @constructor + */ + var Fog = function() { + /** + * true if fog is enabled, false otherwise. + * @type {Boolean} + * @default true + */ + this.enabled = true; + + /** + * A scalar that determines the density of the fog. Terrain that is in full fog are culled. + * The density of the fog increases as this number approaches 1.0 and becomes less dense as it approaches zero. + * The more dense the fog is, the more aggressively the terrain is culled. For example, if the camera is a height of + * 1000.0m above the ellipsoid, increasing the value to 3.0e-3 will cause many tiles close to the viewer be culled. + * Decreasing the value will push the fog further from the viewer, but decrease performance as more of the terrain is rendered. + * @type {Number} + * @default 2.0e-4 + */ + this.density = 2.0e-4; + /** + * A factor used to increase the screen space error of terrain tiles when they are partially in fog. The effect is to reduce + * the number of terrain tiles requested for rendering. If set to zero, the feature will be disabled. If the value is increased + * for mountainous regions, less tiles will need to be requested, but the terrain meshes near the horizon may be a noticeably + * lower resolution. If the value is increased in a relatively flat area, there will be little noticeable change on the horizon. + * @type {Number} + * @default 2.0 + */ + this.screenSpaceErrorFactor = 2.0; + }; + + // These values were found by sampling the density at certain views and finding at what point culled tiles impacted the view at the horizon. + var heightsTable = [359.393, 800.749, 1275.6501, 2151.1192, 3141.7763, 4777.5198, 6281.2493, 12364.307, 15900.765, 49889.0549, 78026.8259, 99260.7344, 120036.3873, 151011.0158, 156091.1953, 203849.3112, 274866.9803, 319916.3149, 493552.0528, 628733.5874]; + var densityTable = [2.0e-5, 2.0e-4, 1.0e-4, 7.0e-5, 5.0e-5, 4.0e-5, 3.0e-5, 1.9e-5, 1.0e-5, 8.5e-6, 6.2e-6, 5.8e-6, 5.3e-6, 5.2e-6, 5.1e-6, 4.2e-6, 4.0e-6, 3.4e-6, 2.6e-6, 2.2e-6]; + + // Scale densities by 1e6 to bring lowest value to ~1. Prevents divide by zero. + for (var i = 0; i < densityTable.length; ++i) { + densityTable[i] *= 1.0e6; + } + // Change range to [0, 1]. + var tableStartDensity = densityTable[1]; + var tableEndDensity = densityTable[densityTable.length - 1]; + for (var j = 0; j < densityTable.length; ++j) { + densityTable[j] = (densityTable[j] - tableEndDensity) / (tableStartDensity - tableEndDensity); + } + + var tableLastIndex = 0; + + function findInterval(height) { + var heights = heightsTable; + var length = heights.length; + + if (height < heights[0]) { + tableLastIndex = 0; + return tableLastIndex; + } else if (height > heights[length - 1]) { + tableLastIndex = length - 2; + return tableLastIndex; + } + + // Take advantage of temporal coherence by checking current, next and previous intervals + // for containment of time. + if (height >= heights[tableLastIndex]) { + if (tableLastIndex + 1 < length && height < heights[tableLastIndex + 1]) { + return tableLastIndex; + } else if (tableLastIndex + 2 < length && height < heights[tableLastIndex + 2]) { + ++tableLastIndex; + return tableLastIndex; + } + } else if (tableLastIndex - 1 >= 0 && height >= heights[tableLastIndex - 1]) { + --tableLastIndex; + return tableLastIndex; + } + + // The above failed so do a linear search. + var i; + for (i = 0; i < length - 2; ++i) { + if (height >= heights[i] && height < heights[i + 1]) { + break; + } + } + + tableLastIndex = i; + return tableLastIndex; + } + + var scratchPositionNormal = new Cartesian3(); + + Fog.prototype.update = function(frameState) { + var enabled = frameState.fog.enabled = this.enabled; + if (!enabled) { + return; + } + + var camera = frameState.camera; + var positionCartographic = camera.positionCartographic; + + // Turn off fog in space. + if (!defined(positionCartographic) || positionCartographic.height > 800000.0 || frameState.mode !== SceneMode.SCENE3D) { + frameState.fog.enabled = false; + return; + } + + var height = positionCartographic.height; + var i = findInterval(height); + var t = CesiumMath.clamp((height - heightsTable[i]) / (heightsTable[i + 1] - heightsTable[i]), 0.0, 1.0); + var density = CesiumMath.lerp(densityTable[i], densityTable[i + 1], t); + + // Again, scale value to be in the range of densityTable (prevents divide by zero) and change to new range. + var startDensity = this.density * 1.0e6; + var endDensity = (startDensity / tableStartDensity) * tableEndDensity; + density = (density * (startDensity - endDensity)) * 1.0e-6; + + // Fade fog in as the camera tilts toward the horizon. + var positionNormal = Cartesian3.normalize(camera.positionWC, scratchPositionNormal); + var dot = CesiumMath.clamp(Cartesian3.dot(camera.directionWC, positionNormal), 0.0, 1.0); + density *= 1.0 - dot; + + frameState.fog.density = density; + frameState.fog.sse = this.screenSpaceErrorFactor; + }; + + return Fog; +}); diff --git a/Source/Scene/FrameState.js b/Source/Scene/FrameState.js index 793ff6f0117b..d81711d57657 100644 --- a/Source/Scene/FrameState.js +++ b/Source/Scene/FrameState.js @@ -135,6 +135,27 @@ define([ * @default false */ this.scene3DOnly = false; + + this.fog = { + /** + * true if fog is enabled, false otherwise. + * @type {Boolean} + * @default false + */ + enabled : false, + /** + * A positive number used to mix the color and fog color based on camera distance. + * @type {Number} + * @default undefined + */ + density : undefined, + /** + * A scalar used to modify the screen space error of geometry partially in fog. + * @type {Number} + * @default undefined + */ + sse : undefined + }; }; /** diff --git a/Source/Scene/Globe.js b/Source/Scene/Globe.js index 173bd884685c..64f251d393ca 100644 --- a/Source/Scene/Globe.js +++ b/Source/Scene/Globe.js @@ -40,6 +40,7 @@ define([ '../Shaders/GlobeFSPole', '../Shaders/GlobeVS', '../Shaders/GlobeVSPole', + '../Shaders/GroundAtmosphere', '../ThirdParty/when', './GlobeSurfaceShaderSet', './GlobeSurfaceTileProvider', @@ -89,6 +90,7 @@ define([ GlobeFSPole, GlobeVS, GlobeVSPole, + GroundAtmosphere, when, GlobeSurfaceShaderSet, GlobeSurfaceTileProvider, @@ -122,7 +124,7 @@ define([ this._surfaceShaderSet = new GlobeSurfaceShaderSet(); this._surfaceShaderSet.baseVertexShaderSource = new ShaderSource({ - sources : [GlobeVS] + sources : [GroundAtmosphere, GlobeVS] }); this._surfaceShaderSet.baseFragmentShaderSource = new ShaderSource({ diff --git a/Source/Scene/GlobeSurfaceShaderSet.js b/Source/Scene/GlobeSurfaceShaderSet.js index 7a7ea854d4b6..2512f5638cd3 100644 --- a/Source/Scene/GlobeSurfaceShaderSet.js +++ b/Source/Scene/GlobeSurfaceShaderSet.js @@ -66,7 +66,7 @@ define([ return useWebMercatorProjection ? get2DYPositionFractionMercatorProjection : get2DYPositionFractionGeographicProjection; } - GlobeSurfaceShaderSet.prototype.getShaderProgram = function(context, sceneMode, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection) { + GlobeSurfaceShaderSet.prototype.getShaderProgram = function(context, sceneMode, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, showReflectiveOcean, showOceanWaves, enableLighting, hasVertexNormals, useWebMercatorProjection, enableFog) { var flags = sceneMode | (applyBrightness << 2) | (applyContrast << 3) | @@ -78,7 +78,8 @@ define([ (showOceanWaves << 9) | (enableLighting << 10) | (hasVertexNormals << 11) | - (useWebMercatorProjection << 12); + (useWebMercatorProjection << 12) | + (enableFog << 13); var surfaceShader = surfaceTile.surfaceShader; if (defined(surfaceShader) && @@ -138,6 +139,11 @@ define([ } } + if (enableFog) { + vs.defines.push('FOG'); + fs.defines.push('FOG'); + } + var computeDayColor = '\ vec4 computeDayColor(vec4 initialColor, vec2 textureCoordinates)\n\ {\n\ diff --git a/Source/Scene/GlobeSurfaceTileProvider.js b/Source/Scene/GlobeSurfaceTileProvider.js index 073603e435ee..9ac7a7101dd4 100644 --- a/Source/Scene/GlobeSurfaceTileProvider.js +++ b/Source/Scene/GlobeSurfaceTileProvider.js @@ -18,6 +18,7 @@ define([ '../Core/GeometryPipeline', '../Core/IndexDatatype', '../Core/Intersect', + '../Core/Math', '../Core/Matrix4', '../Core/OrientedBoundingBox', '../Core/PrimitiveType', @@ -61,6 +62,7 @@ define([ GeometryPipeline, IndexDatatype, Intersect, + CesiumMath, Matrix4, OrientedBoundingBox, PrimitiveType, @@ -438,10 +440,18 @@ define([ * @returns {Visibility} The visibility of the tile. */ GlobeSurfaceTileProvider.prototype.computeTileVisibility = function(tile, frameState, occluders) { - var surfaceTile = tile.data; + var distance = this.computeDistanceToTile(tile, frameState); + tile._distance = distance; - var cullingVolume = frameState.cullingVolume; + if (frameState.fog.enabled) { + if (CesiumMath.fog(distance, frameState.fog.density) >= 1.0) { + // Tile is completely in fog so return that it is not visible. + return Visibility.NONE; + } + } + var surfaceTile = tile.data; + var cullingVolume = frameState.cullingVolume; var boundingVolume = defaultValue(surfaceTile.orientedBoundingBox, surfaceTile.boundingSphere3D); if (frameState.mode !== SceneMode.SCENE3D) { @@ -930,6 +940,7 @@ define([ var oceanNormalMap = tileProvider.oceanNormalMap; var showOceanWaves = showReflectiveOcean && defined(oceanNormalMap); var hasVertexNormals = tileProvider.terrainProvider.ready && tileProvider.terrainProvider.hasVertexNormals; + var enableFog = frameState.fog.enabled; if (showReflectiveOcean) { --maxTextures; @@ -1056,6 +1067,9 @@ define([ uniformMap.southMercatorYAndOneOverHeight.y = oneOverMercatorHeight; Matrix4.clone(modifiedModelViewScratch, uniformMap.modifiedModelView); + // For performance, use fog in the shader only when the tile is in fog. + var applyFog = enableFog && CesiumMath.fog(tile._distance, frameState.fog.density) > CesiumMath.EPSILON3; + var applyBrightness = false; var applyContrast = false; var applyHue = false; @@ -1117,7 +1131,7 @@ define([ uniformMap.waterMask = waterMaskTexture; Cartesian4.clone(surfaceTile.waterMaskTranslationAndScale, uniformMap.waterMaskTranslationAndScale); - command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(context, frameState.mode, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection); + command.shaderProgram = tileProvider._surfaceShaderSet.getShaderProgram(context, frameState.mode, surfaceTile, numberOfDayTextures, applyBrightness, applyContrast, applyHue, applySaturation, applyGamma, applyAlpha, showReflectiveOcean, showOceanWaves, tileProvider.enableLighting, hasVertexNormals, useWebMercatorProjection, applyFog); command.renderState = renderState; command.primitiveType = PrimitiveType.TRIANGLES; command.vertexArray = surfaceTile.vertexArray; diff --git a/Source/Scene/QuadtreePrimitive.js b/Source/Scene/QuadtreePrimitive.js index 4fbb0867c78b..192444a02a4f 100644 --- a/Source/Scene/QuadtreePrimitive.js +++ b/Source/Scene/QuadtreePrimitive.js @@ -8,6 +8,7 @@ define([ '../Core/DeveloperError', '../Core/Event', '../Core/getTimestamp', + '../Core/Math', '../Core/Queue', '../Core/Ray', '../Core/Rectangle', @@ -26,6 +27,7 @@ define([ DeveloperError, Event, getTimestamp, + CesiumMath, Queue, Ray, Rectangle, @@ -440,9 +442,7 @@ define([ var maxGeometricError = primitive._tileProvider.getLevelMaximumGeometricError(tile.level); - var distance = primitive._tileProvider.computeDistanceToTile(tile, frameState); - tile._distance = distance; - + var distance = tile._distance; var height = frameState.context.drawingBufferHeight; var camera = frameState.camera; @@ -450,7 +450,13 @@ define([ var fovy = frustum.fovy; // PERFORMANCE_IDEA: factor out stuff that's constant across tiles. - return (maxGeometricError * height) / (2 * distance * Math.tan(0.5 * fovy)); + var error = (maxGeometricError * height) / (2 * distance * Math.tan(0.5 * fovy)); + + if (frameState.fog.enabled) { + error = error - CesiumMath.fog(distance, frameState.fog.density) * frameState.fog.sse; + } + + return error; } function screenSpaceError2D(primitive, frameState, tile) { diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 212ba9161089..5d929beecf59 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -39,6 +39,7 @@ define([ './CreditDisplay', './CullingVolume', './DepthPlane', + './Fog', './FrameState', './FrustumCommands', './FXAA', @@ -99,6 +100,7 @@ define([ CreditDisplay, CullingVolume, DepthPlane, + Fog, FrameState, FrustumCommands, FXAA, @@ -505,6 +507,13 @@ define([ */ this.copyGlobeDepth = false; + /** + * Blends the atmosphere to geometry far from the camera for horizon views. Allows for additional + * performance improvements by rendering less geometry and dispatching less terrain requests. + * @type {Fog} + */ + this.fog = new Fog(); + this._performanceDisplay = undefined; this._debugVolume = undefined; @@ -1397,6 +1406,7 @@ define([ // Manage celestial and terrestrial environment effects. var renderPass = frameState.passes.render; var skyBoxCommand = (renderPass && defined(scene.skyBox)) ? scene.skyBox.update(frameState) : undefined; + var skyAtmosphereVisible = defined(scene.globe) && scene.globe._surface._tilesToRender.length > 0; var skyAtmosphereCommand = (renderPass && defined(scene.skyAtmosphere)) ? scene.skyAtmosphere.update(frameState) : undefined; var sunCommands = (renderPass && defined(scene.sun)) ? scene.sun.update(scene) : undefined; var sunDrawCommand = defined(sunCommands) ? sunCommands.drawCommand : undefined; @@ -1487,7 +1497,7 @@ define([ executeCommand(skyBoxCommand, scene, context, passState); } - if (defined(skyAtmosphereCommand)) { + if (defined(skyAtmosphereCommand) && skyAtmosphereVisible) { executeCommand(skyAtmosphereCommand, scene, context, passState); } @@ -1720,6 +1730,8 @@ define([ frameState.passes.render = true; frameState.creditDisplay.beginFrame(); + scene.fog.update(frameState); + us.update(frameState); scene._computeCommandList.length = 0; diff --git a/Source/Shaders/Builtin/Functions/fog.glsl b/Source/Shaders/Builtin/Functions/fog.glsl new file mode 100644 index 000000000000..c89553bc9c00 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/fog.glsl @@ -0,0 +1,19 @@ +/** + * Gets the color with fog at a distance from the camera. + * + * @name czm_fog + * @glslFunction + * + * @param {float} distanceToCamera The distance to the camera in meters. + * @param {vec3} color The original color. + * @param {vec3} fogColor The color of the fog. + * + * @returns {vec3} The color adjusted for fog at the distance from the camera. + */ +vec3 czm_fog(float distanceToCamera, vec3 color, vec3 fogColor) +{ + float scalar = distanceToCamera * czm_fogDensity; + float fog = 1.0 - exp(-(scalar * scalar)); + + return mix(color, fogColor, fog); +} diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index 8a46f964c833..6a49c8449fe6 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -53,6 +53,12 @@ varying vec2 v_textureCoordinates; varying vec3 v_normalMC; varying vec3 v_normalEC; +#ifdef FOG +varying float v_distance; +varying vec3 v_rayleighColor; +varying vec3 v_mieColor; +#endif + vec4 sampleAndBlend( vec4 previousColor, sampler2D texture, @@ -158,7 +164,7 @@ void main() #ifdef ENABLE_VERTEX_LIGHTING float diffuseIntensity = clamp(czm_getLambertDiffuse(czm_sunDirectionEC, normalize(v_normalEC)) * 0.9 + 0.3, 0.0, 1.0); - gl_FragColor = vec4(color.rgb * diffuseIntensity, color.a); + vec4 finalColor = vec4(color.rgb * diffuseIntensity, color.a); #elif defined(ENABLE_DAYNIGHT_SHADING) float diffuseIntensity = clamp(czm_getLambertDiffuse(czm_sunDirectionEC, normalEC) * 5.0 + 0.3, 0.0, 1.0); float cameraDist = length(czm_view[3]); @@ -166,9 +172,20 @@ void main() float fadeInDist = u_lightingFadeDistance.y; float t = clamp((cameraDist - fadeOutDist) / (fadeInDist - fadeOutDist), 0.0, 1.0); diffuseIntensity = mix(1.0, diffuseIntensity, t); - gl_FragColor = vec4(color.rgb * diffuseIntensity, color.a); + vec4 finalColor = vec4(color.rgb * diffuseIntensity, color.a); +#else + vec4 finalColor = color; +#endif + + +#ifdef FOG + const float fExposure = 2.0; + vec3 fogColor = v_mieColor + finalColor.rgb * v_rayleighColor; + fogColor = vec3(1.0) - exp(-fExposure * fogColor); + + gl_FragColor = vec4(czm_fog(v_distance, finalColor.rgb, fogColor), finalColor.a); #else - gl_FragColor = color; + gl_FragColor = finalColor; #endif } diff --git a/Source/Shaders/GlobeVS.glsl b/Source/Shaders/GlobeVS.glsl index 7b76d02dbd6b..c81c90f4653e 100644 --- a/Source/Shaders/GlobeVS.glsl +++ b/Source/Shaders/GlobeVS.glsl @@ -16,6 +16,12 @@ varying vec2 v_textureCoordinates; varying vec3 v_normalMC; varying vec3 v_normalEC; +#ifdef FOG +varying float v_distance; +varying vec3 v_mieColor; +varying vec3 v_rayleighColor; +#endif + // These functions are generated at runtime. vec4 getPosition(vec3 position3DWC); float get2DYPositionFraction(); @@ -98,4 +104,11 @@ void main() #endif v_textureCoordinates = textureCoordAndEncodedNormals.xy; + +#ifdef FOG + AtmosphereColor atmosColor = computeGroundAtmosphereFromSpace(position3DWC); + v_mieColor = atmosColor.mie; + v_rayleighColor = atmosColor.rayleigh; + v_distance = length((czm_modelView3D * vec4(position3DWC, 1.0)).xyz); +#endif } diff --git a/Source/Shaders/GroundAtmosphere.glsl b/Source/Shaders/GroundAtmosphere.glsl new file mode 100644 index 000000000000..98115f017b5a --- /dev/null +++ b/Source/Shaders/GroundAtmosphere.glsl @@ -0,0 +1,129 @@ +/*! + * Atmosphere code: + * + * Copyright (c) 2000-2005, Sean O'Neil (s_p_oneil@hotmail.com) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the project nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Modifications made by Analytical Graphics, Inc. + */ + + // Atmosphere: + // Code: http://sponeil.net/ + // GPU Gems 2 Article: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html + +const float fInnerRadius = 6378137.0; +const float fOuterRadius = 6378137.0 * 1.025; +const float fOuterRadius2 = fOuterRadius * fOuterRadius; + +const float Kr = 0.0025; +const float Km = 0.0015; +const float ESun = 15.0; + +const float fKrESun = Kr * ESun; +const float fKmESun = Km * ESun; +const float fKr4PI = Kr * 4.0 * czm_pi; +const float fKm4PI = Km * 4.0 * czm_pi; + +const float fScale = 1.0 / (fOuterRadius - fInnerRadius); +const float fScaleDepth = 0.25; +const float fScaleOverScaleDepth = fScale / fScaleDepth; + +struct AtmosphereColor +{ + vec3 mie; + vec3 rayleigh; +}; + +const int nSamples = 2; +const float fSamples = 2.0; + +float scale(float fCos) +{ + float x = 1.0 - fCos; + return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25)))); +} + +AtmosphereColor computeGroundAtmosphereFromSpace(vec3 v3Pos) +{ + vec3 v3InvWavelength = vec3(1.0 / pow(0.650, 4.0), 1.0 / pow(0.570, 4.0), 1.0 / pow(0.475, 4.0)); + + // Get the ray from the camera to the vertex and its length (which is the far point of the ray passing through the atmosphere) + vec3 v3Ray = v3Pos - czm_viewerPositionWC; + float fFar = length(v3Ray); + v3Ray /= fFar; + + float fCameraHeight = length(czm_viewerPositionWC); + float fCameraHeight2 = fCameraHeight * fCameraHeight; + + // This next line is an ANGLE workaround. It is equivalent to B = 2.0 * dot(czm_viewerPositionWC, v3Ray), + // which is what it should be, but there are problems at the poles. + float B = 2.0 * length(czm_viewerPositionWC) * dot(normalize(czm_viewerPositionWC), v3Ray); + float C = fCameraHeight2 - fOuterRadius2; + float fDet = max(0.0, B*B - 4.0 * C); + float fNear = 0.5 * (-B - sqrt(fDet)); + + // Calculate the ray's starting position, then calculate its scattering offset + vec3 v3Start = czm_viewerPositionWC + v3Ray * fNear; + fFar -= fNear; + float fDepth = exp((fInnerRadius - fOuterRadius) / fScaleDepth); + + // The light angle based on the sun position would be: + // dot(czm_sunDirectionWC, v3Pos) / length(v3Pos); + // We want the atmosphere to be uniform over the globe so it is set to 1.0. + float fLightAngle = 1.0; + float fCameraAngle = dot(-v3Ray, v3Pos) / length(v3Pos); + float fCameraScale = scale(fCameraAngle); + float fLightScale = scale(fLightAngle); + float fCameraOffset = fDepth*fCameraScale; + float fTemp = (fLightScale + fCameraScale); + + // Initialize the scattering loop variables + float fSampleLength = fFar / fSamples; + float fScaledLength = fSampleLength * fScale; + vec3 v3SampleRay = v3Ray * fSampleLength; + vec3 v3SamplePoint = v3Start + v3SampleRay * 0.5; + + // Now loop through the sample rays + vec3 v3FrontColor = vec3(0.0); + vec3 v3Attenuate = vec3(0.0); + for(int i=0; i fInnerRadius) { - czm_raySegment intersection = czm_rayEllipsoidIntersectionInterval(ray, ellipsoid); - if (!czm_isEmpty(intersection)) { - discard; - } - } else { - // The ellipsoid test above will discard fragments when the ray origin is - // inside the ellipsoid. - vec3 radii = ellipsoid.radii; - float maxRadius = max(radii.x, max(radii.y, radii.z)); - vec3 ellipsoidCenter = czm_modelView[3].xyz; - - float t1 = -1.0; - float t2 = -1.0; - - float b = -2.0 * dot(direction, ellipsoidCenter); - float c = dot(ellipsoidCenter, ellipsoidCenter) - maxRadius * maxRadius; - - float discriminant = b * b - 4.0 * c; - if (discriminant >= 0.0) { - t1 = (-b - sqrt(discriminant)) * 0.5; - t2 = (-b + sqrt(discriminant)) * 0.5; - } - - if (t1 < 0.0 && t2 < 0.0) { - // The ray through the fragment intersected the sphere approximating - // the ellipsoid behind the ray origin. - discard; - } - } - // Extra normalize added for Android float fCos = dot(czm_sunDirectionWC, normalize(v_toCamera)) / length(v_toCamera); - float fRayleighPhase = 0.75 * (1.0 + fCos*fCos); - float fMiePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos*fCos) / pow(1.0 + g2 - 2.0*g*fCos, 1.5); + float fRayleighPhase = 0.75 * (1.0 + fCos * fCos); + float fMiePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos * fCos) / pow(1.0 + g2 - 2.0 * g * fCos, 1.5); const float fExposure = 2.0; diff --git a/Source/Shaders/SkyAtmosphereVS.glsl b/Source/Shaders/SkyAtmosphereVS.glsl index 153004f848f9..96bd167356a7 100644 --- a/Source/Shaders/SkyAtmosphereVS.glsl +++ b/Source/Shaders/SkyAtmosphereVS.glsl @@ -63,7 +63,6 @@ const float fSamples = 2.0; varying vec3 v_rayleighColor; varying vec3 v_mieColor; varying vec3 v_toCamera; -varying vec3 v_positionEC; float scale(float fCos) { @@ -126,6 +125,5 @@ void main(void) v_mieColor = v3FrontColor * fKmESun; v_rayleighColor = v3FrontColor * (v3InvWavelength * fKrESun); v_toCamera = czm_viewerPositionWC - v3Pos; - v_positionEC = (czm_modelView * position).xyz; gl_Position = czm_modelViewProjection * position; } diff --git a/Specs/Scene/GlobeSurfaceTileProviderSpec.js b/Specs/Scene/GlobeSurfaceTileProviderSpec.js index 257440d1f96f..2cbd585d022a 100644 --- a/Specs/Scene/GlobeSurfaceTileProviderSpec.js +++ b/Specs/Scene/GlobeSurfaceTileProviderSpec.js @@ -9,6 +9,7 @@ defineSuite([ 'Core/Ellipsoid', 'Core/EllipsoidTerrainProvider', 'Core/GeographicProjection', + 'Core/Math', 'Core/Rectangle', 'Core/WebMercatorProjection', 'Renderer/ContextLimits', @@ -38,6 +39,7 @@ defineSuite([ Ellipsoid, EllipsoidTerrainProvider, GeographicProjection, + CesiumMath, Rectangle, WebMercatorProjection, ContextLimits, @@ -390,6 +392,62 @@ defineSuite([ }); }); + describe('fog', function() { + it('culls tiles in full fog', function() { + var layerCollection = globe.imageryLayers; + layerCollection.removeAll(); + layerCollection.addImageryProvider(new SingleTileImageryProvider({ + url : 'Data/Images/Red16x16.png' + })); + + frameState.camera.setView({ + destination : new Rectangle(0.0001, 0.0001, 0.0025, 0.0025), + orientation : { + pitch : CesiumMath.toRadians(-20.0) + } + }); + + return updateUntilDone(globe).then(function() { + expect(render(frameState, globe)).toBeGreaterThan(0); + frameState.fog.enabled = true; + frameState.fog.density = 0.001; + frameState.fog.sse = 0.0; + globe.update(frameState); + expect(render(frameState, globe)).toEqual(0); + }); + }); + + it('culls tiles because of increased SSE', function() { + var layerCollection = globe.imageryLayers; + layerCollection.removeAll(); + layerCollection.addImageryProvider(new SingleTileImageryProvider({ + url : 'Data/Images/Red16x16.png' + })); + + frameState.camera.setView({ + destination : new Rectangle(0.0001, 0.0001, 0.0025, 0.0025), + orientation : { + pitch : CesiumMath.toRadians(-20.0) + } + }); + + return updateUntilDone(globe).then(function() { + expect(render(frameState, globe)).toBeGreaterThan(0); + + frameState.fog.enabled = true; + frameState.fog.density = 0.0002; + frameState.fog.sse = 0.0; + globe.update(frameState); + var renderCount = render(frameState, globe); + expect(renderCount).toBeGreaterThan(0); + + frameState.fog.sse = 2.0; + globe.update(frameState); + expect(render(frameState, globe)).toBeLessThan(renderCount); + }); + }); + }); + it('can change baseColor', function() { var layerCollection = globe.imageryLayers; layerCollection.removeAll();