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
156 changes: 137 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,121 @@
>
<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) {
console.log(newValue);
jjspace marked this conversation as resolved.
Show resolved Hide resolved
viewer.scene.postProcessStages.exposure = Number.parseFloat(
newValue
);
});

const url =
"../../SampleData/models/DracoCompressed/CesiumMilkTruck.gltf";
Expand All @@ -86,7 +165,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
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
53 changes: 38 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,10 @@ 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. Defaults to `Tonemapper.PBR_NEUTRAL`
jjspace marked this conversation as resolved.
Show resolved Hide resolved
*
* @memberof PostProcessStageCollection.prototype
* @type {Tonemapper}
* @private
*/
tonemapper: {
get: function () {
Expand All @@ -328,7 +328,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 +339,72 @@ 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. Defaults to 1.
jjspace marked this conversation as resolved.
Show resolved Hide resolved
*
* @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
26 changes: 26 additions & 0 deletions packages/engine/Source/Scene/PostProcessStageLibrary.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import DepthOfField from "../Shaders/PostProcessStages/DepthOfField.js";
import DepthView from "../Shaders/PostProcessStages/DepthView.js";
import EdgeDetection from "../Shaders/PostProcessStages/EdgeDetection.js";
import FilmicTonemapping from "../Shaders/PostProcessStages/FilmicTonemapping.js";
import PbrNeutralTonemapping from "../Shaders/PostProcessStages/PbrNeutralTonemapping.js";
import FXAA from "../Shaders/PostProcessStages/FXAA.js";
import GaussianBlur1D from "../Shaders/PostProcessStages/GaussianBlur1D.js";
import LensFlare from "../Shaders/PostProcessStages/LensFlare.js";
Expand Down Expand Up @@ -669,6 +670,7 @@ PostProcessStageLibrary.createAcesTonemappingStage = function (
fragmentShader: fs,
uniforms: {
autoExposure: undefined,
exposure: 1,
jjspace marked this conversation as resolved.
Show resolved Hide resolved
},
});
};
Expand All @@ -689,6 +691,28 @@ PostProcessStageLibrary.createFilmicTonemappingStage = function (
fragmentShader: fs,
uniforms: {
autoExposure: undefined,
exposure: 1,
},
});
};

/**
* Creates a post-process stage that applies filmic tonemapping operator.
* @param {boolean} useAutoExposure Whether or not to use auto-exposure.
* @return {PostProcessStage} A post-process stage that applies filmic tonemapping operator.
* @private
*/
PostProcessStageLibrary.createPbrNeutralTonemappingStage = function (
useAutoExposure
) {
let fs = useAutoExposure ? "#define AUTO_EXPOSURE\n" : "";
fs += PbrNeutralTonemapping;
return new PostProcessStage({
name: "czm_pbr_neutral",
fragmentShader: fs,
uniforms: {
autoExposure: undefined,
exposure: 1,
},
});
};
Expand All @@ -709,6 +733,7 @@ PostProcessStageLibrary.createReinhardTonemappingStage = function (
fragmentShader: fs,
uniforms: {
autoExposure: undefined,
exposure: 1,
},
});
};
Expand All @@ -730,6 +755,7 @@ PostProcessStageLibrary.createModifiedReinhardTonemappingStage = function (
uniforms: {
white: Color.WHITE,
autoExposure: undefined,
exposure: 1.0,
},
});
};
Expand Down
Loading