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

Allow changing the HDR tonemap #12160

Merged
merged 10 commits into from
Sep 3, 2024
155 changes: 136 additions & 19 deletions Apps/Sandcastle/gallery/High Dynamic Range.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,42 +24,120 @@
>
<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"></div>
<div id="toolbar">
<div id="hdr-toggle"></div>
<span>Tonemap</span>
<span id="tonemap-select"></span>
<br />
<span>Exposure</span>
<input
type="range"
min="0.1"
max="10.0"
step="0.1"
data-bind="value: exposure, valueUpdate: 'input'"
/>
<input type="text" size="5" data-bind="value: exposure" />
</div>
<script id="cesium_sandcastle_script">
window.startup = async function (Cesium) {
"use strict";
//Sandcastle_Begin
const viewer = new Cesium.Viewer("cesiumContainer", {
terrain: Cesium.Terrain.fromWorldTerrain(),
shadows: true,
timeline: false,
animation: false,
geocoder: false,
sceneModePicker: false,
baseLayerPicker: false,
});

if (!viewer.scene.highDynamicRangeSupported) {
window.alert("This browser does not support high dynamic range.");
}

viewer.scene.camera.setView({
destination: new Cesium.Cartesian3(
-1915097.7863741855,
-4783356.851539908,
3748887.43462683
),
orientation: new Cesium.HeadingPitchRoll(
6.166004548388564,
-0.043242401760068994,
0.002179961955988574
),
endTransform: Cesium.Matrix4.IDENTITY,
});

viewer.scene.highDynamicRange = true;

Sandcastle.addToggleButton("HDR", true, function (checked) {
viewer.scene.highDynamicRange = checked;
});
Sandcastle.addToggleButton(
"HDR",
true,
function (checked) {
viewer.scene.highDynamicRange = checked;
},
"hdr-toggle"
);

const toneMapOptions = [
{
text: "PBR Neutral",
onselect: function () {
viewer.scene.postProcessStages.tonemapper =
Cesium.Tonemapper.PBR_NEUTRAL;
},
},
{
text: "Aces",
onselect: function () {
viewer.scene.postProcessStages.tonemapper =
Cesium.Tonemapper.ACES;
},
},
{
text: "Reinhard",
onselect: function () {
viewer.scene.postProcessStages.tonemapper =
Cesium.Tonemapper.REINHARD;
},
},
{
text: "Modified_Reinhard",
onselect: function () {
viewer.scene.postProcessStages.tonemapper =
Cesium.Tonemapper.MODIFIED_REINHARD;
},
},
{
text: "Filmic",
onselect: function () {
viewer.scene.postProcessStages.tonemapper =
Cesium.Tonemapper.FILMIC;
},
},
];
Sandcastle.addDefaultToolbarMenu(toneMapOptions, "tonemap-select");

const viewModel = {
exposure: 1,
};
// 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.
const toolbar = document.getElementById("toolbar");
Cesium.knockout.applyBindings(viewModel, toolbar);

Cesium.knockout
.getObservable(viewModel, "exposure")
.subscribe(function (newValue) {
viewer.scene.postProcessStages.exposure = Number.parseFloat(
newValue
);
});

const url =
"../../SampleData/models/DracoCompressed/CesiumMilkTruck.gltf";
Expand All @@ -86,7 +164,46 @@
uri: url,
scale: scale,
},
}); //Sandcastle_End
});

// set up canyon view
viewer.scene.camera.setView({
destination: new Cesium.Cartesian3(
-1915097.7863741855,
-4783356.851539908,
3748887.43462683
),
orientation: new Cesium.HeadingPitchRoll(
6.166004548388564,
-0.043242401760068994,
0.002179961955988574
),
endTransform: Cesium.Matrix4.IDENTITY,
});
// set time so the sun is overhead
viewer.clock.currentTime = new Cesium.JulianDate(2460550, 21637);

