diff --git a/Apps/Sandcastle/gallery/Materials.html b/Apps/Sandcastle/gallery/Materials.html
index b19aacc0068f..d9fea96fdfa0 100644
--- a/Apps/Sandcastle/gallery/Materials.html
+++ b/Apps/Sandcastle/gallery/Materials.html
@@ -107,16 +107,20 @@
}
},
source :
- 'czm_material czm_getMaterial(czm_materialInput materialInput) {' +
- 'czm_material material = czm_getDefaultMaterial(materialInput);' +
- 'float heightValue = texture2D(heightField, materialInput.st).r;' +
- 'material.diffuse = mix(vec3(0.2, 0.6, 0.2), vec3(1.0, 0.5, 0.2), heightValue);' +
- 'material.alpha = (1.0 - texture2D(image, materialInput.st).r) * 0.7;' +
- 'material.normal = bumpMap.normal;' +
- 'material.specular = step(0.1, heightValue);' + // Specular mountain tops
- 'material.shininess = 8.0;' + // Sharpen highlight
- 'return material;' +
- '}'
+ 'czm_material czm_getMaterial(czm_materialInput materialInput) { \n' +
+ ' czm_material material = czm_getDefaultMaterial(materialInput); \n' +
+ ' vec4 color; \n' +
+ ' float heightValue = texture2D(heightField, materialInput.st).r; \n' +
+ ' color.rgb = mix(vec3(0.2, 0.6, 0.2), vec3(1.0, 0.5, 0.2), heightValue); \n' +
+ ' color.a = (1.0 - texture2D(image, materialInput.st).r) * 0.7; \n' +
+ ' color = czm_gammaCorrect(color); \n' +
+ ' material.diffuse = color.rgb; \n' +
+ ' material.alpha = color.a; \n' +
+ ' material.normal = bumpMap.normal; \n' +
+ ' material.specular = step(0.1, heightValue); \n' + // Specular mountain tops
+ ' material.shininess = 8.0; \n' + // Sharpen highlight
+ ' return material; \n' +
+ '} \n'
}
});
}
diff --git a/CHANGES.md b/CHANGES.md
index 832b23c0bc59..ed63272135f8 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -7,6 +7,7 @@ Change Log
* Added functions to get the most detailed height of 3D Tiles on-screen or off-screen. [#7115](https://github.com/AnalyticalGraphicsInc/cesium/pull/7115)
* Added `Scene.sampleHeightMostDetailed`, an asynchronous version of `Scene.sampleHeight` that uses the maximum level of detail for 3D Tiles.
* Added `Scene.clampToHeightMostDetailed`, an asynchronous version of `Scene.clampToHeight` that uses the maximum level of detail for 3D Tiles.
+* Added support for high dynamic range rendering. It is enabled by default when supported, but can be disabled with `Scene.highDynamicRange`. [#7017](https://github.com/AnalyticalGraphicsInc/cesium/pull/7017)
* Added `Scene.invertClassificationSupported` for checking if invert classification is supported.
* Added `computeLineSegmentLineSegmentIntersection` to `Intersections2D`. [#7228](https://github.com/AnalyticalGraphicsInc/Cesium/pull/7228)
diff --git a/Source/Renderer/AutomaticUniforms.js b/Source/Renderer/AutomaticUniforms.js
index 9f6a1d1ba4ff..988f6dbe5c9e 100644
--- a/Source/Renderer/AutomaticUniforms.js
+++ b/Source/Renderer/AutomaticUniforms.js
@@ -1686,6 +1686,34 @@ define([
getValue : function(uniformState) {
return uniformState.invertClassificationColor;
}
+ }),
+
+ /**
+ * An automatic GLSL uniform that is used for gamma correction.
+ *
+ * @alias czm_gamma
+ * @glslUniform
+ */
+ czm_gamma : new AutomaticUniform({
+ size : 1,
+ datatype : WebGLConstants.FLOAT,
+ getValue : function(uniformState) {
+ return uniformState.gamma;
+ }
+ }),
+
+ /**
+ * An automatic GLSL uniform that defines the color of light emitted by the sun.
+ *
+ * @alias czm_sunColor
+ * @glslUniform
+ */
+ czm_sunColor: new AutomaticUniform({
+ size: 1,
+ datatype: WebGLConstants.FLOAT_VEC3,
+ getValue: function(uniformState) {
+ return uniformState.sunColor;
+ }
})
};
diff --git a/Source/Renderer/UniformState.js b/Source/Renderer/UniformState.js
index 0097e65d3dde..998c409602f4 100644
--- a/Source/Renderer/UniformState.js
+++ b/Source/Renderer/UniformState.js
@@ -46,6 +46,10 @@ define([
* @type {Texture}
*/
this.globeDepthTexture = undefined;
+ /**
+ * @type {Number}
+ */
+ this.gamma = undefined;
this._viewport = new BoundingRectangle();
this._viewportCartesian4 = new Cartesian4();
@@ -143,6 +147,7 @@ define([
this._sunPositionColumbusView = new Cartesian3();
this._sunDirectionWC = new Cartesian3();
this._sunDirectionEC = new Cartesian3();
+ this._sunColor = new Cartesian3();
this._moonDirectionEC = new Cartesian3();
this._pass = undefined;
@@ -737,6 +742,17 @@ define([
}
},
+ /**
+ * The color of the light emitted by the sun.
+ * @memberof UniformState.prototype
+ * @type {Color}
+ */
+ sunColor: {
+ get: function() {
+ return this._sunColor;
+ }
+ },
+
/**
* A normalized vector to the moon in eye coordinates at the current scene time. In 3D mode, this
* returns the actual vector from the camera position to the moon position. In 2D and Columbus View, it returns
@@ -1069,6 +1085,7 @@ define([
}
setSunAndMoonDirections(this, frameState);
+ this._sunColor = Cartesian3.clone(frameState.sunColor, this._sunColor);
var brdfLutGenerator = frameState.brdfLutGenerator;
var brdfLut = defined(brdfLutGenerator) ? brdfLutGenerator.colorTexture : undefined;
diff --git a/Source/Scene/AutoExposure.js b/Source/Scene/AutoExposure.js
new file mode 100644
index 000000000000..98ea6ad1481c
--- /dev/null
+++ b/Source/Scene/AutoExposure.js
@@ -0,0 +1,432 @@
+define([
+ '../Core/Cartesian2',
+ '../Core/Color',
+ '../Core/defaultValue',
+ '../Core/defined',
+ '../Core/defineProperties',
+ '../Core/destroyObject',
+ '../Core/PixelFormat',
+ '../Renderer/ClearCommand',
+ '../Renderer/Framebuffer',
+ '../Renderer/PixelDatatype',
+ '../Renderer/Sampler',
+ '../Renderer/Texture',
+ '../Renderer/TextureMagnificationFilter',
+ '../Renderer/TextureMinificationFilter',
+ '../Renderer/TextureWrap'
+ ], function(
+ Cartesian2,
+ Color,
+ defaultValue,
+ defined,
+ defineProperties,
+ destroyObject,
+ PixelFormat,
+ ClearCommand,
+ Framebuffer,
+ PixelDatatype,
+ Sampler,
+ Texture,
+ TextureMagnificationFilter,
+ TextureMinificationFilter,
+ TextureWrap) {
+ 'use strict';
+
+ /**
+ * A post process stage that will get the luminance value at each pixel and
+ * uses parallel reduction to compute the average luminance in a 1x1 texture.
+ * This texture can be used as input for tone mapping.
+ *
+ * @constructor
+ * @private
+ */
+ function AutoExposure() {
+ this._uniformMap = undefined;
+ this._command = undefined;
+
+ this._colorTexture = undefined;
+ this._depthTexture = undefined;
+
+ this._ready = false;
+
+ this._name = 'czm_autoexposure';
+
+ this._logDepthChanged = undefined;
+ this._useLogDepth = undefined;
+
+ this._framebuffers = undefined;
+ this._previousLuminance = undefined;
+
+ this._commands = undefined;
+ this._clearCommand = undefined;
+
+ this._minMaxLuminance = new Cartesian2();
+
+ /**
+ * Whether or not to execute this post-process stage when ready.
+ *
+ * @type {Boolean}
+ */
+ this.enabled = true;
+ this._enabled = true;
+
+ /**
+ * The minimum value used to clamp the luminance.
+ *
+ * @type {Number}
+ * @default 0.1
+ */
+ this.minimumLuminance = 0.1;
+
+ /**
+ * The maximum value used to clamp the luminance.
+ *
+ * @type {Number}
+ * @default 10.0
+ */
+ this.maximumLuminance = 10.0;
+ }
+
+ defineProperties(AutoExposure.prototype, {
+ /**
+ * Determines if this post-process stage is ready to be executed. A stage is only executed when both ready
+ * and {@link AutoExposure#enabled} are true
. A stage will not be ready while it is waiting on textures
+ * to load.
+ *
+ * @memberof AutoExposure.prototype
+ * @type {Boolean}
+ * @readonly
+ */
+ ready : {
+ get : function() {
+ return this._ready;
+ }
+ },
+ /**
+ * The unique name of this post-process stage for reference by other stages.
+ *
+ * @memberof AutoExposure.prototype
+ * @type {String}
+ * @readonly
+ */
+ name : {
+ get : function() {
+ return this._name;
+ }
+ },
+
+ /**
+ * A reference to the texture written to when executing this post process stage.
+ *
+ * @memberof AutoExposure.prototype
+ * @type {Texture}
+ * @readonly
+ * @private
+ */
+ outputTexture : {
+ get : function() {
+ var framebuffers = this._framebuffers;
+ if (!defined(framebuffers)) {
+ return undefined;
+ }
+ return framebuffers[framebuffers.length - 1].getColorTexture(0);
+ }
+ }
+ });
+
+ function destroyFramebuffers(autoexposure) {
+ var framebuffers = autoexposure._framebuffers;
+ if (!defined(framebuffers)) {
+ return;
+ }
+
+ var length = framebuffers.length;
+ for (var i = 0; i < length; ++i) {
+ framebuffers[i].destroy();
+ }
+ autoexposure._framebuffers = undefined;
+
+ autoexposure._previousLuminance.destroy();
+ autoexposure._previousLuminance = undefined;
+ }
+
+ function createFramebuffers(autoexposure, context) {
+ destroyFramebuffers(autoexposure);
+
+ var width = autoexposure._width;
+ var height = autoexposure._height;
+
+ var pixelFormat = PixelFormat.RGBA;
+ var pixelDatatype = context.halfFloatingPointTexture ? PixelDatatype.HALF_FLOAT : PixelDatatype.FLOAT;
+ var sampler = new Sampler({
+ wrapS : TextureWrap.CLAMP_TO_EDGE,
+ wrapT : TextureWrap.CLAMP_TO_EDGE,
+ minificationFilter : TextureMinificationFilter.NEAREST,
+ magnificationFilter : TextureMagnificationFilter.NEAREST
+ });
+
+ var length = Math.ceil(Math.log(Math.max(width, height)) / Math.log(3.0));
+ var framebuffers = new Array(length);
+ for (var i = 0; i < length; ++i) {
+ width = Math.max(Math.ceil(width / 3.0), 1.0);
+ height = Math.max(Math.ceil(height / 3.0), 1.0);
+ framebuffers[i] = new Framebuffer({
+ context : context,
+ colorTextures : [new Texture({
+ context : context,
+ width : width,
+ height : height,
+ pixelFormat : pixelFormat,
+ pixelDatatype : pixelDatatype,
+ sampler : sampler
+ })]
+ });
+ }
+
+ var lastTexture = framebuffers[length - 1].getColorTexture(0);
+ autoexposure._previousLuminance = new Framebuffer({
+ context : context,
+ colorTextures : [new Texture({
+ context : context,
+ width : lastTexture.width,
+ height : lastTexture.height,
+ pixelFormat : pixelFormat,
+ pixelDatatype : pixelDatatype,
+ sampler : sampler
+ })]
+ });
+
+ autoexposure._framebuffers = framebuffers;
+ }
+
+ function destroyCommands(autoexposure) {
+ var commands = autoexposure._commands;
+ if (!defined(commands)) {
+ return;
+ }
+
+ var length = commands.length;
+ for (var i = 0; i < length; ++i) {
+ commands[i].shaderProgram.destroy();
+ }
+ autoexposure._commands = undefined;
+ }
+
+ function createUniformMap(autoexposure, index) {
+ var uniforms;
+ if (index === 0) {
+ uniforms = {
+ colorTexture : function() {
+ return autoexposure._colorTexture;
+ },
+ colorTextureDimensions : function() {
+ return autoexposure._colorTexture.dimensions;
+ }
+ };
+ } else {
+ var texture = autoexposure._framebuffers[index - 1].getColorTexture(0);
+ uniforms = {
+ colorTexture : function() {
+ return texture;
+ },
+ colorTextureDimensions : function() {
+ return texture.dimensions;
+ }
+ };
+ }
+
+ uniforms.minMaxLuminance = function() {
+ return autoexposure._minMaxLuminance;
+ };
+ uniforms.previousLuminance = function() {
+ return autoexposure._previousLuminance.getColorTexture(0);
+ };
+
+ return uniforms;
+ }
+
+ function getShaderSource(index, length) {
+ var source =
+ 'uniform sampler2D colorTexture; \n' +
+ 'varying vec2 v_textureCoordinates; \n' +
+ 'float sampleTexture(vec2 offset) { \n';
+
+ if (index === 0) {
+ source +=
+ ' vec4 color = texture2D(colorTexture, v_textureCoordinates + offset); \n' +
+ ' return czm_luminance(color.rgb); \n';
+ } else {
+ source +=
+ ' return texture2D(colorTexture, v_textureCoordinates + offset).r; \n';
+ }
+
+ source += '}\n\n';
+
+ source +=
+ 'uniform vec2 colorTextureDimensions; \n' +
+ 'uniform vec2 minMaxLuminance; \n' +
+ 'uniform sampler2D previousLuminance; \n' +
+ 'void main() { \n' +
+ ' float color = 0.0; \n' +
+ ' float xStep = 1.0 / colorTextureDimensions.x; \n' +
+ ' float yStep = 1.0 / colorTextureDimensions.y; \n' +
+ ' int count = 0; \n' +
+ ' for (int i = 0; i < 3; ++i) { \n' +
+ ' for (int j = 0; j < 3; ++j) { \n' +
+ ' vec2 offset; \n' +
+ ' offset.x = -xStep + float(i) * xStep; \n' +
+ ' offset.y = -yStep + float(j) * yStep; \n' +
+ ' if (offset.x < 0.0 || offset.x > 1.0 || offset.y < 0.0 || offset.y > 1.0) { \n' +
+ ' continue; \n' +
+ ' } \n' +
+ ' color += sampleTexture(offset); \n' +
+ ' ++count; \n' +
+ ' } \n' +
+ ' } \n' +
+ ' if (count > 0) { \n' +
+ ' color /= float(count); \n' +
+ ' } \n';
+
+ if (index === length - 1) {
+ source +=
+ ' float previous = texture2D(previousLuminance, vec2(0.5)).r; \n' +
+ ' color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n' +
+ ' color = previous + (color - previous) / (60.0 * 1.5); \n' +
+ ' color = clamp(color, minMaxLuminance.x, minMaxLuminance.y); \n';
+ }
+
+ source +=
+ ' gl_FragColor = vec4(color); \n' +
+ '} \n';
+ return source;
+ }
+
+ function createCommands(autoexposure, context) {
+ destroyCommands(autoexposure);
+ var framebuffers = autoexposure._framebuffers;
+ var length = framebuffers.length;
+
+ var commands = new Array(length);
+
+ for (var i = 0; i < length; ++i) {
+ commands[i] = context.createViewportQuadCommand(getShaderSource(i, length), {
+ framebuffer : framebuffers[i],
+ uniformMap : createUniformMap(autoexposure, i)
+ });
+ }
+ autoexposure._commands = commands;
+ }
+
+ /**
+ * A function that will be called before execute. Used to clear any textures attached to framebuffers.
+ * @param {Context} context The context.
+ * @private
+ */
+ AutoExposure.prototype.clear = function(context) {
+ var framebuffers = this._framebuffers;
+ if (!defined(framebuffers)) {
+ return;
+ }
+
+ var clearCommand = this._clearCommand;
+ if (!defined(clearCommand)) {
+ clearCommand = this._clearCommand = new ClearCommand({
+ color : new Color(0.0, 0.0, 0.0, 0.0),
+ framebuffer : undefined
+ });
+ }
+
+ var length = framebuffers.length;
+ for (var i = 0; i < length; ++i) {
+ clearCommand.framebuffer = framebuffers[i];
+ clearCommand.execute(context);
+ }
+ };
+
+ /**
+ * A function that will be called before execute. Used to create WebGL resources and load any textures.
+ * @param {Context} context The context.
+ * @private
+ */
+ AutoExposure.prototype.update = function(context) {
+ var width = context.drawingBufferWidth;
+ var height = context.drawingBufferHeight;
+
+ if (width !== this._width || height !== this._height) {
+ this._width = width;
+ this._height = height;
+
+ createFramebuffers(this, context);
+ createCommands(this, context);
+
+ if (!this._ready) {
+ this._ready = true;
+ }
+ }
+
+ this._minMaxLuminance.x = this.minimumLuminance;
+ this._minMaxLuminance.y = this.maximumLuminance;
+
+ var framebuffers = this._framebuffers;
+ var temp = framebuffers[framebuffers.length - 1];
+ framebuffers[framebuffers.length - 1] = this._previousLuminance;
+ this._commands[this._commands.length - 1].framebuffer = this._previousLuminance;
+ this._previousLuminance = temp;
+ };
+
+ /**
+ * Executes the post-process stage. The color texture is the texture rendered to by the scene or from the previous stage.
+ * @param {Context} context The context.
+ * @param {Texture} colorTexture The input color texture.
+ * @private
+ */
+ AutoExposure.prototype.execute = function(context, colorTexture) {
+ this._colorTexture = colorTexture;
+
+ var commands = this._commands;
+ if (!defined(commands)) {
+ return;
+ }
+
+ var length = commands.length;
+ for (var i = 0; i < length; ++i) {
+ commands[i].execute(context);
+ }
+ };
+
+ /**
+ * Returns true if this object was destroyed; otherwise, false.
+ *
+ * If this object was destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception.
+ *
true
if this object was destroyed; otherwise, false
.
+ *
+ * @see AutoExposure#destroy
+ */
+ AutoExposure.prototype.isDestroyed = function() {
+ return false;
+ };
+
+ /**
+ * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
+ * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
+ *
+ * Once an object is destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
+ * assign the return value (undefined
) to the object as done in the example.
+ *
diff --git a/Source/Scene/PostProcessStageTextureCache.js b/Source/Scene/PostProcessStageTextureCache.js index dd20f961042e..c3a57eafa7e4 100644 --- a/Source/Scene/PostProcessStageTextureCache.js +++ b/Source/Scene/PostProcessStageTextureCache.js @@ -77,6 +77,8 @@ define([ return previousName; } + var originalDependency = previousName; + var inSeries = !defined(composite.inputPreviousStageTexture) || composite.inputPreviousStageTexture; var currentName = previousName; var length = composite.length; @@ -96,14 +98,21 @@ define([ // Stages not in a series depend on every stage executed before it since it could reference it as a uniform. // This prevents looking at the dependencies of each stage in the composite, but might create more framebuffers than necessary. // In practice, there are only 2-3 stages in these composites. + var j; + var name; if (!inSeries) { - for (var j = 1; j < length; ++j) { - var name = getLastStageName(composite.get(j)); + for (j = 1; j < length; ++j) { + name = getLastStageName(composite.get(j)); var currentDependencies = dependencies[name]; for (var k = 0; k < j; ++k) { currentDependencies[getLastStageName(composite.get(k))] = true; } } + } else { + for (j = 1; j < length; ++j) { + name = getLastStageName(composite.get(j)); + dependencies[name][originalDependency] = true; + } } return currentName; @@ -115,10 +124,12 @@ define([ if (defined(collection.ambientOcclusion)) { var ao = collection.ambientOcclusion; var bloom = collection.bloom; + var tonemapping = collection._tonemapping; var fxaa = collection.fxaa; var previousName = getCompositeDependencies(collection, context, dependencies, ao, undefined); previousName = getCompositeDependencies(collection, context, dependencies, bloom, previousName); + previousName = getStageDependencies(collection, context, dependencies, tonemapping, previousName); previousName = getCompositeDependencies(collection, context, dependencies, collection, previousName); getStageDependencies(collection, context, dependencies, fxaa, previousName); } else { @@ -258,8 +269,9 @@ define([ var updateDependencies = this._updateDependencies; var aoEnabled = defined(collection.ambientOcclusion) && collection.ambientOcclusion.enabled && collection.ambientOcclusion._isSupported(context); var bloomEnabled = defined(collection.bloom) && collection.bloom.enabled && collection.bloom._isSupported(context); + var tonemappingEnabled = defined(collection._tonemapping) && collection._tonemapping.enabled && collection._tonemapping._isSupported(context); var fxaaEnabled = defined(collection.fxaa) && collection.fxaa.enabled && collection.fxaa._isSupported(context); - var needsCheckDimensionsUpdate = !defined(collection._activeStages) || collection._activeStages.length > 0 || aoEnabled || bloomEnabled || fxaaEnabled; + var needsCheckDimensionsUpdate = !defined(collection._activeStages) || collection._activeStages.length > 0 || aoEnabled || bloomEnabled || tonemappingEnabled || fxaaEnabled; if (updateDependencies || (!needsCheckDimensionsUpdate && this._framebuffers.length > 0)) { releaseResources(this); this._framebuffers.length = 0; diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index be9e08da12ae..4741c2d2ca3a 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -789,6 +789,12 @@ define([ this._defaultView = new View(this, camera, viewport); this._view = this._defaultView; + this._hdr = undefined; + this._hdrDirty = undefined; + this.highDynamicRange = true; + this.gamma = 2.2; + this._sunColor = new Cartesian3(1.8, 1.85, 2.0); + // Give frameState, camera, and screen space camera controller initial state before rendering updateFrameNumber(this, 0.0, JulianDate.now()); updateFrameState(this); @@ -1476,6 +1482,68 @@ define([ } }, + /** + * The value used for gamma correction. This is only used when rendering with high dynamic range. + * @memberof Scene.prototype + * @type {Number} + * @default 2.2 + */ + gamma : { + get : function() { + return this._context.uniformState.gamma; + }, + set : function(value) { + this._context.uniformState.gamma = value; + } + }, + + /** + * Whether or not to use high dynamic range rendering. + * @memberof Scene.prototype + * @type {Boolean} + * @default true + */ + highDynamicRange : { + get : function() { + return this._hdr; + }, + set : function(value) { + var context = this._context; + var hdr = value && context.depthTexture && (context.colorBufferFloat || context.colorBufferHalfFloat); + this._hdrDirty = hdr !== this._hdr; + this._hdr = hdr; + } + }, + + /** + * Whether or not high dynamic range rendering is supported. + * @memberof Scene.prototype + * @type {Boolean} + * @default true + */ + highDynamicRangeSupported : { + get : function() { + var context = this._context; + return context.depthTexture && (context.colorBufferFloat || context.colorBufferHalfFloat); + } + }, + + /** + * Gets or sets the color of the light emitted by the sun. + * + * @memberof Scene.prototype + * @type {Cartesian3} + * @default Cartesian3(1.8, 1.85, 2.0) + */ + sunColor: { + get: function() { + return this._sunColor; + }, + set: function(value) { + this._sunColor = value; + } + }, + /** * @private */ @@ -1507,14 +1575,26 @@ define([ var derivedCommands = command.derivedCommands; - if (lightShadowsEnabled && command.receiveShadows) { - derivedCommands.shadows = ShadowMap.createReceiveDerivedCommand(lightShadowMaps, command, shadowsDirty, context, derivedCommands.shadows); - } - if (defined(command.pickId)) { derivedCommands.picking = DerivedCommand.createPickDerivedCommand(scene, command, context, derivedCommands.picking); } + if (!command.pickOnly) { + derivedCommands.depth = DerivedCommand.createDepthOnlyDerivedCommand(scene, command, context, derivedCommands.depth); + } + + derivedCommands.originalCommand = command; + + if (scene._hdr) { + derivedCommands.hdr = DerivedCommand.createHdrCommand(command, context, derivedCommands.hdr); + command = derivedCommands.hdr.command; + derivedCommands = command.derivedCommands; + } + + if (lightShadowsEnabled && command.receiveShadows) { + derivedCommands.shadows = ShadowMap.createReceiveDerivedCommand(lightShadowMaps, command, shadowsDirty, context, derivedCommands.shadows); + } + if (command.pass === Pass.TRANSLUCENT && defined(oit) && oit.isSupported()) { if (lightShadowsEnabled && command.receiveShadows) { derivedCommands.oit = defined(derivedCommands.oit) ? derivedCommands.oit : {}; @@ -1523,12 +1603,6 @@ define([ derivedCommands.oit = oit.createDerivedCommands(command, context, derivedCommands.oit); } } - - if (!command.pickOnly) { - derivedCommands.depth = DerivedCommand.createDepthOnlyDerivedCommand(scene, command, context, derivedCommands.depth); - } - - derivedCommands.originalCommand = command; } /** @@ -1553,12 +1627,15 @@ define([ } var useLogDepth = frameState.useLogDepth; + var useHdr = this._hdr; var derivedCommands = command.derivedCommands; var hasLogDepthDerivedCommands = defined(derivedCommands.logDepth); + var hasHdrCommands = defined(derivedCommands.hdr); var hasDerivedCommands = defined(derivedCommands.originalCommand); var needsLogDepthDerivedCommands = useLogDepth && !hasLogDepthDerivedCommands; - var needsDerivedCommands = !useLogDepth && !hasDerivedCommands; - command.dirty = command.dirty || needsLogDepthDerivedCommands || needsDerivedCommands; + var needsHdrCommands = useHdr && !hasHdrCommands; + var needsDerivedCommands = (!useLogDepth || !useHdr) && !hasDerivedCommands; + command.dirty = command.dirty || needsLogDepthDerivedCommands || needsHdrCommands || needsDerivedCommands; if (command.dirty) { command.dirty = false; @@ -1629,6 +1706,7 @@ define([ frameState.minimumDisableDepthTestDistance = scene._minimumDisableDepthTestDistance; frameState.invertClassification = scene.invertClassification; frameState.useLogDepth = scene._logDepthBuffer && !(scene.camera.frustum instanceof OrthographicFrustum || scene.camera.frustum instanceof OrthographicOffCenterFrustum); + frameState.sunColor = scene._sunColor; scene._actualInvertClassificationColor = Color.clone(scene.invertClassificationColor, scene._actualInvertClassificationColor); if (!InvertClassification.isTranslucencySupported(scene._context)) { @@ -1866,6 +1944,10 @@ define([ } var passes = frameState.passes; + if (!passes.pick && scene._hdr && defined(command.derivedCommands) && defined(command.derivedCommands.hdr)) { + command = command.derivedCommands.hdr.command; + } + if (passes.pick || passes.depth) { if (passes.pick && !passes.depth && defined(command.derivedCommands.picking)) { command = command.derivedCommands.picking.pickCommand; @@ -2117,7 +2199,6 @@ define([ } if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer) { - globeDepth.update(context, passState, view.viewport); globeDepth.executeCopyDepth(context, passState); } @@ -2707,8 +2788,8 @@ define([ environmentState.isReadyForAtmosphere = environmentState.isReadyForAtmosphere || globe._surface._tilesToRender.length > 0; } environmentState.skyAtmosphereCommand = defined(skyAtmosphere) ? skyAtmosphere.update(frameState) : undefined; - environmentState.skyBoxCommand = defined(scene.skyBox) ? scene.skyBox.update(frameState) : undefined; - var sunCommands = defined(scene.sun) ? scene.sun.update(frameState, view.passState) : undefined; + environmentState.skyBoxCommand = defined(scene.skyBox) ? scene.skyBox.update(frameState, scene._hdr) : undefined; + var sunCommands = defined(scene.sun) ? scene.sun.update(frameState, view.passState, scene._hdr) : undefined; environmentState.sunDrawCommand = defined(sunCommands) ? sunCommands.drawCommand : undefined; environmentState.sunComputeCommand = defined(sunCommands) ? sunCommands.computeCommand : undefined; environmentState.moonCommand = defined(scene.moon) ? scene.moon.update(frameState) : undefined; @@ -2859,7 +2940,7 @@ define([ // Globe depth is copied for the pick pass to support picking batched geometries in GroundPrimitives. var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer = defined(view.globeDepth); if (useGlobeDepthFramebuffer) { - view.globeDepth.update(context, passState, view.viewport); + view.globeDepth.update(context, passState, view.viewport, scene._hdr); view.globeDepth.clear(context, passState, clearColor); } @@ -2873,13 +2954,13 @@ define([ } var postProcess = scene.postProcessStages; - var usePostProcess = environmentState.usePostProcess = !picking && (postProcess.length > 0 || postProcess.ambientOcclusion.enabled || postProcess.fxaa.enabled || postProcess.bloom.enabled); + var usePostProcess = environmentState.usePostProcess = !picking && (scene._hdr || postProcess.length > 0 || postProcess.ambientOcclusion.enabled || postProcess.fxaa.enabled || postProcess.bloom.enabled); environmentState.usePostProcessSelected = false; if (usePostProcess) { - view.sceneFramebuffer.update(context, view.viewport); + view.sceneFramebuffer.update(context, view.viewport, scene._hdr); view.sceneFramebuffer.clear(context, passState, clearColor); - postProcess.update(context, frameState.useLogDepth); + postProcess.update(context, frameState.useLogDepth, scene._hdr); postProcess.clear(context); usePostProcess = environmentState.usePostProcess = postProcess.ready; @@ -3041,6 +3122,8 @@ define([ frameState.creditDisplay.update(); } + var scratchBackgroundColor = new Color(); + function render(scene) { scene._pickPositionCacheDirty = true; @@ -3056,6 +3139,12 @@ define([ frameState.passes.postProcess = scene.postProcessStages.hasSelected; var backgroundColor = defaultValue(scene.backgroundColor, Color.BLACK); + if (scene._hdr) { + backgroundColor = Color.clone(backgroundColor, scratchBackgroundColor); + backgroundColor.red = Math.pow(backgroundColor.red, scene.gamma); + backgroundColor.green = Math.pow(backgroundColor.green, scene.gamma); + backgroundColor.blue = Math.pow(backgroundColor.blue, scene.gamma); + } frameState.backgroundColor = backgroundColor; frameState.creditDisplay.beginFrame(); @@ -3136,7 +3225,7 @@ define([ this._jobScheduler.resetBudgets(); var cameraChanged = this._view.checkForCameraUpdates(this); - var shouldRender = !this.requestRenderMode || this._renderRequested || cameraChanged || this._logDepthBufferDirty || (this.mode === SceneMode.MORPHING); + var shouldRender = !this.requestRenderMode || this._renderRequested || cameraChanged || this._logDepthBufferDirty || this._hdrDirty || (this.mode === SceneMode.MORPHING); if (!shouldRender && defined(this.maximumRenderTimeChange) && defined(this._lastRenderTime)) { var difference = Math.abs(JulianDate.secondsDifference(this._lastRenderTime, time)); shouldRender = shouldRender || difference > this.maximumRenderTimeChange; @@ -3146,6 +3235,8 @@ define([ this._lastRenderTime = JulianDate.clone(time, this._lastRenderTime); this._renderRequested = false; this._logDepthBufferDirty = false; + this._hdrDirty = false; + var frameNumber = CesiumMath.incrementWrap(frameState.frameNumber, 15000000.0, 1.0); updateFrameNumber(this, frameNumber, time); } diff --git a/Source/Scene/SceneFramebuffer.js b/Source/Scene/SceneFramebuffer.js index fe8c29116555..7b448a06113d 100644 --- a/Source/Scene/SceneFramebuffer.js +++ b/Source/Scene/SceneFramebuffer.js @@ -44,6 +44,9 @@ define([ this._idFramebuffer = undefined; this._idClearColor = new Color(0.0, 0.0, 0.0, 0.0); + + this._useHdr = undefined; + this._clearCommand = new ClearCommand({ color : new Color(0.0, 0.0, 0.0, 0.0), depth : 1.0, @@ -71,22 +74,24 @@ define([ post._depthStencilIdRenderbuffer = undefined; } - SceneFramebuffer.prototype.update = function(context, viewport) { + SceneFramebuffer.prototype.update = function(context, viewport, hdr) { var width = viewport.width; var height = viewport.height; var colorTexture = this._colorTexture; - if (defined(colorTexture) && colorTexture.width === width && colorTexture.height === height) { + if (defined(colorTexture) && colorTexture.width === width && colorTexture.height === height && hdr === this._useHdr) { return; } destroyResources(this); + this._useHdr = hdr; + var pixelDatatype = hdr ? (context.halfFloatingPointTexture ? PixelDatatype.HALF_FLOAT : PixelDatatype.FLOAT) : PixelDatatype.UNSIGNED_BYTE; this._colorTexture = new Texture({ context : context, width : width, height : height, pixelFormat : PixelFormat.RGBA, - pixelDatatype : PixelDatatype.UNSIGNED_BYTE, + pixelDatatype : pixelDatatype, sampler : new Sampler({ wrapS : TextureWrap.CLAMP_TO_EDGE, wrapT : TextureWrap.CLAMP_TO_EDGE, diff --git a/Source/Scene/SkyBox.js b/Source/Scene/SkyBox.js index c2c9ed8b26ef..15a1f360072f 100644 --- a/Source/Scene/SkyBox.js +++ b/Source/Scene/SkyBox.js @@ -14,6 +14,7 @@ define([ '../Renderer/loadCubeMap', '../Renderer/RenderState', '../Renderer/ShaderProgram', + '../Renderer/ShaderSource', '../Renderer/VertexArray', '../Shaders/SkyBoxFS', '../Shaders/SkyBoxVS', @@ -35,6 +36,7 @@ define([ loadCubeMap, RenderState, ShaderProgram, + ShaderSource, VertexArray, SkyBoxFS, SkyBoxVS, @@ -98,6 +100,9 @@ define([ owner : this }); this._cubeMap = undefined; + + this._attributeLocations = undefined; + this._useHdr = undefined; } /** @@ -111,7 +116,7 @@ define([ * @exception {DeveloperError} this.sources is required and must have positiveX, negativeX, positiveY, negativeY, positiveZ, and negativeZ properties. * @exception {DeveloperError} this.sources properties must all be the same type. */ - SkyBox.prototype.update = function(frameState) { + SkyBox.prototype.update = function(frameState, useHdr) { var that = this; if (!this.show) { @@ -181,7 +186,7 @@ define([ dimensions : new Cartesian3(2.0, 2.0, 2.0), vertexFormat : VertexFormat.POSITION_ONLY })); - var attributeLocations = GeometryPipeline.createAttributeLocations(geometry); + var attributeLocations = this._attributeLocations = GeometryPipeline.createAttributeLocations(geometry); command.vertexArray = VertexArray.fromGeometry({ context : context, @@ -190,16 +195,23 @@ define([ bufferUsage : BufferUsage.STATIC_DRAW }); + command.renderState = RenderState.fromCache({ + blending : BlendingState.ALPHA_BLEND + }); + } + + if (!defined(command.shaderProgram) || this._useHdr !== useHdr) { + var fs = new ShaderSource({ + defines : [useHdr ? 'HDR' : ''], + sources : [SkyBoxFS] + }); command.shaderProgram = ShaderProgram.fromCache({ context : context, vertexShaderSource : SkyBoxVS, - fragmentShaderSource : SkyBoxFS, - attributeLocations : attributeLocations - }); - - command.renderState = RenderState.fromCache({ - blending : BlendingState.ALPHA_BLEND + fragmentShaderSource : fs, + attributeLocations : this._attributeLocations }); + this._useHdr = useHdr; } if (!defined(this._cubeMap)) { diff --git a/Source/Scene/Sun.js b/Source/Scene/Sun.js index 383001aa558f..3009a8112058 100644 --- a/Source/Scene/Sun.js +++ b/Source/Scene/Sun.js @@ -16,6 +16,7 @@ define([ '../Renderer/BufferUsage', '../Renderer/ComputeCommand', '../Renderer/DrawCommand', + '../Renderer/PixelDatatype', '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/Texture', @@ -44,6 +45,7 @@ define([ BufferUsage, ComputeCommand, DrawCommand, + PixelDatatype, RenderState, ShaderProgram, Texture, @@ -99,6 +101,8 @@ define([ this.glowFactor = 1.0; this._glowFactorDirty = false; + this._useHdr = undefined; + var that = this; this._uniformMap = { u_texture : function() { @@ -138,7 +142,7 @@ define([ /** * @private */ - Sun.prototype.update = function(frameState, passState) { + Sun.prototype.update = function(frameState, passState, useHdr) { if (!this.show) { return undefined; } @@ -159,11 +163,13 @@ define([ if (!defined(this._texture) || drawingBufferWidth !== this._drawingBufferWidth || drawingBufferHeight !== this._drawingBufferHeight || - this._glowFactorDirty) { + this._glowFactorDirty || + useHdr !== this._useHdr) { this._texture = this._texture && this._texture.destroy(); this._drawingBufferWidth = drawingBufferWidth; this._drawingBufferHeight = drawingBufferHeight; this._glowFactorDirty = false; + this._useHdr = useHdr; var size = Math.max(drawingBufferWidth, drawingBufferHeight); size = Math.pow(2.0, Math.ceil(Math.log(size) / Math.log(2.0)) - 2.0); @@ -173,11 +179,13 @@ define([ // errors in the tests. size = Math.max(1.0, size); + var pixelDatatype = useHdr ? (context.halfFloatingPointTexture ? PixelDatatype.HALF_FLOAT : PixelDatatype.FLOAT) : PixelDatatype.UNSIGNED_BYTE; this._texture = new Texture({ context : context, width : size, height : size, - pixelFormat : PixelFormat.RGBA + pixelFormat : PixelFormat.RGBA, + pixelDatatype : pixelDatatype }); this._glowLengthTS = this._glowFactor * 5.0; @@ -185,9 +193,6 @@ define([ var that = this; var uniformMap = { - u_glowLengthTS : function() { - return that._glowLengthTS; - }, u_radiusTS : function() { return that._radiusTS; } diff --git a/Source/Scene/Tonemapper.js b/Source/Scene/Tonemapper.js new file mode 100644 index 000000000000..05b1bce9b565 --- /dev/null +++ b/Source/Scene/Tonemapper.js @@ -0,0 +1,58 @@ +define([ + '../Core/freezeObject' + ], function( + freezeObject) { + 'use strict'; + + /** + * A tonemapping algorithm when rendering with high dynamic range. + * + * @exports Tonemapper + * @private + */ + var Tonemapper = { + /** + * Use the Reinhard tonemapping operator. + * + * @type {Number} + * @constant + */ + REINHARD : 0, + + /** + * Use the modified Reinhard tonemapping operator. + * + * @type {Number} + * @constant + */ + MODIFIED_REINHARD : 1, + + /** + * Use the Filmic tonemapping operator. + * + * @type {Number} + * @constant + */ + FILMIC : 2, + + /** + * Use the ACES tonemapping operator. + * + * @type {Number} + * @constant + */ + ACES : 3, + + /** + * @private + */ + validate : function(tonemapper) { + return tonemapper === Tonemapper.REINHARD || + tonemapper === Tonemapper.MODIFIED_REINHARD || + tonemapper === Tonemapper.FILMIC || + tonemapper === Tonemapper.ACES; + } + }; + + return freezeObject(Tonemapper); +}); diff --git a/Source/Scene/processPbrMaterials.js b/Source/Scene/processPbrMaterials.js index 920b22b7d617..22b975fea4d5 100644 --- a/Source/Scene/processPbrMaterials.js +++ b/Source/Scene/processPbrMaterials.js @@ -481,7 +481,11 @@ define([ fragmentShader += 'vec3 LINEARtoSRGB(vec3 linearIn) \n' + '{\n' + + '#ifndef HDR \n' + ' return pow(linearIn, vec3(1.0/2.2));\n' + + '#else \n' + + ' return linearIn;\n' + + '#endif \n' + '}\n\n'; fragmentShader += '#ifdef USE_IBL_LIGHTING \n'; diff --git a/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl b/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl index c2709e847ea9..b031af4e8aa7 100644 --- a/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl +++ b/Source/Shaders/Appearances/PerInstanceColorAppearanceFS.glsl @@ -11,12 +11,14 @@ void main() normalEC = faceforward(normalEC, vec3(0.0, 0.0, 1.0), -normalEC); #endif + vec4 color = czm_gammaCorrect(v_color); + czm_materialInput materialInput; materialInput.normalEC = normalEC; materialInput.positionToEyeEC = positionToEyeEC; czm_material material = czm_getDefaultMaterial(materialInput); - material.diffuse = v_color.rgb; - material.alpha = v_color.a; + material.diffuse = color.rgb; + material.alpha = color.a; gl_FragColor = czm_phong(normalize(positionToEyeEC), material); } diff --git a/Source/Shaders/BillboardCollectionFS.glsl b/Source/Shaders/BillboardCollectionFS.glsl index b909019b4954..0fd32b8a1ceb 100644 --- a/Source/Shaders/BillboardCollectionFS.glsl +++ b/Source/Shaders/BillboardCollectionFS.glsl @@ -50,7 +50,9 @@ float getGlobeDepth(vec2 adjustedST, vec2 depthLookupST, bool applyTranslate, ve void main() { - vec4 color = texture2D(u_atlas, v_textureCoordinates) * v_color; + vec4 color = texture2D(u_atlas, v_textureCoordinates); + color = czm_gammaCorrect(color); + color *= czm_gammaCorrect(v_color); // Fully transparent parts of the billboard are not pickable. #if !defined(OPAQUE) && !defined(TRANSLUCENT) diff --git a/Source/Shaders/Builtin/Functions/fog.glsl b/Source/Shaders/Builtin/Functions/fog.glsl index c89553bc9c00..002237504979 100644 --- a/Source/Shaders/Builtin/Functions/fog.glsl +++ b/Source/Shaders/Builtin/Functions/fog.glsl @@ -1,9 +1,9 @@ /** * 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. @@ -14,6 +14,25 @@ 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); +} + +/** + * 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. + * @param {float} fogModifierConstant A constant to modify the appearance of fog. + * + * @returns {vec3} The color adjusted for fog at the distance from the camera. + */ +vec3 czm_fog(float distanceToCamera, vec3 color, vec3 fogColor, float fogModifierConstant) +{ + float scalar = distanceToCamera * czm_fogDensity; + float fog = 1.0 - exp(-((fogModifierConstant * scalar + fogModifierConstant) * (scalar * (1.0 + fogModifierConstant)))); return mix(color, fogColor, fog); } diff --git a/Source/Shaders/Builtin/Functions/gammaCorrect.glsl b/Source/Shaders/Builtin/Functions/gammaCorrect.glsl new file mode 100644 index 000000000000..39a1c162a452 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/gammaCorrect.glsl @@ -0,0 +1,23 @@ +/** + * Converts a color from RGB space to linear space. + * + * @name czm_gammaCorrect + * @glslFunction + * + * @param {vec3} color The color in RGB space. + * @returns {vec3} The color in linear space. + */ +vec3 czm_gammaCorrect(vec3 color) { +#ifdef HDR + color = pow(color, vec3(czm_gamma)); +#endif + return color; +} + +vec4 czm_gammaCorrect(vec4 color) { +#ifdef HDR + color.rgb = pow(color.rgb, vec3(czm_gamma)); + color.a = pow(color.a, 1.0 / czm_gamma); +#endif + return color; +} diff --git a/Source/Shaders/Builtin/Functions/inverseGamma.glsl b/Source/Shaders/Builtin/Functions/inverseGamma.glsl new file mode 100644 index 000000000000..c12b8400b064 --- /dev/null +++ b/Source/Shaders/Builtin/Functions/inverseGamma.glsl @@ -0,0 +1,12 @@ +/** + * Converts a color in linear space to RGB space. + * + * @name czm_inverseGamma + * @glslFunction + * + * @param {vec3} color The color in linear space. + * @returns {vec3} The color in RGB space. + */ +vec3 czm_inverseGamma(vec3 color) { + return pow(color, vec3(1.0 / czm_gamma)); +} diff --git a/Source/Shaders/Builtin/Functions/phong.glsl b/Source/Shaders/Builtin/Functions/phong.glsl index 9dc2dbc1a8e6..540f33b9c2c6 100644 --- a/Source/Shaders/Builtin/Functions/phong.glsl +++ b/Source/Shaders/Builtin/Functions/phong.glsl @@ -16,9 +16,9 @@ float czm_private_getSpecularOfMaterial(vec3 lightDirectionEC, vec3 toEyeEC, czm * * @param {vec3} toEye A normalized vector from the fragment to the eye in eye coordinates. * @param {czm_material} material The fragment's material. - * + * * @returns {vec4} The computed color. - * + * * @example * vec3 positionToEyeEC = // ... * czm_material material = // ... @@ -40,12 +40,17 @@ vec4 czm_phong(vec3 toEye, czm_material material) // Temporary workaround for adding ambient. vec3 materialDiffuse = material.diffuse * 0.5; - + vec3 ambient = materialDiffuse; vec3 color = ambient + material.emission; color += materialDiffuse * diffuse; color += material.specular * specular; +#ifdef HDR + float sunDiffuse = czm_private_getLambertDiffuseOfMaterial(czm_sunDirectionEC, material); + color += materialDiffuse * sunDiffuse * czm_sunColor; +#endif + return vec4(color, material.alpha); } diff --git a/Source/Shaders/GlobeFS.glsl b/Source/Shaders/GlobeFS.glsl index f48c2c6e6fee..fd937528bd1e 100644 --- a/Source/Shaders/GlobeFS.glsl +++ b/Source/Shaders/GlobeFS.glsl @@ -132,6 +132,10 @@ vec4 sampleAndBlend( vec3 color = value.rgb; float alpha = value.a; +#ifdef APPLY_GAMMA + color = pow(color, vec3(textureOneOverGamma)); +#endif + #ifdef APPLY_SPLIT float splitPosition = czm_imagerySplitPosition; // Split to the left @@ -160,9 +164,9 @@ vec4 sampleAndBlend( color = czm_saturation(color, textureSaturation); #endif -#ifdef APPLY_GAMMA - color = pow(color, vec3(textureOneOverGamma)); -#endif + vec4 tempColor = czm_gammaCorrect(vec4(color, alpha)); + color = tempColor.rgb; + alpha = tempColor.a; float sourceAlpha = alpha * textureAlpha; float outAlpha = mix(previousColor.a, 1.0, sourceAlpha); @@ -216,7 +220,7 @@ void main() } #endif -#if defined(SHOW_REFLECTIVE_OCEAN) || defined(ENABLE_DAYNIGHT_SHADING) +#if defined(SHOW_REFLECTIVE_OCEAN) || defined(ENABLE_DAYNIGHT_SHADING) || defined(HDR) vec3 normalMC = czm_geodeticSurfaceNormal(v_positionMC, vec3(0.0), vec3(1.0)); // normalized surface normal in model coordinates vec3 normalEC = czm_normal3D * normalMC; // normalized surface normal in eye coordiantes #endif @@ -279,7 +283,7 @@ void main() color.xyz = mix(color.xyz, material.diffuse, material.alpha); #endif -#if defined(ENABLE_VERTEX_LIGHTING) +#ifdef ENABLE_VERTEX_LIGHTING float diffuseIntensity = clamp(czm_getLambertDiffuse(czm_sunDirectionEC, normalize(v_normalEC)) * 0.9 + 0.3, 0.0, 1.0); vec4 finalColor = vec4(color.rgb * diffuseIntensity, color.a); #elif defined(ENABLE_DAYNIGHT_SHADING) @@ -302,10 +306,12 @@ void main() #endif #if defined(FOG) || defined(GROUND_ATMOSPHERE) - const float fExposure = 2.0; vec3 fogColor = colorCorrect(v_fogMieColor) + finalColor.rgb * colorCorrect(v_fogRayleighColor); +#ifndef HDR + const float fExposure = 2.0; fogColor = vec3(1.0) - exp(-fExposure * fogColor); #endif +#endif #ifdef FOG #if defined(ENABLE_VERTEX_LIGHTING) || defined(ENABLE_DAYNIGHT_SHADING) @@ -313,8 +319,13 @@ void main() fogColor *= darken; #endif +#ifdef HDR + const float modifier = 0.15; + finalColor = vec4(czm_fog(v_distance, finalColor.rgb, fogColor, modifier), finalColor.a); +#else finalColor = vec4(czm_fog(v_distance, finalColor.rgb, fogColor), finalColor.a); #endif +#endif #ifdef GROUND_ATMOSPHERE if (czm_sceneMode != czm_sceneMode3D) @@ -340,17 +351,30 @@ void main() AtmosphereColor atmosColor = computeGroundAtmosphereFromSpace(ellipsoidPosition, true); vec3 groundAtmosphereColor = colorCorrect(atmosColor.mie) + finalColor.rgb * colorCorrect(atmosColor.rayleigh); +#ifndef HDR groundAtmosphereColor = vec3(1.0) - exp(-fExposure * groundAtmosphereColor); +#endif fadeInDist = u_nightFadeDistance.x; fadeOutDist = u_nightFadeDistance.y; float sunlitAtmosphereIntensity = clamp((cameraDist - fadeOutDist) / (fadeInDist - fadeOutDist), 0.0, 1.0); + +#ifdef HDR + // Some tweaking to make HDR look better + sunlitAtmosphereIntensity = max(sunlitAtmosphereIntensity * sunlitAtmosphereIntensity, 0.03); +#endif + groundAtmosphereColor = mix(groundAtmosphereColor, fogColor, sunlitAtmosphereIntensity); #else vec3 groundAtmosphereColor = fogColor; #endif +#ifdef HDR + // Some tweaking to make HDR look better + groundAtmosphereColor = czm_saturation(groundAtmosphereColor, 1.6); +#endif + finalColor = vec4(mix(finalColor.rgb, groundAtmosphereColor, fade), finalColor.a); #endif @@ -443,7 +467,19 @@ vec4 computeWaterColor(vec3 positionEyeCoordinates, vec2 textureCoordinates, mat float surfaceReflectance = mix(0.0, mix(u_zoomedOutOceanSpecularIntensity, oceanSpecularIntensity, waveIntensity), maskValue); float specular = specularIntensity * surfaceReflectance; - return vec4(imageryColor.rgb + diffuseHighlight + nonDiffuseHighlight + specular, imageryColor.a); +#ifdef HDR + specular *= 1.4; + + float e = 0.2; + float d = 3.3; + float c = 1.7; + + vec3 color = imageryColor.rgb + (c * (vec3(e) + imageryColor.rgb * d) * (diffuseHighlight + nonDiffuseHighlight + specular)); +#else + vec3 color = imageryColor.rgb + diffuseHighlight + nonDiffuseHighlight + specular; +#endif + + return vec4(color, imageryColor.a); } #endif // #ifdef SHOW_REFLECTIVE_OCEAN diff --git a/Source/Shaders/GlobeVS.glsl b/Source/Shaders/GlobeVS.glsl index f7c1bb25263e..5e27d963bc30 100644 --- a/Source/Shaders/GlobeVS.glsl +++ b/Source/Shaders/GlobeVS.glsl @@ -162,7 +162,7 @@ void main() vec3 normalMC = czm_octDecode(encodedNormal); v_normalMC = normalMC; v_normalEC = czm_normal3D * v_normalMC; -#elif defined(SHOW_REFLECTIVE_OCEAN) || defined(ENABLE_DAYNIGHT_SHADING) || defined(GENERATE_POSITION) +#elif defined(SHOW_REFLECTIVE_OCEAN) || defined(ENABLE_DAYNIGHT_SHADING) || defined(GENERATE_POSITION) || defined(HDR) v_positionEC = (u_modifiedModelView * vec4(position, 1.0)).xyz; v_positionMC = position3DWC; // position in model coordinates #endif diff --git a/Source/Shaders/Materials/CheckerboardMaterial.glsl b/Source/Shaders/Materials/CheckerboardMaterial.glsl index 5c7a566c3132..7ef18fc45ebf 100644 --- a/Source/Shaders/Materials/CheckerboardMaterial.glsl +++ b/Source/Shaders/Materials/CheckerboardMaterial.glsl @@ -21,6 +21,7 @@ czm_material czm_getMaterial(czm_materialInput materialInput) vec4 currentColor = mix(lightColor, darkColor, b); vec4 color = czm_antialias(lightColor, darkColor, currentColor, value, 0.03); + color = czm_gammaCorrect(color); material.diffuse = color.rgb; material.alpha = color.a; diff --git a/Source/Shaders/Materials/DotMaterial.glsl b/Source/Shaders/Materials/DotMaterial.glsl index 27bfbdea9895..84df550e1faf 100644 --- a/Source/Shaders/Materials/DotMaterial.glsl +++ b/Source/Shaders/Materials/DotMaterial.glsl @@ -5,13 +5,14 @@ uniform vec2 repeat; czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); - + // From Stefan Gustavson's Procedural Textures in GLSL in OpenGL Insights float b = smoothstep(0.3, 0.32, length(fract(repeat * materialInput.st) - 0.5)); // 0.0 or 1.0 vec4 color = mix(lightColor, darkColor, b); + color = czm_gammaCorrect(color); material.diffuse = color.rgb; material.alpha = color.a; - + return material; } diff --git a/Source/Shaders/Materials/ElevationContourMaterial.glsl b/Source/Shaders/Materials/ElevationContourMaterial.glsl index 1634891eb44c..a42a62543761 100644 --- a/Source/Shaders/Materials/ElevationContourMaterial.glsl +++ b/Source/Shaders/Materials/ElevationContourMaterial.glsl @@ -16,12 +16,14 @@ czm_material czm_getMaterial(czm_materialInput materialInput) float dxc = abs(dFdx(materialInput.height)); float dyc = abs(dFdy(materialInput.height)); float dF = max(dxc, dyc) * width; - material.alpha = (distanceToContour < dF) ? 1.0 : 0.0; + float alpha = (distanceToContour < dF) ? 1.0 : 0.0; #else - material.alpha = (distanceToContour < (czm_resolutionScale * width)) ? 1.0 : 0.0; + float alpha = (distanceToContour < (czm_resolutionScale * width)) ? 1.0 : 0.0; #endif - material.diffuse = color.rgb; + vec4 outColor = czm_gammaCorrect(vec4(color.rgb, alpha)); + material.diffuse = outColor.rgb; + material.alpha = outColor.a; return material; } diff --git a/Source/Shaders/Materials/ElevationRampMaterial.glsl b/Source/Shaders/Materials/ElevationRampMaterial.glsl index 36c017422175..a9680c003f63 100644 --- a/Source/Shaders/Materials/ElevationRampMaterial.glsl +++ b/Source/Shaders/Materials/ElevationRampMaterial.glsl @@ -7,6 +7,7 @@ czm_material czm_getMaterial(czm_materialInput materialInput) czm_material material = czm_getDefaultMaterial(materialInput); float scaledHeight = clamp((materialInput.height - minimumHeight) / (maximumHeight - minimumHeight), 0.0, 1.0); vec4 rampColor = texture2D(image, vec2(scaledHeight, 0.5)); + rampColor = czm_gammaCorrect(rampColor); material.diffuse = rampColor.rgb; material.alpha = rampColor.a; return material; diff --git a/Source/Shaders/Materials/FadeMaterial.glsl b/Source/Shaders/Materials/FadeMaterial.glsl index d2283cd40c59..961d21fc707e 100644 --- a/Source/Shaders/Materials/FadeMaterial.glsl +++ b/Source/Shaders/Materials/FadeMaterial.glsl @@ -21,16 +21,17 @@ float getTime(float t, float coord) czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); - + vec2 st = materialInput.st; float s = getTime(time.x, st.s) * fadeDirection.s; float t = getTime(time.y, st.t) * fadeDirection.t; - + float u = length(vec2(s, t)); vec4 color = mix(fadeInColor, fadeOutColor, u); - + + color = czm_gammaCorrect(color); material.emission = color.rgb; material.alpha = color.a; - + return material; } diff --git a/Source/Shaders/Materials/GridMaterial.glsl b/Source/Shaders/Materials/GridMaterial.glsl index b9a24e8e1f18..536cfb1aba5d 100644 --- a/Source/Shaders/Materials/GridMaterial.glsl +++ b/Source/Shaders/Materials/GridMaterial.glsl @@ -48,10 +48,13 @@ czm_material czm_getMaterial(czm_materialInput materialInput) float sRim = smoothstep(0.8, 1.0, dRim); value *= (1.0 - sRim); - vec3 halfColor = color.rgb * 0.5; - material.diffuse = halfColor; - material.emission = halfColor; - material.alpha = color.a * (1.0 - ((1.0 - cellAlpha) * value)); + vec4 halfColor; + halfColor.rgb = color.rgb * 0.5; + halfColor.a = color.a * (1.0 - ((1.0 - cellAlpha) * value)); + halfColor = czm_gammaCorrect(halfColor); + material.diffuse = halfColor.rgb; + material.emission = halfColor.rgb; + material.alpha = halfColor.a; return material; } diff --git a/Source/Shaders/Materials/PolylineArrowMaterial.glsl b/Source/Shaders/Materials/PolylineArrowMaterial.glsl index e50c80a369e4..0de3d651bbda 100644 --- a/Source/Shaders/Materials/PolylineArrowMaterial.glsl +++ b/Source/Shaders/Materials/PolylineArrowMaterial.glsl @@ -61,6 +61,7 @@ czm_material czm_getMaterial(czm_materialInput materialInput) vec4 currentColor = mix(outsideColor, color, clamp(s + t, 0.0, 1.0)); vec4 outColor = czm_antialias(outsideColor, color, currentColor, dist); + outColor = czm_gammaCorrect(outColor); material.diffuse = outColor.rgb; material.alpha = outColor.a; return material; diff --git a/Source/Shaders/Materials/PolylineDashMaterial.glsl b/Source/Shaders/Materials/PolylineDashMaterial.glsl index aae66a543d59..35055cf80030 100644 --- a/Source/Shaders/Materials/PolylineDashMaterial.glsl +++ b/Source/Shaders/Materials/PolylineDashMaterial.glsl @@ -32,7 +32,8 @@ czm_material czm_getMaterial(czm_materialInput materialInput) discard; } + fragColor = czm_gammaCorrect(fragColor); material.emission = fragColor.rgb; material.alpha = fragColor.a; return material; -} \ No newline at end of file +} diff --git a/Source/Shaders/Materials/PolylineGlowMaterial.glsl b/Source/Shaders/Materials/PolylineGlowMaterial.glsl index 5c717701b4e9..66ae9deb8660 100644 --- a/Source/Shaders/Materials/PolylineGlowMaterial.glsl +++ b/Source/Shaders/Materials/PolylineGlowMaterial.glsl @@ -10,8 +10,13 @@ czm_material czm_getMaterial(czm_materialInput materialInput) vec2 st = materialInput.st; float glow = glowPower / abs(st.t - 0.5) - (glowPower / 0.5); - material.emission = max(vec3(glow - 1.0 + color.rgb), color.rgb); - material.alpha = clamp(0.0, 1.0, glow) * color.a; + vec4 fragColor; + fragColor.rgb = max(vec3(glow - 1.0 + color.rgb), color.rgb); + fragColor.a = clamp(0.0, 1.0, glow) * color.a; + fragColor = czm_gammaCorrect(fragColor); + + material.emission = fragColor.rgb; + material.alpha = fragColor.a; return material; } diff --git a/Source/Shaders/Materials/PolylineOutlineMaterial.glsl b/Source/Shaders/Materials/PolylineOutlineMaterial.glsl index afda276626fb..196505bc5891 100644 --- a/Source/Shaders/Materials/PolylineOutlineMaterial.glsl +++ b/Source/Shaders/Materials/PolylineOutlineMaterial.glsl @@ -7,22 +7,23 @@ varying float v_width; czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); - + vec2 st = materialInput.st; float halfInteriorWidth = 0.5 * (v_width - outlineWidth) / v_width; float b = step(0.5 - halfInteriorWidth, st.t); b *= 1.0 - step(0.5 + halfInteriorWidth, st.t); - + // Find the distance from the closest separator (region between two colors) float d1 = abs(st.t - (0.5 - halfInteriorWidth)); float d2 = abs(st.t - (0.5 + halfInteriorWidth)); float dist = min(d1, d2); - + vec4 currentColor = mix(outlineColor, color, b); vec4 outColor = czm_antialias(outlineColor, color, currentColor, dist); - + outColor = czm_gammaCorrect(outColor); + material.diffuse = outColor.rgb; material.alpha = outColor.a; - + return material; } diff --git a/Source/Shaders/Materials/RimLightingMaterial.glsl b/Source/Shaders/Materials/RimLightingMaterial.glsl index 3408b61b036c..6e3267eadd8b 100644 --- a/Source/Shaders/Materials/RimLightingMaterial.glsl +++ b/Source/Shaders/Materials/RimLightingMaterial.glsl @@ -10,9 +10,12 @@ czm_material czm_getMaterial(czm_materialInput materialInput) float d = 1.0 - dot(materialInput.normalEC, normalize(materialInput.positionToEyeEC)); float s = smoothstep(1.0 - width, 1.0, d); - material.diffuse = color.rgb; - material.emission = rimColor.rgb * s; - material.alpha = mix(color.a, rimColor.a, s); + vec4 outColor = czm_gammaCorrect(color); + vec4 outRimColor = czm_gammaCorrect(rimColor); + + material.diffuse = outColor.rgb; + material.emission = outRimColor.rgb * s; + material.alpha = mix(outColor.a, outRimColor.a, s); return material; } diff --git a/Source/Shaders/Materials/SlopeRampMaterial.glsl b/Source/Shaders/Materials/SlopeRampMaterial.glsl index 14e30a9086bb..15b27ae9e37b 100644 --- a/Source/Shaders/Materials/SlopeRampMaterial.glsl +++ b/Source/Shaders/Materials/SlopeRampMaterial.glsl @@ -4,6 +4,7 @@ czm_material czm_getMaterial(czm_materialInput materialInput) { czm_material material = czm_getDefaultMaterial(materialInput); vec4 rampColor = texture2D(image, vec2(materialInput.slope, 0.5)); + rampColor = czm_gammaCorrect(rampColor); material.diffuse = rampColor.rgb; material.alpha = rampColor.a; return material; diff --git a/Source/Shaders/Materials/StripeMaterial.glsl b/Source/Shaders/Materials/StripeMaterial.glsl index 330c92df9a8c..9df8c95999ae 100644 --- a/Source/Shaders/Materials/StripeMaterial.glsl +++ b/Source/Shaders/Materials/StripeMaterial.glsl @@ -12,12 +12,13 @@ czm_material czm_getMaterial(czm_materialInput materialInput) float coord = mix(materialInput.st.s, materialInput.st.t, float(horizontal)); float value = fract((coord - offset) * (repeat * 0.5)); float dist = min(value, min(abs(value - 0.5), 1.0 - value)); - + vec4 currentColor = mix(evenColor, oddColor, step(0.5, value)); vec4 color = czm_antialias(evenColor, oddColor, currentColor, dist); - + color = czm_gammaCorrect(color); + material.diffuse = color.rgb; material.alpha = color.a; - + return material; } diff --git a/Source/Shaders/Materials/Water.glsl b/Source/Shaders/Materials/Water.glsl index 50f1b8ce6c37..7c529b5e8230 100644 --- a/Source/Shaders/Materials/Water.glsl +++ b/Source/Shaders/Materials/Water.glsl @@ -16,41 +16,43 @@ czm_material czm_getMaterial(czm_materialInput materialInput) czm_material material = czm_getDefaultMaterial(materialInput); float time = czm_frameNumber * animationSpeed; - + // fade is a function of the distance from the fragment and the frequency of the waves float fade = max(1.0, (length(materialInput.positionToEyeEC) / 10000000000.0) * frequency * fadeFactor); - + float specularMapValue = texture2D(specularMap, materialInput.st).r; - + // note: not using directional motion at this time, just set the angle to 0.0; vec4 noise = czm_getWaterNoise(normalMap, materialInput.st * frequency, time, 0.0); vec3 normalTangentSpace = noise.xyz * vec3(1.0, 1.0, (1.0 / amplitude)); - + // fade out the normal perturbation as we move further from the water surface normalTangentSpace.xy /= fade; - + // attempt to fade out the normal perturbation as we approach non water areas (low specular map value) normalTangentSpace = mix(vec3(0.0, 0.0, 50.0), normalTangentSpace, specularMapValue); - + normalTangentSpace = normalize(normalTangentSpace); - + // get ratios for alignment of the new normal vector with a vector perpendicular to the tangent plane float tsPerturbationRatio = clamp(dot(normalTangentSpace, vec3(0.0, 0.0, 1.0)), 0.0, 1.0); - + // fade out water effect as specular map value decreases material.alpha = specularMapValue; - + // base color is a blend of the water and non-water color based on the value from the specular map // may need a uniform blend factor to better control this material.diffuse = mix(blendColor.rgb, baseWaterColor.rgb, specularMapValue); - + // diffuse highlights are based on how perturbed the normal is material.diffuse += (0.1 * tsPerturbationRatio); - + + material.diffuse = material.diffuse; + material.normal = normalize(materialInput.tangentToEyeMatrix * normalTangentSpace); - + material.specular = specularIntensity; material.shininess = 10.0; - + return material; } diff --git a/Source/Shaders/PointPrimitiveCollectionFS.glsl b/Source/Shaders/PointPrimitiveCollectionFS.glsl index 9c3fbb0b58e6..b7586705435e 100644 --- a/Source/Shaders/PointPrimitiveCollectionFS.glsl +++ b/Source/Shaders/PointPrimitiveCollectionFS.glsl @@ -38,6 +38,6 @@ void main() #endif #endif - gl_FragColor = color; + gl_FragColor = czm_gammaCorrect(color); czm_writeLogDepth(); } diff --git a/Source/Shaders/PostProcessStages/AcesTonemapping.glsl b/Source/Shaders/PostProcessStages/AcesTonemapping.glsl new file mode 100644 index 000000000000..94e905ad8012 --- /dev/null +++ b/Source/Shaders/PostProcessStages/AcesTonemapping.glsl @@ -0,0 +1,31 @@ +uniform sampler2D colorTexture; + +varying vec2 v_textureCoordinates; + +#ifdef AUTO_EXPOSURE +uniform sampler2D autoExposure; +#endif + +// See: +// https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ + +void main() +{ + vec3 color = texture2D(colorTexture, v_textureCoordinates).rgb; + +#ifdef AUTO_EXPOSURE + color /= texture2D(autoExposure, vec2(0.5)).r; +#endif + float g = 0.985; + + float a = 0.065; + float b = 0.0001; + float c = 0.433; + float d = 0.238; + + color = (color * (color + a) - b) / (color * (g * color + c) + d); + + color = clamp(color, 0.0, 1.0); + color = czm_inverseGamma(color); + gl_FragColor = vec4(color, 1.0); +} diff --git a/Source/Shaders/PostProcessStages/FilmicTonemapping.glsl b/Source/Shaders/PostProcessStages/FilmicTonemapping.glsl new file mode 100644 index 000000000000..425166663a2e --- /dev/null +++ b/Source/Shaders/PostProcessStages/FilmicTonemapping.glsl @@ -0,0 +1,35 @@ +uniform sampler2D colorTexture; + +varying vec2 v_textureCoordinates; + +#ifdef AUTO_EXPOSURE +uniform sampler2D autoExposure; +#endif + +// See slides 142 and 143: +// http://www.gdcvault.com/play/1012459/Uncharted_2__HDR_Lighting + +void main() +{ + vec3 color = texture2D(colorTexture, v_textureCoordinates).rgb; + +#ifdef AUTO_EXPOSURE + float exposure = texture2D(autoExposure, vec2(0.5)).r; + color /= exposure; +#endif + + const float A = 0.22; // shoulder strength + const float B = 0.30; // linear strength + const float C = 0.10; // linear angle + const float D = 0.20; // toe strength + const float E = 0.01; // toe numerator + const float F = 0.30; // toe denominator + + const float white = 11.2; // linear white point value + + vec3 c = ((color * (A * color + C * B) + D * E) / (color * ( A * color + B) + D * F)) - E / F; + float w = ((white * (A * white + C * B) + D * E) / (white * ( A * white + B) + D * F)) - E / F; + + c = czm_inverseGamma(c / w); + gl_FragColor = vec4(c, 1.0); +} diff --git a/Source/Shaders/PostProcessStages/ModifiedReinhardTonemapping.glsl b/Source/Shaders/PostProcessStages/ModifiedReinhardTonemapping.glsl new file mode 100644 index 000000000000..bfa3988720fe --- /dev/null +++ b/Source/Shaders/PostProcessStages/ModifiedReinhardTonemapping.glsl @@ -0,0 +1,23 @@ +uniform sampler2D colorTexture; +uniform vec3 white; + +varying vec2 v_textureCoordinates; + +#ifdef AUTO_EXPOSURE +uniform sampler2D autoExposure; +#endif + +// See equation 4: +// http://www.cs.utah.edu/~reinhard/cdrom/tonemap.pdf + +void main() +{ + vec3 color = texture2D(colorTexture, v_textureCoordinates).rgb; +#ifdef AUTO_EXPOSURE + float exposure = texture2D(autoExposure, vec2(0.5)).r; + color /= exposure; +#endif + color = (color * (1.0 + color / white)) / (1.0 + color); + color = czm_inverseGamma(color); + gl_FragColor = vec4(color, 1.0); +} diff --git a/Source/Shaders/PostProcessStages/ReinhardTonemapping.glsl b/Source/Shaders/PostProcessStages/ReinhardTonemapping.glsl new file mode 100644 index 000000000000..29f06ea2f6a5 --- /dev/null +++ b/Source/Shaders/PostProcessStages/ReinhardTonemapping.glsl @@ -0,0 +1,22 @@ +uniform sampler2D colorTexture; + +varying vec2 v_textureCoordinates; + +#ifdef AUTO_EXPOSURE +uniform sampler2D autoExposure; +#endif + +// See equation 3: +// http://www.cs.utah.edu/~reinhard/cdrom/tonemap.pdf + +void main() +{ + vec3 color = texture2D(colorTexture, v_textureCoordinates).rgb; +#ifdef AUTO_EXPOSURE + float exposure = texture2D(autoExposure, vec2(0.5)).r; + color /= exposure; +#endif + color = color / (1.0 + color); + color = czm_inverseGamma(color); + gl_FragColor = vec4(color, 1.0); +} diff --git a/Source/Shaders/ShadowVolumeAppearanceFS.glsl b/Source/Shaders/ShadowVolumeAppearanceFS.glsl index 6b78c130eac9..ab8903295863 100644 --- a/Source/Shaders/ShadowVolumeAppearanceFS.glsl +++ b/Source/Shaders/ShadowVolumeAppearanceFS.glsl @@ -96,15 +96,16 @@ void main(void) #ifdef PER_INSTANCE_COLOR + vec4 color = czm_gammaCorrect(v_color); #ifdef FLAT - gl_FragColor = v_color; + gl_FragColor = color; #else // FLAT czm_materialInput materialInput; materialInput.normalEC = normalEC; materialInput.positionToEyeEC = -eyeCoordinate.xyz; czm_material material = czm_getDefaultMaterial(materialInput); - material.diffuse = v_color.rgb; - material.alpha = v_color.a; + material.diffuse = color.rgb; + material.alpha = color.a; gl_FragColor = czm_phong(normalize(-eyeCoordinate.xyz), material); #endif // FLAT diff --git a/Source/Shaders/ShadowVolumeFS.glsl b/Source/Shaders/ShadowVolumeFS.glsl index 981c6f56fddf..b1af3c3ff493 100644 --- a/Source/Shaders/ShadowVolumeFS.glsl +++ b/Source/Shaders/ShadowVolumeFS.glsl @@ -9,7 +9,7 @@ uniform vec4 u_highlightColor; void main(void) { #ifdef VECTOR_TILE - gl_FragColor = u_highlightColor; + gl_FragColor = czm_gammaCorrect(u_highlightColor); #else gl_FragColor = vec4(1.0); #endif diff --git a/Source/Shaders/SkyAtmosphereFS.glsl b/Source/Shaders/SkyAtmosphereFS.glsl index 52935fbe6986..b90fbec67933 100644 --- a/Source/Shaders/SkyAtmosphereFS.glsl +++ b/Source/Shaders/SkyAtmosphereFS.glsl @@ -54,10 +54,12 @@ void main (void) float rayleighPhase = 0.75 * (1.0 + cosAngle * cosAngle); float miePhase = 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + cosAngle * cosAngle) / pow(1.0 + g2 - 2.0 * g * cosAngle, 1.5); - const float exposure = 2.0; - vec3 rgb = rayleighPhase * v_rayleighColor + miePhase * v_mieColor; + +#ifndef HDR + const float exposure = 1.1; rgb = vec3(1.0) - exp(-exposure * rgb); +#endif #ifdef COLOR_CORRECT // Convert rgb color to hsb diff --git a/Source/Shaders/SkyBoxFS.glsl b/Source/Shaders/SkyBoxFS.glsl index 4a4e3b52c3bc..d40f8a4e59c7 100644 --- a/Source/Shaders/SkyBoxFS.glsl +++ b/Source/Shaders/SkyBoxFS.glsl @@ -4,6 +4,6 @@ varying vec3 v_texCoord; void main() { - vec3 rgb = textureCube(u_cubeMap, normalize(v_texCoord)).rgb; - gl_FragColor = vec4(rgb, czm_morphTime); + vec4 color = textureCube(u_cubeMap, normalize(v_texCoord)); + gl_FragColor = vec4(czm_gammaCorrect(color).rgb, czm_morphTime); } diff --git a/Source/Shaders/SunFS.glsl b/Source/Shaders/SunFS.glsl index 58852482c392..68abd170627f 100644 --- a/Source/Shaders/SunFS.glsl +++ b/Source/Shaders/SunFS.glsl @@ -4,5 +4,6 @@ varying vec2 v_textureCoordinates; void main() { - gl_FragColor = texture2D(u_texture, v_textureCoordinates); + vec4 color = texture2D(u_texture, v_textureCoordinates); + gl_FragColor = czm_gammaCorrect(color); } diff --git a/Source/Shaders/SunTextureFS.glsl b/Source/Shaders/SunTextureFS.glsl index 05a674e7d4a6..d211be042ef5 100644 --- a/Source/Shaders/SunTextureFS.glsl +++ b/Source/Shaders/SunTextureFS.glsl @@ -1,4 +1,3 @@ -uniform float u_glowLengthTS; uniform float u_radiusTS; varying vec2 v_textureCoordinates; @@ -8,21 +7,21 @@ vec2 rotate(vec2 p, vec2 direction) return vec2(p.x * direction.x - p.y * direction.y, p.x * direction.y + p.y * direction.x); } -vec4 addBurst(vec2 position, vec2 direction) +vec4 addBurst(vec2 position, vec2 direction, float lengthScalar) { vec2 rotatedPosition = rotate(position, direction) * vec2(25.0, 0.75); - float radius = length(rotatedPosition); + float radius = length(rotatedPosition) * lengthScalar; float burst = 1.0 - smoothstep(0.0, 0.55, radius); - return vec4(burst); } void main() { + float lengthScalar = 2.0 / sqrt(2.0); vec2 position = v_textureCoordinates - vec2(0.5); - float radius = length(position); + float radius = length(position) * lengthScalar; float surface = step(radius, u_radiusTS); - vec4 color = vec4(1.0, 1.0, surface + 0.2, surface); + vec4 color = vec4(vec2(1.0), surface + 0.2, surface); float glow = 1.0 - smoothstep(0.0, 0.55, radius); color.ba += mix(vec2(0.0), vec2(1.0), glow) * 0.75; @@ -34,23 +33,23 @@ void main() // //for (float i = 0.4; i < 3.2; i += 1.047) { // vec2 direction = vec2(sin(i), cos(i)); - // burst += 0.4 * addBurst(position, direction); + // burst += 0.4 * addBurst(position, direction, lengthScalar); // // direction = vec2(sin(i - 0.08), cos(i - 0.08)); - // burst += 0.3 * addBurst(position, direction); + // burst += 0.3 * addBurst(position, direction, lengthScalar); //} - burst += 0.4 * addBurst(position, vec2(0.38942, 0.92106)); // angle == 0.4 - burst += 0.4 * addBurst(position, vec2(0.99235, 0.12348)); // angle == 0.4 + 1.047 - burst += 0.4 * addBurst(position, vec2(0.60327, -0.79754)); // angle == 0.4 + 1.047 * 2.0 + burst += 0.4 * addBurst(position, vec2(0.38942, 0.92106), lengthScalar); // angle == 0.4 + burst += 0.4 * addBurst(position, vec2(0.99235, 0.12348), lengthScalar); // angle == 0.4 + 1.047 + burst += 0.4 * addBurst(position, vec2(0.60327, -0.79754), lengthScalar); // angle == 0.4 + 1.047 * 2.0 - burst += 0.3 * addBurst(position, vec2(0.31457, 0.94924)); // angle == 0.4 - 0.08 - burst += 0.3 * addBurst(position, vec2(0.97931, 0.20239)); // angle == 0.4 + 1.047 - 0.08 - burst += 0.3 * addBurst(position, vec2(0.66507, -0.74678)); // angle == 0.4 + 1.047 * 2.0 - 0.08 + burst += 0.3 * addBurst(position, vec2(0.31457, 0.94924), lengthScalar); // angle == 0.4 - 0.08 + burst += 0.3 * addBurst(position, vec2(0.97931, 0.20239), lengthScalar); // angle == 0.4 + 1.047 - 0.08 + burst += 0.3 * addBurst(position, vec2(0.66507, -0.74678), lengthScalar); // angle == 0.4 + 1.047 * 2.0 - 0.08 // End of manual loop unrolling. color += clamp(burst, vec4(0.0), vec4(1.0)) * 0.15; - + gl_FragColor = clamp(color, vec4(0.0), vec4(1.0)); } diff --git a/Specs/Renderer/AutomaticUniformSpec.js b/Specs/Renderer/AutomaticUniformSpec.js index 25778ed564f9..af277d2447db 100644 --- a/Specs/Renderer/AutomaticUniformSpec.js +++ b/Specs/Renderer/AutomaticUniformSpec.js @@ -1340,4 +1340,34 @@ defineSuite([ }).contextToRender(); }); + it('has czm_gamma', function() { + context.uniformState.gamma = 1.0; + var fs = + 'void main() {' + + ' gl_FragColor = vec4(czm_gamma == 1.0);' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + }); + + it('has czm_sunColor', function() { + var us = context.uniformState; + var frameState = createFrameState(context, createMockCamera()); + frameState.sunColor = new Cartesian3(1.0, 2.0, 3.0); + us.update(frameState); + var fs = + 'void main() {' + + ' bool b0 = czm_sunColor.x == 1.0;' + + ' bool b1 = czm_sunColor.y == 2.0;' + + ' bool b2 = czm_sunColor.z == 3.0;' + + ' gl_FragColor = vec4(b0 && b1 && b2);' + + '}'; + expect({ + context : context, + fragmentShader : fs + }).contextToRender(); + }); + }, 'WebGL'); diff --git a/Specs/Scene/PostProcessStageCollectionSpec.js b/Specs/Scene/PostProcessStageCollectionSpec.js index 474fabae31c1..3dc70cb0d512 100644 --- a/Specs/Scene/PostProcessStageCollectionSpec.js +++ b/Specs/Scene/PostProcessStageCollectionSpec.js @@ -2,12 +2,16 @@ defineSuite([ 'Scene/PostProcessStageCollection', 'Core/Color', 'Scene/PostProcessStage', - 'Specs/createScene' + 'Scene/Tonemapper', + 'Specs/createScene', + 'Specs/ViewportPrimitive' ], function( PostProcessStageCollection, Color, PostProcessStage, - createScene) { + Tonemapper, + createScene, + ViewportPrimitive) { 'use strict'; var scene; @@ -235,6 +239,110 @@ defineSuite([ expect(scene.postProcessStages.getOutputTexture(stage1.name)).not.toBeDefined(); }); + it('uses Reinhard tonemapping', function() { + if (!scene.highDynamicRangeSupported) { + return; + } + + var fs = + 'void main() { \n' + + ' gl_FragColor = vec4(4.0, 0.0, 0.0, 1.0); \n' + + '} \n'; + scene.primitives.add(new ViewportPrimitive(fs)); + + scene.postProcessStages.tonemapper = Tonemapper.REINHARD; + + expect(scene).toRender([255, 0, 0, 255]); + scene.highDynamicRange = true; + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual([255, 0, 0, 255]); + expect(rgba[0]).toBeGreaterThan(0); + expect(rgba[1]).toEqual(0); + expect(rgba[2]).toEqual(0); + expect(rgba[3]).toEqual(255); + }); + scene.highDynamicRange = false; + }); + + it('uses modified Reinhard tonemapping', function() { + if (!scene.highDynamicRangeSupported) { + return; + } + + var fs = + 'void main() { \n' + + ' gl_FragColor = vec4(0.5, 0.0, 0.0, 1.0); \n' + + '} \n'; + scene.primitives.add(new ViewportPrimitive(fs)); + + scene.postProcessStages.tonemapper = Tonemapper.MODIFIED_REINHARD; + + expect(scene).toRender([127, 0, 0, 255]); + scene.highDynamicRange = true; + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual([127, 0, 0, 255]); + expect(rgba[0]).toBeGreaterThan(0); + expect(rgba[1]).toEqual(0); + expect(rgba[2]).toEqual(0); + expect(rgba[3]).toEqual(255); + }); + scene.highDynamicRange = false; + }); + + it('uses filmic tonemapping', function() { + if (!scene.highDynamicRangeSupported) { + return; + } + + var fs = + 'void main() { \n' + + ' gl_FragColor = vec4(4.0, 0.0, 0.0, 1.0); \n' + + '} \n'; + scene.primitives.add(new ViewportPrimitive(fs)); + + scene.postProcessStages.tonemapper = Tonemapper.FILMIC; + + expect(scene).toRender([255, 0, 0, 255]); + scene.highDynamicRange = true; + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual([255, 0, 0, 255]); + expect(rgba[0]).toBeGreaterThan(0); + expect(rgba[1]).toEqual(0); + expect(rgba[2]).toEqual(0); + expect(rgba[3]).toEqual(255); + }); + scene.highDynamicRange = false; + }); + + it('uses ACES tonemapping', function() { + if (!scene.highDynamicRangeSupported) { + return; + } + + var fs = + 'void main() { \n' + + ' gl_FragColor = vec4(4.0, 0.0, 0.0, 1.0); \n' + + '} \n'; + scene.primitives.add(new ViewportPrimitive(fs)); + + scene.postProcessStages.tonemapper = Tonemapper.ACES; + + expect(scene).toRender([255, 0, 0, 255]); + scene.highDynamicRange = true; + expect(scene).toRenderAndCall(function(rgba) { + expect(rgba).not.toEqual([0, 0, 0, 255]); + expect(rgba).not.toEqual([255, 0, 0, 255]); + expect(rgba[0]).toBeGreaterThan(0); + expect(rgba[1]).toEqual(0); + expect(rgba[2]).toEqual(0); + expect(rgba[3]).toEqual(255); + }); + scene.highDynamicRange = false; + }); + it('destroys', function() { var stages = new PostProcessStageCollection(); var stage = stages.add(new PostProcessStage({ diff --git a/Specs/Scene/PostProcessStageLibrarySpec.js b/Specs/Scene/PostProcessStageLibrarySpec.js index 64b9281a2648..1d73cd5cd5b9 100644 --- a/Specs/Scene/PostProcessStageLibrarySpec.js +++ b/Specs/Scene/PostProcessStageLibrarySpec.js @@ -13,7 +13,8 @@ defineSuite([ 'ThirdParty/when', 'Specs/createCanvas', 'Specs/createScene', - 'Specs/pollToPromise' + 'Specs/pollToPromise', + 'Specs/ViewportPrimitive' ], function( PostProcessStageLibrary, Cartesian3, @@ -29,7 +30,8 @@ defineSuite([ when, createCanvas, createScene, - pollToPromise) { + pollToPromise, + ViewportPrimitive) { 'use strict'; var scene; @@ -53,32 +55,6 @@ defineSuite([ scene.postProcessStages.ambientOcclusion.enabled = false; }); - var ViewportPrimitive = function(fragmentShader) { - this._fs = fragmentShader; - this._command = undefined; - }; - - ViewportPrimitive.prototype.update = function(frameState) { - if (!defined(this._command)) { - this._command = frameState.context.createViewportQuadCommand(this._fs, { - renderState : RenderState.fromCache(), - pass : Pass.OPAQUE - }); - } - frameState.commandList.push(this._command); - }; - - ViewportPrimitive.prototype.isDestroyed = function() { - return false; - }; - - ViewportPrimitive.prototype.destroy = function() { - if (defined(this._command)) { - this._command.shaderProgram = this._command.shaderProgram && this._command.shaderProgram.destroy(); - } - return destroyObject(this); - }; - var model; function loadModel(url) { diff --git a/Specs/Scene/SceneSpec.js b/Specs/Scene/SceneSpec.js index 4d299e9285bc..04c7b96b2790 100644 --- a/Specs/Scene/SceneSpec.js +++ b/Specs/Scene/SceneSpec.js @@ -700,6 +700,34 @@ defineSuite([ s.destroyForSpecs(); }); + it('renders with HDR when available', function() { + if (!scene.highDynamicRangeSupported) { + return; + } + + var s = createScene(); + s.highDynamicRange = true; + + var rectangle = Rectangle.fromDegrees(-100.0, 30.0, -90.0, 40.0); + + var rectanglePrimitive = createRectangle(rectangle, 1000.0); + rectanglePrimitive.appearance.material.uniforms.color = new Color(10.0, 0.0, 0.0, 1.0); + + var primitives = s.primitives; + primitives.add(rectanglePrimitive); + + s.camera.setView({ destination : rectangle }); + + expect(s).toRenderAndCall(function(rgba) { + expect(rgba[0]).toBeGreaterThan(0); + expect(rgba[0]).toBeLessThanOrEqualTo(255); + expect(rgba[1]).toEqual(0); + expect(rgba[2]).toEqual(0); + }); + + s.destroyForSpecs(); + }); + it('copies the globe depth', function() { var scene = createScene(); if (scene.context.depthTexture) { diff --git a/Specs/Scene/ShadowMapSpec.js b/Specs/Scene/ShadowMapSpec.js index d8609a591018..ae6d8e266d0e 100644 --- a/Specs/Scene/ShadowMapSpec.js +++ b/Specs/Scene/ShadowMapSpec.js @@ -1118,9 +1118,8 @@ defineSuite([ scene.render(); } - // Expect derived commands to be updated twice for both the floor and box, - // once on the first frame and again when the shadow map is dirty - expect(spy1.calls.count()).toEqual(4); + // When using WebGL, this value is 8. When using the stub, this value is 4. + expect(spy1.calls.count()).toBeLessThanOrEqualTo(8); expect(spy2.calls.count()).toEqual(4); box.show = false; diff --git a/Specs/ViewportPrimitive.js b/Specs/ViewportPrimitive.js new file mode 100644 index 000000000000..e52d9dd0e704 --- /dev/null +++ b/Specs/ViewportPrimitive.js @@ -0,0 +1,40 @@ +define([ + 'Core/defined', + 'Core/destroyObject', + 'Renderer/Pass', + 'Renderer/RenderState' + ], function( + defined, + destroyObject, + Pass, + RenderState) { + 'use strict'; + + var ViewportPrimitive = function(fragmentShader) { + this._fs = fragmentShader; + this._command = undefined; + }; + + ViewportPrimitive.prototype.update = function(frameState) { + if (!defined(this._command)) { + this._command = frameState.context.createViewportQuadCommand(this._fs, { + renderState : RenderState.fromCache(), + pass : Pass.OPAQUE + }); + } + frameState.commandList.push(this._command); + }; + + ViewportPrimitive.prototype.isDestroyed = function() { + return false; + }; + + ViewportPrimitive.prototype.destroy = function() { + if (defined(this._command)) { + this._command.shaderProgram = this._command.shaderProgram && this._command.shaderProgram.destroy(); + } + return destroyObject(this); + }; + + return ViewportPrimitive; +}); diff --git a/Specs/createScene.js b/Specs/createScene.js index 277548492693..ef117fcbe48e 100644 --- a/Specs/createScene.js +++ b/Specs/createScene.js @@ -37,6 +37,7 @@ define([ } var scene = new Scene(options); + scene.highDynamicRange = false; if (!!window.webglValidation) { var context = scene.context;