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
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- 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
- As part of this change we switched the default tonemap for models, atmosphere and globe when _not_ using HDR to the new PBR Neutral tonemap from the ACES tonemap to improve visuals.
jjspace marked this conversation as resolved.
Show resolved Hide resolved
- 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 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.PBR_NEUTRAL;
jjspace marked this conversation as resolved.
Show resolved Hide resolved
*
* @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
jjspace marked this conversation as resolved.
Show resolved Hide resolved
*
* @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