viewer.scene.debugShowFramesPerSecond = true;
// override the default home location to return to the start
viewer.homeButton.viewModel.command.beforeExecute.addEventListener(
(e) => {
e.cancel = true;
viewer.scene.camera.setView({
destination: new Cesium.Cartesian3(
-1915097.7863741855,
-4783356.851539908,
3748887.43462683
),
orientation: new Cesium.HeadingPitchRoll(
6.166004548388564,
-0.043242401760068994,
0.002179961955988574
),
endTransform: Cesium.Matrix4.IDENTITY,
});
}
);
//Sandcastle_End
};
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
Expand Down
8 changes: 5 additions & 3 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
- Enable MSAA by default with 4 samples. To turn MSAA off set `scene.msaaSamples = 1` [#12158](https://github.com/CesiumGS/cesium/pull/12158)
- Made the `time` parameter optional for `Property`, using `JulianDate.now()` as default. [#12099](https://github.com/CesiumGS/cesium/pull/12099)
- Exposes `ScreenSpaceCameraController.zoomFactor` to allow adjusting the zoom factor (speed). [#9145](https://github.com/CesiumGS/cesium/pull/9145)
- Expose the `tonemapper` property of `PostProcessStageCollection` to allow changing the tonemap used when HDR is turned on. This defaults to the [PBR Neutral Tonemap from Khronos](https://github.com/KhronosGroup/ToneMapping/tree/main/PBR_Neutral) [#12160](https://github.com/CesiumGS/cesium/pull/12160)
jjspace marked this conversation as resolved.
Show resolved Hide resolved
- The enum `Tonemapper` contains the list of valid tonemap options to use with the `tonemapper` setting
- Expose the `exposure` property of `PostProcessStageCollection` to allow changing the exposure used for the current HDR tonemap [#12160](https://github.com/CesiumGS/cesium/pull/12160)
- Added `WaterMask` globe material, which visualizes areas of water or land based on the terrain's water mask. [#12149](https://github.com/CesiumGS/cesium/pull/12149)

##### Fixes :wrench:
Expand All @@ -23,13 +26,12 @@

##### Breaking Changes :mega:

- Switched the default (non-HDR) tonemapping for models, atmosphere, and globe from ACES to [PBR Neutral Tonemap from Khronos](https://github.com/KhronosGroup/ToneMapping/tree/main/PBR_Neutral). The widens the gamut of possible colors, and provides more consistent color appearances across renderers. [#12160](https://github.com/CesiumGS/cesium/pull/12160)
- Switched the default tonemapper when HDR is turned _on_ from ACES to [PBR Neutral Tonemap from Khronos](https://github.com/KhronosGroup/ToneMapping/tree/main/PBR_Neutral). To preserve the previous behavior set `viewer.scene.postProcessStages.tonemapper = Cesium.Tonemapper.ACES;` [#12160](https://github.com/CesiumGS/cesium/pull/12160)
- `SceneTransforms.wgs84ToWindowCoordinates` has been removed. Use `SceneTransforms.worldToWindowCoordinates` instead.
- `SceneTransforms.wgs84ToDrawingBufferCoordinates` has been removed. Use `SceneTransforms.worldToDrawingBufferCoordinates` instead.

- Removed `jitter` option from `VoxelPrimitive.js`, `VoxelRenderResources.js`, and related test code in `VoxelPrimitiveSpec.js`. [#11913](https://github.com/CesiumGS/cesium/issues/11913)

- Custom specular environment maps in `ImageBasedLighting` now require either a WebGL2 context or a WebGL1 context that supports the [`EXT_shader_texture_lod` extension](https://registry.khronos.org/webgl/extensions/EXT_shader_texture_lod/).

- `ScreenSpaceCameraController._zoomFactor` replaced with public zoomFactor attribute.

### 1.120 - 2024-08-01
Expand Down
7 changes: 7 additions & 0 deletions packages/engine/Source/Renderer/ShaderProgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,13 @@ ShaderProgram.prototype._setUniforms = function (
len = manualUniforms.length;
for (i = 0; i < len; ++i) {
const mu = manualUniforms[i];

//>>includeStart('debug', pragmas.debug);
if (!defined(uniformMap[mu.name])) {
throw new DeveloperError(`Unknown uniform: ${mu.name}`);
}
//>>includeEnd('debug');
jjspace marked this conversation as resolved.
Show resolved Hide resolved

mu.value = uniformMap[mu.name]();
}
}
Expand Down
60 changes: 45 additions & 15 deletions packages/engine/Source/Scene/PostProcessStageCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import TextureWrap from "../Renderer/TextureWrap.js";
import PassThrough from "../Shaders/PostProcessStages/PassThrough.js";
import PostProcessStageLibrary from "./PostProcessStageLibrary.js";
import PostProcessStageTextureCache from "./PostProcessStageTextureCache.js";
import Tonemapper from "./Tonemapper.js";
import Tonemapper, { validateTonemapper } from "./Tonemapper.js";

const stackScratch = [];

Expand Down Expand Up @@ -41,11 +41,12 @@ function PostProcessStageCollection() {
// Some shaders, such as the atmosphere and ground atmosphere, output values slightly over 1.0.
this._autoExposureEnabled = false;
this._autoExposure = PostProcessStageLibrary.createAutoExposureStage();
this._exposure = 1.0;
this._tonemapping = undefined;
this._tonemapper = undefined;

// set tonemapper and tonemapping
this.tonemapper = Tonemapper.ACES;
// set tonemapper and tonemapping using the setter
this.tonemapper = Tonemapper.PBR_NEUTRAL;

const tonemapping = this._tonemapping;

Expand Down Expand Up @@ -313,11 +314,14 @@ Object.defineProperties(PostProcessStageCollection.prototype, {
},

/**
* Gets and sets the tonemapping algorithm used when rendering with high dynamic range.
* Specifies the tonemapping algorithm used when rendering with high dynamic range.
* {@link https://sandcastle.cesium.com/?src=High%20Dynamic%20Range.html|Sandcastle Demo}
*
* @example viewer.scene.postProcessStages.tonemapper = Cesium.Tonemapper.ACES;
*
* @default Tonemapper.PBR_NEUTRAL
* @memberof PostProcessStageCollection.prototype
* @type {Tonemapper}
* @private
*/
tonemapper: {
get: function () {
Expand All @@ -328,7 +332,7 @@ Object.defineProperties(PostProcessStageCollection.prototype, {
return;
}
//>>includeStart('debug', pragmas.debug);
if (!Tonemapper.validate(value)) {
if (!validateTonemapper(value)) {
throw new DeveloperError("tonemapper was set to an invalid value.");
}
//>>includeEnd('debug');
Expand All @@ -339,49 +343,75 @@ Object.defineProperties(PostProcessStageCollection.prototype, {
}

const useAutoExposure = this._autoExposureEnabled;
let tonemapper;
let tonemapping;

switch (value) {
case Tonemapper.REINHARD:
tonemapper = PostProcessStageLibrary.createReinhardTonemappingStage(
tonemapping = PostProcessStageLibrary.createReinhardTonemappingStage(
useAutoExposure
);
break;
case Tonemapper.MODIFIED_REINHARD:
tonemapper = PostProcessStageLibrary.createModifiedReinhardTonemappingStage(
tonemapping = PostProcessStageLibrary.createModifiedReinhardTonemappingStage(
useAutoExposure
);
break;
case Tonemapper.FILMIC:
tonemapper = PostProcessStageLibrary.createFilmicTonemappingStage(
tonemapping = PostProcessStageLibrary.createFilmicTonemappingStage(
useAutoExposure
);
break;
case Tonemapper.PBR_NEUTRAL:
tonemapping = PostProcessStageLibrary.createPbrNeutralTonemappingStage(
useAutoExposure
);
break;
default:
tonemapper = PostProcessStageLibrary.createAcesTonemappingStage(
tonemapping = PostProcessStageLibrary.createAcesTonemappingStage(
useAutoExposure
);
break;
}

if (useAutoExposure) {
const autoexposure = this._autoExposure;
tonemapper.uniforms.autoExposure = function () {
tonemapping.uniforms.autoExposure = function () {
return autoexposure.outputTexture;
};
} else {
tonemapping.uniforms.exposure = this._exposure;
}

this._tonemapper = value;
this._tonemapping = tonemapper;
this._tonemapping = tonemapping;

if (defined(this._stageNames)) {
this._stageNames[tonemapper.name] = tonemapper;
tonemapper._textureCache = this._textureCache;
this._stageNames[tonemapping.name] = tonemapping;
tonemapping._textureCache = this._textureCache;
}

this._textureCacheDirty = true;
},
},

/**
* Control the exposure when HDR is on. Less than 1.0 makes the tonemapping darker while greater than 1.0 makes it brighter.
*
* @example viewer.scene.postProcessStages.exposure = 1.0;
*
* @default 1.0
* @memberof PostProcessStageCollection.prototype
* @type {number}
jjspace marked this conversation as resolved.
Show resolved Hide resolved
*/
exposure: {
get: function () {
return this._exposure;
},
set: function (value) {
this._tonemapping.uniforms.exposure = value;
this._exposure = value;
},
},
});

function removeStages(collection) {
Expand Down
Loading