Skip to content

Commit

Permalink
Merge pull request #3154 from AnalyticalGraphicsInc/fog
Browse files Browse the repository at this point in the history
Fog
  • Loading branch information
pjcozzi committed Nov 9, 2015
2 parents 5096bd4 + 3ba3717 commit ade6742
Show file tree
Hide file tree
Showing 20 changed files with 650 additions and 58 deletions.
16 changes: 16 additions & 0 deletions Apps/Sandcastle/gallery/Terrain.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
<div id="zoomButtons"></div>
<div id="toggleLighting"></div>
<div id="sampleButtons"></div>
<table><tbody>
<tr>
<td>Enable fog</td>
<td><input type="checkbox" data-bind="checked: fogEnabled"/></td>
</tr>
</tbody></table>
</div>
<script id="cesium_sandcastle_script">
function startup(Cesium) {
Expand Down Expand Up @@ -177,6 +183,16 @@

Cesium.when(Cesium.sampleTerrain(viewer.terrainProvider, 9, terrainSamplePositions), sampleTerrainSuccess);
}, 'sampleButtons');

var viewModel = { fogEnabled : true };
Cesium.knockout.track(viewModel);

var toolbar = document.getElementById('toolbar');
Cesium.knockout.applyBindings(viewModel, toolbar);
Cesium.knockout.getObservable(viewModel, 'fogEnabled').subscribe(function(newValue) {
viewer.scene.fog.enabled = newValue;
});
viewModel.enabled = viewer.scene.fog.enabled;
//Sandcastle_End
Sandcastle.finishedLoading();
}
Expand Down
139 changes: 139 additions & 0 deletions Apps/Sandcastle/gallery/development/Fog.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
<meta name="description" content="Control fog parameters.">
<meta name="cesium-sandcastle-labels" content="Development">
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script type="text/javascript" src="../../../ThirdParty/requirejs-2.1.20/require.js"></script>
<script type="text/javascript">
require.config({
baseUrl : '../../../Source',
waitSeconds : 60
});
</script>
</head>
<body class="sandcastle-loading" data-sandcastle-bucket="bucket-requirejs.html">
<style>
@import url(../templates/bucket.css);
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar">
<table><tbody>
<tr>
<td>enabled</td>
<td><input type="checkbox" data-bind="checked: enabled"/></td>
</tr>
<tr>
<td>density</td>
<td><input type="text" size="5" data-bind="value: density"></td>
</tr>
<tr>
<td>sse increase factor</td>
<td><input type="text" size="5" data-bind="value: sse"></td>
</tr>
</tbody></table>
<div id="zoomButtons"></div>
</div>
<script id="cesium_sandcastle_script">
function startup(Cesium) {
"use strict";
//Sandcastle_Begin
var viewer = new Cesium.Viewer('cesiumContainer');
viewer.terrainProvider = new Cesium.CesiumTerrainProvider({
url : '//assets.agi.com/stk-terrain/world',
requestWaterMask : true,
requestVertexNormals : true
});

viewer.extend(Cesium.viewerCesiumInspectorMixin);

//The viewModel tracks the state of our mini application.
var viewModel = {
enabled : true,
density : 0,
sse : 0
};
// Convert the viewModel members into knockout observables.
Cesium.knockout.track(viewModel);

// Bind the viewModel to the DOM elements of the UI that call for it.
var toolbar = document.getElementById('toolbar');
Cesium.knockout.applyBindings(viewModel, toolbar);

Cesium.knockout.getObservable(viewModel, 'enabled').subscribe(
function(newValue) {
viewer.scene.fog.enabled = newValue;
}
);

Cesium.knockout.getObservable(viewModel, 'density').subscribe(
function(newValue) {
viewer.scene.fog.density = newValue;
}
);

Cesium.knockout.getObservable(viewModel, 'sse').subscribe(
function(newValue) {
viewer.scene.fog.screenSpaceErrorFactor = newValue;
}
);

viewModel.enabled = viewer.scene.fog.enabled;
viewModel.density = viewer.scene.fog.density;
viewModel.sse = viewer.scene.fog.screenSpaceErrorFactor;

Sandcastle.addToolbarButton("Horizon high altitude", function() {
viewer.camera.setView({
destination: new Cesium.Cartesian3(-2467730.5740817646, -4390507.315824514, 3906155.113316938),
orientation: {
heading: 4.492211521856625,
pitch: -0.2687139437696304
}
});
});

Sandcastle.addToolbarButton("Horizon low altitude", function() {
viewer.camera.setView({
destination : new Cesium.Cartesian3(-734001.9511656855, -4214090.596769834, 4715898.125886317),
orientation : {
heading : 5.634257362559497,
pitch : -0.019548505785381032
}
});
});

viewer.scene.globe._surface._debug.enableDebugOutput = true;

Sandcastle.addToolbarButton("Snap", function() {
var container = document.getElementById("cesiumContainer");
var tmpH = container.style.height;
var tmpW = container.style.width;

container.style.height = "600px";
container.style.width = "800px";

viewer.resize();
viewer.render();
window.open(viewer.canvas.toDataURL("image/png"));
container.style.height = tmpH;
container.style.width = tmpW;
viewer.resize();
viewer.render();
});

//Sandcastle_End
Sandcastle.finishedLoading();
}
if (typeof Cesium !== "undefined") {
startup(Cesium);
} else if (typeof require === "function") {
require(["Cesium"], startup);
}
</script>
</body>
</html>
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 8 additions & 0 deletions Source/Core/Math.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
16 changes: 16 additions & 0 deletions Source/Renderer/AutomaticUniforms.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
})
};

Expand Down
15 changes: 15 additions & 0 deletions Source/Renderer/UniformState.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ define([
this._frustum2DWidth = 0.0;
this._eyeHeight2D = new Cartesian2();
this._resolutionScale = 1.0;

this._fogDensity = undefined;
};

defineProperties(UniformState.prototype, {
Expand Down Expand Up @@ -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;
}
}
});

Expand Down Expand Up @@ -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);
};
Expand Down
142 changes: 142 additions & 0 deletions Source/Scene/Fog.js
Original file line number Diff line number Diff line change
@@ -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() {
/**
* <code>true</code> if fog is enabled, <code>false</code> 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;
});
Loading

0 comments on commit ade6742

Please sign in to comment.