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
99 changes: 84 additions & 15 deletions Apps/Sandcastle/gallery/High Dynamic Range.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,32 +35,62 @@
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;
});

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);

const url =
"../../SampleData/models/DracoCompressed/CesiumMilkTruck.gltf";
const position = Cesium.Cartesian3.fromRadians(
Expand All @@ -86,7 +116,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
34 changes: 19 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 @@ -44,8 +44,8 @@ function PostProcessStageCollection() {
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 +313,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 +327,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,44 +338,49 @@ 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;
};
}

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;
Expand Down
21 changes: 21 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 @@ -693,6 +694,26 @@ PostProcessStageLibrary.createFilmicTonemappingStage = function (
});
};

/**
* 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,
},
});
};

/**
* Creates a post-process stage that applies Reinhard tonemapping operator.
* @param {boolean} useAutoExposure Whether or not to use auto-exposure.
Expand Down
50 changes: 31 additions & 19 deletions packages/engine/Source/Scene/Tonemapper.js
Original file line number Diff line number Diff line change
@@ -1,52 +1,64 @@
/**
* A tonemapping algorithm when rendering with high dynamic range.
*
* @enum {number}
* @private
* @enum {string}
*/
const Tonemapper = {
/**
* Use the Reinhard tonemapping operator.
*
* @type {number}
* @type {string}
* @constant
*/
REINHARD: 0,
REINHARD: "REINHARD",
jjspace marked this conversation as resolved.
Show resolved Hide resolved

/**
* Use the modified Reinhard tonemapping operator.
*
* @type {number}
* @type {string}
* @constant
*/
MODIFIED_REINHARD: 1,
MODIFIED_REINHARD: "MODIFIED_REINHARD",

/**
* Use the Filmic tonemapping operator.
*
* @type {number}
* @type {string}
* @constant
*/
FILMIC: 2,
FILMIC: "FILMIC",

/**
* Use the ACES tonemapping operator.
*
* @type {number}
* @type {string}
* @constant
*/
ACES: 3,
ACES: "ACES",

/**
* @private
* Use the PBRNeutral tonemapping operator.
*
* @type {string}
* @constant
*/
validate: function (tonemapper) {
return (
tonemapper === Tonemapper.REINHARD ||
tonemapper === Tonemapper.MODIFIED_REINHARD ||
tonemapper === Tonemapper.FILMIC ||
tonemapper === Tonemapper.ACES
);
},
PBR_NEUTRAL: "PBR_NEUTRAL",
};

/**
* Validate whether the provided value is a known Tonemapper type
* @private
*
* @param {string} tonemapper
*/
export function validateToneMapper(tonemapper) {
jjspace marked this conversation as resolved.
Show resolved Hide resolved
return (
tonemapper === Tonemapper.REINHARD ||
tonemapper === Tonemapper.MODIFIED_REINHARD ||
tonemapper === Tonemapper.FILMIC ||
tonemapper === Tonemapper.ACES ||
tonemapper === Tonemapper.PBR_NEUTRAL
);
}

export default Object.freeze(Tonemapper);
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// KhronosGroup https://github.com/KhronosGroup/ToneMapping/tree/main/PBR_Neutral

// Input color is non-negative and resides in the Linear Rec. 709 color space.
// Output color is also Linear Rec. 709, but in the [0, 1] range.

vec3 PBRNeutralToneMapping( vec3 color ) {
jjspace marked this conversation as resolved.
Show resolved Hide resolved
const float startCompression = 0.8 - 0.04;
const float desaturation = 0.15;

float x = min(color.r, min(color.g, color.b));
float offset = x < 0.08 ? x - 6.25 * x * x : 0.04;
color -= offset;

float peak = max(color.r, max(color.g, color.b));
if (peak < startCompression) return color;

const float d = 1.0 - startCompression;
float newPeak = 1.0 - d * d / (peak + d - startCompression);
color *= newPeak / peak;

float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0);
return mix(color, newPeak * vec3(1, 1, 1), g);
}
jjspace marked this conversation as resolved.
Show resolved Hide resolved

uniform sampler2D colorTexture;

in vec2 v_textureCoordinates;

#ifdef AUTO_EXPOSURE
uniform sampler2D autoExposure;
#endif

void main()
{
vec4 fragmentColor = texture(colorTexture, v_textureCoordinates);
vec3 color = fragmentColor.rgb;

#ifdef AUTO_EXPOSURE
color /= texture(autoExposure, vec2(0.5)).r;
#endif
jjspace marked this conversation as resolved.
Show resolved Hide resolved
color = PBRNeutralToneMapping(color);
color = czm_inverseGamma(color);

out_FragColor = vec4(color, fragmentColor.a);
}
Loading