From f75cc7a6f70d78c48d8a25f49c1bd67bc06a3347 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Tue, 27 Aug 2024 16:53:08 -0400 Subject: [PATCH 1/8] allow changing the hdr tonemap, add pbr neutral tonemap --- .../gallery/High Dynamic Range.html | 99 ++++++-- .../Scene/PostProcessStageCollection.js | 34 +-- .../Source/Scene/PostProcessStageLibrary.js | 21 ++ packages/engine/Source/Scene/Tonemapper.js | 50 ++-- .../PBRNeutralTonemapping.glsl | 45 ++++ .../Scene/PostProcessStageCollectionSpec.js | 234 +++++++++++------- 6 files changed, 338 insertions(+), 145 deletions(-) create mode 100644 packages/engine/Source/Shaders/PostProcessStages/PBRNeutralTonemapping.glsl diff --git a/Apps/Sandcastle/gallery/High Dynamic Range.html b/Apps/Sandcastle/gallery/High Dynamic Range.html index 597ab24155eb..7df608c9bf80 100644 --- a/Apps/Sandcastle/gallery/High Dynamic Range.html +++ b/Apps/Sandcastle/gallery/High Dynamic Range.html @@ -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( @@ -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; diff --git a/packages/engine/Source/Scene/PostProcessStageCollection.js b/packages/engine/Source/Scene/PostProcessStageCollection.js index 1bdb38ca5e34..50fc6df79517 100644 --- a/packages/engine/Source/Scene/PostProcessStageCollection.js +++ b/packages/engine/Source/Scene/PostProcessStageCollection.js @@ -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 = []; @@ -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; @@ -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` * * @memberof PostProcessStageCollection.prototype * @type {Tonemapper} - * @private */ tonemapper: { get: function () { @@ -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'); @@ -339,26 +338,31 @@ 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; @@ -366,17 +370,17 @@ Object.defineProperties(PostProcessStageCollection.prototype, { 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; diff --git a/packages/engine/Source/Scene/PostProcessStageLibrary.js b/packages/engine/Source/Scene/PostProcessStageLibrary.js index 9d3f8a2fd7e2..f205b0cf687c 100644 --- a/packages/engine/Source/Scene/PostProcessStageLibrary.js +++ b/packages/engine/Source/Scene/PostProcessStageLibrary.js @@ -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"; @@ -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. diff --git a/packages/engine/Source/Scene/Tonemapper.js b/packages/engine/Source/Scene/Tonemapper.js index b799e43ee7be..5d3ea84d50b1 100644 --- a/packages/engine/Source/Scene/Tonemapper.js +++ b/packages/engine/Source/Scene/Tonemapper.js @@ -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", /** * 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) { + return ( + tonemapper === Tonemapper.REINHARD || + tonemapper === Tonemapper.MODIFIED_REINHARD || + tonemapper === Tonemapper.FILMIC || + tonemapper === Tonemapper.ACES || + tonemapper === Tonemapper.PBR_NEUTRAL + ); +} + export default Object.freeze(Tonemapper); diff --git a/packages/engine/Source/Shaders/PostProcessStages/PBRNeutralTonemapping.glsl b/packages/engine/Source/Shaders/PostProcessStages/PBRNeutralTonemapping.glsl new file mode 100644 index 000000000000..619913042e94 --- /dev/null +++ b/packages/engine/Source/Shaders/PostProcessStages/PBRNeutralTonemapping.glsl @@ -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 ) { + 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); +} + +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 + color = PBRNeutralToneMapping(color); + color = czm_inverseGamma(color); + + out_FragColor = vec4(color, fragmentColor.a); +} diff --git a/packages/engine/Specs/Scene/PostProcessStageCollectionSpec.js b/packages/engine/Specs/Scene/PostProcessStageCollectionSpec.js index 85bca942fbfb..eaa1eec8b7ed 100644 --- a/packages/engine/Specs/Scene/PostProcessStageCollectionSpec.js +++ b/packages/engine/Specs/Scene/PostProcessStageCollectionSpec.js @@ -381,110 +381,152 @@ describe( expect(scene).toRender([0, 255, 255, 255]); }); - it("uses Reinhard tonemapping", function () { - if (!scene.highDynamicRangeSupported) { - return; - } - - const fs = - "void main() { \n" + - " out_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); + describe("HDR tonemapping", () => { + const black = [0, 0, 0, 255]; + + it("uses Reinhard tonemapping", function () { + if (!scene.highDynamicRangeSupported) { + return; + } + + const fs = + "void main() { \n" + + " out_FragColor = vec4(4.0, 0.0, 0.0, 1.0); \n" + + "} \n"; + scene.primitives.add(new ViewportPrimitive(fs)); + + scene.postProcessStages.tonemapper = Tonemapper.REINHARD; + + const colorWithoutHdr = [255, 0, 0, 255]; + + expect(scene).toRender(colorWithoutHdr); + scene.highDynamicRange = true; + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual(black); + expect(rgba).not.toEqual(colorWithoutHdr); + expect(rgba[0]).withContext("r").toBeGreaterThan(0); + expect(rgba[1]).withContext("g").toEqual(0); + expect(rgba[2]).withContext("b").toEqual(0); + expect(rgba[3]).withContext("a").toEqual(255); + }); + scene.highDynamicRange = false; }); - scene.highDynamicRange = false; - }); - - it("uses modified Reinhard tonemapping", function () { - if (!scene.highDynamicRangeSupported) { - return; - } - const fs = - "void main() { \n" + - " out_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).toRenderAndCall(function (rgba) { - expect(rgba).toEqualEpsilon([127, 0, 0, 255], 5); + it("uses modified Reinhard tonemapping", function () { + if (!scene.highDynamicRangeSupported) { + return; + } + + const fs = + "void main() { \n" + + " out_FragColor = vec4(0.5, 0.0, 0.0, 1.0); \n" + + "} \n"; + scene.primitives.add(new ViewportPrimitive(fs)); + + scene.postProcessStages.tonemapper = Tonemapper.MODIFIED_REINHARD; + + const colorWithoutHdr = [127, 0, 0, 255]; + + // expect(scene).toRenderAndCall(function (rgba) { + // expect(rgba).toEqualEpsilon(colorWithoutHdr, 5); + // }); + expect(scene).toRender(colorWithoutHdr); + scene.highDynamicRange = true; + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual(black); + expect(rgba).not.toEqual(colorWithoutHdr); + expect(rgba[0]).withContext("r").toBeGreaterThan(0); + expect(rgba[1]).withContext("g").toEqual(0); + expect(rgba[2]).withContext("b").toEqual(0); + expect(rgba[3]).withContext("a").toEqual(255); + }); + scene.highDynamicRange = false; }); - 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); + + it("uses filmic tonemapping", function () { + if (!scene.highDynamicRangeSupported) { + return; + } + + const fs = + "void main() { \n" + + " out_FragColor = vec4(4.0, 0.0, 0.0, 1.0); \n" + + "} \n"; + scene.primitives.add(new ViewportPrimitive(fs)); + + scene.postProcessStages.tonemapper = Tonemapper.FILMIC; + + const colorWithoutHdr = [255, 0, 0, 255]; + + expect(scene).toRender(colorWithoutHdr); + scene.highDynamicRange = true; + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual(black); + expect(rgba).not.toEqual(colorWithoutHdr); + expect(rgba[0]).withContext("r").toBeGreaterThan(0); + expect(rgba[1]).withContext("g").toEqual(0); + expect(rgba[2]).withContext("b").toEqual(0); + expect(rgba[3]).withContext("a").toEqual(255); + }); + scene.highDynamicRange = false; }); - scene.highDynamicRange = false; - }); - it("uses filmic tonemapping", function () { - if (!scene.highDynamicRangeSupported) { - return; - } - - const fs = - "void main() { \n" + - " out_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); + it("uses ACES tonemapping", function () { + if (!scene.highDynamicRangeSupported) { + return; + } + + const fs = + "void main() { \n" + + " out_FragColor = vec4(4.0, 0.0, 0.0, 1.0); \n" + + "} \n"; + scene.primitives.add(new ViewportPrimitive(fs)); + + scene.postProcessStages.tonemapper = Tonemapper.ACES; + + const colorWithoutHdr = [255, 0, 0, 255]; + + expect(scene).toRender(colorWithoutHdr); + scene.highDynamicRange = true; + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual(black); + expect(rgba).not.toEqual(colorWithoutHdr); + expect(rgba[0]).withContext("r").toBeGreaterThan(0); + expect(rgba[1]).withContext("g").toEqual(0); + expect(rgba[2]).withContext("b").toEqual(0); + expect(rgba[3]).withContext("a").toEqual(255); + }); + scene.highDynamicRange = false; }); - scene.highDynamicRange = false; - }); - it("uses ACES tonemapping", function () { - if (!scene.highDynamicRangeSupported) { - return; - } - - const fs = - "void main() { \n" + - " out_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); + it("uses PBR Neutral tonemapping", function () { + if (!scene.highDynamicRangeSupported) { + return; + } + + const fs = + "void main() { \n" + + " out_FragColor = vec4(4.0, 0.0, 0.0, 1.0); \n" + + "} \n"; + scene.primitives.add(new ViewportPrimitive(fs)); + + scene.postProcessStages.tonemapper = Tonemapper.PBR_NEUTRAL; + + const colorWithoutHdr = [255, 0, 0, 255]; + expect(scene).toRender(colorWithoutHdr); + scene.highDynamicRange = true; + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual(black); + expect(rgba).not.toEqual(colorWithoutHdr); + expect(rgba[0]).withContext("r").toBeGreaterThan(0); + // TODO: I'm not 100% sure how to verify this. The other tests only modify the red channel... + expect(rgba[0]).withContext("r").toEqual(253); + expect(rgba[1]).withContext("g").toEqual(149); + expect(rgba[2]).withContext("b").toEqual(149); + expect(rgba[3]).withContext("a").toEqual(255); + }); + scene.highDynamicRange = false; }); - scene.highDynamicRange = false; }); it("destroys", function () { From 517884f58c1a5ab6aee2dbbef77cf805b5113db4 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Wed, 28 Aug 2024 13:05:33 -0400 Subject: [PATCH 2/8] change default tonemap when HDR is off --- .../Scene/PostProcessStageCollection.js | 6 ++--- .../Source/Scene/PostProcessStageLibrary.js | 6 ++--- packages/engine/Source/Scene/Tonemapper.js | 12 +++++----- .../Functions/pbrNeutralTonemapping.glsl} | 24 +------------------ packages/engine/Source/Shaders/GlobeFS.glsl | 4 ++-- .../Shaders/Model/AtmosphereStageFS.glsl | 2 +- .../Source/Shaders/Model/LightingStageFS.glsl | 2 +- .../PbrNeutralTonemapping.glsl | 21 ++++++++++++++++ .../Source/Shaders/SkyAtmosphereFS.glsl | 2 +- 9 files changed, 39 insertions(+), 40 deletions(-) rename packages/engine/Source/Shaders/{PostProcessStages/PBRNeutralTonemapping.glsl => Builtin/Functions/pbrNeutralTonemapping.glsl} (61%) create mode 100644 packages/engine/Source/Shaders/PostProcessStages/PbrNeutralTonemapping.glsl diff --git a/packages/engine/Source/Scene/PostProcessStageCollection.js b/packages/engine/Source/Scene/PostProcessStageCollection.js index 50fc6df79517..f8ba7031473c 100644 --- a/packages/engine/Source/Scene/PostProcessStageCollection.js +++ b/packages/engine/Source/Scene/PostProcessStageCollection.js @@ -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, { validateToneMapper } from "./Tonemapper.js"; +import Tonemapper, { validateTonemapper } from "./Tonemapper.js"; const stackScratch = []; @@ -327,7 +327,7 @@ Object.defineProperties(PostProcessStageCollection.prototype, { return; } //>>includeStart('debug', pragmas.debug); - if (!validateToneMapper(value)) { + if (!validateTonemapper(value)) { throw new DeveloperError("tonemapper was set to an invalid value."); } //>>includeEnd('debug'); @@ -357,7 +357,7 @@ Object.defineProperties(PostProcessStageCollection.prototype, { ); break; case Tonemapper.PBR_NEUTRAL: - tonemapping = PostProcessStageLibrary.createPBRNeutralTonemappingStage( + tonemapping = PostProcessStageLibrary.createPbrNeutralTonemappingStage( useAutoExposure ); break; diff --git a/packages/engine/Source/Scene/PostProcessStageLibrary.js b/packages/engine/Source/Scene/PostProcessStageLibrary.js index f205b0cf687c..c55477bac06d 100644 --- a/packages/engine/Source/Scene/PostProcessStageLibrary.js +++ b/packages/engine/Source/Scene/PostProcessStageLibrary.js @@ -14,7 +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 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"; @@ -700,11 +700,11 @@ PostProcessStageLibrary.createFilmicTonemappingStage = function ( * @return {PostProcessStage} A post-process stage that applies filmic tonemapping operator. * @private */ -PostProcessStageLibrary.createPBRNeutralTonemappingStage = function ( +PostProcessStageLibrary.createPbrNeutralTonemappingStage = function ( useAutoExposure ) { let fs = useAutoExposure ? "#define AUTO_EXPOSURE\n" : ""; - fs += PBRNeutralTonemapping; + fs += PbrNeutralTonemapping; return new PostProcessStage({ name: "czm_pbr_neutral", fragmentShader: fs, diff --git a/packages/engine/Source/Scene/Tonemapper.js b/packages/engine/Source/Scene/Tonemapper.js index 5d3ea84d50b1..3d41e0f3f469 100644 --- a/packages/engine/Source/Scene/Tonemapper.js +++ b/packages/engine/Source/Scene/Tonemapper.js @@ -5,7 +5,7 @@ */ const Tonemapper = { /** - * Use the Reinhard tonemapping operator. + * Use the Reinhard tonemapping. * * @type {string} * @constant @@ -13,7 +13,7 @@ const Tonemapper = { REINHARD: "REINHARD", /** - * Use the modified Reinhard tonemapping operator. + * Use the modified Reinhard tonemapping. * * @type {string} * @constant @@ -21,7 +21,7 @@ const Tonemapper = { MODIFIED_REINHARD: "MODIFIED_REINHARD", /** - * Use the Filmic tonemapping operator. + * Use the Filmic tonemapping. * * @type {string} * @constant @@ -29,7 +29,7 @@ const Tonemapper = { FILMIC: "FILMIC", /** - * Use the ACES tonemapping operator. + * Use the ACES tonemapping. * * @type {string} * @constant @@ -37,7 +37,7 @@ const Tonemapper = { ACES: "ACES", /** - * Use the PBRNeutral tonemapping operator. + * Use the PbrNeutral tonemapping from Khronos. * * @type {string} * @constant @@ -51,7 +51,7 @@ const Tonemapper = { * * @param {string} tonemapper */ -export function validateToneMapper(tonemapper) { +export function validateTonemapper(tonemapper) { return ( tonemapper === Tonemapper.REINHARD || tonemapper === Tonemapper.MODIFIED_REINHARD || diff --git a/packages/engine/Source/Shaders/PostProcessStages/PBRNeutralTonemapping.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrNeutralTonemapping.glsl similarity index 61% rename from packages/engine/Source/Shaders/PostProcessStages/PBRNeutralTonemapping.glsl rename to packages/engine/Source/Shaders/Builtin/Functions/pbrNeutralTonemapping.glsl index 619913042e94..79f56962538f 100644 --- a/packages/engine/Source/Shaders/PostProcessStages/PBRNeutralTonemapping.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrNeutralTonemapping.glsl @@ -3,7 +3,7 @@ // 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 ) { +vec3 czm_pbrNeutralTonemapping( vec3 color ) { const float startCompression = 0.8 - 0.04; const float desaturation = 0.15; @@ -21,25 +21,3 @@ vec3 PBRNeutralToneMapping( vec3 color ) { float g = 1.0 - 1.0 / (desaturation * (peak - newPeak) + 1.0); return mix(color, newPeak * vec3(1, 1, 1), g); } - -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 - color = PBRNeutralToneMapping(color); - color = czm_inverseGamma(color); - - out_FragColor = vec4(color, fragmentColor.a); -} diff --git a/packages/engine/Source/Shaders/GlobeFS.glsl b/packages/engine/Source/Shaders/GlobeFS.glsl index 3f9f9f0609fa..d7cf2a2ba8e2 100644 --- a/packages/engine/Source/Shaders/GlobeFS.glsl +++ b/packages/engine/Source/Shaders/GlobeFS.glsl @@ -422,7 +422,7 @@ void main() #ifdef ENABLE_CLIPPING_POLYGONS vec2 clippingPosition = v_clippingPosition; int regionIndex = v_regionIndex; - clipPolygons(u_clippingDistance, CLIPPING_POLYGON_REGIONS_LENGTH, clippingPosition, regionIndex); + clipPolygons(u_clippingDistance, CLIPPING_POLYGON_REGIONS_LENGTH, clippingPosition, regionIndex); #endif #ifdef HIGHLIGHT_FILL_TILE @@ -489,7 +489,7 @@ void main() #endif #ifndef HDR - fogColor.rgb = czm_acesTonemapping(fogColor.rgb); + fogColor.rgb = czm_pbrNeutralTonemapping(fogColor.rgb); fogColor.rgb = czm_inverseGamma(fogColor.rgb); #endif diff --git a/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl b/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl index 3a0b20e86fa0..e46dc6e68782 100644 --- a/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/AtmosphereStageFS.glsl @@ -52,7 +52,7 @@ void applyFog(inout vec4 color, vec4 groundAtmosphereColor, vec3 lightDirection, // Tonemap if HDR rendering is disabled #ifndef HDR - fogColor.rgb = czm_acesTonemapping(fogColor.rgb); + fogColor.rgb = czm_pbrNeutralTonemapping(fogColor.rgb); fogColor.rgb = czm_inverseGamma(fogColor.rgb); #endif diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 244c0f16aefd..e51c17ede10f 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -120,7 +120,7 @@ void lightingStage(inout czm_modelMaterial material, ProcessedAttributes attribu // tonemapping. However, if HDR is not enabled, we must tonemap else large // values may be clamped to 1.0 #ifndef HDR - color = czm_acesTonemapping(color); + color = czm_pbrNeutralTonemapping(color); #endif #else // unlit vec3 color = material.diffuse; diff --git a/packages/engine/Source/Shaders/PostProcessStages/PbrNeutralTonemapping.glsl b/packages/engine/Source/Shaders/PostProcessStages/PbrNeutralTonemapping.glsl new file mode 100644 index 000000000000..23f19eb27e9d --- /dev/null +++ b/packages/engine/Source/Shaders/PostProcessStages/PbrNeutralTonemapping.glsl @@ -0,0 +1,21 @@ +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 + color = czm_pbrNeutralTonemapping(color); + color = czm_inverseGamma(color); + + out_FragColor = vec4(color, fragmentColor.a); +} diff --git a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl index d8f213396452..06a3f630c327 100644 --- a/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl +++ b/packages/engine/Source/Shaders/SkyAtmosphereFS.glsl @@ -38,7 +38,7 @@ void main (void) vec4 color = computeAtmosphereColor(v_outerPositionWC, lightDirection, rayleighColor, mieColor, opacity); #ifndef HDR - color.rgb = czm_acesTonemapping(color.rgb); + color.rgb = czm_pbrNeutralTonemapping(color.rgb); color.rgb = czm_inverseGamma(color.rgb); #endif From ebc84ee6235138f74b21ee43cb98e65cc8af8c2e Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Thu, 29 Aug 2024 18:06:15 -0400 Subject: [PATCH 3/8] add exposure to tonemaps --- .../gallery/High Dynamic Range.html | 59 +++++++++++++++++-- .../engine/Source/Renderer/ShaderProgram.js | 7 +++ .../Scene/PostProcessStageCollection.js | 19 ++++++ .../Source/Scene/PostProcessStageLibrary.js | 5 ++ .../AcesTonemappingStage.glsl | 3 + .../PostProcessStages/FilmicTonemapping.glsl | 3 + .../ModifiedReinhardTonemapping.glsl | 3 + .../PbrNeutralTonemapping.glsl | 3 + .../ReinhardTonemapping.glsl | 3 + 9 files changed, 100 insertions(+), 5 deletions(-) diff --git a/Apps/Sandcastle/gallery/High Dynamic Range.html b/Apps/Sandcastle/gallery/High Dynamic Range.html index 7df608c9bf80..577acdcf0e7e 100644 --- a/Apps/Sandcastle/gallery/High Dynamic Range.html +++ b/Apps/Sandcastle/gallery/High Dynamic Range.html @@ -24,10 +24,36 @@ >

Loading...

-
+
+
+ Tonemap + +
+ Exposure + + +