Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added HSB color adjustment to the atmosphere and a sandcastle demo #3971

Merged
129 changes: 129 additions & 0 deletions Apps/Sandcastle/gallery/Atmosphere Color.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
<!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="Adjust hue, saturation, and brightness of the sky/atmosphere.">
<meta name="cesium-sandcastle-labels" content="Showcases">
<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);
#toolbar {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border-radius: 4px;
}
#toolbar input {
vertical-align: middle;
padding-top: 2px;
padding-bottom: 2px;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar">
<table>
<tbody><tr>
<td>hueShift</td>
<td>
<input type="range" min="-1" max="1" step="0.01" data-bind="value: hueShift, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: hueShift">
</td>
</tr>
<tr>
<td>saturationShift</td>
<td>
<input type="range" min="-1" max="1" step="0.01" data-bind="value: saturationShift, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: saturationShift">
</td>
</tr>
<tr>
<td>brightnessShift</td>
<td>
<input type="range" min="-1" max="1" step="0.01" data-bind="value: brightnessShift, valueUpdate: 'input'">
<input type="text" size="5" data-bind="value: brightnessShift">
</td>
</tr>
</tbody></table>
<div id="toggleColorCorrection"></div>
<div id="toggleLighting"></div>
<div id="toggleFog"></div>
</div>
<script id="cesium_sandcastle_script">
function startup(Cesium) {
'use strict';
//Sandcastle_Begin
var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;
var skyAtmosphere = scene.skyAtmosphere;

// The viewModel tracks the state of our mini application.
var viewModel = {
hueShift: 0.0,
saturationShift: 0.0,
brightnessShift: 0.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);

// Make the skyAtmosphere's HSB parameters subscribers of the viewModel.
function subscribeParameter(name) {
Cesium.knockout.getObservable(viewModel, name).subscribe(
function(newValue) {
skyAtmosphere[name] = newValue;
}
);
}

subscribeParameter('hueShift');
subscribeParameter('saturationShift');
subscribeParameter('brightnessShift');

Sandcastle.addToolbarButton('Toggle Color Correction', function() {
skyAtmosphere.colorCorrect = !skyAtmosphere.colorCorrect;
}, 'toggleFog');

Sandcastle.addToolbarButton('Toggle Lighting', function() {
scene.globe.enableLighting = !scene.globe.enableLighting;
}, 'toggleLighting');

Sandcastle.addToolbarButton('Toggle Fog', function() {
scene.fog.enabled = !scene.fog.enabled;
}, 'toggleFog');

var camera = viewer.camera;
camera.setView({
destination : Cesium.Cartesian3.fromDegrees(-75.5847, 40.0397, 1000.0),
orientation: {
heading : -Cesium.Math.PI_OVER_TWO,
pitch : 0.2,
roll : 0.0
}
});

//Sandcastle_End
Sandcastle.finishedLoading();
}
if (typeof Cesium !== "undefined") {
startup(Cesium);
} else if (typeof require === "function") {
require(["Cesium"], startup);
}
</script>
</body>
</html>
Binary file added Apps/Sandcastle/gallery/Atmosphere Color.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Change Log
* Fixed issue where billboards on terrain didn't always update when the terrain provider was changed. [#3921](https://github.com/AnalyticalGraphicsInc/cesium/issues/3921)
* Fixed issue where `Matrix4.fromCamera` was taking eye/target instead of position/direction. [#3927](https://github.com/AnalyticalGraphicsInc/cesium/issues/3927)
* Added `Scene.nearToFarDistance2D` that determines the size of each frustum of the multifrustum in 2D.
* Added support for hue, saturation, and brightness color shifts in the atmosphere in `SkyAtmosphere` [#3439](https://github.com/AnalyticalGraphicsInc/cesium/issues/3439)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also include a link to where the new Sandcastle example will be hosted, e.g., for 3D models, the link is http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=3D%20Models.html&label=Showcases

* Added `Matrix4.computeView`.
* Added `CullingVolume.fromBoundingSphere`.
* Added `debugShowShadowVolume` to `GroundPrimitive`.
Expand Down
64 changes: 62 additions & 2 deletions Source/Scene/SkyAtmosphere.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,42 @@ define([
this._spSkyFromSpace = undefined;
this._spSkyFromAtmosphere = undefined;

this._spSkyFromSpaceColorCorrect = undefined;
this._spSkyFromAtmosphereColorCorrect = undefined;

/**
* Use hue/saturation/brightness shifts to postprocess the sky/atmosphere color.
* @type (Boolean)
* @default false
*/
this.colorCorrect = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this variable it's easier to just check in update that hue, saturation, or brightness are not 0.0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mainly this is just from a usability standpoint, the user doesn't need to know to turn on colorCorrect first, it just works when changing hue, saturation, or brightness.


/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the doc for hue, saturation, and brightness, can you provide a bit more info like the range of each value an an example value to get a certain effect? These parameters may not be obvious to all our users.

* The hue shift to apply to the atmosphere. Defaults to 0.0 (no shift).
* A hue shift of 1.0 indicates a complete rotation of the hues available.
* @type {Number}
* @default 0.0
*/
this.hueShift = 0.0;

/**
* The saturation shift to apply to the atmosphere. Defaults to 0.0 (no shift).
* A saturation shift of -1.0 is monochrome.
* @type {Number}
* @default 0.0
*/
this.saturationShift = 0.0;

/**
* The brightness shift to apply to the atmosphere. Defaults to 0.0 (no shift).
* A brightness shift of -1.0 is complete darkness, which will let space show through.
* @type {Number}
* @default 0.0
*/
this.brightnessShift = 0.0;

this._hueSaturationBrightness = new Cartesian3();

// camera height, outer radius, inner radius, dynamic atmosphere color flag
var cameraAndRadiiAndDynamicAtmosphereColor = new Cartesian4();

Expand All @@ -96,6 +132,12 @@ define([
this._command.uniformMap = {
cameraAndRadiiAndDynamicAtmosphereColor : function() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this from before, but for consistency this should be u_cameraAndRadiiAndDynamicAtmosphereColor.

return that._cameraAndRadiiAndDynamicAtmosphereColor;
},
u_hsbShift : function() {
that._hueSaturationBrightness.x = that.hueShift;
that._hueSaturationBrightness.y = that.saturationShift;
that._hueSaturationBrightness.z = that.brightnessShift;
return that._hueSaturationBrightness;
}
};
}
Expand Down Expand Up @@ -169,11 +211,22 @@ define([
defines : ['SKY_FROM_SPACE'],
sources : [SkyAtmosphereVS]
});

var fsColorCorrect = new ShaderSource({
defines : ['COLOR_CORRECT'],
sources : [SkyAtmosphereFS]
});

this._spSkyFromSpace = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
fragmentShaderSource : SkyAtmosphereFS
});
this._spSkyFromSpaceColorCorrect = ShaderProgram.fromCache({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The color correct shaders could just be compiled on demand. Compile them when h,s,b are not 0.0 and the shaders aren't yet defined.

context : context,
vertexShaderSource : vs,
fragmentShaderSource : fsColorCorrect
});

vs = new ShaderSource({
defines : ['SKY_FROM_ATMOSPHERE'],
Expand All @@ -184,6 +237,11 @@ define([
vertexShaderSource : vs,
fragmentShaderSource : SkyAtmosphereFS
});
this._spSkyFromAtmosphereColorCorrect = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
fragmentShaderSource : fsColorCorrect
});
}

var cameraPosition = frameState.camera.positionWC;
Expand All @@ -193,10 +251,10 @@ define([

if (cameraHeight > this._cameraAndRadiiAndDynamicAtmosphereColor.y) {
// Camera in space
command.shaderProgram = this._spSkyFromSpace;
command.shaderProgram = this.colorCorrect ? this._spSkyFromSpaceColorCorrect : this._spSkyFromSpace;
} else {
// Camera in atmosphere
command.shaderProgram = this._spSkyFromAtmosphere;
command.shaderProgram = this.colorCorrect ? this._spSkyFromAtmosphereColorCorrect : this._spSkyFromAtmosphere;
}

return command;
Expand Down Expand Up @@ -239,6 +297,8 @@ define([
command.vertexArray = command.vertexArray && command.vertexArray.destroy();
this._spSkyFromSpace = this._spSkyFromSpace && this._spSkyFromSpace.destroy();
this._spSkyFromAtmosphere = this._spSkyFromAtmosphere && this._spSkyFromAtmosphere.destroy();
this._spSkyFromSpaceColorCorrect = this._spSkyFromSpaceColorCorrect && this._spSkyFromSpaceColorCorrect.destroy();
this._spSkyFromAtmosphereColorCorrect = this._spSkyFromAtmosphereColorCorrect && this._spSkyFromAtmosphereColorCorrect.destroy();
return destroyObject(this);
};

Expand Down
43 changes: 42 additions & 1 deletion Source/Shaders/SkyAtmosphereFS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,40 @@

// Code: http://sponeil.net/
// GPU Gems 2 Article: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html

// HSV/HSB <-> RGB conversion with minimal branching: http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl

#ifdef COLOR_CORRECT
uniform vec3 u_hsbShift; // hue, saturation, value
#endif

const float g = -0.95;
const float g2 = g * g;
const vec4 K_RGB2HSB = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
const vec4 K_HSB2RGB = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);


varying vec3 v_rayleighColor;
varying vec3 v_mieColor;
varying vec3 v_toCamera;
varying vec3 v_positionEC;

#ifdef COLOR_CORRECT
vec3 rgb2hsb(vec3 rgbColor)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also experiment with the czm_hue and czm_saturation built-in functions.

Copy link
Contributor Author

@likangning93 likangning93 May 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the addition of the brightness adjustment makes computing to/from HSB worthwhile. I'm not 100% sure how that works over in just RGB land.

I think there's also less trig to do hue this way, czm_hue seems to involve an atan.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah true the brightness is nice to have. I'm fine with keeping it your way, and rgb2hsb and hsb2rgb could even be built-in functions themselves if we ever need them for other areas in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
vec4 p = mix(vec4(rgbColor.bg, K_RGB2HSB.wz), vec4(rgbColor.gb, K_RGB2HSB.xy), step(rgbColor.b, rgbColor.g));
vec4 q = mix(vec4(p.xyw, rgbColor.r), vec4(rgbColor.r, p.yzx), step(p.x, rgbColor.r));

float d = q.x - min(q.w, q.y);
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + czm_epsilon7)), d / (q.x + czm_epsilon7), q.x);
}

