From 947ba72d40e04a2263a9daa4aed8d9c8f995c9dd Mon Sep 17 00:00:00 2001 From: YunVlad Date: Mon, 13 May 2024 17:31:02 +0300 Subject: [PATCH 01/47] Add splitDirection to PointPrimitive Added the splitDirection property to PointPrimitive. Updated PointPrimitive Collection and shaders to work with the property. --- .../engine/Source/Scene/PointPrimitive.js | 30 ++++++++++++++- .../Source/Scene/PointPrimitiveCollection.js | 37 +++++++++++++++++++ .../Shaders/PointPrimitiveCollectionFS.glsl | 4 ++ .../Shaders/PointPrimitiveCollectionVS.glsl | 3 ++ 4 files changed, 72 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Scene/PointPrimitive.js b/packages/engine/Source/Scene/PointPrimitive.js index 08307252f649..a4c9cf979bb9 100644 --- a/packages/engine/Source/Scene/PointPrimitive.js +++ b/packages/engine/Source/Scene/PointPrimitive.js @@ -11,6 +11,7 @@ import Matrix4 from "../Core/Matrix4.js"; import NearFarScalar from "../Core/NearFarScalar.js"; import SceneMode from "./SceneMode.js"; import SceneTransforms from "./SceneTransforms.js"; +import SplitDirection from "./SplitDirection.js" /** *
@@ -117,6 +118,11 @@ function PointPrimitive(options, pointPrimitiveCollection) { this._pointPrimitiveCollection = pointPrimitiveCollection; this._dirty = false; this._index = -1; //Used only by PointPrimitiveCollection + + this._splitDirection = defaultValue( + options.splitDirection, + SplitDirection.NONE + ); } const SHOW_INDEX = (PointPrimitive.SHOW_INDEX = 0); @@ -129,7 +135,8 @@ const SCALE_BY_DISTANCE_INDEX = (PointPrimitive.SCALE_BY_DISTANCE_INDEX = 6); const TRANSLUCENCY_BY_DISTANCE_INDEX = (PointPrimitive.TRANSLUCENCY_BY_DISTANCE_INDEX = 7); const DISTANCE_DISPLAY_CONDITION_INDEX = (PointPrimitive.DISTANCE_DISPLAY_CONDITION_INDEX = 8); const DISABLE_DEPTH_DISTANCE_INDEX = (PointPrimitive.DISABLE_DEPTH_DISTANCE_INDEX = 9); -PointPrimitive.NUMBER_OF_PROPERTIES = 10; +const SPLIT_DIRECTION_INDEX = (PointPrimitive.SPLIT_DIRECTION_INDEX = 10); +PointPrimitive.NUMBER_OF_PROPERTIES = 11; function makeDirty(pointPrimitive, propertyChanged) { const pointPrimitiveCollection = pointPrimitive._pointPrimitiveCollection; @@ -486,6 +493,24 @@ Object.defineProperties(PointPrimitive.prototype, { } }, }, + + /** + * The {@link SplitDirection} to apply to this point. + * @memberof PointPrimitive.prototype + * @type {SplitDirection} + * @default {@link SplitDirection.NONE} + */ + splitDirection: { + get: function () { + return this._splitDirection; + }, + set: function (value) { + if (this._splitDirection !== value) { + this._splitDirection = value; + makeDirty(this, SPLIT_DIRECTION_INDEX); + } + }, + }, }); PointPrimitive.prototype.getPickId = function (context) { @@ -657,7 +682,8 @@ PointPrimitive.prototype.equals = function (other) { this._distanceDisplayCondition, other._distanceDisplayCondition ) && - this._disableDepthTestDistance === other._disableDepthTestDistance) + this._disableDepthTestDistance === other._disableDepthTestDistance && + this._splitDirection === other._splitDirection) ); }; diff --git a/packages/engine/Source/Scene/PointPrimitiveCollection.js b/packages/engine/Source/Scene/PointPrimitiveCollection.js index ce42db08cf2b..a945c9feedc1 100644 --- a/packages/engine/Source/Scene/PointPrimitiveCollection.js +++ b/packages/engine/Source/Scene/PointPrimitiveCollection.js @@ -38,6 +38,7 @@ const DISTANCE_DISPLAY_CONDITION_INDEX = PointPrimitive.DISTANCE_DISPLAY_CONDITION_INDEX; const DISABLE_DEPTH_DISTANCE_INDEX = PointPrimitive.DISABLE_DEPTH_DISTANCE_INDEX; +const SPLIT_DIRECTION_INDEX = PointPrimitive.SPLIT_DIRECTION_INDEX; const NUMBER_OF_PROPERTIES = PointPrimitive.NUMBER_OF_PROPERTIES; const attributeLocations = { @@ -47,6 +48,7 @@ const attributeLocations = { compressedAttribute1: 3, // show, translucency by distance, some free space scaleByDistance: 4, distanceDisplayConditionAndDisableDepth: 5, + splitDirection: 6, }; /** @@ -216,6 +218,7 @@ function PointPrimitiveCollection(options) { BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // DISTANCE_DISPLAY_CONDITION_INDEX + BufferUsage.STATIC_DRAW, // SPLIT_DIRECTION_INDEX ]; const that = this; @@ -497,6 +500,12 @@ function createVAF(context, numberOfPointPrimitives, buffersUsage) { componentDatatype: ComponentDatatype.FLOAT, usage: buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX], }, + { + index: attributeLocations.splitDirection, + componentsPerAttribute: 1, + componentDatatype: ComponentDatatype.FLOAT, + usage: buffersUsage[SPLIT_DIRECTION_INDEX], + }, ], numberOfPointPrimitives ); // 1 vertex per pointPrimitive @@ -700,6 +709,24 @@ function writeDistanceDisplayConditionAndDepthDisable( writer(i, near, far, disableDepthTestDistance); } +function writeSplitDirection( + pointPrimitiveCollection, + context, + vafWriters, + pointPrimitive +) { + const i = pointPrimitive._index; + const writer = vafWriters[attributeLocations.splitDirection]; + let direction = 0.0; + + const split = pointPrimitive.splitDirection; + if (defined(split)) { + direction = split; + } + + writer(i, direction); +} + function writePointPrimitive( pointPrimitiveCollection, context, @@ -736,6 +763,12 @@ function writePointPrimitive( vafWriters, pointPrimitive ); + writeSplitDirection( + pointPrimitiveCollection, + context, + vafWriters, + pointPrimitive + ); } function recomputeActualPositions( @@ -930,6 +963,10 @@ PointPrimitiveCollection.prototype.update = function (frameState) { writers.push(writeDistanceDisplayConditionAndDepthDisable); } + if (properties[SPLIT_DIRECTION_INDEX]) { + writers.push(writeSplitDirection); + } + const numWriters = writers.length; vafWriters = this._vaf.writers; diff --git a/packages/engine/Source/Shaders/PointPrimitiveCollectionFS.glsl b/packages/engine/Source/Shaders/PointPrimitiveCollectionFS.glsl index 69fddf8ba256..edfa6e221144 100644 --- a/packages/engine/Source/Shaders/PointPrimitiveCollectionFS.glsl +++ b/packages/engine/Source/Shaders/PointPrimitiveCollectionFS.glsl @@ -3,9 +3,13 @@ in vec4 v_outlineColor; in float v_innerPercent; in float v_pixelDistance; in vec4 v_pickColor; +in float v_splitDirection; void main() { + if (v_splitDirection < 0.0 && gl_FragCoord.x > czm_splitPosition) discard; + if (v_splitDirection > 0.0 && gl_FragCoord.x < czm_splitPosition) discard; + // The distance in UV space from this fragment to the center of the point, at most 0.5. float distanceToCenter = length(gl_PointCoord - vec2(0.5)); // The max distance stops one pixel shy of the edge to leave space for anti-aliasing. diff --git a/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl b/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl index a4028d50ab1c..0e725388a042 100644 --- a/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl +++ b/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl @@ -6,12 +6,14 @@ in vec4 compressedAttribute0; // color, outlineColor, pick in vec4 compressedAttribute1; // show, translucency by distance, some free space in vec4 scaleByDistance; // near, nearScale, far, farScale in vec3 distanceDisplayConditionAndDisableDepth; // near, far, disableDepthTestDistance +in float splitDirection; // splitDirection out vec4 v_color; out vec4 v_outlineColor; out float v_innerPercent; out float v_pixelDistance; out vec4 v_pickColor; +out float v_splitDirection; const float SHIFT_LEFT8 = 256.0; const float SHIFT_RIGHT8 = 1.0 / 256.0; @@ -180,4 +182,5 @@ void main() gl_Position *= show; v_pickColor = pickColor; + v_splitDirection = splitDirection; } From ddea3b718a847bfb60fb38d65c27ed340bdfa503 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Mon, 13 May 2024 17:59:28 +0300 Subject: [PATCH 02/47] Add splitDirection to PointPrimitive Added the splitDirection property to PointPrimitive. Updated PointGraphics and PointVisualizer. --- .../engine/Source/DataSources/PointGraphics.js | 17 +++++++++++++++++ .../Source/DataSources/PointVisualizer.js | 7 +++++++ 2 files changed, 24 insertions(+) diff --git a/packages/engine/Source/DataSources/PointGraphics.js b/packages/engine/Source/DataSources/PointGraphics.js index 6f1cbba104eb..4e287c605efe 100644 --- a/packages/engine/Source/DataSources/PointGraphics.js +++ b/packages/engine/Source/DataSources/PointGraphics.js @@ -19,6 +19,7 @@ import createPropertyDescriptor from "./createPropertyDescriptor.js"; * @property {Property | NearFarScalar} [translucencyByDistance] A {@link NearFarScalar} Property used to set translucency based on distance from the camera. * @property {Property | DistanceDisplayCondition} [distanceDisplayCondition] A Property specifying at what distance from the camera that this point will be displayed. * @property {Property | number} [disableDepthTestDistance] A Property specifying the distance from the camera at which to disable the depth test to. + * @property {Property | SplitDirection} [splitDirection] A Property specifying the {@link SplitDirection} split to apply to this point. */ /** @@ -51,6 +52,8 @@ function PointGraphics(options) { this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; + this._splitDirection = undefined; + this._splitDirectionSubscription = undefined; this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); } @@ -154,6 +157,14 @@ Object.defineProperties(PointGraphics.prototype, { disableDepthTestDistance: createPropertyDescriptor( "disableDepthTestDistance" ), + + /** + * Gets or sets the Property specifying the {@link SplitDirection} of this point. + * @memberof PointGraphics.prototype + * @type {Property|undefined} + * @default SplitDirection.NONE + */ + splitDirection: createPropertyDescriptor("splitDirection"), }); /** @@ -176,6 +187,7 @@ PointGraphics.prototype.clone = function (result) { result.translucencyByDistance = this._translucencyByDistance; result.distanceDisplayCondition = this.distanceDisplayCondition; result.disableDepthTestDistance = this.disableDepthTestDistance; + result.splitDirection = this.splitDirection; return result; }; @@ -217,5 +229,10 @@ PointGraphics.prototype.merge = function (source) { this.disableDepthTestDistance, source.disableDepthTestDistance ); + + this.splitDirection = defaultValue( + this.splitDirection, + source.splitDirection + ); }; export default PointGraphics; diff --git a/packages/engine/Source/DataSources/PointVisualizer.js b/packages/engine/Source/DataSources/PointVisualizer.js index 2210646daa9a..330050669749 100644 --- a/packages/engine/Source/DataSources/PointVisualizer.js +++ b/packages/engine/Source/DataSources/PointVisualizer.js @@ -10,12 +10,14 @@ import createBillboardPointCallback from "../Scene/createBillboardPointCallback. import HeightReference from "../Scene/HeightReference.js"; import BoundingSphereState from "./BoundingSphereState.js"; import Property from "./Property.js"; +import SplitDirection from "../Scene/SplitDirection.js" const defaultColor = Color.WHITE; const defaultOutlineColor = Color.BLACK; const defaultOutlineWidth = 0.0; const defaultPixelSize = 1.0; const defaultDisableDepthTestDistance = 0.0; +const defaultSplitDirection = SplitDirection.NONE; const colorScratch = new Color(); const positionScratch = new Cartesian3(); @@ -192,6 +194,11 @@ PointVisualizer.prototype.update = function (time) { time, defaultDisableDepthTestDistance ); + pointPrimitive.splitDirection = Property.getValueOrDefault( + pointGraphics._splitDirection, + time, + defaultSplitDirection + ); } else if (defined(billboard)) { billboard.show = true; billboard.position = position; From 2681891b2f574bc873e6dc2f3ce47fb108b1ef7f Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 14 May 2024 11:30:57 +0300 Subject: [PATCH 03/47] Specs Update Updated Specs PointPrimitive Collection, Point Graphics and PointVisualizer to work with the new splitDirection property --- .../Specs/DataSources/PointGraphicsSpec.js | 18 ++++++++++++++++++ .../Specs/DataSources/PointVisualizerSpec.js | 9 +++++++++ .../Scene/PointPrimitiveCollectionSpec.js | 6 ++++++ 3 files changed, 33 insertions(+) diff --git a/packages/engine/Specs/DataSources/PointGraphicsSpec.js b/packages/engine/Specs/DataSources/PointGraphicsSpec.js index 1ff48e39dbd6..e48878dc4406 100644 --- a/packages/engine/Specs/DataSources/PointGraphicsSpec.js +++ b/packages/engine/Specs/DataSources/PointGraphicsSpec.js @@ -5,6 +5,7 @@ import { ConstantProperty, PointGraphics, HeightReference, + SplitDirection, } from "../../index.js"; describe("DataSources/PointGraphics", function () { @@ -19,6 +20,7 @@ describe("DataSources/PointGraphics", function () { heightReference: HeightReference.RELATIVE_TO_GROUND, distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, + splitDirection: SplitDirection.LEFT, }; const point = new PointGraphics(options); @@ -31,6 +33,7 @@ describe("DataSources/PointGraphics", function () { expect(point.heightReference).toBeInstanceOf(ConstantProperty); expect(point.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(point.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); + expect(point.splitDirection).toBeInstanceOf(ConstantProperty); expect(point.color.getValue()).toEqual(options.color); expect(point.pixelSize.getValue()).toEqual(options.pixelSize); @@ -45,6 +48,9 @@ describe("DataSources/PointGraphics", function () { expect(point.disableDepthTestDistance.getValue()).toEqual( options.disableDepthTestDistance ); + expect(point.splitDirection.getValue()).toEqual( + options.splitDirection + ); }); it("merge assigns unassigned properties", function () { @@ -62,6 +68,7 @@ describe("DataSources/PointGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const target = new PointGraphics(); target.merge(source); @@ -78,6 +85,9 @@ describe("DataSources/PointGraphics", function () { expect(target.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); + expect(target.splitDirection).toBe( + source.splitDirection + ); }); it("merge does not assign assigned properties", function () { @@ -95,6 +105,7 @@ describe("DataSources/PointGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const color = new ConstantProperty(Color.WHITE); const pixelSize = new ConstantProperty(1); @@ -108,6 +119,7 @@ describe("DataSources/PointGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); const disableDepthTestDistance = new ConstantProperty(20.0); + const splitDirection = new ConstantProperty(SplitDirection.RIGHT); const target = new PointGraphics(); target.color = color; @@ -119,6 +131,7 @@ describe("DataSources/PointGraphics", function () { target.heightReference = heightReference; target.distanceDisplayCondition = distanDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; + target.splitDirection = splitDirection; target.merge(source); expect(target.color).toBe(color); @@ -130,6 +143,7 @@ describe("DataSources/PointGraphics", function () { expect(target.heightReference).toBe(heightReference); expect(target.distanceDisplayCondition).toBe(distanDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); + expect(target.splitDirection).toBe(splitDirection); }); it("clone works", function () { @@ -147,6 +161,7 @@ describe("DataSources/PointGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const result = source.clone(); expect(result.color).toBe(source.color); @@ -162,6 +177,9 @@ describe("DataSources/PointGraphics", function () { expect(result.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); + expect(result.splitDirection).toBe( + source.splitDirection + ); }); it("merge throws if source undefined", function () { diff --git a/packages/engine/Specs/DataSources/PointVisualizerSpec.js b/packages/engine/Specs/DataSources/PointVisualizerSpec.js index 95ccba067581..0fa97473b4af 100644 --- a/packages/engine/Specs/DataSources/PointVisualizerSpec.js +++ b/packages/engine/Specs/DataSources/PointVisualizerSpec.js @@ -17,6 +17,7 @@ import { BillboardCollection, HeightReference, PointPrimitiveCollection, + SplitDirection, } from "../../index.js"; import createScene from "../../../../Specs/createScene.js"; @@ -151,6 +152,7 @@ describe( scaleByDistance: new NearFarScalar(11, 12, 13, 14), distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, + splitDirection: SplitDirection.LEFT, }, }); const point = entity.point; @@ -180,6 +182,9 @@ describe( expect(pointPrimitive.disableDepthTestDistance).toEqual( point.disableDepthTestDistance.getValue(time) ); + expect(pointPrimitive.splitDirection).toEqual( + point.splitDirection.getValue(time) + ); point.color = new Color(0.15, 0.16, 0.17, 0.18); point.outlineColor = new Color(0.19, 0.2, 0.21, 0.22); @@ -191,6 +196,7 @@ describe( 1000000.0 ); point.disableDepthTestDistance = 20.0; + point.splitDirection = SplitDirection.RIGHT; visualizer.update(time); @@ -212,6 +218,9 @@ describe( expect(pointPrimitive.disableDepthTestDistance).toEqual( point.disableDepthTestDistance.getValue(time) ); + expect(pointPrimitive.splitDirection).toEqual( + point.splitDirection.getValue(time) + ); point.show = false; visualizer.update(time); diff --git a/packages/engine/Specs/Scene/PointPrimitiveCollectionSpec.js b/packages/engine/Specs/Scene/PointPrimitiveCollectionSpec.js index fc5844f0bdfe..748cb3175d1a 100644 --- a/packages/engine/Specs/Scene/PointPrimitiveCollectionSpec.js +++ b/packages/engine/Specs/Scene/PointPrimitiveCollectionSpec.js @@ -10,6 +10,7 @@ import { BlendOption, PointPrimitive, PointPrimitiveCollection, + SplitDirection, } from "../../index.js"; import { Math as CesiumMath } from "../../index.js"; @@ -65,6 +66,7 @@ describe( expect(p.distanceDisplayCondition).not.toBeDefined(); expect(p.disableDepthTestDistance).toEqual(0.0); expect(p.id).not.toBeDefined(); + expect(p.splitDirection).toEqual(SplitDirection.NONE); }); it("can add and remove before first render.", function () { @@ -96,6 +98,7 @@ describe( distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, id: "id", + splitDirection: SplitDirection.LEFT, }); expect(p.show).toEqual(false); @@ -121,6 +124,7 @@ describe( ); expect(p.disableDepthTestDistance).toEqual(10.0); expect(p.id).toEqual("id"); + expect(p.splitDirection).toEqual(SplitDirection.LEFT); }); it("sets pointPrimitive properties", function () { @@ -135,6 +139,7 @@ describe( p.translucencyByDistance = new NearFarScalar(1.0e6, 1.0, 1.0e8, 0.0); p.distanceDisplayCondition = new DistanceDisplayCondition(10.0, 100.0); p.disableDepthTestDistance = 10.0; + p.splitDirection = SplitDirection.LEFT; expect(p.show).toEqual(false); expect(p.position).toEqual(new Cartesian3(1.0, 2.0, 3.0)); @@ -158,6 +163,7 @@ describe( new DistanceDisplayCondition(10.0, 100.0) ); expect(p.disableDepthTestDistance).toEqual(10.0); + expect(p.splitDirection).toEqual(SplitDirection.LEFT); }); it("is not destroyed", function () { From d141e935582949cc2f57af21a29fc542fb33ddd9 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 14 May 2024 12:18:27 +0300 Subject: [PATCH 04/47] Update CONTRIBUTORS.md --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 0ab8b351ec62..368a4124da6e 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -389,3 +389,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu - [dslming](https://github.com/dslming) - [Peter A. Jonsson](https://github.com/pjonsson) - [Zhongxiang Wang](https://github.com/plainheart) +- [Vladislav Yunev](https://github.com/YunVlad) From 90fba653c030ec88ced629d43a190117a38558ec Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 14 May 2024 12:42:24 +0300 Subject: [PATCH 05/47] Update CHANGES.md --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c6577709d2b1..3c8d60d4fcba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,7 @@ # Change Log +- Added SplitDirection property for display PointPrimitive relative to the `Scene.splitPosition`. + ### 1.118 #### @cesium/engine From 2a6e949706289d15183d33822ceff5cbb44c4c4e Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 14 May 2024 16:22:54 +0300 Subject: [PATCH 06/47] Fixed formatting Fixed formatting in PointGraphics, PointVisualizer, PointPrimitive, PointGraphicsSpec --- packages/engine/Source/DataSources/PointGraphics.js | 2 +- .../engine/Source/DataSources/PointVisualizer.js | 2 +- packages/engine/Source/Scene/PointPrimitive.js | 2 +- .../engine/Specs/DataSources/PointGraphicsSpec.js | 12 +++--------- 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/engine/Source/DataSources/PointGraphics.js b/packages/engine/Source/DataSources/PointGraphics.js index 4e287c605efe..87cacbeb827f 100644 --- a/packages/engine/Source/DataSources/PointGraphics.js +++ b/packages/engine/Source/DataSources/PointGraphics.js @@ -164,7 +164,7 @@ Object.defineProperties(PointGraphics.prototype, { * @type {Property|undefined} * @default SplitDirection.NONE */ - splitDirection: createPropertyDescriptor("splitDirection"), + splitDirection: createPropertyDescriptor("splitDirection"), }); /** diff --git a/packages/engine/Source/DataSources/PointVisualizer.js b/packages/engine/Source/DataSources/PointVisualizer.js index 330050669749..c1b252a1d692 100644 --- a/packages/engine/Source/DataSources/PointVisualizer.js +++ b/packages/engine/Source/DataSources/PointVisualizer.js @@ -10,7 +10,7 @@ import createBillboardPointCallback from "../Scene/createBillboardPointCallback. import HeightReference from "../Scene/HeightReference.js"; import BoundingSphereState from "./BoundingSphereState.js"; import Property from "./Property.js"; -import SplitDirection from "../Scene/SplitDirection.js" +import SplitDirection from "../Scene/SplitDirection.js"; const defaultColor = Color.WHITE; const defaultOutlineColor = Color.BLACK; diff --git a/packages/engine/Source/Scene/PointPrimitive.js b/packages/engine/Source/Scene/PointPrimitive.js index a4c9cf979bb9..dc911db6eca9 100644 --- a/packages/engine/Source/Scene/PointPrimitive.js +++ b/packages/engine/Source/Scene/PointPrimitive.js @@ -11,7 +11,7 @@ import Matrix4 from "../Core/Matrix4.js"; import NearFarScalar from "../Core/NearFarScalar.js"; import SceneMode from "./SceneMode.js"; import SceneTransforms from "./SceneTransforms.js"; -import SplitDirection from "./SplitDirection.js" +import SplitDirection from "./SplitDirection.js"; /** *
diff --git a/packages/engine/Specs/DataSources/PointGraphicsSpec.js b/packages/engine/Specs/DataSources/PointGraphicsSpec.js index e48878dc4406..699e801e71de 100644 --- a/packages/engine/Specs/DataSources/PointGraphicsSpec.js +++ b/packages/engine/Specs/DataSources/PointGraphicsSpec.js @@ -48,9 +48,7 @@ describe("DataSources/PointGraphics", function () { expect(point.disableDepthTestDistance.getValue()).toEqual( options.disableDepthTestDistance ); - expect(point.splitDirection.getValue()).toEqual( - options.splitDirection - ); + expect(point.splitDirection.getValue()).toEqual(options.splitDirection); }); it("merge assigns unassigned properties", function () { @@ -85,9 +83,7 @@ describe("DataSources/PointGraphics", function () { expect(target.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); - expect(target.splitDirection).toBe( - source.splitDirection - ); + expect(target.splitDirection).toBe(source.splitDirection); }); it("merge does not assign assigned properties", function () { @@ -177,9 +173,7 @@ describe("DataSources/PointGraphics", function () { expect(result.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); - expect(result.splitDirection).toBe( - source.splitDirection - ); + expect(result.splitDirection).toBe(source.splitDirection); }); it("merge throws if source undefined", function () { From 65431b370a0849c6df19295bbf64ac9253ef4f21 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Wed, 15 May 2024 15:02:19 +0300 Subject: [PATCH 07/47] Update CHANGES.md Resolving conflict --- CHANGES.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3c8d60d4fcba..01fb57271203 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,16 +2,17 @@ - Added SplitDirection property for display PointPrimitive relative to the `Scene.splitPosition`. -### 1.118 +### 1.118 - 2024-06-01 #### @cesium/engine ##### Fixes :wrench: +- Fixed a bug where `scene.pickPosition` returned incorrect results against the globe when `depthTestAgainstTerrain` is `false`. [#4368](https://github.com/CesiumGS/cesium/issues/4368) - Fixed a bug where `TaskProcessor` worker loading would check the worker module ID rather than the absolute URL when determining if it is cross-origin. [#11833](https://github.com/CesiumGS/cesium/pull/11833) - Fixed a bug where cross-origin workers would error when loaded with the CommonJS `importScripts` shim instead of an ESM `import`. [#11833](https://github.com/CesiumGS/cesium/pull/11833) -### 1.117 +### 1.117 - 2024-05-01 #### @cesium/engine From 39485e0826eccda9ed146570ad2875bd2ce26fa7 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Wed, 22 May 2024 12:31:13 -0400 Subject: [PATCH 08/47] Moon lighting --- Apps/Sandcastle/gallery/Moon.html | 18 ++--- .../Core/Simon1994PlanetaryPositions.js | 10 +-- packages/engine/Source/Core/Transforms.js | 71 +++++++++++++++++++ .../engine/Source/Renderer/UniformState.js | 24 ++++--- 4 files changed, 100 insertions(+), 23 deletions(-) diff --git a/Apps/Sandcastle/gallery/Moon.html b/Apps/Sandcastle/gallery/Moon.html index 381a78ae9a62..e3d49678815a 100644 --- a/Apps/Sandcastle/gallery/Moon.html +++ b/Apps/Sandcastle/gallery/Moon.html @@ -37,13 +37,10 @@ Cesium.Ion.defaultAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJhZDUyNTc2YS1hMmQyLTQ2NGUtYmE2Ny01ZmQ5MmRmNGEyOGQiLCJpZCI6MTQsInNjb3BlcyI6WyJhc3IiLCJnYyJdLCJpYXQiOjE1ODgxODAxNDB9.q3KJVaTTtaMs404afZYo2RQSbYglE09Xfrm7BkZP694"; + Cesium.Ellipsoid.default = Cesium.Ellipsoid.MOON; const moonOptions = { - ellipsoid: Cesium.Ellipsoid.MOON, terrainProvider: await Cesium.CesiumTerrainProvider.fromIonAssetId( - 1238, - { - ellipsoid: Cesium.Ellipsoid.MOON, - } + 1238 ), baseLayer: Cesium.ImageryLayer.fromProviderAsync( Cesium.IonImageryProvider.fromAssetId(1208) @@ -53,12 +50,17 @@ const viewer = new Cesium.Viewer("cesiumContainer", { ...moonOptions, useBrowserRecommendedResolution: false, - timeline: false, - animation: false, + //timeline: false, + //animation: false, baseLayerPicker: false, geocoder: false, }); - viewer.scene.msaaSamples = 4; + viewer.clock.multiplier = 3 * 60 * 60 * 24; + viewer.clock.shouldAnimate = true; + + const scene = viewer.scene; + scene.msaaSamples = 4; + scene.globe.enableLighting = true; // Positions courtesy of https://www.sciencedirect.com/science/article/abs/pii/S0019103516301518?via%3Dihub const pointsOfInterest = [ diff --git a/packages/engine/Source/Core/Simon1994PlanetaryPositions.js b/packages/engine/Source/Core/Simon1994PlanetaryPositions.js index 990a9f850b7f..cf733d6a02a4 100644 --- a/packages/engine/Source/Core/Simon1994PlanetaryPositions.js +++ b/packages/engine/Source/Core/Simon1994PlanetaryPositions.js @@ -627,6 +627,7 @@ const axesTransformation = new Matrix3( 0.9174820620691819 ); let translation = new Cartesian3(); + /** * Computes the position of the Sun in the Earth-centered inertial frame * @@ -662,7 +663,7 @@ Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame = function ( /** * Computes the position of the Moon in the Earth-centered inertial frame * - * @param {JulianDate} [julianDate] The time at which to compute the Sun's position, if not provided the current system time is used. + * @param {JulianDate} [julianDate] The time at which to compute the Moon's position, if not provided the current system time is used. * @param {Cartesian3} [result] The object onto which to store the result. * @returns {Cartesian3} Calculated moon position */ @@ -681,11 +682,11 @@ Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame = function ( }; /** - * Computes the position of the Sun in the Moon-centered inertial frame + * Computes the position of the Sun in the Moon-centered fixed frame * * @param {JulianDate} [julianDate] The time at which to compute the Sun's position, if not provided the current system time is used. * @param {Cartesian3} [result] The object onto which to store the result. - * @returns {Cartesian3} Calculated sun position + * @returns {Cartesian3} Calculated Sun position */ Simon1994PlanetaryPositions.computeSunPositionInMoonInertialFrame = function ( julianDate, @@ -707,7 +708,8 @@ Simon1994PlanetaryPositions.computeSunPositionInMoonInertialFrame = function ( computeSimonMoon(julianDate, translation); Cartesian3.subtract(result, translation, result); - Matrix3.multiplyByVector(axesTransformation, result, result); + + //Matrix3.multiplyByVector(rotation, result, result); TODO: ? return result; }; diff --git a/packages/engine/Source/Core/Transforms.js b/packages/engine/Source/Core/Transforms.js index 3bea2f359d85..713b663d157a 100644 --- a/packages/engine/Source/Core/Transforms.js +++ b/packages/engine/Source/Core/Transforms.js @@ -726,6 +726,77 @@ Transforms.computeIcrfToFixedMatrix = function (date, result) { return Matrix3.transpose(fixedToIcrfMtx, result); }; +const TdtMinusTai = 32.184; +const J2000d = 2451545; +const scratchHpr = new HeadingPitchRoll(); +const scratchRotationMatrix = new Matrix3(); +const dateScratch = new JulianDate(); +Transforms.computeMoonFixedToIcrfMatrix = function (date, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(date)) { + throw new DeveloperError("date is required."); + } + //>>includeEnd('debug'); + + if (!defined(result)) { + result = new Matrix3(); + } + + // Converts TAI to TT + const secondsTT = JulianDate.addSeconds(date, TdtMinusTai, dateScratch); + + // Converts TT to TDB, interval in days since the standard epoch + const d = JulianDate.totalDays(secondsTT) - J2000d; + + // Compute the approximate rotation, using https://articles.adsabs.harvard.edu//full/1980CeMec..22..205D/0000209.000.html + const e1 = CesiumMath.toRadians(12.112) - CesiumMath.toRadians(0.052992) * d; + const e2 = CesiumMath.toRadians(24.224) - CesiumMath.toRadians(0.105984) * d; + const e3 = CesiumMath.toRadians(227.645) + CesiumMath.toRadians(13.012) * d; + const e4 = + CesiumMath.toRadians(261.105) + CesiumMath.toRadians(13.340716) * d; + const e5 = CesiumMath.toRadians(358.0) + CesiumMath.toRadians(0.9856) * d; + + scratchHpr.heading = + CesiumMath.toRadians(270.0) - + CesiumMath.toRadians(3.878) * Math.sin(e1) - + CesiumMath.toRadians(0.12) * Math.sin(e2) + + CesiumMath.toRadians(0.07) * Math.sin(e3) - + CesiumMath.toRadians(0.017) * Math.sin(e4); + scratchHpr.pitch = + CesiumMath.toRadians(66.534) + + CesiumMath.toRadians(1.543) * Math.cos(e1) + + CesiumMath.toRadians(0.24) * Math.cos(e2) - + CesiumMath.toRadians(0.028) * Math.cos(e3) + + CesiumMath.toRadians(0.007) * Math.cos(e4); + scratchHpr.roll = + CesiumMath.toRadians(244.375) + + CesiumMath.toRadians(13.17635831) * d + + CesiumMath.toRadians(3.558) * Math.sin(e1) + + CesiumMath.toRadians(0.121) * Math.sin(e2) - + CesiumMath.toRadians(0.064) * Math.sin(e3) + + CesiumMath.toRadians(0.016) * Math.sin(e4) + + CesiumMath.toRadians(0.025) * Math.sin(e5); + return Matrix3.fromHeadingPitchRoll(scratchHpr, scratchRotationMatrix); +}; + +Transforms.computeIcrfToMoonFixedMatrix = function (date, result) { + //>>includeStart('debug', pragmas.debug); + if (!defined(date)) { + throw new DeveloperError("date is required."); + } + //>>includeEnd('debug'); + if (!defined(result)) { + result = new Matrix3(); + } + + const fixedToIcrfMtx = Transforms.computeMoonFixedToIcrfMatrix(date, result); + if (!defined(fixedToIcrfMtx)) { + return undefined; + } + + return Matrix3.transpose(fixedToIcrfMtx, result); +}; + const xysScratch = new Iau2006XysSample(0.0, 0.0, 0.0); const eopScratch = new EarthOrientationParametersSample( 0.0, diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index 24083c548468..2192fe0bc02c 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -1305,19 +1305,21 @@ function setCamera(uniformState, camera) { ); } -let transformMatrix = new Matrix3(); +const transformMatrix = new Matrix3(); const sunCartographicScratch = new Cartographic(); function setSunAndMoonDirections(uniformState, frameState) { - if ( - !defined( - Transforms.computeIcrfToFixedMatrix(frameState.time, transformMatrix) - ) - ) { - transformMatrix = Transforms.computeTemeToPseudoFixedMatrix( - frameState.time, - transformMatrix - ); - } + // if ( + // !defined( + // Transforms.computeIcrfToFixedMatrix(frameState.time, transformMatrix) + // ) + // ) { + // transformMatrix = Transforms.computeTemeToPseudoFixedMatrix( + // frameState.time, + // transformMatrix + // ); + // } + + Transforms.computeIcrfToMoonFixedMatrix(frameState.time, transformMatrix); let position = Simon1994PlanetaryPositions.computeSunPositionInMoonInertialFrame( frameState.time, From 97863f2268594a6cd2ebc1a5faec3f35e79a4b08 Mon Sep 17 00:00:00 2001 From: Gabby Getz Date: Wed, 22 May 2024 12:51:21 -0400 Subject: [PATCH 09/47] Correct axis transformation --- Apps/Sandcastle/gallery/Moon.html | 7 +++++++ packages/engine/Source/Core/Transforms.js | 12 ++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Apps/Sandcastle/gallery/Moon.html b/Apps/Sandcastle/gallery/Moon.html index e3d49678815a..16095a7fa970 100644 --- a/Apps/Sandcastle/gallery/Moon.html +++ b/Apps/Sandcastle/gallery/Moon.html @@ -57,6 +57,13 @@ }); viewer.clock.multiplier = 3 * 60 * 60 * 24; viewer.clock.shouldAnimate = true; + viewer.camera.flyTo({ + destination: new Cesium.Cartesian3.fromDegrees( + 0, + 0, + Cesium.Ellipsoid.default.minimumRadius * 2.6 + ), + }); const scene = viewer.scene; scene.msaaSamples = 4; diff --git a/packages/engine/Source/Core/Transforms.js b/packages/engine/Source/Core/Transforms.js index 713b663d157a..cd71b83cc576 100644 --- a/packages/engine/Source/Core/Transforms.js +++ b/packages/engine/Source/Core/Transforms.js @@ -756,20 +756,20 @@ Transforms.computeMoonFixedToIcrfMatrix = function (date, result) { CesiumMath.toRadians(261.105) + CesiumMath.toRadians(13.340716) * d; const e5 = CesiumMath.toRadians(358.0) + CesiumMath.toRadians(0.9856) * d; - scratchHpr.heading = - CesiumMath.toRadians(270.0) - + scratchHpr.pitch = + CesiumMath.toRadians(270.0 - 90) - CesiumMath.toRadians(3.878) * Math.sin(e1) - CesiumMath.toRadians(0.12) * Math.sin(e2) + CesiumMath.toRadians(0.07) * Math.sin(e3) - CesiumMath.toRadians(0.017) * Math.sin(e4); - scratchHpr.pitch = - CesiumMath.toRadians(66.534) + + scratchHpr.roll = + CesiumMath.toRadians(66.53 - 90) + CesiumMath.toRadians(1.543) * Math.cos(e1) + CesiumMath.toRadians(0.24) * Math.cos(e2) - CesiumMath.toRadians(0.028) * Math.cos(e3) + CesiumMath.toRadians(0.007) * Math.cos(e4); - scratchHpr.roll = - CesiumMath.toRadians(244.375) + + scratchHpr.heading = + CesiumMath.toRadians(244.375 - 90) + CesiumMath.toRadians(13.17635831) * d + CesiumMath.toRadians(3.558) * Math.sin(e1) + CesiumMath.toRadians(0.121) * Math.sin(e2) - From 16f21fa599121d7728f37a10fe8acf69ebfe683a Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 28 May 2024 14:10:56 +0300 Subject: [PATCH 10/47] Add splitDirection to Billboard Added the splitDirection property to Billboard. Updated BillboardCollection and shaders to work with the property. Updated BillboardGraphics and BillboardVisualizer. --- .../Source/DataSources/BillboardGraphics.js | 16 ++++++ .../Source/DataSources/BillboardVisualizer.js | 7 +++ packages/engine/Source/Scene/Billboard.js | 31 +++++++++++- .../Source/Scene/BillboardCollection.js | 49 +++++++++++++++++++ .../Source/Shaders/BillboardCollectionFS.glsl | 4 ++ .../Source/Shaders/BillboardCollectionVS.glsl | 4 +- 6 files changed, 108 insertions(+), 3 deletions(-) diff --git a/packages/engine/Source/DataSources/BillboardGraphics.js b/packages/engine/Source/DataSources/BillboardGraphics.js index e6686ecaf4ff..bdd950736c2b 100644 --- a/packages/engine/Source/DataSources/BillboardGraphics.js +++ b/packages/engine/Source/DataSources/BillboardGraphics.js @@ -29,6 +29,7 @@ import createPropertyDescriptor from "./createPropertyDescriptor.js"; * @property {Property | BoundingRectangle} [imageSubRegion] A Property specifying a {@link BoundingRectangle} that defines a sub-region of the image to use for the billboard, rather than the entire image, measured in pixels from the bottom-left. * @property {Property | DistanceDisplayCondition} [distanceDisplayCondition] A Property specifying at what distance from the camera that this billboard will be displayed. * @property {Property | number} [disableDepthTestDistance] A Property specifying the distance from the camera at which to disable the depth test to. + * @property {Property | SplitDirection} [splitDirection] A Property specifying the {@link SplitDirection} of the billboard. */ /** @@ -89,6 +90,8 @@ function BillboardGraphics(options) { this._distanceDisplayConditionSubscription = undefined; this._disableDepthTestDistance = undefined; this._disableDepthTestDistanceSubscription = undefined; + this._splitDirection = undefined; + this._splitDirectionSubscription = undefined; this.merge(defaultValue(options, defaultValue.EMPTY_OBJECT)); } @@ -330,6 +333,14 @@ Object.defineProperties(BillboardGraphics.prototype, { disableDepthTestDistance: createPropertyDescriptor( "disableDepthTestDistance" ), + + /** + * Gets or sets the Property specifying the {@link SplitDirection} of this billboard. + * @memberof BillboardGraphics.prototype + * @type {Property|undefined} + * @default SplitDirection.NONE + */ + splitDirection: createPropertyDescriptor("splitDirection"), }); /** @@ -362,6 +373,7 @@ BillboardGraphics.prototype.clone = function (result) { result.imageSubRegion = this._imageSubRegion; result.distanceDisplayCondition = this._distanceDisplayCondition; result.disableDepthTestDistance = this._disableDepthTestDistance; + result.splitDirection = this._splitDirection; return result; }; @@ -425,5 +437,9 @@ BillboardGraphics.prototype.merge = function (source) { this._disableDepthTestDistance, source.disableDepthTestDistance ); + this.splitDirection = defaultValue( + this.splitDirection, + source.splitDirection + ); }; export default BillboardGraphics; diff --git a/packages/engine/Source/DataSources/BillboardVisualizer.js b/packages/engine/Source/DataSources/BillboardVisualizer.js index 954e72047a6a..a8cff2aa4053 100644 --- a/packages/engine/Source/DataSources/BillboardVisualizer.js +++ b/packages/engine/Source/DataSources/BillboardVisualizer.js @@ -13,6 +13,7 @@ import HorizontalOrigin from "../Scene/HorizontalOrigin.js"; import VerticalOrigin from "../Scene/VerticalOrigin.js"; import BoundingSphereState from "./BoundingSphereState.js"; import Property from "./Property.js"; +import SplitDirection from "../Scene/SplitDirection.js"; const defaultColor = Color.WHITE; const defaultEyeOffset = Cartesian3.ZERO; @@ -24,6 +25,7 @@ const defaultAlignedAxis = Cartesian3.ZERO; const defaultHorizontalOrigin = HorizontalOrigin.CENTER; const defaultVerticalOrigin = VerticalOrigin.CENTER; const defaultSizeInMeters = false; +const defaultSplitDirection = SplitDirection.NONE; const positionScratch = new Cartesian3(); const colorScratch = new Color(); @@ -219,6 +221,11 @@ BillboardVisualizer.prototype.update = function (time) { billboardGraphics._disableDepthTestDistance, time ); + billboard.splitDirection = Property.getValueOrDefault( + billboardGraphics._splitDirection, + time, + defaultSplitDirection + ); const subRegion = Property.getValueOrUndefined( billboardGraphics._imageSubRegion, diff --git a/packages/engine/Source/Scene/Billboard.js b/packages/engine/Source/Scene/Billboard.js index f7c0264ae1fb..cdf018b10c5c 100644 --- a/packages/engine/Source/Scene/Billboard.js +++ b/packages/engine/Source/Scene/Billboard.js @@ -21,6 +21,7 @@ import HorizontalOrigin from "./HorizontalOrigin.js"; import SceneMode from "./SceneMode.js"; import SceneTransforms from "./SceneTransforms.js"; import VerticalOrigin from "./VerticalOrigin.js"; +import SplitDirection from "./SplitDirection.js"; /** * @typedef {object} Billboard.ConstructorOptions @@ -49,6 +50,7 @@ import VerticalOrigin from "./VerticalOrigin.js"; * @property {BoundingRectangle} [imageSubRegion] A {@link BoundingRectangle} Specifying the sub-region of the image to use for the billboard, rather than the entire image. * @property {DistanceDisplayCondition} [distanceDisplayCondition] A {@link DistanceDisplayCondition} Specifying the distance from the camera at which this billboard will be displayed. * @property {number} [disableDepthTestDistance] A number specifying the distance from the camera at which to disable the depth test to, for example, prevent clipping against terrain. + * @property {SplitDirection} [splitDirection] A {@link SplitDirection} Specifying the split property of the billboard. */ /** @@ -250,6 +252,11 @@ function Billboard(options, billboardCollection) { this._outlineWidth = defaultValue(options.outlineWidth, 0.0); this._updateClamping(); + + this._splitDirection = defaultValue( + options.splitDirection, + SplitDirection.NONE + ); } const SHOW_INDEX = (Billboard.SHOW_INDEX = 0); @@ -270,7 +277,8 @@ const DISTANCE_DISPLAY_CONDITION = (Billboard.DISTANCE_DISPLAY_CONDITION = 14); const DISABLE_DEPTH_DISTANCE = (Billboard.DISABLE_DEPTH_DISTANCE = 15); Billboard.TEXTURE_COORDINATE_BOUNDS = 16; const SDF_INDEX = (Billboard.SDF_INDEX = 17); -Billboard.NUMBER_OF_PROPERTIES = 18; +const SPLIT_DIRECTION_INDEX = (Billboard.SPLIT_DIRECTION_INDEX = 18); +Billboard.NUMBER_OF_PROPERTIES = 19; function makeDirty(billboard, propertyChanged) { const billboardCollection = billboard._billboardCollection; @@ -1072,6 +1080,24 @@ Object.defineProperties(Billboard.prototype, { } }, }, + + /** + * Gets or sets the {@link SplitDirection} of this billboard. + * @memberof Billboard.prototype + * @type {SplitDirection} + * @default {@link SplitDirection.NONE} + */ + splitDirection: { + get: function () { + return this._splitDirection; + }, + set: function (value) { + if (this._splitDirection !== value) { + this._splitDirection = value; + makeDirty(this, SPLIT_DIRECTION_INDEX); + } + }, + }, }); Billboard.prototype.getPickId = function (context) { @@ -1565,7 +1591,8 @@ Billboard.prototype.equals = function (other) { this._distanceDisplayCondition, other._distanceDisplayCondition ) && - this._disableDepthTestDistance === other._disableDepthTestDistance) + this._disableDepthTestDistance === other._disableDepthTestDistance && + this._splitDirection === other._splitDirection) ); }; diff --git a/packages/engine/Source/Scene/BillboardCollection.js b/packages/engine/Source/Scene/BillboardCollection.js index f62dc2dffea4..b4d2ffdf7ab9 100644 --- a/packages/engine/Source/Scene/BillboardCollection.js +++ b/packages/engine/Source/Scene/BillboardCollection.js @@ -54,6 +54,7 @@ const DISTANCE_DISPLAY_CONDITION_INDEX = Billboard.DISTANCE_DISPLAY_CONDITION; const DISABLE_DEPTH_DISTANCE = Billboard.DISABLE_DEPTH_DISTANCE; const TEXTURE_COORDINATE_BOUNDS = Billboard.TEXTURE_COORDINATE_BOUNDS; const SDF_INDEX = Billboard.SDF_INDEX; +const SPLIT_DIRECTION_INDEX = Billboard.SPLIT_DIRECTION_INDEX; const NUMBER_OF_PROPERTIES = Billboard.NUMBER_OF_PROPERTIES; let attributeLocations; @@ -71,6 +72,7 @@ const attributeLocationsBatched = { textureCoordinateBoundsOrLabelTranslate: 9, a_batchId: 10, sdf: 11, + splitDirection: 12, }; const attributeLocationsInstanced = { @@ -87,6 +89,7 @@ const attributeLocationsInstanced = { textureCoordinateBoundsOrLabelTranslate: 10, a_batchId: 11, sdf: 12, + splitDirection: 13, }; /** @@ -311,6 +314,7 @@ function BillboardCollection(options) { BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // DISTANCE_DISPLAY_CONDITION_INDEX BufferUsage.STATIC_DRAW, // TEXTURE_COORDINATE_BOUNDS + BufferUsage.STATIC_DRAW, // SPLIT_DIRECTION_INDEX ]; this._highlightColor = Color.clone(Color.WHITE); // Only used by Vector3DTilePoints @@ -777,6 +781,12 @@ function createVAF( componentDatatype: ComponentDatatype.FLOAT, usage: buffersUsage[TEXTURE_COORDINATE_BOUNDS], }, + { + index: attributeLocations.splitDirection, + componentsPerAttribute: 1, + componentDatatype: ComponentDatatype.FLOAT, + usage: buffersUsage[SPLIT_DIRECTION_INDEX], + }, ]; // Instancing requires one non-instanced attribute. @@ -1586,6 +1596,34 @@ function writeSDF( } } +function writeSplitDirection( + billboardCollection, + frameState, + textureAtlasCoordinates, + vafWriters, + billboard +) { + const writer = vafWriters[attributeLocations.splitDirection]; + let direction = 0.0; + + const split = billboard.splitDirection; + if (defined(split)) { + direction = split; + } + + let i; + if (billboardCollection._instanced) { + i = billboard._index; + writer(i, direction); + } else { + i = billboard._index * 4; + writer(i + 0, direction); + writer(i + 1, direction); + writer(i + 2, direction); + writer(i + 3, direction); + } +} + function writeBillboard( billboardCollection, frameState, @@ -1670,6 +1708,13 @@ function writeBillboard( vafWriters, billboard ); + writeSplitDirection( + billboardCollection, + frameState, + textureAtlasCoordinates, + vafWriters, + billboard + ); } function recomputeActualPositions( @@ -1981,6 +2026,10 @@ BillboardCollection.prototype.update = function (frameState) { writers.push(writeSDF); } + if (properties[SPLIT_DIRECTION_INDEX]) { + writers.push(writeSplitDirection); + } + const numWriters = writers.length; vafWriters = this._vaf.writers; diff --git a/packages/engine/Source/Shaders/BillboardCollectionFS.glsl b/packages/engine/Source/Shaders/BillboardCollectionFS.glsl index d4c72d2c3bbd..4a6d8731ddd9 100644 --- a/packages/engine/Source/Shaders/BillboardCollectionFS.glsl +++ b/packages/engine/Source/Shaders/BillboardCollectionFS.glsl @@ -7,6 +7,7 @@ uniform vec4 u_highlightColor; in vec2 v_textureCoordinates; in vec4 v_pickColor; in vec4 v_color; +in float v_splitDirection; #ifdef SDF in vec4 v_outlineColor; @@ -86,6 +87,9 @@ vec4 getSDFColor(vec2 position, float outlineWidth, vec4 outlineColor, float smo void main() { + if (v_splitDirection < 0.0 && gl_FragCoord.x > czm_splitPosition) discard; + if (v_splitDirection > 0.0 && gl_FragCoord.x < czm_splitPosition) discard; + vec4 color = texture(u_atlas, v_textureCoordinates); #ifdef SDF diff --git a/packages/engine/Source/Shaders/BillboardCollectionVS.glsl b/packages/engine/Source/Shaders/BillboardCollectionVS.glsl index d8f927d07b59..2d454d487f38 100644 --- a/packages/engine/Source/Shaders/BillboardCollectionVS.glsl +++ b/packages/engine/Source/Shaders/BillboardCollectionVS.glsl @@ -11,6 +11,7 @@ in vec4 scaleByDistance; // near, nearScale, far, far in vec4 pixelOffsetScaleByDistance; // near, nearScale, far, farScale in vec4 compressedAttribute3; // distance display condition near, far, disableDepthTestDistance, dimensions in vec2 sdf; // sdf outline color (rgb) and width (w) +in float splitDirection; // splitDirection #if defined(VERTEX_DEPTH_CHECK) || defined(FRAGMENT_DEPTH_CHECK) in vec4 textureCoordinateBoundsOrLabelTranslate; // the min and max x and y values for the texture coordinates #endif @@ -28,6 +29,7 @@ out mat2 v_rotationMatrix; out vec4 v_pickColor; out vec4 v_color; +out float v_splitDirection; #ifdef SDF out vec4 v_outlineColor; out float v_outlineWidth; @@ -430,5 +432,5 @@ if (lengthSq < disableDepthTestDistance) { v_color = color; v_color.a *= translucency; - + v_splitDirection = splitDirection; } From 5fa741d232ddd36d79ba283a3939a128ef19161e Mon Sep 17 00:00:00 2001 From: YunVlad Date: Thu, 30 May 2024 18:48:55 +0300 Subject: [PATCH 11/47] Add Sandcastle example Add Sandcastle example for SplitDirection property for Points --- .../gallery/Points SplitDirection.html | 135 ++++++++++++++++++ .../gallery/Points SplitDirection.jpg | Bin 0 -> 46796 bytes 2 files changed, 135 insertions(+) create mode 100644 Apps/Sandcastle/gallery/Points SplitDirection.html create mode 100644 Apps/Sandcastle/gallery/Points SplitDirection.jpg diff --git a/Apps/Sandcastle/gallery/Points SplitDirection.html b/Apps/Sandcastle/gallery/Points SplitDirection.html new file mode 100644 index 000000000000..35dc2a965809 --- /dev/null +++ b/Apps/Sandcastle/gallery/Points SplitDirection.html @@ -0,0 +1,135 @@ + + + + + + + + + Cesium Demo + + + + + + +
+
+
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Points SplitDirection.jpg b/Apps/Sandcastle/gallery/Points SplitDirection.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f0367d7cac37d3268a62d57031e82b6771e7347b GIT binary patch literal 46796 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 literal 0 HcmV?d00001 From fffcf2bf5012a902c5d5e6c4ad197cafd97901fc Mon Sep 17 00:00:00 2001 From: YunVlad Date: Thu, 30 May 2024 19:01:18 +0300 Subject: [PATCH 12/47] Changed the transmission of the splitDirection attribute The splitDirection attribute was packaged as the 4th component to distanceDisplayConditionAndDisableDepth. Updated PointPrimitiveCollection and shader. --- .../Source/Scene/PointPrimitiveCollection.js | 56 ++++++------------- .../Shaders/PointPrimitiveCollectionVS.glsl | 17 +++--- 2 files changed, 24 insertions(+), 49 deletions(-) diff --git a/packages/engine/Source/Scene/PointPrimitiveCollection.js b/packages/engine/Source/Scene/PointPrimitiveCollection.js index a945c9feedc1..20ab1c5d6e1d 100644 --- a/packages/engine/Source/Scene/PointPrimitiveCollection.js +++ b/packages/engine/Source/Scene/PointPrimitiveCollection.js @@ -47,8 +47,7 @@ const attributeLocations = { compressedAttribute0: 2, // color, outlineColor, pick color compressedAttribute1: 3, // show, translucency by distance, some free space scaleByDistance: 4, - distanceDisplayConditionAndDisableDepth: 5, - splitDirection: 6, + distanceDisplayConditionAndDisableDepthAndSplitDirection: 5, }; /** @@ -218,7 +217,6 @@ function PointPrimitiveCollection(options) { BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX BufferUsage.STATIC_DRAW, // DISTANCE_DISPLAY_CONDITION_INDEX - BufferUsage.STATIC_DRAW, // SPLIT_DIRECTION_INDEX ]; const that = this; @@ -495,17 +493,12 @@ function createVAF(context, numberOfPointPrimitives, buffersUsage) { usage: buffersUsage[SCALE_BY_DISTANCE_INDEX], }, { - index: attributeLocations.distanceDisplayConditionAndDisableDepth, - componentsPerAttribute: 3, + index: + attributeLocations.distanceDisplayConditionAndDisableDepthAndSplitDirection, + componentsPerAttribute: 4, componentDatatype: ComponentDatatype.FLOAT, usage: buffersUsage[DISTANCE_DISPLAY_CONDITION_INDEX], }, - { - index: attributeLocations.splitDirection, - componentsPerAttribute: 1, - componentDatatype: ComponentDatatype.FLOAT, - usage: buffersUsage[SPLIT_DIRECTION_INDEX], - }, ], numberOfPointPrimitives ); // 1 vertex per pointPrimitive @@ -674,7 +667,7 @@ function writeScaleByDistance( writer(i, near, nearValue, far, farValue); } -function writeDistanceDisplayConditionAndDepthDisable( +function writeDistanceDisplayConditionAndDepthDisableAndSplitDirection( pointPrimitiveCollection, context, vafWriters, @@ -682,7 +675,10 @@ function writeDistanceDisplayConditionAndDepthDisable( ) { const i = pointPrimitive._index; const writer = - vafWriters[attributeLocations.distanceDisplayConditionAndDisableDepth]; + vafWriters[ + attributeLocations + .distanceDisplayConditionAndDisableDepthAndSplitDirection + ]; let near = 0.0; let far = Number.MAX_VALUE; @@ -706,25 +702,12 @@ function writeDistanceDisplayConditionAndDepthDisable( } } - writer(i, near, far, disableDepthTestDistance); -} - -function writeSplitDirection( - pointPrimitiveCollection, - context, - vafWriters, - pointPrimitive -) { - const i = pointPrimitive._index; - const writer = vafWriters[attributeLocations.splitDirection]; let direction = 0.0; - const split = pointPrimitive.splitDirection; if (defined(split)) { direction = split; } - - writer(i, direction); + writer(i, near, far, disableDepthTestDistance, direction); } function writePointPrimitive( @@ -757,13 +740,7 @@ function writePointPrimitive( vafWriters, pointPrimitive ); - writeDistanceDisplayConditionAndDepthDisable( - pointPrimitiveCollection, - context, - vafWriters, - pointPrimitive - ); - writeSplitDirection( + writeDistanceDisplayConditionAndDepthDisableAndSplitDirection( pointPrimitiveCollection, context, vafWriters, @@ -958,13 +935,12 @@ PointPrimitiveCollection.prototype.update = function (frameState) { if ( properties[DISTANCE_DISPLAY_CONDITION_INDEX] || - properties[DISABLE_DEPTH_DISTANCE_INDEX] + properties[DISABLE_DEPTH_DISTANCE_INDEX] || + properties[SPLIT_DIRECTION_INDEX] ) { - writers.push(writeDistanceDisplayConditionAndDepthDisable); - } - - if (properties[SPLIT_DIRECTION_INDEX]) { - writers.push(writeSplitDirection); + writers.push( + writeDistanceDisplayConditionAndDepthDisableAndSplitDirection + ); } const numWriters = writers.length; diff --git a/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl b/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl index 0e725388a042..936556b494f8 100644 --- a/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl +++ b/packages/engine/Source/Shaders/PointPrimitiveCollectionVS.glsl @@ -2,11 +2,10 @@ uniform float u_maxTotalPointSize; in vec4 positionHighAndSize; in vec4 positionLowAndOutline; -in vec4 compressedAttribute0; // color, outlineColor, pick color -in vec4 compressedAttribute1; // show, translucency by distance, some free space -in vec4 scaleByDistance; // near, nearScale, far, farScale -in vec3 distanceDisplayConditionAndDisableDepth; // near, far, disableDepthTestDistance -in float splitDirection; // splitDirection +in vec4 compressedAttribute0; // color, outlineColor, pick color +in vec4 compressedAttribute1; // show, translucency by distance, some free space +in vec4 scaleByDistance; // near, nearScale, far, farScale +in vec4 distanceDisplayConditionAndDisableDepthAndSplitDirection; // near, far, disableDepthTestDistance, splitDirection out vec4 v_color; out vec4 v_outlineColor; @@ -137,8 +136,8 @@ void main() #endif #ifdef DISTANCE_DISPLAY_CONDITION - float nearSq = distanceDisplayConditionAndDisableDepth.x; - float farSq = distanceDisplayConditionAndDisableDepth.y; + float nearSq = distanceDisplayConditionAndDisableDepthAndSplitDirection.x; + float farSq = distanceDisplayConditionAndDisableDepthAndSplitDirection.y; if (lengthSq < nearSq || lengthSq > farSq) { // push vertex behind camera to force it to be clipped positionEC.xyz = vec3(0.0, 0.0, 1.0); @@ -149,7 +148,7 @@ void main() czm_vertexLogDepth(); #ifdef DISABLE_DEPTH_DISTANCE - float disableDepthTestDistance = distanceDisplayConditionAndDisableDepth.z; + float disableDepthTestDistance = distanceDisplayConditionAndDisableDepthAndSplitDirection.z; if (disableDepthTestDistance == 0.0 && czm_minimumDisableDepthTestDistance != 0.0) { disableDepthTestDistance = czm_minimumDisableDepthTestDistance; @@ -182,5 +181,5 @@ void main() gl_Position *= show; v_pickColor = pickColor; - v_splitDirection = splitDirection; + v_splitDirection = distanceDisplayConditionAndDisableDepthAndSplitDirection.w; } From fe68fb340894bcbfe78bb4638308dc24c76f9045 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 4 Jun 2024 14:27:39 +0300 Subject: [PATCH 13/47] Updated PointVisualizer and example Fixed a bug where the splitDirection property stopped working when applying the heightReference parameter. Updated the example for Billboards. --- .../gallery/Points SplitDirection.html | 135 ------------- Apps/Sandcastle/gallery/SplitDirection.html | 191 ++++++++++++++++++ ... SplitDirection.jpg => SplitDirection.jpg} | Bin .../Source/DataSources/PointVisualizer.js | 5 + 4 files changed, 196 insertions(+), 135 deletions(-) delete mode 100644 Apps/Sandcastle/gallery/Points SplitDirection.html create mode 100644 Apps/Sandcastle/gallery/SplitDirection.html rename Apps/Sandcastle/gallery/{Points SplitDirection.jpg => SplitDirection.jpg} (100%) diff --git a/Apps/Sandcastle/gallery/Points SplitDirection.html b/Apps/Sandcastle/gallery/Points SplitDirection.html deleted file mode 100644 index 35dc2a965809..000000000000 --- a/Apps/Sandcastle/gallery/Points SplitDirection.html +++ /dev/null @@ -1,135 +0,0 @@ - - - - - - - - - Cesium Demo - - - - - - -
-
-
-

Loading...

-
- - - diff --git a/Apps/Sandcastle/gallery/SplitDirection.html b/Apps/Sandcastle/gallery/SplitDirection.html new file mode 100644 index 000000000000..9afc74a99854 --- /dev/null +++ b/Apps/Sandcastle/gallery/SplitDirection.html @@ -0,0 +1,191 @@ + + + + + + + + + Cesium Demo + + + + + +
+
+
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Points SplitDirection.jpg b/Apps/Sandcastle/gallery/SplitDirection.jpg similarity index 100% rename from Apps/Sandcastle/gallery/Points SplitDirection.jpg rename to Apps/Sandcastle/gallery/SplitDirection.jpg diff --git a/packages/engine/Source/DataSources/PointVisualizer.js b/packages/engine/Source/DataSources/PointVisualizer.js index c1b252a1d692..0fe2f9f00c81 100644 --- a/packages/engine/Source/DataSources/PointVisualizer.js +++ b/packages/engine/Source/DataSources/PointVisualizer.js @@ -222,6 +222,11 @@ PointVisualizer.prototype.update = function (time) { time, defaultDisableDepthTestDistance ); + billboard.splitDirection = Property.getValueOrDefault( + pointGraphics._splitDirection, + time, + defaultSplitDirection + ); billboard.heightReference = heightReference; const newColor = Property.getValueOrDefault( From 505196a5073134b3ea91553a5c51affe483fee52 Mon Sep 17 00:00:00 2001 From: YunVlad Date: Tue, 4 Jun 2024 15:14:43 +0300 Subject: [PATCH 14/47] Specs Update Updated Specs BillboardCollectionSpec, BillboardGraphicsSpec and BillboardVisualizerSpec to work with the new splitDirection property. Updated PointVisualizerSpec to work with billboard example. --- .../Specs/DataSources/BillboardGraphicsSpec.js | 14 +++++++++++++- .../Specs/DataSources/BillboardVisualizerSpec.js | 9 +++++++++ .../Specs/DataSources/PointVisualizerSpec.js | 4 ++++ .../engine/Specs/Scene/BillboardCollectionSpec.js | 6 ++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/engine/Specs/DataSources/BillboardGraphicsSpec.js b/packages/engine/Specs/DataSources/BillboardGraphicsSpec.js index 7e09f073df0b..6da4f355301c 100644 --- a/packages/engine/Specs/DataSources/BillboardGraphicsSpec.js +++ b/packages/engine/Specs/DataSources/BillboardGraphicsSpec.js @@ -9,6 +9,7 @@ import { HeightReference, HorizontalOrigin, VerticalOrigin, + SplitDirection, } from "../../index.js"; describe("DataSources/BillboardGraphics", function () { @@ -33,6 +34,7 @@ describe("DataSources/BillboardGraphics", function () { sizeInMeters: true, distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, + splitDirection: SplitDirection.LEFT, }; const billboard = new BillboardGraphics(options); @@ -56,6 +58,7 @@ describe("DataSources/BillboardGraphics", function () { expect(billboard.sizeInMeters).toBeInstanceOf(ConstantProperty); expect(billboard.distanceDisplayCondition).toBeInstanceOf(ConstantProperty); expect(billboard.disableDepthTestDistance).toBeInstanceOf(ConstantProperty); + expect(billboard.splitDirection).toBeInstanceOf(ConstantProperty); expect(billboard.image.getValue()).toEqual(options.image); expect(billboard.rotation.getValue()).toEqual(options.rotation); @@ -89,6 +92,7 @@ describe("DataSources/BillboardGraphics", function () { expect(billboard.disableDepthTestDistance.getValue()).toEqual( options.disableDepthTestDistance ); + expect(billboard.splitDirection.getValue()).toEqual(options.splitDirection); }); it("merge assigns unassigned properties", function () { @@ -118,7 +122,8 @@ describe("DataSources/BillboardGraphics", function () { source.distanceDisplayCondition = new ConstantProperty( new DistanceDisplayCondition(10.0, 100.0) ); - source.disableDepthTestDistance = 10.0; + source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const target = new BillboardGraphics(); target.merge(source); @@ -149,6 +154,7 @@ describe("DataSources/BillboardGraphics", function () { expect(target.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); + expect(target.splitDirection).toBe(source.splitDirection); }); it("merge does not assign assigned properties", function () { @@ -179,6 +185,7 @@ describe("DataSources/BillboardGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const image = new ConstantProperty(""); const imageSubRegion = new ConstantProperty(); @@ -206,6 +213,7 @@ describe("DataSources/BillboardGraphics", function () { new DistanceDisplayCondition() ); const disableDepthTestDistance = new ConstantProperty(10.0); + const splitDirection = new ConstantProperty(SplitDirection.LEFT); const target = new BillboardGraphics(); target.image = image; @@ -228,6 +236,7 @@ describe("DataSources/BillboardGraphics", function () { target.sizeInMeters = sizeInMeters; target.distanceDisplayCondition = distanceDisplayCondition; target.disableDepthTestDistance = disableDepthTestDistance; + target.splitDirection = splitDirection; target.merge(source); @@ -251,6 +260,7 @@ describe("DataSources/BillboardGraphics", function () { expect(target.sizeInMeters).toBe(sizeInMeters); expect(target.distanceDisplayCondition).toBe(distanceDisplayCondition); expect(target.disableDepthTestDistance).toBe(disableDepthTestDistance); + expect(target.splitDirection).toBe(splitDirection); }); it("clone works", function () { @@ -281,6 +291,7 @@ describe("DataSources/BillboardGraphics", function () { new DistanceDisplayCondition(10.0, 100.0) ); source.disableDepthTestDistance = new ConstantProperty(10.0); + source.splitDirection = new ConstantProperty(SplitDirection.LEFT); const result = source.clone(); expect(result.image).toBe(source.image); @@ -309,6 +320,7 @@ describe("DataSources/BillboardGraphics", function () { expect(result.disableDepthTestDistance).toBe( source.disableDepthTestDistance ); + expect(result.splitDirection).toBe(source.splitDirection); }); it("merge throws if source undefined", function () { diff --git a/packages/engine/Specs/DataSources/BillboardVisualizerSpec.js b/packages/engine/Specs/DataSources/BillboardVisualizerSpec.js index 90268aad7121..a01bec49ccbf 100644 --- a/packages/engine/Specs/DataSources/BillboardVisualizerSpec.js +++ b/packages/engine/Specs/DataSources/BillboardVisualizerSpec.js @@ -17,6 +17,7 @@ import { HeightReference, HorizontalOrigin, VerticalOrigin, + SplitDirection, } from "../../index.js"; import createGlobe from "../../../../Specs/createGlobe.js"; @@ -167,6 +168,7 @@ describe( new DistanceDisplayCondition(10.0, 100.0) ); billboard.disableDepthTestDistance = new ConstantProperty(10.0); + billboard.splitDirection = new ConstantProperty(SplitDirection.LEFT); visualizer.update(time); @@ -220,6 +222,9 @@ describe( expect(bb.disableDepthTestDistance).toEqual( testObject.billboard.disableDepthTestDistance.getValue(time) ); + expect(bb.splitDirection).toEqual( + testObject.billboard.splitDirection.getValue(time) + ); expect(bb._imageSubRegion).toEqual( testObject.billboard.imageSubRegion.getValue(time) ); @@ -275,6 +280,7 @@ describe( new DistanceDisplayCondition(10.0, 100.0) ); billboard.disableDepthTestDistance = new ConstantProperty(10.0); + billboard.splitDirection = new ConstantProperty(SplitDirection.LEFT); visualizer.update(time); @@ -342,6 +348,9 @@ describe( expect(bb.disableDepthTestDistance).toEqual( testObject.billboard.disableDepthTestDistance.getValue(time) ); + expect(bb.splitDirection).toEqual( + testObject.billboard.splitDirection.getValue(time) + ); expect(bb.image).toBeDefined(); expect(bb._imageSubRegion).toEqual( testObject.billboard.imageSubRegion.getValue(time) diff --git a/packages/engine/Specs/DataSources/PointVisualizerSpec.js b/packages/engine/Specs/DataSources/PointVisualizerSpec.js index 0fa97473b4af..6f17a7e0b033 100644 --- a/packages/engine/Specs/DataSources/PointVisualizerSpec.js +++ b/packages/engine/Specs/DataSources/PointVisualizerSpec.js @@ -245,6 +245,7 @@ describe( distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, heightReference: HeightReference.CLAMP_TO_GROUND, + splitDirection: SplitDirection.LEFT, }, }); const point = entity.point; @@ -267,6 +268,9 @@ describe( expect(billboard.disableDepthTestDistance).toEqual( point.disableDepthTestDistance.getValue(time) ); + expect(billboard.splitDirection).toEqual( + point.splitDirection.getValue(time) + ); //expect(billboard.color).toEqual(point.color.getValue(time)); //expect(billboard.outlineColor).toEqual(point.outlineColor.getValue(time)); //expect(billboard.outlineWidth).toEqual(point.outlineWidth.getValue(time)); diff --git a/packages/engine/Specs/Scene/BillboardCollectionSpec.js b/packages/engine/Specs/Scene/BillboardCollectionSpec.js index a47d52468462..5d1b2791bc85 100644 --- a/packages/engine/Specs/Scene/BillboardCollectionSpec.js +++ b/packages/engine/Specs/Scene/BillboardCollectionSpec.js @@ -22,6 +22,7 @@ import { HorizontalOrigin, TextureAtlas, VerticalOrigin, + SplitDirection, } from "../../index.js"; import createScene from "../../../../Specs/createScene.js"; @@ -120,6 +121,7 @@ describe( expect(b.sizeInMeters).toEqual(false); expect(b.distanceDisplayCondition).toBeUndefined(); expect(b.disableDepthTestDistance).toBeUndefined(); + expect(b.splitDirection).toEqual(SplitDirection.NONE); }); it("can add and remove before first update.", function () { @@ -155,6 +157,7 @@ describe( distanceDisplayCondition: new DistanceDisplayCondition(10.0, 100.0), disableDepthTestDistance: 10.0, id: "id", + splitDirection: SplitDirection.LEFT, }); expect(b.show).toEqual(false); @@ -188,6 +191,7 @@ describe( ); expect(b.disableDepthTestDistance).toEqual(10.0); expect(b.id).toEqual("id"); + expect(b.splitDirection).toEqual(SplitDirection.LEFT); }); it("sets billboard properties", function () { @@ -211,6 +215,7 @@ describe( b.sizeInMeters = true; b.distanceDisplayCondition = new DistanceDisplayCondition(10.0, 100.0); b.disableDepthTestDistance = 10.0; + b.splitDirection = SplitDirection.LEFT; expect(b.show).toEqual(false); expect(b.position).toEqual(new Cartesian3(1.0, 2.0, 3.0)); @@ -242,6 +247,7 @@ describe( new DistanceDisplayCondition(10.0, 100.0) ); expect(b.disableDepthTestDistance).toEqual(10.0); + expect(b.splitDirection).toEqual(SplitDirection.LEFT); }); it("required properties throw for undefined", function () { From fce86ad332d78856341dc3691dcf34f3c9e93ba7 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 25 Jun 2024 14:31:42 -0400 Subject: [PATCH 15/47] Update Smith visibility function for PBR materials --- .../Builtin/Functions/pbrLighting.glsl | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl index ad8b7a809a76..f7518bda6f6a 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl @@ -41,31 +41,31 @@ float GGX_anisotropic(float roughness, float tangentialRoughness, vec3 halfwayDi } #endif -float smithVisibilityG1(float NdotV, float roughness) -{ - // this is the k value for direct lighting. - // for image based lighting it will be roughness^2 / 2 - float k = (roughness + 1.0) * (roughness + 1.0) / 8.0; - return NdotV / (NdotV * (1.0 - k) + k); -} - /** * Estimate the geometric self-shadowing of the microfacets in a surface, - * using the Schlick GGX approximation of a Smith visibility function. + * using the Smith Joint GGX visibility function. + * Note: Vis = G / (4 * NdotL * NdotV) + * see Eric Heitz. 2014. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3 + * see Real-Time Rendering. Page 331 to 336. + * see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg) * - * @param {float} roughness The roughness of the material. + * @param {float} alphaRoughness The roughness of the material, expressed as the square of perceptual roughness. * @param {float} NdotL The cosine of the angle between the surface normal and the direction to the light source. * @param {float} NdotV The cosine of the angle between the surface normal and the direction to the camera. */ -float smithVisibilityGGX(float roughness, float NdotL, float NdotV) +float smithVisibilityGGX(float alphaRoughness, float NdotL, float NdotV) { - // Avoid divide-by-zero errors - NdotL = clamp(NdotL, 0.001, 1.0); - NdotV += 0.001; - return ( - smithVisibilityG1(NdotL, roughness) * - smithVisibilityG1(NdotV, roughness) - ) / (4.0 * NdotL * NdotV); + float alphaRoughnessSq = alphaRoughness * alphaRoughness; + + float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaRoughnessSq) + alphaRoughnessSq); + float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaRoughnessSq) + alphaRoughnessSq); + + float GGX = GGXV + GGXL; + if (GGX > 0.0) + { + return 0.5 / GGX; + } + return 0.0; } /** From 8b8ae9c42f11702a081b2339f65c8dfc9e5d7cb1 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 1 Jul 2024 12:55:21 -0400 Subject: [PATCH 16/47] Fix base color calculation for 3D Tile styles --- .../Source/Shaders/Model/MaterialStageFS.glsl | 7 +- .../engine/Specs/Scene/Cesium3DTilesetSpec.js | 245 +++++++++--------- 2 files changed, 124 insertions(+), 128 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index 7f23f3b274cc..391223bd9cbf 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -459,12 +459,11 @@ void materialStage(inout czm_modelMaterial material, ProcessedAttributes attribu #endif material.baseColor = baseColorWithAlpha; - material.diffuse = baseColorWithAlpha.rgb; - material.alpha = baseColorWithAlpha.a; - #ifdef USE_CPU_STYLING - material.diffuse = blend(material.diffuse, feature.color.rgb, model_colorBlend); + material.baseColor.rgb = blend(baseColorWithAlpha.rgb, feature.color.rgb, model_colorBlend); #endif + material.diffuse = baseColorWithAlpha.rgb; + material.alpha = baseColorWithAlpha.a; #ifdef HAS_OCCLUSION_TEXTURE vec2 occlusionTexCoords = TEXCOORD_OCCLUSION; diff --git a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js index 3befe8dc6d14..c462f5195096 100644 --- a/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js +++ b/packages/engine/Specs/Scene/Cesium3DTilesetSpec.js @@ -3312,7 +3312,7 @@ describe( ); }); - function testColorBlendMode(url) { + async function testColorBlendMode(url) { // Check that the feature is red let sourceRed; let sourceGreen; @@ -3322,148 +3322,145 @@ describe( scene: scene, time: new JulianDate(2457522.154792), }; - let tileset; - return Cesium3DTilesTester.loadTileset(scene, url).then(function (t) { - tileset = t; - tileset.luminanceAtZenith = undefined; - - expect(renderOptions).toRenderAndCall(function (rgba) { - sourceRed = rgba[0]; - sourceGreen = rgba[1]; - }); + const tileset = await Cesium3DTilesTester.loadTileset(scene, url); + tileset.luminanceAtZenith = undefined; - expect(renderOptions).toRenderAndCall(function (rgba) { - expect(rgba[0]).toBeGreaterThan(200); - expect(rgba[1]).toBeLessThan(25); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + expect(renderOptions).toRenderAndCall(function (rgba) { + sourceRed = rgba[0]; + sourceGreen = rgba[1]; + }); - // Use HIGHLIGHT blending - tileset.colorBlendMode = Cesium3DTileColorBlendMode.HIGHLIGHT; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba[0]).toBeGreaterThan(200); + expect(rgba[1]).toBeLessThan(25); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); + }); - // Style with dark yellow. Expect the red channel to be darker than before. - tileset.style = new Cesium3DTileStyle({ - color: "rgb(128, 128, 0)", - }); + // Use HIGHLIGHT blending + tileset.colorBlendMode = Cesium3DTileColorBlendMode.HIGHLIGHT; - expect(renderOptions).toRenderAndCall(function (rgba) { - expect(rgba[0]).toBeGreaterThan(100); - expect(rgba[0]).toBeLessThan(sourceRed); - expect(rgba[1]).toBeLessThan(25); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + // Style with dark yellow. Expect the red channel to be darker than before. + tileset.style = new Cesium3DTileStyle({ + color: "rgb(128, 128, 0)", + }); - // Style with yellow + alpha. Expect the red channel to be darker than before. - tileset.style = new Cesium3DTileStyle({ - color: "rgba(255, 255, 0, 0.5)", - }); + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba[0]).toBeGreaterThan(100); + expect(rgba[0]).toBeLessThan(sourceRed); + expect(rgba[1]).toBeLessThan(25); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); + }); - expect(renderOptions).toRenderAndCall(function (rgba) { - expect(rgba[0]).toBeGreaterThan(100); - expect(rgba[0]).toBeLessThan(sourceRed); - expect(rgba[1]).toBeLessThan(25); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + // Style with yellow + alpha. Expect the red channel to be darker than before. + tileset.style = new Cesium3DTileStyle({ + color: "rgba(255, 255, 0, 0.5)", + }); - // Use REPLACE blending - tileset.colorBlendMode = Cesium3DTileColorBlendMode.REPLACE; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba[0]).toBeGreaterThan(100); + expect(rgba[0]).toBeLessThan(sourceRed); + expect(rgba[1]).toBeLessThan(25); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); + }); - // Style with dark yellow. Expect the red and green channels to be roughly dark yellow. - tileset.style = new Cesium3DTileStyle({ - color: "rgb(128, 128, 0)", - }); + // Use REPLACE blending + tileset.colorBlendMode = Cesium3DTileColorBlendMode.REPLACE; - expect(renderOptions).toRenderAndCall(function (rgba) { - replaceRed = rgba[0]; - replaceGreen = rgba[1]; - expect(rgba[0]).toBeGreaterThan(100); - expect(rgba[0]).toBeLessThan(255); - expect(rgba[1]).toBeGreaterThan(100); - expect(rgba[1]).toBeLessThan(255); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + // Style with dark yellow. Expect the red and green channels to be roughly dark yellow. + tileset.style = new Cesium3DTileStyle({ + color: "rgb(128, 128, 0)", + }); - // Style with yellow + alpha. Expect the red and green channels to be a shade of yellow. - tileset.style = new Cesium3DTileStyle({ - color: "rgba(255, 255, 0, 0.5)", - }); + expect(renderOptions).toRenderAndCall(function (rgba) { + replaceRed = rgba[0]; + replaceGreen = rgba[1]; + expect(rgba[0]).toBeGreaterThan(100); + expect(rgba[0]).toBeLessThan(255); + expect(rgba[1]).toBeGreaterThan(100); + expect(rgba[1]).toBeLessThan(255); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); + }); - expect(renderOptions).toRenderAndCall(function (rgba) { - expect(rgba[0]).toBeGreaterThan(100); - expect(rgba[0]).toBeLessThan(255); - expect(rgba[1]).toBeGreaterThan(100); - expect(rgba[1]).toBeLessThan(255); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + // Style with yellow + alpha. Expect the red and green channels to be a shade of yellow. + tileset.style = new Cesium3DTileStyle({ + color: "rgba(255, 255, 0, 0.5)", + }); - // Use MIX blending - tileset.colorBlendMode = Cesium3DTileColorBlendMode.MIX; - tileset.colorBlendAmount = 0.5; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba[0]).toBeGreaterThan(100); + expect(rgba[0]).toBeLessThan(255); + expect(rgba[1]).toBeGreaterThan(100); + expect(rgba[1]).toBeLessThan(255); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); + }); - // Style with dark yellow. Expect color to be a mix of the source and style colors. - tileset.style = new Cesium3DTileStyle({ - color: "rgb(128, 128, 0)", - }); + // Use MIX blending + tileset.colorBlendMode = Cesium3DTileColorBlendMode.MIX; + tileset.colorBlendAmount = 0.5; - let mixRed; - let mixGreen; - expect(renderOptions).toRenderAndCall(function (rgba) { - mixRed = rgba[0]; - mixGreen = rgba[1]; - expect(rgba[0]).toBeGreaterThan(replaceRed); - expect(rgba[0]).toBeLessThan(sourceRed); - expect(rgba[1]).toBeGreaterThan(sourceGreen); - expect(rgba[1]).toBeLessThan(replaceGreen); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + // Style with dark yellow. Expect color to be a mix of the source and style colors. + tileset.style = new Cesium3DTileStyle({ + color: "rgb(128, 128, 0)", + }); + + let mixRed; + let mixGreen; + expect(renderOptions).toRenderAndCall(function (rgba) { + mixRed = rgba[0]; + mixGreen = rgba[1]; + expect(rgba[0]).toBeGreaterThan(replaceRed); + expect(rgba[0]).toBeLessThan(sourceRed); + expect(rgba[1]).toBeGreaterThan(sourceGreen); + expect(rgba[1]).toBeLessThan(replaceGreen); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); + }); - // Set colorBlendAmount to 0.25. Expect color to be closer to the source color. - tileset.colorBlendAmount = 0.25; - expect(renderOptions).toRenderAndCall(function (rgba) { - expect(rgba[0]).toBeGreaterThan(mixRed); - expect(rgba[0]).toBeLessThan(sourceRed); - expect(rgba[1]).toBeGreaterThan(0); - expect(rgba[1]).toBeLessThan(mixGreen); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + // Set colorBlendAmount to 0.25. Expect color to be closer to the source color. + tileset.colorBlendAmount = 0.25; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba[0]).toBeGreaterThan(mixRed); + expect(rgba[0]).toBeLessThan(sourceRed); + expect(rgba[1]).toBeGreaterThan(0); + expect(rgba[1]).toBeLessThan(mixGreen); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); + }); - // Set colorBlendAmount to 0.0. Expect color to equal the source color - tileset.colorBlendAmount = 0.0; - expect(renderOptions).toRenderAndCall(function (rgba) { - expect(rgba[0]).toEqual(sourceRed); - expect(rgba[1]).toBeLessThan(25); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + // Set colorBlendAmount to 0.0. Expect color to equal the source color + tileset.colorBlendAmount = 0.0; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba[0]).toEqual(sourceRed); + expect(rgba[1]).toBeLessThan(25); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); + }); - // Set colorBlendAmount to 1.0. Expect color to equal the style color - tileset.colorBlendAmount = 1.0; - expect(renderOptions).toRenderAndCall(function (rgba) { - expect(rgba[0]).toEqual(replaceRed); - expect(rgba[1]).toEqual(replaceGreen); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + // Set colorBlendAmount to 1.0. Expect color to equal the style color + tileset.colorBlendAmount = 1.0; + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba[0]).toEqual(replaceRed); + expect(rgba[1]).toEqual(replaceGreen); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); + }); - // Style with yellow + alpha. Expect color to be a mix of the source and style colors. - tileset.colorBlendAmount = 0.5; - tileset.style = new Cesium3DTileStyle({ - color: "rgba(255, 255, 0, 0.5)", - }); + // Style with yellow + alpha. Expect color to be a mix of the source and style colors. + tileset.colorBlendAmount = 0.5; + tileset.style = new Cesium3DTileStyle({ + color: "rgba(255, 255, 0, 0.5)", + }); - expect(renderOptions).toRenderAndCall(function (rgba) { - expect(rgba[0]).toBeGreaterThan(0); - expect(rgba[1]).toBeGreaterThan(0); - expect(rgba[2]).toBeLessThan(25); - expect(rgba[3]).toEqual(255); - }); + expect(renderOptions).toRenderAndCall(function (rgba) { + expect(rgba[0]).toBeGreaterThan(0); + expect(rgba[1]).toBeGreaterThan(0); + expect(rgba[2]).toBeLessThan(25); + expect(rgba[3]).toEqual(255); }); } From 6082d9dc1bdb3e030cf1e90f59c47b365b12fdf6 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 2 Jul 2024 12:11:04 -0400 Subject: [PATCH 17/47] Update PBR Sandcastle for self-shadowing tests --- Apps/Sandcastle/gallery/glTF PBR Extensions.html | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Apps/Sandcastle/gallery/glTF PBR Extensions.html b/Apps/Sandcastle/gallery/glTF PBR Extensions.html index 279a457fbecc..2f464ad4f4ba 100644 --- a/Apps/Sandcastle/gallery/glTF PBR Extensions.html +++ b/Apps/Sandcastle/gallery/glTF PBR Extensions.html @@ -140,6 +140,8 @@ onselect: () => { imageBasedLighting.sphericalHarmonicCoefficients = coefficients; imageBasedLighting.specularEnvironmentMaps = environmentMapURL; + imageBasedLighting.imageBasedLightingFactor = + Cesium.Cartesian2.ONE; }, }, { @@ -147,6 +149,15 @@ onselect: () => { imageBasedLighting.sphericalHarmonicCoefficients = undefined; imageBasedLighting.specularEnvironmentMaps = undefined; + imageBasedLighting.imageBasedLightingFactor = + Cesium.Cartesian2.ONE; + }, + }, + { + text: "Direct lighting only", + onselect: () => { + imageBasedLighting.imageBasedLightingFactor = + Cesium.Cartesian2.ZERO; }, }, ]; @@ -194,6 +205,10 @@ text: "Barn Lamp", onselect: () => loadModel(2583726), }, + { + text: "Metal-Roughness Spheres", + onselect: () => loadModel(2635364), + }, ]; Sandcastle.addToolbarMenu(modelOptions, "modelsToolbar"); loadModel(2584329); From 97959aa43cfc7236022d70ed5ca272f9441d7ce3 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 2 Jul 2024 12:57:09 -0400 Subject: [PATCH 18/47] Update CHANGES.md --- CHANGES.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 7ea0d41a011e..f9ac5056b878 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,13 @@ # Change Log +### 1.120 - 2024-08-01 + +#### @cesium/engine + +##### Fixes :wrench: + +- Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063) + ### 1.119 - 2024-07-01 #### @cesium/engine From 11a9611baef76cba78f31e9b51745d73eaccdc47 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 8 Jul 2024 17:11:17 -0400 Subject: [PATCH 19/47] Fix LOD interpolation in sampleOctahedralProjection --- packages/engine/Source/Renderer/AutomaticUniforms.js | 2 +- .../Shaders/Builtin/Functions/sampleOctahedralProjection.glsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Renderer/AutomaticUniforms.js b/packages/engine/Source/Renderer/AutomaticUniforms.js index 0b16c23ae9a3..95fc8c39d185 100644 --- a/packages/engine/Source/Renderer/AutomaticUniforms.js +++ b/packages/engine/Source/Renderer/AutomaticUniforms.js @@ -1454,7 +1454,7 @@ const AutomaticUniforms = { * // Example: For a given roughness and NdotV value, find the material's BRDF information in the red and green channels * float roughness = 0.5; * float NdotV = dot(normal, view); - * vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, 1.0 - roughness)).rg; + * vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, roughness)).rg; */ czm_brdfLut: new AutomaticUniform({ size: 1, diff --git a/packages/engine/Source/Shaders/Builtin/Functions/sampleOctahedralProjection.glsl b/packages/engine/Source/Shaders/Builtin/Functions/sampleOctahedralProjection.glsl index 3a625ccfe417..86b2a2c7089a 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/sampleOctahedralProjection.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/sampleOctahedralProjection.glsl @@ -68,7 +68,7 @@ vec3 czm_sampleOctahedralProjectionWithFiltering(sampler2D projectedMap, vec2 te * @returns {vec3} The color of the cube map at the direction. */ vec3 czm_sampleOctahedralProjection(sampler2D projectedMap, vec2 textureSize, vec3 direction, float lod, float maxLod) { - float currentLod = floor(lod + 0.5); + float currentLod = floor(lod); float nextLod = min(currentLod + 1.0, maxLod); vec3 colorCurrentLod = czm_sampleOctahedralProjectionWithFiltering(projectedMap, textureSize, direction, currentLod); From 11ee2857bb47a089d9e827e2c967694fc4bbaf35 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 8 Jul 2024 17:12:07 -0400 Subject: [PATCH 20/47] Use more mip levels for environment maps --- .../engine/Source/Scene/OctahedralProjectedCubeMap.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/engine/Source/Scene/OctahedralProjectedCubeMap.js b/packages/engine/Source/Scene/OctahedralProjectedCubeMap.js index 8ba8225759d7..6ec070004f45 100644 --- a/packages/engine/Source/Scene/OctahedralProjectedCubeMap.js +++ b/packages/engine/Source/Scene/OctahedralProjectedCubeMap.js @@ -337,11 +337,10 @@ OctahedralProjectedCubeMap.prototype.update = function (frameState) { }, }); - // We only need up to 6 mip levels to avoid artifacts. - const length = Math.min(cubeMapBuffers.length, 6); - this._maximumMipmapLevel = length - 1; - const cubeMaps = (this._cubeMaps = new Array(length)); - const mipTextures = (this._mipTextures = new Array(length)); + const mipLevels = cubeMapBuffers.length; + this._maximumMipmapLevel = mipLevels - 1; + const cubeMaps = (this._cubeMaps = new Array(mipLevels)); + const mipTextures = (this._mipTextures = new Array(mipLevels)); const originalSize = cubeMapBuffers[0].positiveX.width * 2.0; const uniformMap = { originalSize: function () { @@ -350,7 +349,7 @@ OctahedralProjectedCubeMap.prototype.update = function (frameState) { }; // First we project each cubemap onto a flat octahedron, and write that to a texture. - for (let i = 0; i < length; ++i) { + for (let i = 0; i < mipLevels; ++i) { // Swap +Y/-Y faces since the octahedral projection expects this order. const positiveY = cubeMapBuffers[i].positiveY; cubeMapBuffers[i].positiveY = cubeMapBuffers[i].negativeY; From d9ced58580111697cf0d0c3d79e25dbdbeab4b35 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 8 Jul 2024 17:34:09 -0400 Subject: [PATCH 21/47] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index f9ac5056b878..0a7993d11679 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ ##### Fixes :wrench: - Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063) +- Fixed environment map LOD selection in image-based lighting. [#12070](https://github.com/CesiumGS/cesium/pull/12070) ### 1.119 - 2024-07-01 From ba2da20acf3585acd6ef7ece4f85afe0a5cd360e Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 8 Jul 2024 18:05:39 -0400 Subject: [PATCH 22/47] Update OctahedralProjectedCubeMapSpec --- .../Scene/OctahedralProjectedCubeMapSpec.js | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/packages/engine/Specs/Scene/OctahedralProjectedCubeMapSpec.js b/packages/engine/Specs/Scene/OctahedralProjectedCubeMapSpec.js index ac9efc89fb90..df499910dc3b 100644 --- a/packages/engine/Specs/Scene/OctahedralProjectedCubeMapSpec.js +++ b/packages/engine/Specs/Scene/OctahedralProjectedCubeMapSpec.js @@ -120,7 +120,7 @@ describe( }); } - it("creates a packed texture with the right dimensions", function () { + it("creates a packed texture with the right dimensions", async function () { if (!OctahedralProjectedCubeMap.isSupported(context)) { return; } @@ -128,17 +128,16 @@ describe( octahedralMap = new OctahedralProjectedCubeMap(environmentMapUrl); const frameState = createFrameState(context); - return pollToPromise(function () { + await pollToPromise(function () { octahedralMap.update(frameState); return octahedralMap.ready; - }).then(function () { - expect(octahedralMap.texture.width).toEqual(770); - expect(octahedralMap.texture.height).toEqual(512); - expect(octahedralMap.maximumMipmapLevel).toEqual(5); }); + expect(octahedralMap.texture.width).toEqual(770); + expect(octahedralMap.texture.height).toEqual(512); + expect(octahedralMap.maximumMipmapLevel).toEqual(7); }); - it("correctly projects the given cube map and all mip levels", function () { + it("correctly projects the given cube map and all mip levels", async function () { if (!OctahedralProjectedCubeMap.isSupported(context)) { return; } @@ -146,7 +145,7 @@ describe( octahedralMap = new OctahedralProjectedCubeMap(environmentMapUrl); const frameState = createFrameState(context); - return pollToPromise(function () { + await pollToPromise(function () { // We manually call update and execute the commands // because calling scene.renderForSpecs does not // actually execute these commands, and we need @@ -155,34 +154,32 @@ describe( executeCommands(frameState); return octahedralMap.ready; - }).then(function () { - const directions = { - positiveX: new Cartesian3(1, 0, 0), - negativeX: new Cartesian3(-1, 0, 0), - positiveY: new Cartesian3(0, 1, 0), - negativeY: new Cartesian3(0, -1, 0), - positiveZ: new Cartesian3(0, 0, 1), - negativeZ: new Cartesian3(0, 0, -1), - }; - - for ( - let mipLevel = 0; - mipLevel < octahedralMap.maximumMipmapLevel; - mipLevel++ - ) { - for (const key in directions) { - if (directions.hasOwnProperty(key)) { - const direction = directions[key]; - - expectCubeMapAndOctahedralMapEqual( - octahedralMap, - direction, - mipLevel - ); - } + }); + const directions = { + positiveX: new Cartesian3(1, 0, 0), + negativeX: new Cartesian3(-1, 0, 0), + positiveY: new Cartesian3(0, 1, 0), + negativeY: new Cartesian3(0, -1, 0), + positiveZ: new Cartesian3(0, 0, 1), + negativeZ: new Cartesian3(0, 0, -1), + }; + + // The projection is less accurate for the last mip levels, + // where the input cubemap only has a few samples. + const lastAccurateMip = octahedralMap.maximumMipmapLevel - 2; + for (let mipLevel = 0; mipLevel < lastAccurateMip; mipLevel++) { + for (const key in directions) { + if (directions.hasOwnProperty(key)) { + const direction = directions[key]; + + expectCubeMapAndOctahedralMapEqual( + octahedralMap, + direction, + mipLevel + ); } } - }); + } }); it("caches projected textures", function () { From e5106dfdf4e6f51360cfc12805098a3c0d3f1cd5 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 11 Jul 2024 18:41:13 -0400 Subject: [PATCH 23/47] Fix spherical harmonic coefficients in IBL Sandcastles --- .../gallery/Image-Based Lighting.html | 62 +++++++++---------- .../gallery/glTF PBR Extensions.html | 57 ++++++++--------- .../engine/Source/Scene/ImageBasedLighting.js | 4 +- 3 files changed, 62 insertions(+), 61 deletions(-) diff --git a/Apps/Sandcastle/gallery/Image-Based Lighting.html b/Apps/Sandcastle/gallery/Image-Based Lighting.html index 7c038f6ab58b..448a7ee13bed 100644 --- a/Apps/Sandcastle/gallery/Image-Based Lighting.html +++ b/Apps/Sandcastle/gallery/Image-Based Lighting.html @@ -65,63 +65,63 @@ ); } - const environmentMapURL = - "https://cesium.com/public/SandcastleSampleData/kiara_6_afternoon_2k_ibl.ktx2"; const modelURL = "../../SampleData/models/Pawns/Pawns.glb"; // This environment map was processed using Khronos's glTF IBL Sampler. To process your own: // 1 - Download and build the Khronos glTF IBL Sampler (https://github.com/KhronosGroup/glTF-IBL-Sampler). // 2 - Run `cli -inputPath /path/to/image.hdr -outCubeMap /path/to/output.ktx2`. Run `cli -h` for all options. + const environmentMapURL = + "https://cesium.com/public/SandcastleSampleData/kiara_6_afternoon_2k_ibl.ktx2"; // To generate the spherical harmonic coefficients below, use Google's Filament project: // 1 - Download the Filament release (https://github.com/google/filament/releases). - // 2 - Run `cmgen --type=ktx --deploy=/path/to/output /path/to/image.hdr`. Other formats are also supported. Run `cmgen --help` for all options. + // 2 - Run `cmgen --no-mirror --type=ktx --deploy=/path/to/output /path/to/image.hdr`. + // Other formats are also supported. Run `cmgen --help` for all options. // 3 - Take the generated coefficients and load them in CesiumJS as shown below. - const L00 = new Cesium.Cartesian3( - 1.234709620475769, - 1.221461296081543, - 1.273156881332397 + 1.234897375106812, + 1.221635103225708, + 1.273374080657959 ); const L1_1 = new Cesium.Cartesian3( - 1.135921120643616, - 1.171217799186707, - 1.287644743919373 + 1.136140108108521, + 1.171419978141785, + 1.287894368171692 ); const L10 = new Cesium.Cartesian3( - 1.245193719863892, - 1.245591878890991, - 1.282818794250488 + 1.245410919189453, + 1.245791077613831, + 1.283067107200623 ); const L11 = new Cesium.Cartesian3( - -1.106930732727051, - -1.112522482872009, - -1.153198838233948 + 1.107124328613281, + 1.112697005271912, + 1.153419137001038 ); const L2_2 = new Cesium.Cartesian3( - -1.086226940155029, - -1.079731941223145, - -1.101912498474121 + 1.08641505241394, + 1.079904079437256, + 1.10212504863739 ); const L2_1 = new Cesium.Cartesian3( - 1.189834713935852, - 1.185906887054443, - 1.214385271072388 + 1.190043210983276, + 1.186099290847778, + 1.214627981185913 ); const L20 = new Cesium.Cartesian3( - 0.01778045296669, - 0.02013735473156, - 0.025313569232821 + 0.017783647403121, + 0.020140396431088, + 0.025317270308733 ); const L21 = new Cesium.Cartesian3( - -1.086826920509338, - -1.084611177444458, - -1.111204028129578 + 1.087014317512512, + 1.084779262542725, + 1.111417651176453 ); const L22 = new Cesium.Cartesian3( - -0.05241484940052, - -0.048303380608559, - -0.041960217058659 + -0.052426788955927, + -0.048315055668354, + -0.041973855346441 ); const coefficients = [L00, L1_1, L10, L11, L2_2, L2_1, L20, L21, L22]; diff --git a/Apps/Sandcastle/gallery/glTF PBR Extensions.html b/Apps/Sandcastle/gallery/glTF PBR Extensions.html index 2f464ad4f4ba..9c3b588f4285 100644 --- a/Apps/Sandcastle/gallery/glTF PBR Extensions.html +++ b/Apps/Sandcastle/gallery/glTF PBR Extensions.html @@ -79,52 +79,53 @@ // To generate the spherical harmonic coefficients below, use Google's Filament project: // 1 - Download the Filament release (https://github.com/google/filament/releases). - // 2 - Run `cmgen --type=ktx --deploy=/path/to/output /path/to/image.hdr`. Other formats are also supported. Run `cmgen --help` for all options. + // 2 - Run `cmgen --no-mirror --type=ktx --deploy=/path/to/output /path/to/image.hdr`. + // Other formats are also supported. Run `cmgen --help` for all options. // 3 - Take the generated coefficients and load them in CesiumJS as shown below. const L00 = new Cesium.Cartesian3( - 1.234709620475769, - 1.221461296081543, - 1.273156881332397 + 1.234897375106812, + 1.221635103225708, + 1.273374080657959 ); const L1_1 = new Cesium.Cartesian3( - 1.135921120643616, - 1.171217799186707, - 1.287644743919373 + 1.136140108108521, + 1.171419978141785, + 1.287894368171692 ); const L10 = new Cesium.Cartesian3( - 1.245193719863892, - 1.245591878890991, - 1.282818794250488 + 1.245410919189453, + 1.245791077613831, + 1.283067107200623 ); const L11 = new Cesium.Cartesian3( - -1.106930732727051, - -1.112522482872009, - -1.153198838233948 + 1.107124328613281, + 1.112697005271912, + 1.153419137001038 ); const L2_2 = new Cesium.Cartesian3( - -1.086226940155029, - -1.079731941223145, - -1.101912498474121 + 1.08641505241394, + 1.079904079437256, + 1.10212504863739 ); const L2_1 = new Cesium.Cartesian3( - 1.189834713935852, - 1.185906887054443, - 1.214385271072388 + 1.190043210983276, + 1.186099290847778, + 1.214627981185913 ); const L20 = new Cesium.Cartesian3( - 0.01778045296669, - 0.02013735473156, - 0.025313569232821 + 0.017783647403121, + 0.020140396431088, + 0.025317270308733 ); const L21 = new Cesium.Cartesian3( - -1.086826920509338, - -1.084611177444458, - -1.111204028129578 + 1.087014317512512, + 1.084779262542725, + 1.111417651176453 ); const L22 = new Cesium.Cartesian3( - -0.05241484940052, - -0.048303380608559, - -0.041960217058659 + -0.052426788955927, + -0.048315055668354, + -0.041973855346441 ); const coefficients = [L00, L1_1, L10, L11, L2_2, L2_1, L20, L21, L22]; diff --git a/packages/engine/Source/Scene/ImageBasedLighting.js b/packages/engine/Source/Scene/ImageBasedLighting.js index e5822bc5e576..33a5a8c6db89 100644 --- a/packages/engine/Source/Scene/ImageBasedLighting.js +++ b/packages/engine/Source/Scene/ImageBasedLighting.js @@ -186,8 +186,8 @@ Object.defineProperties(ImageBasedLighting.prototype, { *

* * These values can be obtained by preprocessing the environment map using the cmgen tool of - * {@link https://github.com/google/filament/releases|Google's Filament project}. This will also generate a KTX file that can be - * supplied to {@link Model#specularEnvironmentMaps}. + * {@link https://github.com/google/filament/releases|Google's Filament project}. + * Be sure to use the --no-mirror option in cmgen. * * @memberof ImageBasedLighting.prototype * From 8213dac642fbde3d6f5c41420dc0975112e2112b Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 11 Jul 2024 20:40:51 -0400 Subject: [PATCH 24/47] Use surface normal for diffuse IBL lookup --- .../Model/ImageBasedLightingStageFS.glsl | 21 +++++++++---------- .../Source/Shaders/Model/LightingStageFS.glsl | 8 +++---- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index e94ffd0a0dcc..e079ec120105 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -121,7 +121,7 @@ float getSunLuminance(vec3 positionWC, vec3 normalEC, vec3 lightDirectionEC) ) { vec3 viewDirectionEC = -normalize(positionEC); vec3 positionWC = vec3(czm_inverseView * vec4(positionEC, 1.0)); - vec3 reflectionWC = normalize(czm_inverseViewRotation * normalize(reflect(viewDirectionEC, normalEC))); + vec3 reflectionWC = normalize(czm_inverseViewRotation * reflect(viewDirectionEC, normalEC)); vec3 skyMetrics = getProceduralSkyMetrics(positionWC, reflectionWC); float roughness = material.roughness; @@ -202,34 +202,33 @@ vec3 textureIBL( vec3 lightDirectionEC, czm_modelMaterial material ) { - // Find the direction in which to sample the environment map - vec3 cubeDir = normalize(model_iblReferenceFrameMatrix * normalize(reflect(-viewDirectionEC, normalEC))); - #ifdef DIFFUSE_IBL - vec3 diffuseContribution = computeDiffuseIBL(cubeDir) * material.diffuse; + vec3 normalMC = normalize(model_iblReferenceFrameMatrix * normalEC); + vec3 diffuseContribution = computeDiffuseIBL(normalMC) * material.diffuse; #else vec3 diffuseContribution = vec3(0.0); #endif - float roughness = material.roughness; - #ifdef USE_ANISOTROPY - // Update environment map sampling direction to account for anisotropic distortion of specular reflection + // Bend normal to account for anisotropic distortion of specular reflection vec3 anisotropyDirection = material.anisotropicB; vec3 anisotropicTangent = cross(anisotropyDirection, viewDirectionEC); vec3 anisotropicNormal = cross(anisotropicTangent, anisotropyDirection); - float bendFactor = 1.0 - material.anisotropyStrength * (1.0 - roughness); + float bendFactor = 1.0 - material.anisotropyStrength * (1.0 - material.roughness); float bendFactorPow4 = bendFactor * bendFactor * bendFactor * bendFactor; vec3 bentNormal = normalize(mix(anisotropicNormal, normalEC, bendFactorPow4)); - cubeDir = normalize(model_iblReferenceFrameMatrix * normalize(reflect(-viewDirectionEC, bentNormal))); + vec3 reflectEC = reflect(-viewDirectionEC, bentNormal); + #else + vec3 reflectEC = reflect(-viewDirectionEC, normalEC); #endif #ifdef SPECULAR_IBL + vec3 reflectMC = normalize(model_iblReferenceFrameMatrix * reflectEC); float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; vec3 halfwayDirectionEC = normalize(viewDirectionEC + lightDirectionEC); float VdotH = clamp(dot(viewDirectionEC, halfwayDirectionEC), 0.0, 1.0); vec3 f0 = material.specular; - vec3 specularContribution = computeSpecularIBL(cubeDir, NdotV, VdotH, f0, roughness); + vec3 specularContribution = computeSpecularIBL(reflectMC, NdotV, VdotH, f0, material.roughness); #else vec3 specularContribution = vec3(0.0); #endif diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 2b04c1708713..5338599cc77e 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -40,12 +40,12 @@ vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirect #ifdef SPECULAR_IBL // Find the direction in which to sample the environment map - vec3 cubeDir = normalize(model_iblReferenceFrameMatrix * normalize(reflect(-viewDirection, normal))); - vec3 iblColor = computeSpecularIBL(cubeDir, NdotV, NdotV, f0, roughness); + vec3 reflectMC = normalize(model_iblReferenceFrameMatrix * reflect(-viewDirection, normal)); + vec3 iblColor = computeSpecularIBL(reflectMC, NdotV, NdotV, f0, roughness); color += iblColor * material.occlusion; #elif defined(USE_IBL_LIGHTING) vec3 positionWC = vec3(czm_inverseView * vec4(position, 1.0)); - vec3 reflectionWC = normalize(czm_inverseViewRotation * normalize(reflect(viewDirection, normal))); + vec3 reflectionWC = normalize(czm_inverseViewRotation * reflect(viewDirection, normal)); vec3 skyMetrics = getProceduralSkyMetrics(positionWC, reflectionWC); vec3 specularIrradiance = getProceduralSpecularIrradiance(reflectionWC, skyMetrics, roughness); @@ -57,7 +57,7 @@ vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirect #endif float maximumComponent = czm_maximumComponent(lightColorHdr); vec3 clampedLightColor = lightColorHdr / max(maximumComponent, 1.0); - color += clampedLightColor* iblColor * material.occlusion; + color += clampedLightColor * iblColor * material.occlusion; #endif float clearcoatFactor = material.clearcoatFactor; From 3ffd571b1382c8a8fd59e840fa06f78cc654c4b3 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 12 Jul 2024 10:10:33 -0400 Subject: [PATCH 25/47] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 0a7993d11679..af1c081c3808 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063) - Fixed environment map LOD selection in image-based lighting. [#12070](https://github.com/CesiumGS/cesium/pull/12070) +- Corrected calculation of diffuse component in image-based lighting. [#12082](https://github.com/CesiumGS/cesium/pull/12082) ### 1.119 - 2024-07-01 From 27c6fb81df84dd8987e2584a339e4c713a1b66b4 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 12 Jul 2024 19:06:05 -0400 Subject: [PATCH 26/47] Update BRDF for image-based lighting --- .../Source/Shaders/BrdfLutGeneratorFS.glsl | 43 +++++++++++++------ .../Builtin/Functions/pbrLighting.glsl | 43 ++++++++++--------- .../Model/ImageBasedLightingStageFS.glsl | 27 ++++++------ .../Source/Shaders/Model/LightingStageFS.glsl | 5 ++- .../Source/Shaders/Model/MaterialStageFS.glsl | 17 +++----- 5 files changed, 76 insertions(+), 59 deletions(-) diff --git a/packages/engine/Source/Shaders/BrdfLutGeneratorFS.glsl b/packages/engine/Source/Shaders/BrdfLutGeneratorFS.glsl index 32bee9afd4fe..99a27592cd68 100644 --- a/packages/engine/Source/Shaders/BrdfLutGeneratorFS.glsl +++ b/packages/engine/Source/Shaders/BrdfLutGeneratorFS.glsl @@ -27,11 +27,11 @@ vec2 hammersley2D(int i, int N) return vec2(float(i) / float(N), vdcRadicalInverse(i)); } -vec3 importanceSampleGGX(vec2 xi, float roughness, vec3 N) +vec3 importanceSampleGGX(vec2 xi, float alphaRoughness, vec3 N) { - float a = roughness * roughness; + float alphaRoughnessSquared = alphaRoughness * alphaRoughness; float phi = 2.0 * M_PI * xi.x; - float cosTheta = sqrt((1.0 - xi.y) / (1.0 + (a * a - 1.0) * xi.y)); + float cosTheta = sqrt((1.0 - xi.y) / (1.0 + (alphaRoughnessSquared - 1.0) * xi.y)); float sinTheta = sqrt(1.0 - cosTheta * cosTheta); vec3 H = vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta); vec3 upVector = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); @@ -40,15 +40,31 @@ vec3 importanceSampleGGX(vec2 xi, float roughness, vec3 N) return tangentX * H.x + tangentY * H.y + N * H.z; } -float G1_Smith(float NdotV, float k) +/** + * Estimate the geometric self-shadowing of the microfacets in a surface, + * using the Smith Joint GGX visibility function. + * Note: Vis = G / (4 * NdotL * NdotV) + * see Eric Heitz. 2014. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3 + * see Real-Time Rendering. Page 331 to 336. + * see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg) + * + * @param {float} alphaRoughness The roughness of the material, expressed as the square of perceptual roughness. + * @param {float} NdotL The cosine of the angle between the surface normal and the direction to the light source. + * @param {float} NdotV The cosine of the angle between the surface normal and the direction to the camera. + */ +float smithVisibilityGGX(float alphaRoughness, float NdotL, float NdotV) { - return NdotV / (NdotV * (1.0 - k) + k); -} + float alphaRoughnessSq = alphaRoughness * alphaRoughness; -float G_Smith(float roughness, float NdotV, float NdotL) -{ - float k = roughness * roughness / 2.0; - return G1_Smith(NdotV, k) * G1_Smith(NdotL, k); + float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaRoughnessSq) + alphaRoughnessSq); + float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaRoughnessSq) + alphaRoughnessSq); + + float GGX = GGXV + GGXL; // 2.0 if NdotL = NdotV = 1.0 + if (GGX > 0.0) + { + return 0.5 / GGX; // 1/4 if NdotL = NdotV = 1.0 + } + return 0.0; } vec2 integrateBrdf(float roughness, float NdotV) @@ -57,18 +73,19 @@ vec2 integrateBrdf(float roughness, float NdotV) float A = 0.0; float B = 0.0; const int NumSamples = 1024; + float alphaRoughness = roughness * roughness; for (int i = 0; i < NumSamples; i++) { vec2 xi = hammersley2D(i, NumSamples); - vec3 H = importanceSampleGGX(xi, roughness, vec3(0.0, 0.0, 1.0)); + vec3 H = importanceSampleGGX(xi, alphaRoughness, vec3(0.0, 0.0, 1.0)); vec3 L = 2.0 * dot(V, H) * H - V; float NdotL = clamp(L.z, 0.0, 1.0); float NdotH = clamp(H.z, 0.0, 1.0); float VdotH = clamp(dot(V, H), 0.0, 1.0); if (NdotL > 0.0) { - float G = G_Smith(roughness, NdotV, NdotL); - float G_Vis = G * VdotH / (NdotH * NdotV); + float G = smithVisibilityGGX(alphaRoughness, NdotL, NdotV); + float G_Vis = 4.0 * G * VdotH * NdotL / NdotH; float Fc = pow(1.0 - VdotH, 5.0); A += (1.0 - Fc) * G_Vis; B += Fc * G_Vis; diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl index f7518bda6f6a..e0bc9eff73cc 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl @@ -13,14 +13,14 @@ vec3 fresnelSchlick2(vec3 f0, vec3 f90, float VdotH) #ifdef USE_ANISOTROPY /** - * @param {float} roughness Material roughness (along the anisotropy bitangent) + * @param {float} bitangentRoughness Material roughness (along the anisotropy bitangent) * @param {float} tangentialRoughness Anisotropic roughness (along the anisotropy tangent) * @param {vec3} lightDirection The direction from the fragment to the light source, transformed to tangent-bitangent-normal coordinates * @param {vec3} viewDirection The direction from the fragment to the camera, transformed to tangent-bitangent-normal coordinates */ -float smithVisibilityGGX_anisotropic(float roughness, float tangentialRoughness, vec3 lightDirection, vec3 viewDirection) +float smithVisibilityGGX_anisotropic(float bitangentRoughness, float tangentialRoughness, vec3 lightDirection, vec3 viewDirection) { - vec3 roughnessScale = vec3(tangentialRoughness, roughness, 1.0); + vec3 roughnessScale = vec3(tangentialRoughness, bitangentRoughness, 1.0); float GGXV = lightDirection.z * length(roughnessScale * viewDirection); float GGXL = viewDirection.z * length(roughnessScale * lightDirection); float v = 0.5 / (GGXV + GGXL); @@ -28,14 +28,14 @@ float smithVisibilityGGX_anisotropic(float roughness, float tangentialRoughness, } /** - * @param {float} roughness Material roughness (along the anisotropy bitangent) + * @param {float} bitangentRoughness Material roughness (along the anisotropy bitangent) * @param {float} tangentialRoughness Anisotropic roughness (along the anisotropy tangent) * @param {vec3} halfwayDirection The unit vector halfway between light and view directions, transformed to tangent-bitangent-normal coordinates */ -float GGX_anisotropic(float roughness, float tangentialRoughness, vec3 halfwayDirection) +float GGX_anisotropic(float bitangentRoughness, float tangentialRoughness, vec3 halfwayDirection) { - float roughnessSquared = roughness * tangentialRoughness; - vec3 f = halfwayDirection * vec3(roughness, tangentialRoughness, roughnessSquared); + float roughnessSquared = bitangentRoughness * tangentialRoughness; + vec3 f = halfwayDirection * vec3(bitangentRoughness, tangentialRoughness, roughnessSquared); float w2 = roughnessSquared / dot(f, f); return roughnessSquared * w2 * w2 / czm_pi; } @@ -73,15 +73,15 @@ float smithVisibilityGGX(float alphaRoughness, float NdotL, float NdotV) * the halfway vector, which is aligned halfway between the directions from * the fragment to the camera and from the fragment to the light source. * - * @param {float} roughness The roughness of the material. + * @param {float} alphaRoughness The roughness of the material, expressed as the square of perceptual roughness. * @param {float} NdotH The cosine of the angle between the surface normal and the halfway vector. * @return {float} The fraction of microfacets aligned to the halfway vector. */ -float GGX(float roughness, float NdotH) +float GGX(float alphaRoughness, float NdotH) { - float roughnessSquared = roughness * roughness; - float f = (NdotH * roughnessSquared - NdotH) * NdotH + 1.0; - return roughnessSquared / (czm_pi * f * f); + float alphaRoughnessSquared = alphaRoughness * alphaRoughness; + float f = (NdotH * alphaRoughnessSquared - NdotH) * NdotH + 1.0; + return alphaRoughnessSquared / (czm_pi * f * f); } /** @@ -91,16 +91,16 @@ float GGX(float roughness, float NdotH) * @param {vec3} lightDirection The unit vector pointing from the fragment to the light source. * @param {vec3} viewDirection The unit vector pointing from the fragment to the camera. * @param {vec3} halfwayDirection The unit vector pointing from the fragment to halfway between the light source and the camera. - * @param {float} roughness The roughness of the material. + * @param {float} alphaRoughness The roughness of the material, expressed as the square of perceptual roughness. * @return {float} The strength of the specular reflection. */ -float computeDirectSpecularStrength(vec3 normal, vec3 lightDirection, vec3 viewDirection, vec3 halfwayDirection, float roughness) +float computeDirectSpecularStrength(vec3 normal, vec3 lightDirection, vec3 viewDirection, vec3 halfwayDirection, float alphaRoughness) { float NdotL = dot(normal, lightDirection); float NdotV = abs(dot(normal, viewDirection)); - float G = smithVisibilityGGX(roughness, NdotL, NdotV); + float G = smithVisibilityGGX(alphaRoughness, NdotL, NdotV); float NdotH = clamp(dot(normal, halfwayDirection), 0.0, 1.0); - float D = GGX(roughness, NdotH); + float D = GGX(alphaRoughness, NdotH); return G * D; } @@ -138,19 +138,20 @@ vec3 czm_pbrLighting(vec3 viewDirectionEC, vec3 normalEC, vec3 lightDirectionEC, F *= material.specularWeight; #endif - float alpha = material.roughness; + float alphaRoughness = material.roughness * material.roughness; #ifdef USE_ANISOTROPY mat3 tbn = mat3(material.anisotropicT, material.anisotropicB, normalEC); vec3 lightDirection = lightDirectionEC * tbn; vec3 viewDirection = viewDirectionEC * tbn; vec3 halfwayDirection = halfwayDirectionEC * tbn; float anisotropyStrength = material.anisotropyStrength; - float tangentialRoughness = mix(alpha, 1.0, anisotropyStrength * anisotropyStrength); - float G = smithVisibilityGGX_anisotropic(alpha, tangentialRoughness, lightDirection, viewDirection); - float D = GGX_anisotropic(alpha, tangentialRoughness, halfwayDirection); + float tangentialRoughness = mix(alphaRoughness, 1.0, anisotropyStrength * anisotropyStrength); + float bitangentRoughness = clamp(alphaRoughness, 0.001, 1.0); + float G = smithVisibilityGGX_anisotropic(bitangentRoughness, tangentialRoughness, lightDirection, viewDirection); + float D = GGX_anisotropic(bitangentRoughness, tangentialRoughness, halfwayDirection); vec3 specularContribution = F * G * D; #else - float specularStrength = computeDirectSpecularStrength(normalEC, lightDirectionEC, viewDirectionEC, halfwayDirectionEC, alpha); + float specularStrength = computeDirectSpecularStrength(normalEC, lightDirectionEC, viewDirectionEC, halfwayDirectionEC, alphaRoughness); vec3 specularContribution = F * specularStrength; #endif diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index e079ec120105..b33c5b05d2b6 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -16,10 +16,10 @@ vec3 getProceduralSkyMetrics(vec3 positionWC, vec3 reflectionWC) } /** - * Compute the diffuse irradiance for a procedural sky lighting model + * Compute the diffuse irradiance for a procedural sky lighting model. * * @param {vec3} skyMetrics The dot products of the horizon and reflection directions with the nadir, and an atmosphere boundary distance. - * @return {vec3} The computed diffuse irradiance + * @return {vec3} The computed diffuse irradiance. */ vec3 getProceduralDiffuseIrradiance(vec3 skyMetrics) { @@ -30,10 +30,12 @@ vec3 getProceduralDiffuseIrradiance(vec3 skyMetrics) } /** - * Compute the specular irradiance for a procedural sky lighting model + * Compute the specular irradiance for a procedural sky lighting model. * + * @param {vec3} reflectionWC The reflection vector in world coordinates. * @param {vec3} skyMetrics The dot products of the horizon and reflection directions with the nadir, and an atmosphere boundary distance. - * @return {vec3} The computed specular irradiance + * @param {float} roughness The roughness of the material. + * @return {vec3} The computed specular irradiance. */ vec3 getProceduralSpecularIrradiance(vec3 reflectionWC, vec3 skyMetrics, float roughness) { @@ -42,7 +44,7 @@ vec3 getProceduralSpecularIrradiance(vec3 reflectionWC, vec3 skyMetrics, float r reflectionWC = -normalize(czm_temeToPseudoFixed * reflectionWC); reflectionWC.x = -reflectionWC.x; - float inverseRoughness = 1.04 - roughness; + float inverseRoughness = 1.0 - roughness; inverseRoughness *= inverseRoughness; vec3 sceneSkyBox = czm_textureCube(czm_environmentMap, reflectionWC).rgb * inverseRoughness; @@ -173,11 +175,12 @@ vec3 sampleSpecularEnvironment(vec3 cubeDir, float roughness) return czm_sampleOctahedralProjection(czm_specularEnvironmentMaps, czm_specularEnvironmentMapSize, cubeDir, lod, maxLod); #endif } -vec3 computeSpecularIBL(vec3 cubeDir, float NdotV, float VdotH, vec3 f0, float roughness) +vec3 computeSpecularIBL(vec3 cubeDir, float NdotV, vec3 f0, float roughness) { - float reflectance = czm_maximumComponent(f0); - vec3 f90 = vec3(clamp(reflectance * 25.0, 0.0, 1.0)); - vec3 F = fresnelSchlick2(f0, f90, VdotH); + // see https://bruop.github.io/ibl/ at Single Scattering Results + // Roughness dependent fresnel, from Fdez-Aguera + vec3 f90 = max(vec3(1.0 - roughness), f0); + vec3 F = fresnelSchlick2(f0, f90, NdotV); vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, roughness)).rg; vec3 specularSample = sampleSpecularEnvironment(cubeDir, roughness); @@ -224,11 +227,9 @@ vec3 textureIBL( #ifdef SPECULAR_IBL vec3 reflectMC = normalize(model_iblReferenceFrameMatrix * reflectEC); - float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; - vec3 halfwayDirectionEC = normalize(viewDirectionEC + lightDirectionEC); - float VdotH = clamp(dot(viewDirectionEC, halfwayDirectionEC), 0.0, 1.0); + float NdotV = abs(dot(normalEC, viewDirectionEC)); vec3 f0 = material.specular; - vec3 specularContribution = computeSpecularIBL(reflectMC, NdotV, VdotH, f0, material.roughness); + vec3 specularContribution = computeSpecularIBL(reflectMC, NdotV, f0, material.roughness); #else vec3 specularContribution = vec3(0.0); #endif diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 5338599cc77e..bd9327b7ca60 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -34,14 +34,15 @@ vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirect // compute specular reflection from direct lighting float roughness = material.clearcoatRoughness; - float directStrength = computeDirectSpecularStrength(normal, lightDirection, viewDirection, halfwayDirection, roughness); + float alphaRoughness = roughness * roughness; + float directStrength = computeDirectSpecularStrength(normal, lightDirection, viewDirection, halfwayDirection, alphaRoughness); vec3 directReflection = F * directStrength * NdotL; vec3 color = lightColorHdr * directReflection; #ifdef SPECULAR_IBL // Find the direction in which to sample the environment map vec3 reflectMC = normalize(model_iblReferenceFrameMatrix * reflect(-viewDirection, normal)); - vec3 iblColor = computeSpecularIBL(reflectMC, NdotV, NdotV, f0, roughness); + vec3 iblColor = computeSpecularIBL(reflectMC, NdotV, f0, roughness); color += iblColor * material.occlusion; #elif defined(USE_IBL_LIGHTING) vec3 positionWC = vec3(czm_inverseView * vec4(position, 1.0)); diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index 4fa2f28769d9..824523a27a47 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -258,8 +258,7 @@ void setSpecularGlossiness(inout czm_modelMaterial material) material.specular = specular; // glossiness is the opposite of roughness, but easier for artists to use. - float roughness = 1.0 - glossiness; - material.roughness = roughness * roughness; + material.roughness = 1.0 - glossiness; } #elif defined(LIGHTING_PBR) float setMetallicRoughness(inout czm_modelMaterial material) @@ -272,7 +271,7 @@ float setMetallicRoughness(inout czm_modelMaterial material) vec3 metallicRoughness = texture(u_metallicRoughnessTexture, metallicRoughnessTexCoords).rgb; float metalness = clamp(metallicRoughness.b, 0.0, 1.0); - float roughness = clamp(metallicRoughness.g, 0.04, 1.0); + float roughness = clamp(metallicRoughness.g, 0.0, 1.0); #ifdef HAS_METALLIC_FACTOR metalness = clamp(metalness * u_metallicFactor, 0.0, 1.0); #endif @@ -288,7 +287,7 @@ float setMetallicRoughness(inout czm_modelMaterial material) #endif #ifdef HAS_ROUGHNESS_FACTOR - float roughness = clamp(u_roughnessFactor, 0.04, 1.0); + float roughness = clamp(u_roughnessFactor, 0.0, 1.0); #else float roughness = 1.0; #endif @@ -303,9 +302,8 @@ float setMetallicRoughness(inout czm_modelMaterial material) // diffuse only applies to dielectrics. material.diffuse = mix(material.baseColor.rgb, vec3(0.0), metalness); - // roughness is authored as perceptual roughness - // square it to get material roughness - material.roughness = roughness * roughness; + // This is perceptual roughness. The square of this value is used for direct lighting + material.roughness = roughness; return metalness; } @@ -418,9 +416,8 @@ void setClearcoat(inout czm_modelMaterial material, in ProcessedAttributes attri #endif material.clearcoatFactor = clearcoatFactor; - // roughness is authored as perceptual roughness - // square it to get material roughness - material.clearcoatRoughness = clearcoatRoughness * clearcoatRoughness; + // This is perceptual roughness. The square of this value is used for direct lighting + material.clearcoatRoughness = clearcoatRoughness; #ifdef HAS_CLEARCOAT_NORMAL_TEXTURE material.clearcoatNormal = getClearcoatNormalFromTexture(attributes, attributes.normalEC); #else From e90c08c68d359a7e329dd0bfebfedc4a696a9b9e Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 12 Jul 2024 19:35:15 -0400 Subject: [PATCH 27/47] Clamp NdotV for specular IBL calculation --- .../engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index b33c5b05d2b6..63358eec9a7d 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -227,7 +227,7 @@ vec3 textureIBL( #ifdef SPECULAR_IBL vec3 reflectMC = normalize(model_iblReferenceFrameMatrix * reflectEC); - float NdotV = abs(dot(normalEC, viewDirectionEC)); + float NdotV = clamp(dot(normalEC, viewDirectionEC), 0.0, 1.0); vec3 f0 = material.specular; vec3 specularContribution = computeSpecularIBL(reflectMC, NdotV, f0, material.roughness); #else From 9487152175695f4dd44e9b7268ab11fa4a0b12bc Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 12 Jul 2024 20:37:11 -0400 Subject: [PATCH 28/47] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index af1c081c3808..ea5ccc683acf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ - Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063) - Fixed environment map LOD selection in image-based lighting. [#12070](https://github.com/CesiumGS/cesium/pull/12070) - Corrected calculation of diffuse component in image-based lighting. [#12082](https://github.com/CesiumGS/cesium/pull/12082) +- Updated specular BRDF for image-based lighting. [#12083](https://github.com/CesiumGS/cesium/pull/12083) ### 1.119 - 2024-07-01 From fbfbe2ed9486b9ead7d18929322978d5c81ba3f7 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 22 Jul 2024 15:45:10 -0400 Subject: [PATCH 29/47] Flip x-axis in IBL environment map transform --- packages/engine/Source/Scene/Model/Model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 3bf1c787578d..3da886945f37 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2251,7 +2251,7 @@ function updatePickIds(model) { // Matrix3 is a row-major constructor. // The same constructor in GLSL will produce the transpose of this. -const yUpToZUp = new Matrix3(-1, 0, 0, 0, 0, 1, 0, -1, 0); +const yUpToZUp = new Matrix3(1, 0, 0, 0, 0, 1, 0, -1, 0); function updateReferenceMatrices(model, frameState) { const modelMatrix = defined(model._clampedModelMatrix) From 7a78ea6e17629c3d46a9fbff680f0aff2011f153 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 22 Jul 2024 15:51:37 -0400 Subject: [PATCH 30/47] Tweak Sandcastle for glTF PBR Extensions --- Apps/Sandcastle/gallery/glTF PBR Extensions.html | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Apps/Sandcastle/gallery/glTF PBR Extensions.html b/Apps/Sandcastle/gallery/glTF PBR Extensions.html index 9c3b588f4285..552dfab96d10 100644 --- a/Apps/Sandcastle/gallery/glTF PBR Extensions.html +++ b/Apps/Sandcastle/gallery/glTF PBR Extensions.html @@ -61,14 +61,13 @@ hpr ); - scene.globe.enableLighting = true; - scene.light = new Cesium.DirectionalLight({ direction: new Cesium.Cartesian3( 0.2454278300540191, 0.8842635425193919, 0.39729481195458805 ), + intensity: 0.0, }); // This environment map was processed using Khronos's glTF IBL Sampler. To process your own: @@ -143,6 +142,7 @@ imageBasedLighting.specularEnvironmentMaps = environmentMapURL; imageBasedLighting.imageBasedLightingFactor = Cesium.Cartesian2.ONE; + scene.light.intensity = 0.0; }, }, { @@ -152,6 +152,7 @@ imageBasedLighting.specularEnvironmentMaps = undefined; imageBasedLighting.imageBasedLightingFactor = Cesium.Cartesian2.ONE; + scene.light.intensity = 1.0; }, }, { @@ -159,6 +160,7 @@ onselect: () => { imageBasedLighting.imageBasedLightingFactor = Cesium.Cartesian2.ZERO; + scene.light.intensity = 1.0; }, }, ]; @@ -210,6 +212,10 @@ text: "Metal-Roughness Spheres", onselect: () => loadModel(2635364), }, + { + text: "Water Bottle", + onselect: () => loadModel(2654597), + }, ]; Sandcastle.addToolbarMenu(modelOptions, "modelsToolbar"); loadModel(2584329); From 13a75b71f8769858a83a8e6c7c809098df84ea99 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 22 Jul 2024 18:12:50 -0400 Subject: [PATCH 31/47] Add Mirror Ball model to glTF PBR Extensions Sandcastle --- Apps/Sandcastle/gallery/glTF PBR Extensions.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Apps/Sandcastle/gallery/glTF PBR Extensions.html b/Apps/Sandcastle/gallery/glTF PBR Extensions.html index 552dfab96d10..e9b2257c5a06 100644 --- a/Apps/Sandcastle/gallery/glTF PBR Extensions.html +++ b/Apps/Sandcastle/gallery/glTF PBR Extensions.html @@ -216,6 +216,10 @@ text: "Water Bottle", onselect: () => loadModel(2654597), }, + { + text: "Mirror Ball", + onselect: () => loadModel(2674524), + }, ]; Sandcastle.addToolbarMenu(modelOptions, "modelsToolbar"); loadModel(2584329); From 026831e8b46e175d4a7901fac475b100ce5286be Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 22 Jul 2024 19:01:24 -0400 Subject: [PATCH 32/47] Update CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index ea5ccc683acf..0bf11ffaefa4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ - Fixed environment map LOD selection in image-based lighting. [#12070](https://github.com/CesiumGS/cesium/pull/12070) - Corrected calculation of diffuse component in image-based lighting. [#12082](https://github.com/CesiumGS/cesium/pull/12082) - Updated specular BRDF for image-based lighting. [#12083](https://github.com/CesiumGS/cesium/pull/12083) +- Fixed environment map transform for image-based lighting. [#12091](https://github.com/CesiumGS/cesium/pull/12091) ### 1.119 - 2024-07-01 From ad46de4e080edf8c381ca55e1d0dd5806568dfca Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 23 Jul 2024 12:47:51 -0400 Subject: [PATCH 33/47] Add spec for IBL transform matrix --- .../engine/Specs/Scene/Model/ModelSpec.js | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index 7fc682127e03..1f48c0f6f4d8 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -31,6 +31,7 @@ import { JobScheduler, JulianDate, Math as CesiumMath, + Matrix3, Matrix4, Model, ModelFeature, @@ -1754,6 +1755,45 @@ describe( }); }); + describe("reference matrices", function () { + it("sets IBL transform matrix", async function () { + const resource = Resource.createIfNeeded(boxTexturedGlbUrl); + const buffer = await resource.fetchArrayBuffer(); + const imageBasedLighting = new ImageBasedLighting({ + specularEnvironmentMaps: + "./Data/EnvironmentMap/kiara_6_afternoon_2k_ibl.ktx2", + }); + const model = await loadAndZoomToModelAsync( + { + gltf: new Uint8Array(buffer), + imageBasedLighting: imageBasedLighting, + }, + scene + ); + await pollToPromise(function () { + scene.render(); + return ( + defined(imageBasedLighting.specularEnvironmentMapAtlas) && + imageBasedLighting.specularEnvironmentMapAtlas.ready + ); + }); + expect(model.modelMatrix).toEqual(Matrix4.IDENTITY); + const { view3D } = scene.context.uniformState; + const viewRotation = Matrix4.getRotation(view3D, new Matrix3()); + Matrix3.transpose(viewRotation, viewRotation); + const yUpToZUp = new Matrix3(1, 0, 0, 0, 0, 1, 0, -1, 0); + const expectedIblTransform = Matrix3.multiply( + yUpToZUp, + viewRotation, + new Matrix3() + ); + expect(model._iblReferenceFrameMatrix).toEqualEpsilon( + expectedIblTransform, + CesiumMath.EPSILON14 + ); + }); + }); + describe("picking and id", function () { it("initializes with id", async function () { // This model gets clipped if log depth is disabled, so zoom out From 4ad410c7f110a8b75290464cc5e419fdba4ddc98 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 23 Jul 2024 14:20:16 -0400 Subject: [PATCH 34/47] Skip IBL transform spec on WebGL stub --- packages/engine/Specs/Scene/Model/ModelSpec.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index 1f48c0f6f4d8..f91aab5bd9a5 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -1757,6 +1757,9 @@ describe( describe("reference matrices", function () { it("sets IBL transform matrix", async function () { + if (!scene.highDynamicRangeSupported) { + return; + } const resource = Resource.createIfNeeded(boxTexturedGlbUrl); const buffer = await resource.fetchArrayBuffer(); const imageBasedLighting = new ImageBasedLighting({ From 1e02de6826f1c361ad5141abefae5b4549e6fb90 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:36:09 -0400 Subject: [PATCH 35/47] add check for valid provider structure for Bing imagery --- packages/engine/Source/Scene/BingMapsImageryProvider.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/engine/Source/Scene/BingMapsImageryProvider.js b/packages/engine/Source/Scene/BingMapsImageryProvider.js index 5904bc193ce1..85a76392ceb6 100644 --- a/packages/engine/Source/Scene/BingMapsImageryProvider.js +++ b/packages/engine/Source/Scene/BingMapsImageryProvider.js @@ -120,7 +120,12 @@ function metadataSuccess(data, imageryProviderBuilder) { imageryProviderBuilder.maximumLevel = resource.zoomMax - 1; imageryProviderBuilder.imageUrlSubdomains = resource.imageUrlSubdomains; imageryProviderBuilder.imageUrlTemplate = resource.imageUrl; - imageryProviderBuilder.attributionList = resource.imageryProviders; + const validProviders = resource.imageryProviders.filter((provider) => + // prevent issues with the imagery API from crashing the viewer when the expected properties are not there + // See https://github.com/CesiumGS/cesium/issues/12088 + provider.coverageAreas?.some((area) => defined(area.bbox)) + ); + imageryProviderBuilder.attributionList = validProviders; } function metadataFailure(metadataResource, error, provider) { From e61477382f4242b2056bfb948476d5fd30c008a1 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:40:46 -0400 Subject: [PATCH 36/47] update changes --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index af1c081c3808..2568aa21992a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ - Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063) - Fixed environment map LOD selection in image-based lighting. [#12070](https://github.com/CesiumGS/cesium/pull/12070) - Corrected calculation of diffuse component in image-based lighting. [#12082](https://github.com/CesiumGS/cesium/pull/12082) +- Prevent Bing Imagery API format issues from throwing errors [#12094](https://github.com/CesiumGS/cesium/pull/12094) ### 1.119 - 2024-07-01 From eb9b111c3e0b9a5463cf4445e307f8a133d9ec84 Mon Sep 17 00:00:00 2001 From: ggetz Date: Thu, 25 Jul 2024 15:05:26 -0400 Subject: [PATCH 37/47] Allow the moon orientation to be set via API --- Apps/Sandcastle/gallery/Moon.html | 145 ------------------ CHANGES.md | 5 + .../Core/Simon1994PlanetaryPositions.js | 33 ---- packages/engine/Source/Core/Transforms.js | 78 +++++++++- .../Source/DataSources/PathVisualizer.js | 8 +- .../Source/DataSources/PositionProperty.js | 11 +- .../engine/Source/Renderer/UniformState.js | 6 +- .../Core/Simon1994PlanetaryPositionsSpec.js | 9 +- packages/engine/Specs/Core/TransformsSpec.js | 74 +++++++++ 9 files changed, 169 insertions(+), 200 deletions(-) delete mode 100644 Apps/Sandcastle/gallery/Moon.html diff --git a/Apps/Sandcastle/gallery/Moon.html b/Apps/Sandcastle/gallery/Moon.html deleted file mode 100644 index 16095a7fa970..000000000000 --- a/Apps/Sandcastle/gallery/Moon.html +++ /dev/null @@ -1,145 +0,0 @@ - - - - - - - - - Cesium Demo - - - - - - -
-

Loading...

-
- - - diff --git a/CHANGES.md b/CHANGES.md index ea5ccc683acf..77b6bafb0297 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,11 @@ #### @cesium/engine +##### Additions :tada: + +- Added `Transforms.computeIcrfToMoonFixedMatrix` and `Transforms.computeMoonFixedToIcrfMatrix` to compute the transformations between the Moon's fixed frame and ICRF at a given time. +- Added `Transforms.computeIcrfToCentralBodyFixedMatrix` to specific the default ICRF to fixed frame transformation to use internally, including for lighting calculations. + ##### Fixes :wrench: - Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063) diff --git a/packages/engine/Source/Core/Simon1994PlanetaryPositions.js b/packages/engine/Source/Core/Simon1994PlanetaryPositions.js index cf733d6a02a4..8aa55b72c2bc 100644 --- a/packages/engine/Source/Core/Simon1994PlanetaryPositions.js +++ b/packages/engine/Source/Core/Simon1994PlanetaryPositions.js @@ -681,37 +681,4 @@ Simon1994PlanetaryPositions.computeMoonPositionInEarthInertialFrame = function ( return result; }; -/** - * Computes the position of the Sun in the Moon-centered fixed frame - * - * @param {JulianDate} [julianDate] The time at which to compute the Sun's position, if not provided the current system time is used. - * @param {Cartesian3} [result] The object onto which to store the result. - * @returns {Cartesian3} Calculated Sun position - */ -Simon1994PlanetaryPositions.computeSunPositionInMoonInertialFrame = function ( - julianDate, - result -) { - if (!defined(julianDate)) { - julianDate = JulianDate.now(); - } - - if (!defined(result)) { - result = new Cartesian3(); - } - - //first forward transformation - translation = computeSimonEarthMoonBarycenter(julianDate, translation); - result = Cartesian3.negate(translation, result); - - //second forward transformation - computeSimonMoon(julianDate, translation); - - Cartesian3.subtract(result, translation, result); - - //Matrix3.multiplyByVector(rotation, result, result); TODO: ? - - return result; -}; - export default Simon1994PlanetaryPositions; diff --git a/packages/engine/Source/Core/Transforms.js b/packages/engine/Source/Core/Transforms.js index cd71b83cc576..10df23a65a1d 100644 --- a/packages/engine/Source/Core/Transforms.js +++ b/packages/engine/Source/Core/Transforms.js @@ -538,6 +538,35 @@ const wgs84WRPrecessing = 7.2921158553e-5; const twoPiOverSecondsInDay = CesiumMath.TWO_PI / 86400.0; let dateInUtc = new JulianDate(); +/** + * The default function to compute a rotation matrix to transform a point or vector from the International Celestial + * Reference Frame (GCRF/ICRF) inertial frame axes to the central body, typically Earth, fixed frame axis at a given + * time for use in lighting and transformation from inertial reference frames. This function may return undefined if + * the data necessary to do the transformation is not yet loaded. + * + * @param {JulianDate} date The time at which to compute the rotation matrix. + * @param {Matrix3} [result] The object onto which to store the result. If this parameter is + * not specified, a new instance is created and returned. + * @returns {Matrix3|undefined} The rotation matrix, or undefined if the data necessary to do the + * transformation is not yet loaded. + * + * @example + * // Set the default ICRF to fixed transformation to that of the Moon. + * Cesium.Transforms.computeIcrfToCentralBodyFixedMatrix = Cesium.Transforms.computeIcrfToMoonFixedMatrix; + * + * @see Transforms.computeIcrfToFixedMatrix + * @see Transforms.computeTemeToPseudoFixedMatrix + * @see Transforms.computeIcrfToMoonFixedMatrix + */ +Transforms.computeIcrfToCentralBodyFixedMatrix = function (date, result) { + let transformMatrix = Transforms.computeIcrfToFixedMatrix(date, result); + if (!defined(transformMatrix)) { + transformMatrix = Transforms.computeTemeToPseudoFixedMatrix(date, result); + } + + return transformMatrix; +}; + /** * Computes a rotation matrix to transform a point or vector from True Equator Mean Equinox (TEME) axes to the * pseudo-fixed axes at a given time. This method treats the UT1 time standard as equivalent to UTC. @@ -691,7 +720,7 @@ Transforms.preloadIcrfFixed = function (timeInterval) { * @param {JulianDate} date The time at which to compute the rotation matrix. * @param {Matrix3} [result] The object onto which to store the result. If this parameter is * not specified, a new instance is created and returned. - * @returns {Matrix3} The rotation matrix, or undefined if the data necessary to do the + * @returns {Matrix3|undefined} The rotation matrix, or undefined if the data necessary to do the * transformation is not yet loaded. * * @@ -731,6 +760,27 @@ const J2000d = 2451545; const scratchHpr = new HeadingPitchRoll(); const scratchRotationMatrix = new Matrix3(); const dateScratch = new JulianDate(); + +/** + * Computes a rotation matrix to transform a point or vector from the Moon-Fixed frame axes + * to the International Celestial Reference Frame (GCRF/ICRF) inertial frame axes + * at a given time. + * + * @param {JulianDate} date The time at which to compute the rotation matrix. + * @param {Matrix3} [result] The object onto which to store the result. If this parameter is + * not specified, a new instance is created and returned. + * @returns {Matrix3} The rotation matrix. + * + * @example + * // Transform a point from the Fixed axes to the ICRF axes. + * const now = Cesium.JulianDate.now(); + * const pointInFixed = Cesium.Cartesian3.fromDegrees(0.0, 0.0); + * const fixedToIcrf = Cesium.Transforms.computeMoonFixedToIcrfMatrix(now); + * let pointInInertial = new Cesium.Cartesian3(); + * if (Cesium.defined(fixedToIcrf)) { + * pointInInertial = Cesium.Matrix3.multiplyByVector(fixedToIcrf, pointInFixed, pointInInertial); + * } + */ Transforms.computeMoonFixedToIcrfMatrix = function (date, result) { //>>includeStart('debug', pragmas.debug); if (!defined(date)) { @@ -779,6 +829,30 @@ Transforms.computeMoonFixedToIcrfMatrix = function (date, result) { return Matrix3.fromHeadingPitchRoll(scratchHpr, scratchRotationMatrix); }; +/** + * Computes a rotation matrix to transform a point or vector from the International Celestial + * Reference Frame (GCRF/ICRF) inertial frame axes to the Moon-Fixed frame axes + * at a given time. + * + * @param {JulianDate} date The time at which to compute the rotation matrix. + * @param {Matrix3} [result] The object onto which to store the result. If this parameter is + * not specified, a new instance is created and returned. + * @returns {Matrix3} The rotation matrix. + * + * @example + * TODO + * + * @example + * scene.postUpdate.addEventListener(function(scene, time) { + * // View in ICRF. + * const toFixed = Cesium.Transforms.computeMoonFixedToIcrfMatrix(time); + * if (Cesium.defined(toFixed)) { + * const offset = Cesium.Cartesian3.clone(camera.position); + * const transform = Cesium.Matrix4.fromRotationTranslation(toFixed); + * camera.lookAtTransform(transform, offset); + * } + * }); + */ Transforms.computeIcrfToMoonFixedMatrix = function (date, result) { //>>includeStart('debug', pragmas.debug); if (!defined(date)) { @@ -818,7 +892,7 @@ const rotation2Scratch = new Matrix3(); * @param {JulianDate} date The time at which to compute the rotation matrix. * @param {Matrix3} [result] The object onto which to store the result. If this parameter is * not specified, a new instance is created and returned. - * @returns {Matrix3} The rotation matrix, or undefined if the data necessary to do the + * @returns {Matrix3|undefined} The rotation matrix, or undefined if the data necessary to do the * transformation is not yet loaded. * * diff --git a/packages/engine/Source/DataSources/PathVisualizer.js b/packages/engine/Source/DataSources/PathVisualizer.js index 47aac6026255..8ae9a415fff1 100644 --- a/packages/engine/Source/DataSources/PathVisualizer.js +++ b/packages/engine/Source/DataSources/PathVisualizer.js @@ -430,10 +430,10 @@ function PolylineUpdater(scene, referenceFrame) { PolylineUpdater.prototype.update = function (time) { if (this._referenceFrame === ReferenceFrame.INERTIAL) { - let toFixed = Transforms.computeIcrfToFixedMatrix(time, toFixedScratch); - if (!defined(toFixed)) { - toFixed = Transforms.computeTemeToPseudoFixedMatrix(time, toFixedScratch); - } + const toFixed = Transforms.computeIcrfToCentralBodyFixedMatrix( + time, + toFixedScratch + ); Matrix4.fromRotationTranslation( toFixed, Cartesian3.ZERO, diff --git a/packages/engine/Source/DataSources/PositionProperty.js b/packages/engine/Source/DataSources/PositionProperty.js index 88b0e2371a64..5f6b8fa6699f 100644 --- a/packages/engine/Source/DataSources/PositionProperty.js +++ b/packages/engine/Source/DataSources/PositionProperty.js @@ -112,13 +112,10 @@ PositionProperty.convertToReferenceFrame = function ( return Cartesian3.clone(value, result); } - let icrfToFixed = Transforms.computeIcrfToFixedMatrix(time, scratchMatrix3); - if (!defined(icrfToFixed)) { - icrfToFixed = Transforms.computeTemeToPseudoFixedMatrix( - time, - scratchMatrix3 - ); - } + const icrfToFixed = Transforms.computeIcrfToCentralBodyFixedMatrix( + time, + scratchMatrix3 + ); if (inputFrame === ReferenceFrame.INERTIAL) { return Matrix3.multiplyByVector(icrfToFixed, value, result); } diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index ba04823d09e0..648a1f1b04a0 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -1319,7 +1319,11 @@ function setSunAndMoonDirections(uniformState, frameState) { // ); // } - Transforms.computeIcrfToMoonFixedMatrix(frameState.time, transformMatrix); + //Transforms.computeIcrfToMoonFixedMatrix(frameState.time, transformMatrix); + Transforms.computeIcrfToCentralBodyFixedMatrix( + frameState.time, + transformMatrix + ); let position = Simon1994PlanetaryPositions.computeSunPositionInEarthInertialFrame( frameState.time, diff --git a/packages/engine/Specs/Core/Simon1994PlanetaryPositionsSpec.js b/packages/engine/Specs/Core/Simon1994PlanetaryPositionsSpec.js index a370609935fe..01d26b41a644 100644 --- a/packages/engine/Specs/Core/Simon1994PlanetaryPositionsSpec.js +++ b/packages/engine/Specs/Core/Simon1994PlanetaryPositionsSpec.js @@ -1,5 +1,4 @@ import { - defined, JulianDate, Matrix3, Math as CesiumMath, @@ -83,16 +82,10 @@ describe("Core/Simon1994PlanetaryPositions", function () { } const angles = []; for (i = 0; i < 24; i++) { - transformMatrix = Transforms.computeIcrfToFixedMatrix( + transformMatrix = Transforms.computeIcrfToCentralBodyFixedMatrix( timesOfDay[i], transformMatrix ); - if (!defined(transformMatrix)) { - transformMatrix = Transforms.computeTemeToPseudoFixedMatrix( - timesOfDay[i], - transformMatrix - ); - } const position = PlanetaryPositions.computeSunPositionInEarthInertialFrame( timesOfDay[i] ); diff --git a/packages/engine/Specs/Core/TransformsSpec.js b/packages/engine/Specs/Core/TransformsSpec.js index b6e09f0cdacc..a919516fec5c 100644 --- a/packages/engine/Specs/Core/TransformsSpec.js +++ b/packages/engine/Specs/Core/TransformsSpec.js @@ -1220,6 +1220,80 @@ describe("Core/Transforms", function () { expect(tAngle).toEqualEpsilon(uAngle, CesiumMath.EPSILON6); }); + describe("computeIcrfToMoonFixedMatrix", function () { + it("throws if the date parameter is not specified", function () { + expect(function () { + Transforms.computeIcrfToMoonFixedMatrix(undefined); + }).toThrowDeveloperError(); + + expect(function () { + Transforms.computeFixedToIcrfMatrix(undefined); + }).toThrowDeveloperError(); + }); + + it("works", async function () { + // 2011-07-03 00:00:00 UTC + let time = new JulianDate(2455745, 43200); + + const resultT = new Matrix3(); + const t = Transforms.computeIcrfToMoonFixedMatrix(time, resultT); + expect(t).toBe(resultT); + + // rotation matrix determinants are 1.0 + const det = + t[0] * t[4] * t[8] + + t[3] * t[7] * t[2] + + t[6] * t[1] * t[5] - + t[6] * t[4] * t[2] - + t[3] * t[1] * t[8] - + t[0] * t[7] * t[5]; + expect(det).toEqualEpsilon(1.0, CesiumMath.EPSILON14); + console.log(t); + + // rotation matrix inverses are equal to its transpose + const t4 = Matrix4.fromRotationTranslation(t); + expect(Matrix4.inverse(t4, new Matrix4())).toEqualEpsilon( + Matrix4.inverseTransformation(t4, new Matrix4()), + CesiumMath.EPSILON14 + ); + + time = JulianDate.addHours(time, 27.321661 * 24, new JulianDate()); // add one sidereal month + const resultU = new Matrix3(); + const u = Transforms.computeIcrfToMoonFixedMatrix(time, resultU); + expect(u).toBe(resultU); + const tAngle = Quaternion.computeAngle(Quaternion.fromRotationMatrix(t)); + const uAngle = Quaternion.computeAngle(Quaternion.fromRotationMatrix(u)); + expect(tAngle).toEqualEpsilon(uAngle, CesiumMath.EPSILON3); + + const expectedMtx = new Matrix3( + -0.44796811269393627, + 0.8934634849604557, + 0.03236620230657612, + 0.8184479558129512, + 0.3952490953922868, + 0.4170384828971786, + 0.3598159441089767, + 0.2133099942194372, + -0.9083123541662688 + ); + + const testInverse = Matrix3.multiply( + Matrix3.transpose(t, new Matrix3()), + expectedMtx, + new Matrix3() + ); + const testDiff = new Matrix3(); + for (let i = 0; i < 9; i++) { + testDiff[i] = t[i] - expectedMtx[i]; + } + expect(testInverse).toEqualEpsilon( + Matrix3.IDENTITY, + CesiumMath.EPSILON14 + ); + expect(testDiff).toEqualEpsilon(new Matrix3(), CesiumMath.EPSILON14); + }); + }); + describe("computeIcrfToFixedMatrix", function () { async function preloadTransformationData(start, stop, eopUrl) { if (defined(eopUrl)) { From bf14419c368c9f792ce6c7c900760f3e091242c7 Mon Sep 17 00:00:00 2001 From: ggetz Date: Thu, 25 Jul 2024 15:08:54 -0400 Subject: [PATCH 38/47] Cleanup --- packages/engine/Source/Core/Transforms.js | 14 ++------------ packages/engine/Source/Renderer/UniformState.js | 12 ------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/packages/engine/Source/Core/Transforms.js b/packages/engine/Source/Core/Transforms.js index 10df23a65a1d..faae113b1d2d 100644 --- a/packages/engine/Source/Core/Transforms.js +++ b/packages/engine/Source/Core/Transforms.js @@ -840,18 +840,8 @@ Transforms.computeMoonFixedToIcrfMatrix = function (date, result) { * @returns {Matrix3} The rotation matrix. * * @example - * TODO - * - * @example - * scene.postUpdate.addEventListener(function(scene, time) { - * // View in ICRF. - * const toFixed = Cesium.Transforms.computeMoonFixedToIcrfMatrix(time); - * if (Cesium.defined(toFixed)) { - * const offset = Cesium.Cartesian3.clone(camera.position); - * const transform = Cesium.Matrix4.fromRotationTranslation(toFixed); - * camera.lookAtTransform(transform, offset); - * } - * }); + * // Set the default ICRF to fixed transformation to that of the Moon. + * Cesium.Transforms.computeIcrfToCentralBodyFixedMatrix = Cesium.Transforms.computeIcrfToMoonFixedMatrix; */ Transforms.computeIcrfToMoonFixedMatrix = function (date, result) { //>>includeStart('debug', pragmas.debug); diff --git a/packages/engine/Source/Renderer/UniformState.js b/packages/engine/Source/Renderer/UniformState.js index 648a1f1b04a0..993a7cda26a6 100644 --- a/packages/engine/Source/Renderer/UniformState.js +++ b/packages/engine/Source/Renderer/UniformState.js @@ -1308,18 +1308,6 @@ function setCamera(uniformState, camera) { const transformMatrix = new Matrix3(); const sunCartographicScratch = new Cartographic(); function setSunAndMoonDirections(uniformState, frameState) { - // if ( - // !defined( - // Transforms.computeIcrfToFixedMatrix(frameState.time, transformMatrix) - // ) - // ) { - // transformMatrix = Transforms.computeTemeToPseudoFixedMatrix( - // frameState.time, - // transformMatrix - // ); - // } - - //Transforms.computeIcrfToMoonFixedMatrix(frameState.time, transformMatrix); Transforms.computeIcrfToCentralBodyFixedMatrix( frameState.time, transformMatrix From a098f751a9ed5d7a268a6b96b1de64db02b7716c Mon Sep 17 00:00:00 2001 From: ggetz Date: Thu, 25 Jul 2024 15:13:06 -0400 Subject: [PATCH 39/47] cleanup --- packages/engine/Specs/Core/TransformsSpec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/engine/Specs/Core/TransformsSpec.js b/packages/engine/Specs/Core/TransformsSpec.js index a919516fec5c..1f2277b58330 100644 --- a/packages/engine/Specs/Core/TransformsSpec.js +++ b/packages/engine/Specs/Core/TransformsSpec.js @@ -1248,7 +1248,6 @@ describe("Core/Transforms", function () { t[3] * t[1] * t[8] - t[0] * t[7] * t[5]; expect(det).toEqualEpsilon(1.0, CesiumMath.EPSILON14); - console.log(t); // rotation matrix inverses are equal to its transpose const t4 = Matrix4.fromRotationTranslation(t); From dceb99a8418b9709f7a88d73fca9c2e23f957a4d Mon Sep 17 00:00:00 2001 From: ggetz Date: Fri, 26 Jul 2024 10:46:44 -0400 Subject: [PATCH 40/47] Update CHANGES.md --- CHANGES.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 170bca45ab45..816d0cb07cfc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,13 @@ # Change Log -- Added SplitDirection property for display PointPrimitive and Billboard relative to the `Scene.splitPosition`. [#11982](https://github.com/CesiumGS/cesium/pull/11982) - ### 1.120 - 2024-08-01 #### @cesium/engine +##### Additions :tada: + +- Added SplitDirection property for display PointPrimitive and Billboard relative to the `Scene.splitPosition`. [#11982](https://github.com/CesiumGS/cesium/pull/11982) + ##### Fixes :wrench: - Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063) From 481b372a51e92a74343b4f430b629a0742e0214c Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:54:41 -0400 Subject: [PATCH 41/47] fix tests --- .../engine/Source/Scene/BingMapsImageryProvider.js | 12 +++++++----- .../Specs/Scene/BingMapsImageryProviderSpec.js | 8 +++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/engine/Source/Scene/BingMapsImageryProvider.js b/packages/engine/Source/Scene/BingMapsImageryProvider.js index 85a76392ceb6..90c18532d5b3 100644 --- a/packages/engine/Source/Scene/BingMapsImageryProvider.js +++ b/packages/engine/Source/Scene/BingMapsImageryProvider.js @@ -120,11 +120,13 @@ function metadataSuccess(data, imageryProviderBuilder) { imageryProviderBuilder.maximumLevel = resource.zoomMax - 1; imageryProviderBuilder.imageUrlSubdomains = resource.imageUrlSubdomains; imageryProviderBuilder.imageUrlTemplate = resource.imageUrl; - const validProviders = resource.imageryProviders.filter((provider) => - // prevent issues with the imagery API from crashing the viewer when the expected properties are not there - // See https://github.com/CesiumGS/cesium/issues/12088 - provider.coverageAreas?.some((area) => defined(area.bbox)) - ); + const validProviders = !defined(resource.imageryProviders) + ? resource.imageryProviders + : resource.imageryProviders.filter((provider) => + // prevent issues with the imagery API from crashing the viewer when the expected properties are not there + // See https://github.com/CesiumGS/cesium/issues/12088 + provider.coverageAreas?.some((area) => defined(area.bbox)) + ); imageryProviderBuilder.attributionList = validProviders; } diff --git a/packages/engine/Specs/Scene/BingMapsImageryProviderSpec.js b/packages/engine/Specs/Scene/BingMapsImageryProviderSpec.js index ccfb7b37b9e6..8417fdd1e640 100644 --- a/packages/engine/Specs/Scene/BingMapsImageryProviderSpec.js +++ b/packages/engine/Specs/Scene/BingMapsImageryProviderSpec.js @@ -243,7 +243,7 @@ describe("Scene/BingMapsImageryProvider", function () { }); //These are the same instance only if the cache has been used - expect(provider._attributionList).toBe(provider2._attributionList); + expect(provider._imageUrlSubdomains).toBe(provider2._imageUrlSubdomains); installFakeMetadataRequest(url, BingMapsStyle.AERIAL); installFakeImageRequest(); @@ -253,8 +253,10 @@ describe("Scene/BingMapsImageryProvider", function () { mapStyle: BingMapsStyle.AERIAL, }); - // Because the road is different, a non-cached request should have happened - expect(provider3._attributionList).not.toBe(provider._attributionList); + // Because the style is different, a non-cached request should have happened + expect(provider3._imageUrlSubdomains).not.toBe( + provider._imageUrlSubdomains + ); }); it("fromUrl resolves with a path", async function () { From 688f71e452916743a32e9a5f0905c681f38caaf8 Mon Sep 17 00:00:00 2001 From: jjspace <8007967+jjspace@users.noreply.github.com> Date: Mon, 29 Jul 2024 10:50:19 -0400 Subject: [PATCH 42/47] adjust conditional --- .../Source/Scene/BingMapsImageryProvider.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/engine/Source/Scene/BingMapsImageryProvider.js b/packages/engine/Source/Scene/BingMapsImageryProvider.js index 90c18532d5b3..8024cea285de 100644 --- a/packages/engine/Source/Scene/BingMapsImageryProvider.js +++ b/packages/engine/Source/Scene/BingMapsImageryProvider.js @@ -120,13 +120,15 @@ function metadataSuccess(data, imageryProviderBuilder) { imageryProviderBuilder.maximumLevel = resource.zoomMax - 1; imageryProviderBuilder.imageUrlSubdomains = resource.imageUrlSubdomains; imageryProviderBuilder.imageUrlTemplate = resource.imageUrl; - const validProviders = !defined(resource.imageryProviders) - ? resource.imageryProviders - : resource.imageryProviders.filter((provider) => - // prevent issues with the imagery API from crashing the viewer when the expected properties are not there - // See https://github.com/CesiumGS/cesium/issues/12088 - provider.coverageAreas?.some((area) => defined(area.bbox)) - ); + + let validProviders = resource.imageryProviders; + if (defined(resource.imageryProviders)) { + // prevent issues with the imagery API from crashing the viewer when the expected properties are not there + // See https://github.com/CesiumGS/cesium/issues/12088 + validProviders = resource.imageryProviders.filter((provider) => + provider.coverageAreas?.some((area) => defined(area.bbox)) + ); + } imageryProviderBuilder.attributionList = validProviders; } From e2bc6ff4e1a100e05de45868f25ed5da473f589e Mon Sep 17 00:00:00 2001 From: ggetz Date: Wed, 31 Jul 2024 15:29:22 -0400 Subject: [PATCH 43/47] Add moon Sandcastle example --- Apps/Sandcastle/gallery/Moon.html | 381 ++++++++++++++++++++++++++++++ Apps/Sandcastle/gallery/Moon.jpg | Bin 0 -> 29545 bytes 2 files changed, 381 insertions(+) create mode 100644 Apps/Sandcastle/gallery/Moon.html create mode 100644 Apps/Sandcastle/gallery/Moon.jpg diff --git a/Apps/Sandcastle/gallery/Moon.html b/Apps/Sandcastle/gallery/Moon.html new file mode 100644 index 000000000000..d0fe6545408d --- /dev/null +++ b/Apps/Sandcastle/gallery/Moon.html @@ -0,0 +1,381 @@ + + + + + + + + + Cesium Moon Terrain + + + + + + +
+

Loading...

+
+ + + diff --git a/Apps/Sandcastle/gallery/Moon.jpg b/Apps/Sandcastle/gallery/Moon.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c9d8f75fe2b6033187fe6799ce456b0890c5c6a0 GIT binary patch literal 29545 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 Date: Wed, 31 Jul 2024 15:30:55 -0400 Subject: [PATCH 44/47] Tweak wording --- Apps/Sandcastle/gallery/Moon.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/Sandcastle/gallery/Moon.html b/Apps/Sandcastle/gallery/Moon.html index d0fe6545408d..bedaba0b4f0d 100644 --- a/Apps/Sandcastle/gallery/Moon.html +++ b/Apps/Sandcastle/gallery/Moon.html @@ -294,7 +294,7 @@ const options1 = [ { - text: "Fly to POI", + text: "Fly to...", onselect: () => {}, }, { From 3cb228d008befb674a09984ddbc78495385822c8 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 1 Aug 2024 11:38:01 -0400 Subject: [PATCH 45/47] Update dependencies esbuild, earcut, and rbush --- package.json | 4 ++-- packages/engine/package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index c488de53be1a..ef9e92b644cd 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "chokidar": "^3.5.3", "cloc": "^2.0.0-cloc", "compression": "^1.7.4", - "esbuild": "^0.21.4", + "esbuild": "^0.23.0", "eslint": "^9.1.1", "eslint-config-cesium": "^11.0.1", "eslint-plugin-html": "^8.1.1", @@ -158,4 +158,4 @@ "packages/engine", "packages/widgets" ] -} \ No newline at end of file +} diff --git a/packages/engine/package.json b/packages/engine/package.json index 7f834a23d907..80420eaccc9d 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -38,7 +38,7 @@ "bitmap-sdf": "^1.0.3", "dompurify": "^3.0.2", "draco3d": "^1.5.1", - "earcut": "^2.2.4", + "earcut": "^3.0.0", "grapheme-splitter": "^1.0.4", "jsep": "^1.3.8", "kdbush": "^4.0.1", @@ -48,7 +48,7 @@ "meshoptimizer": "^0.21.0", "pako": "^2.0.4", "protobufjs": "^7.1.0", - "rbush": "^3.0.1", + "rbush": "^4.0.0", "topojson-client": "^3.1.0", "urijs": "^1.19.7" }, From f37d987084b53b457b5294afc9e2b3763bd1d16a Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 1 Aug 2024 14:12:04 -0400 Subject: [PATCH 46/47] Fix blending for CPU styling of models --- packages/engine/Source/Shaders/Model/MaterialStageFS.glsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index 824523a27a47..9f32bee26bdf 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -455,10 +455,10 @@ void materialStage(inout czm_modelMaterial material, ProcessedAttributes attribu baseColorWithAlpha *= color; #endif - material.baseColor = baseColorWithAlpha; #ifdef USE_CPU_STYLING - material.baseColor.rgb = blend(baseColorWithAlpha.rgb, feature.color.rgb, model_colorBlend); + baseColorWithAlpha.rgb = blend(baseColorWithAlpha.rgb, feature.color.rgb, model_colorBlend); #endif + material.baseColor = baseColorWithAlpha; material.diffuse = baseColorWithAlpha.rgb; material.alpha = baseColorWithAlpha.a; From 2a1dbe219b86399b727602e02c304750a658c3f1 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 1 Aug 2024 14:34:07 -0400 Subject: [PATCH 47/47] Updates for 1.120 release --- CHANGES.md | 4 ++-- ThirdParty.json | 14 +++++++------- package.json | 6 +++--- packages/engine/Source/Core/Ion.js | 2 +- packages/engine/Source/Scene/ArcGisMapService.js | 2 +- packages/engine/package.json | 2 +- packages/widgets/package.json | 4 ++-- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index e43311e3a506..f23bfa536ea0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,11 +12,11 @@ ##### Fixes :wrench: -- Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063) - Fixed environment map LOD selection in image-based lighting. [#12070](https://github.com/CesiumGS/cesium/pull/12070) - Corrected calculation of diffuse component in image-based lighting. [#12082](https://github.com/CesiumGS/cesium/pull/12082) - Updated specular BRDF for image-based lighting. [#12083](https://github.com/CesiumGS/cesium/pull/12083) - Fixed environment map transform for image-based lighting. [#12091](https://github.com/CesiumGS/cesium/pull/12091) +- Updated geometric self-shadowing function to improve direct lighting on models using physically-based rendering. [#12063](https://github.com/CesiumGS/cesium/pull/12063) - Prevent Bing Imagery API format issues from throwing errors [#12094](https://github.com/CesiumGS/cesium/pull/12094) ### 1.119 - 2024-07-01 @@ -46,7 +46,7 @@ ##### Deprecated :hourglass_flowing_sand: - `SceneTransforms.wgs84ToDrawingBufferCoordinates` has been deprecated. It will be removed in 1.121. Use `SceneTransforms.worldToDrawingBufferCoordinates` instead. -- `SceneTransforms.wgs84ToWindowCoordinates` has been deprecated. It will be removed in 1.21. Use `SceneTransforms.worldToWindowCoordinates` instead. +- `SceneTransforms.wgs84ToWindowCoordinates` has been deprecated. It will be removed in 1.121. Use `SceneTransforms.worldToWindowCoordinates` instead. #### @cesium/widgets diff --git a/ThirdParty.json b/ThirdParty.json index 584602c2c309..a619b37b99a7 100644 --- a/ThirdParty.json +++ b/ThirdParty.json @@ -4,7 +4,7 @@ "license": [ "MIT" ], - "version": "23.1.2", + "version": "23.1.3", "url": "https://www.npmjs.com/package/@tweenjs/tween.js" }, { @@ -12,7 +12,7 @@ "license": [ "BSD-3-Clause" ], - "version": "2.7.45", + "version": "2.7.48", "url": "https://www.npmjs.com/package/@zip.js/zip.js" }, { @@ -44,7 +44,7 @@ "license": [ "Apache-2.0" ], - "version": "3.1.5", + "version": "3.1.6", "url": "https://www.npmjs.com/package/dompurify", "notes": "dompurify is available as both MPL-2.0 OR Apache-2.0" }, @@ -61,7 +61,7 @@ "license": [ "ISC" ], - "version": "2.2.4", + "version": "3.0.0", "url": "https://www.npmjs.com/package/earcut" }, { @@ -77,7 +77,7 @@ "license": [ "MIT" ], - "version": "1.3.8", + "version": "1.3.9", "url": "https://www.npmjs.com/package/jsep" }, { @@ -109,7 +109,7 @@ "license": [ "MIT" ], - "version": "0.7.0", + "version": "0.7.1", "url": "https://www.npmjs.com/package/ktx-parse" }, { @@ -166,7 +166,7 @@ "license": [ "MIT" ], - "version": "3.0.1", + "version": "4.0.0", "url": "https://www.npmjs.com/package/rbush" }, { diff --git a/package.json b/package.json index ef9e92b644cd..97d54811cc45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cesium", - "version": "1.119.0", + "version": "1.120.0", "description": "CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.", "homepage": "http://cesium.com/cesiumjs/", "license": "Apache-2.0", @@ -50,8 +50,8 @@ "./Specs/**/*" ], "dependencies": { - "@cesium/engine": "^10.0.0", - "@cesium/widgets": "^7.0.0" + "@cesium/engine": "^10.1.0", + "@cesium/widgets": "^7.1.0" }, "devDependencies": { "@playwright/test": "^1.41.1", diff --git a/packages/engine/Source/Core/Ion.js b/packages/engine/Source/Core/Ion.js index 7169f05ddc8a..49476cca4456 100644 --- a/packages/engine/Source/Core/Ion.js +++ b/packages/engine/Source/Core/Ion.js @@ -4,7 +4,7 @@ import Resource from "./Resource.js"; let defaultTokenCredit; const defaultAccessToken = - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyYTI3ZDMxOS1mODc4LTQ5NzktOGUzNy04OTA0ZGI5MmM1NGIiLCJpZCI6MjU5LCJpYXQiOjE3MTk4NDM5OTN9.FDHD-mZOIAzA9J_kzOz6AkKK2PHzl2yoMbFqbpsfH6Q"; + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxY2UxZDdkOS02YTJkLTQ2Y2EtYmMxNS03ZDI2NWQ1NzUzMjAiLCJpZCI6MjU5LCJpYXQiOjE3MjI1Mjg0MTl9.IMe1VGs-5blTCJJA1y76M8WNixMgms2C87kIkXQVcww"; /** * Default settings for accessing the Cesium ion API. * diff --git a/packages/engine/Source/Scene/ArcGisMapService.js b/packages/engine/Source/Scene/ArcGisMapService.js index 91e0b2a8c3fc..0d48547786b3 100644 --- a/packages/engine/Source/Scene/ArcGisMapService.js +++ b/packages/engine/Source/Scene/ArcGisMapService.js @@ -4,7 +4,7 @@ import Resource from "../Core/Resource.js"; let defaultTokenCredit; const defaultAccessToken = - "AAPTxy8BH1VEsoebNVZXo8HurEOF051kAEKlhkOhBEc9BmRcs5cw5x8CvIyg2oiotXJmY_GjoEUuuKAUvdWxusIx25kUy2AttkOs0FgosGzFJSsjafpWC40GiE5hiD0FLNSh3kxIW6w-5qqUQCEAwGY1t4yH7Fj-PjrkjPfSpw0_r2xF3lKLd0_LSmPWXCkYhqV1O67tWLNImHyr7SurJ92sA5YIG1pMJKEQU3Qv5k18p0g.AT1_sGbfSYDL"; + "AAPTxy8BH1VEsoebNVZXo8HurEOF051kAEKlhkOhBEc9BmTRbx8gaNqtBYKRq3vEnwSKnwUE1aRn2Siyo0gHWzzPwpJd-ZUeTyDgQZPqW8C6pXNLOE3bRO6-Jz3NFmp-qNquqGBQTATcCHbNsL-h9CPyMMPGsTcUvzPlIInazMfkWJNbFmqJeSzPj_1DHshXa0XCfkQn5mVxlF2dFt10Mq2bGo0HkpdpXwNQdoGwdphsLoU.AT1_CpQKgEim"; /** * Default options for accessing the ArcGIS image tile service. * diff --git a/packages/engine/package.json b/packages/engine/package.json index 80420eaccc9d..bc74fc1d261e 100644 --- a/packages/engine/package.json +++ b/packages/engine/package.json @@ -1,6 +1,6 @@ { "name": "@cesium/engine", - "version": "10.0.0", + "version": "10.1.0", "description": "CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.", "keywords": [ "3D", diff --git a/packages/widgets/package.json b/packages/widgets/package.json index 92d206a6801e..5712399a2d93 100644 --- a/packages/widgets/package.json +++ b/packages/widgets/package.json @@ -1,6 +1,6 @@ { "name": "@cesium/widgets", - "version": "7.0.0", + "version": "7.1.0", "description": "A widgets library for use with CesiumJS. CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.", "keywords": [ "3D", @@ -28,7 +28,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@cesium/engine": "^10.0.0", + "@cesium/engine": "^10.1.0", "nosleep.js": "^0.12.0" }, "type": "module",