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
124 changes: 124 additions & 0 deletions Apps/Sandcastle/gallery/Atmosphere Color.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<!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="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 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
84 changes: 81 additions & 3 deletions Source/Scene/SkyAtmosphere.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ define([
'../Core/Ellipsoid',
'../Core/EllipsoidGeometry',
'../Core/GeometryPipeline',
'../Core/Math',
'../Core/VertexFormat',
'../Renderer/BufferUsage',
'../Renderer/DrawCommand',
Expand All @@ -31,6 +32,7 @@ define([
Ellipsoid,
EllipsoidGeometry,
GeometryPipeline,
CesiumMath,
VertexFormat,
BufferUsage,
DrawCommand,
Expand Down Expand Up @@ -81,6 +83,35 @@ define([
this._spSkyFromSpace = undefined;
this._spSkyFromAtmosphere = undefined;

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

/**
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 @@ -94,8 +125,14 @@ define([
var that = this;

this._command.uniformMap = {
cameraAndRadiiAndDynamicAtmosphereColor : function() {
u_cameraAndRadiiAndDynamicAtmosphereColor : function() {
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 All @@ -122,6 +159,15 @@ define([
this._cameraAndRadiiAndDynamicAtmosphereColor.w = enableLighting ? 1 : 0;
};

/**
* @private
*/
SkyAtmosphere.prototype.colorCorrect = function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

For better encapsulation, make this a local function and pass skyAtmosphere to it.

See https://github.com/AnalyticalGraphicsInc/cesium/tree/master/Documentation/Contributors/CodingGuide#private-functions

return !(CesiumMath.equalsEpsilon(this.hueShift, 0.0, CesiumMath.EPSILON7) &&
CesiumMath.equalsEpsilon(this.saturationShift, 0.0, CesiumMath.EPSILON7) &&
CesiumMath.equalsEpsilon(this.brightnessShift, 0.0, CesiumMath.EPSILON7));
};

/**
* @private
*/
Expand Down Expand Up @@ -169,6 +215,7 @@ define([
defines : ['SKY_FROM_SPACE'],
sources : [SkyAtmosphereVS]
});

this._spSkyFromSpace = ShaderProgram.fromCache({
context : context,
vertexShaderSource : vs,
Expand All @@ -186,17 +233,46 @@ define([
});
}

// Compile the color correcting versions of the shader on demand
if (this.colorCorrect() && (!defined(this._spSkyFromSpaceColorCorrect) || !defined(this._spSkyFromAtmosphereColorCorrect))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Call this.colorCorrect() just once here in update and store it off so it is not computed twice. Burn a tad less CPU.

var contextColorCorrect = frameState.context;

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

this._spSkyFromSpaceColorCorrect = ShaderProgram.fromCache({
context : contextColorCorrect,
vertexShaderSource : vsColorCorrect,
fragmentShaderSource : fsColorCorrect
});
vsColorCorrect = new ShaderSource({
defines : ['SKY_FROM_ATMOSPHERE'],
sources : [SkyAtmosphereVS]
});
this._spSkyFromAtmosphereColorCorrect = ShaderProgram.fromCache({
context : contextColorCorrect,
vertexShaderSource : vsColorCorrect,
fragmentShaderSource : fsColorCorrect
});
}

var cameraPosition = frameState.camera.positionWC;

var cameraHeight = Cartesian3.magnitude(cameraPosition);
this._cameraAndRadiiAndDynamicAtmosphereColor.x = cameraHeight;

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 +315,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
42 changes: 41 additions & 1 deletion Source/Shaders/SkyAtmosphereFS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,39 @@

// 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 +76,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
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));
}
12 changes: 6 additions & 6 deletions Source/Shaders/SkyAtmosphereVS.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

attribute vec4 position;

uniform vec4 cameraAndRadiiAndDynamicAtmosphereColor; // camera height, outer radius, inner radius, dynamic atmosphere color flag
uniform vec4 u_cameraAndRadiiAndDynamicAtmosphereColor; // Camera height, outer radius, inner radius, dynamic atmosphere color flag

const float Kr = 0.0025;
const float Kr4PI = Kr * 4.0 * czm_pi;
Expand Down Expand Up @@ -65,10 +65,10 @@ float scale(float cosAngle)

void main(void)
{
// unpack attributes
float cameraHeight = cameraAndRadiiAndDynamicAtmosphereColor.x;
float outerRadius = cameraAndRadiiAndDynamicAtmosphereColor.y;
float innerRadius = cameraAndRadiiAndDynamicAtmosphereColor.z;
// Unpack attributes
float cameraHeight = u_cameraAndRadiiAndDynamicAtmosphereColor.x;
float outerRadius = u_cameraAndRadiiAndDynamicAtmosphereColor.y;
float innerRadius = u_cameraAndRadiiAndDynamicAtmosphereColor.z;

// 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 positionV3 = position.xyz;
Expand Down Expand Up @@ -107,7 +107,7 @@ void main(void)

// Now loop through the sample rays
vec3 frontColor = vec3(0.0, 0.0, 0.0);
vec3 lightDir = (cameraAndRadiiAndDynamicAtmosphereColor.w > 0.0) ? czm_sunPositionWC - czm_viewerPositionWC : czm_viewerPositionWC;
vec3 lightDir = (u_cameraAndRadiiAndDynamicAtmosphereColor.w > 0.0) ? czm_sunPositionWC - czm_viewerPositionWC : czm_viewerPositionWC;
lightDir = normalize(lightDir);

for(int i=0; i<nSamples; i++)
Expand Down
Loading