vec3 hsb2rgb(vec3 hsbColor)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment.

{
vec3 p = abs(fract(hsbColor.xxx + K_HSB2RGB.xyz) * 6.0 - K_HSB2RGB.www);
return hsbColor.z * mix(K_HSB2RGB.xxx, clamp(p - K_HSB2RGB.xxx, 0.0, 1.0), hsbColor.y);
}
#endif

void main (void)
{
// Extra normalize added for Android
Expand All @@ -52,6 +77,22 @@ void main (void)

vec3 rgb = rayleighPhase * v_rayleighColor + miePhase * v_mieColor;
rgb = vec3(1.0) - exp(-exposure * rgb);
// compute luminance before color correction to avoid strangely gray night skies
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we capitalize the first word in comments. You may need to change one line in SkyAtmosphereVS.glsl as well.

float l = czm_luminance(rgb);

#ifdef COLOR_CORRECT
// convert rgb color to hsb
vec3 hsb = rgb2hsb(rgb);
// perform hsb shift
hsb.x += u_hsbShift.x; // hue
hsb.y = clamp(hsb.y + u_hsbShift.y, 0.0, 1.0); // saturation
hsb.z = hsb.z > czm_epsilon7 ? hsb.z + u_hsbShift.z : 0.0; // brightness
// convert shifted hsb back to rgb
rgb = hsb2rgb(hsb);

// check if correction decreased the luminance to 0
l = min(l, czm_luminance(rgb));
#endif

gl_FragColor = vec4(rgb, min(smoothstep(0.0, 0.1, l), 1.0) * smoothstep(0.0, 1.0, czm_morphTime));
}
34 changes: 34 additions & 0 deletions Specs/Scene/SkyAtmosphereSpec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/*global defineSuite*/
defineSuite([
'Scene/SkyAtmosphere',
'Core/Math',
'Core/Cartesian3',
'Core/Ellipsoid',
'Renderer/ClearCommand',
'Scene/SceneMode',
'Specs/createScene'
], function(
SkyAtmosphere,
CesiumMath,
Cartesian3,
Ellipsoid,
ClearCommand,
Expand Down Expand Up @@ -85,6 +87,38 @@ defineSuite([
s.destroy();
});

it('draws sky with color correction active', function() {
var oldSkyAtmosphere = scene.skyAtmosphere;
var s = new SkyAtmosphere();

scene.skyAtmosphere = s;
scene._environmentState.isReadyForAtmosphere = true;

scene.camera.setView({
destination : Cartesian3.fromDegrees(-75.5847, 40.0397, 1000.0),
orientation: {
heading : -CesiumMath.PI_OVER_TWO,
pitch : 0.2,
roll : 0.0
}
});

var color = scene.renderForSpecs();
expect(color).not.toEqual([0, 0, 0, 255]);

s.hueShift = 0.5;
var hueColor = scene.renderForSpecs();
expect(hueColor).toEqual(color);

// There should only be differences when color correction activates
s.colorCorrect = true;
hueColor = scene.renderForSpecs();
expect(hueColor).not.toEqual([0, 0, 0, 255]);
expect(hueColor).not.toEqual(color);

scene.skyAtmosphere = oldSkyAtmosphere;
});

it('does not render when show is false', function() {
var s = new SkyAtmosphere();
s.show = false;
Expand Down