From a3c3919c83ff418a3ce3c43f6db8a0ed3d1c5fa0 Mon Sep 17 00:00:00 2001 From: Janine Liu Date: Tue, 29 Mar 2022 17:12:59 -0400 Subject: [PATCH 1/5] Add clipping planes to ModelExperimental --- .../ModelClippingPlanesPipelineStage.js | 126 +++++++++++ .../ModelExperimental/ModelExperimental.js | 103 ++++++++- .../ModelExperimentalSceneGraph.js | 5 + .../ModelClippingPlanesStageFS.glsl | 93 ++++++++ .../ModelExperimentalFS.glsl | 4 + .../ModelExperimentalSpec.js | 202 ++++++++++++++++++ .../loadAndZoomToModelExperimental.js | 1 + 7 files changed, 531 insertions(+), 3 deletions(-) create mode 100644 Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js create mode 100644 Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl diff --git a/Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js b/Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js new file mode 100644 index 000000000000..c1d67dc92084 --- /dev/null +++ b/Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js @@ -0,0 +1,126 @@ +import Cartesian2 from "../../Core/Cartesian2.js"; +import ClippingPlaneCollection from "../ClippingPlaneCollection.js"; +import combine from "../../Core/combine.js"; +import Color from "../../Core/Color.js"; +import defined from "../../Core/defined.js"; +import ModelClippingPlanesStageFS from "../../Shaders/ModelExperimental/ModelClippingPlanesStageFS.js"; +import ShaderDestination from "../../Renderer/ShaderDestination.js"; + +/** + * The model clipping planes stage is responsible for applying clipping planes to the model. + * + * @namespace ModelClippingPlanesPipelineStage + * + * @private + */ +const ModelClippingPlanesPipelineStage = {}; +ModelClippingPlanesPipelineStage.name = "ModelClippingPlanesPipelineStage"; // Helps with debugging + +const textureResolutionScratch = new Cartesian2(); +/** + * Process a model. This modifies the following parts of the render resources: + * + * + * + * @param {ModelRenderResources} renderResources The render resources for this model. + * @param {ModelExperimental} model The model. + * @param {FrameState} frameState The frameState. + * + * @private + */ +ModelClippingPlanesPipelineStage.process = function ( + renderResources, + model, + frameState +) { + const clippingPlanes = model.clippingPlanes; + const context = frameState.context; + const shaderBuilder = renderResources.shaderBuilder; + + shaderBuilder.addDefine( + "HAS_CLIPPING_PLANES", + undefined, + ShaderDestination.FRAGMENT + ); + + shaderBuilder.addDefine( + "CLIPPING_PLANES_LENGTH", + clippingPlanes.length, + ShaderDestination.FRAGMENT + ); + + if (clippingPlanes.unionClippingRegions) { + shaderBuilder.addDefine( + "UNION_CLIPPING_REGIONS", + undefined, + ShaderDestination.FRAGMENT + ); + } + + if (ClippingPlaneCollection.useFloatTexture(context)) { + shaderBuilder.addDefine( + "USE_FLOAT_CLIPPING_PLANES_TEXTURE", + undefined, + ShaderDestination.FRAGMENT + ); + } + + const textureResolution = ClippingPlaneCollection.getTextureResolution( + clippingPlanes, + context, + textureResolutionScratch + ); + + shaderBuilder.addDefine( + "CLIPPING_PLANES_TEXTURE_WIDTH", + textureResolution.x, + ShaderDestination.FRAGMENT + ); + + shaderBuilder.addDefine( + "CLIPPING_PLANES_TEXTURE_HEIGHT", + textureResolution.y, + ShaderDestination.FRAGMENT + ); + + shaderBuilder.addUniform( + "sampler2D", + "model_clippingPlanes", + ShaderDestination.FRAGMENT + ); + shaderBuilder.addUniform( + "vec4", + "model_clippingPlanesEdgeStyle", + ShaderDestination.FRAGMENT + ); + shaderBuilder.addUniform( + "mat4", + "model_clippingPlanesMatrix", + ShaderDestination.FRAGMENT + ); + + shaderBuilder.addFragmentLines([ModelClippingPlanesStageFS]); + + const uniformMap = { + model_clippingPlanes: function () { + return model._clippingPlanes.texture; + }, + model_clippingPlanesEdgeStyle: function () { + const style = Color.clone(model._clippingPlanes.edgeColor); + style.alpha = model._clippingPlanes.edgeWidth; + return style; + }, + model_clippingPlanesMatrix: function () { + return model._clippingPlanesMatrix; + }, + }; + + renderResources.uniformMap = combine(uniformMap, renderResources.uniformMap); +}; + +export default ModelClippingPlanesPipelineStage; diff --git a/Source/Scene/ModelExperimental/ModelExperimental.js b/Source/Scene/ModelExperimental/ModelExperimental.js index 7d8fcd1beb9d..55c41016f46d 100644 --- a/Source/Scene/ModelExperimental/ModelExperimental.js +++ b/Source/Scene/ModelExperimental/ModelExperimental.js @@ -2,6 +2,7 @@ import BoundingSphere from "../../Core/BoundingSphere.js"; import Cartesian3 from "../../Core/Cartesian3.js"; import Check from "../../Core/Check.js"; import ColorBlendMode from "../ColorBlendMode.js"; +import ClippingPlaneCollection from "../ClippingPlaneCollection.js"; import defined from "../../Core/defined.js"; import defer from "../../Core/defer.js"; import defaultValue from "../../Core/defaultValue.js"; @@ -54,6 +55,7 @@ import SplitDirection from "../SplitDirection.js"; * @param {String|Number} [options.featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @param {String|Number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. + * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model. * @param {Cartesian3} [options.lightColor] The light color when shading the model. When undefined the scene's light color is used instead. * @param {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting on this model. * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the material's doubleSided property; when false, back face culling is disabled. Back faces are not culled if the model's color is translucent. @@ -139,9 +141,9 @@ export default function ModelExperimental(options) { /** * If defined, this matrix is used to transform miscellaneous properties like - * image-based lighting instead of the modelMatrix. This is so that when models - * are part of a tileset these properties get transformed relative to common reference - * (such as the root). + * clipping planes and image-based lighting instead of the modelMatrix. This is + * so that when models are part of a tileset, these properties get transformed + * relative to a common reference (such as the root). * * @type {Matrix4} * @private @@ -158,6 +160,7 @@ export default function ModelExperimental(options) { this._content = options.content; this._texturesLoaded = false; + this._defaultTexture = undefined; const color = options.color; this._color = defaultValue(color) ? Color.clone(color) : undefined; @@ -204,6 +207,17 @@ export default function ModelExperimental(options) { this._attenuation = pointCloudShading.attenuation; this._pointCloudShading = pointCloudShading; + // If the given clipping planes don't have an owner, make this model its owner. + // Otherwise, the clipping planes are passed down from a tileset. + const clippingPlanes = options.clippingPlanes; + if (defined(clippingPlanes) && clippingPlanes.owner === undefined) { + ClippingPlaneCollection.setOwner(clippingPlanes, this, "_clippingPlanes"); + } else { + this._clippingPlanes = clippingPlanes; + } + this._clippingPlanesState = 0; // Used for checking if shaders need to be regenerated due to clipping plane changes. + this._clippingPlanesMatrix = Matrix4.clone(Matrix4.IDENTITY); // Derived from reference matrix and the current view matrix + this._lightColor = Cartesian3.clone(options.lightColor); this._imageBasedLighting = defined(options.imageBasedLighting) @@ -765,6 +779,26 @@ Object.defineProperties(ModelExperimental.prototype, { }, }, + /** + * The {@link ClippingPlaneCollection} used to selectively disable rendering the model. + * + * @memberof ModelExperimental.prototype + * + * @type {ClippingPlaneCollection} + */ + clippingPlanes: { + get: function () { + return this._clippingPlanes; + }, + set: function (value) { + if (value !== this._clippingPlanes) { + // Handle destroying old clipping planes, new clipping planes ownership + ClippingPlaneCollection.setOwner(value, this, "_clippingPlanes"); + this.resetDrawCommands(); + } + }, + }, + /** * The light color when shading the model. When undefined the scene's light color is used instead. *

@@ -1002,6 +1036,7 @@ ModelExperimental.prototype.resetDrawCommands = function () { const scratchIBLReferenceFrameMatrix4 = new Matrix4(); const scratchIBLReferenceFrameMatrix3 = new Matrix3(); +const scratchClippingPlanesMatrix = new Matrix4(); /** * Called when {@link Viewer} or {@link CesiumWidget} render the scene to @@ -1069,6 +1104,39 @@ ModelExperimental.prototype.update = function (frameState) { this.resetDrawCommands(); } + // Update the clipping planes collection for this model to detect any changes. + let currentClippingPlanesState = 0; + if (this.isClippingEnabled()) { + if (this._clippingPlanes.owner === this) { + this._clippingPlanes.update(frameState); + } + + let clippingPlanesMatrix = scratchClippingPlanesMatrix; + clippingPlanesMatrix = Matrix4.multiply( + context.uniformState.view3D, + referenceMatrix, + clippingPlanesMatrix + ); + clippingPlanesMatrix = Matrix4.multiply( + clippingPlanesMatrix, + this._clippingPlanes.modelMatrix, + clippingPlanesMatrix + ); + this._clippingPlanesMatrix = Matrix4.inverseTranspose( + clippingPlanesMatrix, + this._clippingPlanesMatrix + ); + + currentClippingPlanesState = this._clippingPlanes.clippingPlanesState; + } + + if (currentClippingPlanesState !== this._clippingPlanesState) { + this.resetDrawCommands(); + this._clippingPlanesState = currentClippingPlanesState; + } + + this._defaultTexture = context.defaultTexture; + // short-circuit if the model resources aren't ready. if (!this._resourcesLoaded) { return; @@ -1232,6 +1300,21 @@ function getScale(model, frameState) { : scale; } +/** + * Gets whether or not clipping planes are enabled for this model. + * + * @returns {Boolean} true if clipping planes are enabled for this model, false. + * @private + */ +ModelExperimental.prototype.isClippingEnabled = function () { + const clippingPlanes = this._clippingPlanes; + return ( + defined(clippingPlanes) && + clippingPlanes.enabled && + clippingPlanes.length !== 0 + ); +}; + /** * Returns true if this object was destroyed; otherwise, false. *

@@ -1277,6 +1360,18 @@ ModelExperimental.prototype.destroy = function () { this.destroyResources(); + // Only destroy the ClippingPlaneCollection if this is the owner. + const clippingPlaneCollection = this._clippingPlanes; + if ( + defined(clippingPlaneCollection) && + !clippingPlaneCollection.isDestroyed() && + clippingPlaneCollection.owner === this + ) { + clippingPlaneCollection.destroy(); + } + this._clippingPlanes = undefined; + + // Only destroy the ImageBasedLighting if this is the owner. if ( this._shouldDestroyImageBasedLighting && !this._imageBasedLighting.isDestroyed() @@ -1332,6 +1427,7 @@ ModelExperimental.prototype.destroyResources = function () { * @param {String|Number} [options.featureIdLabel="featureId_0"] Label of the feature ID set to use for picking and styling. For EXT_mesh_features, this is the feature ID's label property, or "featureId_N" (where N is the index in the featureIds array) when not specified. EXT_feature_metadata did not have a label field, so such feature ID sets are always labeled "featureId_N" where N is the index in the list of all feature Ids, where feature ID attributes are listed before feature ID textures. If featureIdLabel is an integer N, it is converted to the string "featureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @param {String|Number} [options.instanceFeatureIdLabel="instanceFeatureId_0"] Label of the instance feature ID set used for picking and styling. If instanceFeatureIdLabel is set to an integer N, it is converted to the string "instanceFeatureId_N" automatically. If both per-primitive and per-instance feature IDs are present, the instance feature IDs take priority. * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation and lighting. + * @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model. * @param {Cartesian3} [options.lightColor] The light color when shading the model. When undefined the scene's light color is used instead. * @param {ImageBasedLighting} [options.imageBasedLighting] The properties for managing image-based lighting on this model. * @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the material's doubleSided property; when false, back face culling is disabled. Back faces are not culled if the model's color is translucent. @@ -1514,6 +1610,7 @@ function makeModelOptions(loader, modelType, options) { featureIdLabel: options.featureIdLabel, instanceFeatureIdLabel: options.instanceFeatureIdLabel, pointCloudShading: options.pointCloudShading, + clippingPlanes: options.clippingPlanes, lightColor: options.lightColor, imageBasedLighting: options.imageBasedLighting, backFaceCulling: options.backFaceCulling, diff --git a/Source/Scene/ModelExperimental/ModelExperimentalSceneGraph.js b/Source/Scene/ModelExperimental/ModelExperimentalSceneGraph.js index a20faf1328ab..fb0ce4afd072 100644 --- a/Source/Scene/ModelExperimental/ModelExperimentalSceneGraph.js +++ b/Source/Scene/ModelExperimental/ModelExperimentalSceneGraph.js @@ -17,6 +17,7 @@ import PrimitiveRenderResources from "./PrimitiveRenderResources.js"; import RenderState from "../../Renderer/RenderState.js"; import ShadowMode from "../ShadowMode.js"; import SplitDirection from "../SplitDirection.js"; +import ModelClippingPlanesPipelineStage from "./ModelClippingPlanesPipelineStage.js"; /** * An in memory representation of the scene graph for a {@link ModelExperimental} @@ -385,6 +386,10 @@ ModelExperimentalSceneGraph.prototype.configurePipeline = function () { modelPipelineStages.push(ImageBasedLightingPipelineStage); } + if (model.isClippingEnabled()) { + modelPipelineStages.push(ModelClippingPlanesPipelineStage); + } + if ( defined(model.splitDirection) && model.splitDirection !== SplitDirection.NONE diff --git a/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl b/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl new file mode 100644 index 000000000000..c56789f37d9b --- /dev/null +++ b/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl @@ -0,0 +1,93 @@ +#ifdef USE_FLOAT_CLIPPING_PLANES_TEXTURE +vec4 getClippingPlane( + highp sampler2D packedClippingPlanes, + int clippingPlaneNumber, + mat4 transform +) { + int pixY = clippingPlaneNumber / CLIPPING_PLANES_TEXTURE_WIDTH; + int pixX = clippingPlaneNumber - (pixY * CLIPPING_PLANES_TEXTURE_WIDTH); + float pixelWidth = 1.0 / float(CLIPPING_PLANES_TEXTURE_WIDTH); + float pixelHeight = 1.0 / float(CLIPPING_PLANES_TEXTURE_HEIGHT); + float u = (float(pixX) + 0.5) * pixelWidth;// sample from center of pixel + float v = (float(pixY) + 0.5) * pixelHeight; + vec4 plane = texture2D(packedClippingPlanes, vec2(u, v)); + return czm_transformPlane(plane, transform); +} +#else +// Handle uint8 clipping texture instead +vec4 getClippingPlane( + highp sampler2D packedClippingPlanes, + int clippingPlaneNumber, + mat4 transform +) { + int clippingPlaneStartIndex = clippingPlaneNumber * 2; // clipping planes are two pixels each + int pixY = clippingPlaneStartIndex / CLIPPING_PLANES_TEXTURE_WIDTH; + int pixX = clippingPlaneStartIndex - (pixY * CLIPPING_PLANES_TEXTURE_WIDTH); + float pixelWidth = 1.0 / float(CLIPPING_PLANES_TEXTURE_WIDTH); + float pixelHeight = 1.0 / float(CLIPPING_PLANES_TEXTURE_HEIGHT); + float u = (float(pixX) + 0.5) * pixelWidth; // sample from center of pixel + float v = (float(pixY) + 0.5) * pixelHeight; + vec4 oct32 = texture2D(packedClippingPlanes, vec2(u, v)) * 255.0; + vec2 oct = vec2(oct32.x * 256.0 + oct32.y, oct32.z * 256.0 + oct32.w); + vec4 plane; + plane.xyz = czm_octDecode(oct, 65535.0); + plane.w = czm_unpackFloat(texture2D(packedClippingPlanes, vec2(u + pixelWidth, v))); + return czm_transformPlane(plane, transform); +} +#endif + +float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix) { + vec4 position = czm_windowToEyeCoordinates(fragCoord); + vec3 clipNormal = vec3(0.0); + vec3 clipPosition = vec3(0.0); + float pixelWidth = czm_metersPerPixel(position); + + #ifdef UNION_CLIPPING_REGIONS + float clipAmount; // For union planes, we want to get the min distance. So we set the initial value to the first plane distance in the loop below. + bool breakAndDiscard = false; + #else + float clipAmount = 0.0; + bool clipped = true; + #endif + + for (int i = 0; i < CLIPPING_PLANES_LENGTH; ++i) { + vec4 clippingPlane = getClippingPlane(clippingPlanes, i, clippingPlanesMatrix); + clipNormal = clippingPlane.xyz; + clipPosition = -clippingPlane.w * clipNormal; + float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth; + + #ifdef UNION_CLIPPING_REGIONS + clipAmount = czm_branchFreeTernary(i == 0, amount, min(amount, clipAmount)); + if (amount <= 0.0) { + breakAndDiscard = true; + break; // HLSL compiler bug if we discard here: https://bugs.chromium.org/p/angleproject/issues/detail?id=1945#c6 + } + #else + clipAmount = max(amount, clipAmount); + clipped = clipped && (amount <= 0.0); + #endif + } + + #ifdef UNION_CLIPPING_REGIONS + if (breakAndDiscard) { + discard; + } + #else + if (clipped) { + discard; + } + #endif + return clipAmount; +} + +void modelClippingPlanesStage(inout vec4 color) +{ + float clipDistance = clip(gl_FragCoord, model_clippingPlanes, model_clippingPlanesMatrix); + vec4 clippingPlanesEdgeColor = vec4(1.0); + clippingPlanesEdgeColor.rgb = model_clippingPlanesEdgeStyle.rgb; + float clippingPlanesEdgeWidth = model_clippingPlanesEdgeStyle.a; + + if (clipDistance > 0.0 && clipDistance < clippingPlanesEdgeWidth) { + color = clippingPlanesEdgeColor; + } +} diff --git a/Source/Shaders/ModelExperimental/ModelExperimentalFS.glsl b/Source/Shaders/ModelExperimental/ModelExperimentalFS.glsl index 949df7075980..396b88dfebd1 100644 --- a/Source/Shaders/ModelExperimental/ModelExperimentalFS.glsl +++ b/Source/Shaders/ModelExperimental/ModelExperimentalFS.glsl @@ -74,5 +74,9 @@ void main() vec4 color = handleAlpha(material.diffuse, material.alpha); + #ifdef HAS_CLIPPING_PLANES + modelClippingPlanesStage(color); + #endif + gl_FragColor = color; } diff --git a/Specs/Scene/ModelExperimental/ModelExperimentalSpec.js b/Specs/Scene/ModelExperimental/ModelExperimentalSpec.js index ade79aea1b88..06d377f15d3e 100644 --- a/Specs/Scene/ModelExperimental/ModelExperimentalSpec.js +++ b/Specs/Scene/ModelExperimental/ModelExperimentalSpec.js @@ -1,6 +1,8 @@ import { Cartesian2, Cesium3DTileStyle, + ClippingPlane, + ClippingPlaneCollection, FeatureDetection, ImageBasedLighting, JulianDate, @@ -1238,6 +1240,166 @@ describe( }); }); + it("throws when given clipping planes attached to another model", function () { + const plane = new ClippingPlane(Cartesian3.UNIT_X, 0.0); + const clippingPlanes = new ClippingPlaneCollection({ + planes: [plane], + }); + return loadAndZoomToModelExperimental( + { gltf: boxTexturedGlbUrl, clippingPlanes: clippingPlanes }, + scene + ) + .then(function (model) { + return loadAndZoomToModelExperimental( + { gltf: boxTexturedGlbUrl }, + scene + ); + }) + .then(function (model2) { + expect(function () { + model2.clippingPlanes = clippingPlanes; + }).toThrowDeveloperError(); + }); + }); + + it("updates clipping planes when clipping planes are enabled", function () { + const plane = new ClippingPlane(Cartesian3.UNIT_X, 0.0); + const clippingPlanes = new ClippingPlaneCollection({ + planes: [plane], + }); + return loadAndZoomToModelExperimental( + { gltf: boxTexturedGlbUrl }, + scene + ).then(function (model) { + const gl = scene.frameState.context._gl; + spyOn(gl, "texImage2D").and.callThrough(); + + scene.renderForSpecs(); + const callsBeforeClipping = gl.texImage2D.calls.count(); + + model.clippingPlanes = clippingPlanes; + scene.renderForSpecs(); + scene.renderForSpecs(); + // When clipping planes are created, we expect two calls to texImage2D + // (one for initial creation, and one for copying the data in) + // because clipping planes is stored inside a texture. + expect(gl.texImage2D.calls.count() - callsBeforeClipping).toEqual(2); + }); + }); + + it("initializes and updates with clipping planes", function () { + const plane = new ClippingPlane(Cartesian3.UNIT_X, -2.5); + const clippingPlanes = new ClippingPlaneCollection({ + planes: [plane], + }); + return loadAndZoomToModelExperimental( + { gltf: boxTexturedGlbUrl, clippingPlanes: clippingPlanes }, + scene + ).then(function (model) { + scene.renderForSpecs(); + verifyRender(model, false); + + model.clippingPlanes = undefined; + scene.renderForSpecs(); + verifyRender(model, true); + }); + }); + + it("updating clipping planes properties works", function () { + const direction = Cartesian3.multiplyByScalar( + Cartesian3.UNIT_X, + -1, + new Cartesian3() + ); + const plane = new ClippingPlane(direction, 0.0); + const clippingPlanes = new ClippingPlaneCollection({ + planes: [plane], + }); + return loadAndZoomToModelExperimental( + { gltf: boxTexturedGlbUrl }, + scene + ).then(function (model) { + let modelColor; + scene.renderForSpecs(); + expect(scene).toRenderAndCall(function (rgba) { + modelColor = rgba; + }); + + // The clipping plane should cut the model in half such that + // we see the back faces. + model.clippingPlanes = clippingPlanes; + scene.renderForSpecs(); + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).not.toEqual(modelColor); + }); + + plane.distance = 10.0; // Move the plane away from the model + scene.renderForSpecs(); + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).toEqual(modelColor); + }); + }); + }); + + it("clipping planes apply edge styling", function () { + const plane = new ClippingPlane(Cartesian3.UNIT_X, 0); + const clippingPlanes = new ClippingPlaneCollection({ + planes: [plane], + edgeWidth: 25.0, // make large enough to show up on the render + edgeColor: Color.BLUE, + }); + + return loadAndZoomToModelExperimental( + { gltf: boxTexturedGlbUrl }, + scene + ).then(function (model) { + let modelColor; + scene.renderForSpecs(); + expect(scene).toRenderAndCall(function (rgba) { + modelColor = rgba; + }); + + model.clippingPlanes = clippingPlanes; + + scene.renderForSpecs(); + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).toEqual([0, 0, 255, 255]); + }); + + clippingPlanes.edgeWidth = 0.0; + scene.renderForSpecs(); + expect(scene).toRenderAndCall(function (rgba) { + expect(rgba).toEqual(modelColor); + }); + }); + }); + + it("clipping planes union regions", function () { + const clippingPlanes = new ClippingPlaneCollection({ + planes: [ + new ClippingPlane(Cartesian3.UNIT_Z, 5.0), + new ClippingPlane(Cartesian3.UNIT_X, -2.0), + ], + unionClippingRegions: true, + }); + return loadAndZoomToModelExperimental( + { gltf: boxTexturedGlbUrl }, + scene + ).then(function (model) { + scene.renderForSpecs(); + verifyRender(model, true); + + // These planes are defined such that the model is outside their union. + model.clippingPlanes = clippingPlanes; + scene.renderForSpecs(); + verifyRender(model, false); + + model.clippingPlanes.unionClippingRegions = false; + scene.renderForSpecs(); + verifyRender(model, true); + }); + }); + it("destroy works", function () { spyOn(ShaderProgram.prototype, "destroy").and.callThrough(); return loadAndZoomToModelExperimental( @@ -1272,6 +1434,46 @@ describe( }); }); + it("destroys attached ClippingPlaneCollections", function () { + return loadAndZoomToModelExperimental( + { + gltf: boxTexturedGlbUrl, + }, + scene + ).then(function (model) { + const clippingPlanes = new ClippingPlaneCollection({ + planes: [new ClippingPlane(Cartesian3.UNIT_X, 0.0)], + }); + + model.clippingPlanes = clippingPlanes; + expect(model.isDestroyed()).toEqual(false); + expect(clippingPlanes.isDestroyed()).toEqual(false); + + scene.primitives.remove(model); + expect(model.isDestroyed()).toEqual(true); + expect(clippingPlanes.isDestroyed()).toEqual(true); + }); + }); + + it("destroys ClippingPlaneCollections that are detached", function () { + let clippingPlanes; + return loadAndZoomToModelExperimental( + { + gltf: boxTexturedGlbUrl, + }, + scene + ).then(function (model) { + clippingPlanes = new ClippingPlaneCollection({ + planes: [new ClippingPlane(Cartesian3.UNIT_X, 0.0)], + }); + model.clippingPlanes = clippingPlanes; + expect(clippingPlanes.isDestroyed()).toBe(false); + + model.clippingPlanes = undefined; + expect(clippingPlanes.isDestroyed()).toBe(true); + }); + }); + it("destroy doesn't destroy resources when they're in use", function () { return Promise.all([ loadAndZoomToModelExperimental({ gltf: boxTexturedGlbUrl }, scene), diff --git a/Specs/Scene/ModelExperimental/loadAndZoomToModelExperimental.js b/Specs/Scene/ModelExperimental/loadAndZoomToModelExperimental.js index 3796cadae7f0..2b912e296794 100644 --- a/Specs/Scene/ModelExperimental/loadAndZoomToModelExperimental.js +++ b/Specs/Scene/ModelExperimental/loadAndZoomToModelExperimental.js @@ -23,6 +23,7 @@ function loadAndZoomToModelExperimental(options, scene) { featureIdLabel: options.featureIdLabel, instanceFeatureIdLabel: options.instanceFeatureIdLabel, incrementallyLoadTextures: options.incrementallyLoadTextures, + clippingPlanes: options.clippingPlanes, lightColor: options.lightColor, imageBasedLighting: options.imageBasedLighting, backFaceCulling: options.backFaceCulling, From 2443480a1dd117e09227b876e9f84a0d322eabea Mon Sep 17 00:00:00 2001 From: Janine Liu Date: Wed, 30 Mar 2022 15:06:57 -0400 Subject: [PATCH 2/5] Add unit tests for pipeline stages --- .../ModelClippingPlanesPipelineStage.js | 9 +- .../ModelClippingPlanesStageFS.glsl | 2 +- .../ImageBasedLightingPipelineStageSpec.js | 201 ++++++++++++++++++ .../ModelClippingPlanesPipelineStageSpec.js | 162 ++++++++++++++ 4 files changed, 368 insertions(+), 6 deletions(-) create mode 100644 Specs/Scene/ModelExperimental/ImageBasedLightingPipelineStageSpec.js create mode 100644 Specs/Scene/ModelExperimental/ModelClippingPlanesPipelineStageSpec.js diff --git a/Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js b/Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js index c1d67dc92084..412db354b9ce 100644 --- a/Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js +++ b/Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js @@ -2,7 +2,6 @@ import Cartesian2 from "../../Core/Cartesian2.js"; import ClippingPlaneCollection from "../ClippingPlaneCollection.js"; import combine from "../../Core/combine.js"; import Color from "../../Core/Color.js"; -import defined from "../../Core/defined.js"; import ModelClippingPlanesStageFS from "../../Shaders/ModelExperimental/ModelClippingPlanesStageFS.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; @@ -64,7 +63,7 @@ ModelClippingPlanesPipelineStage.process = function ( if (ClippingPlaneCollection.useFloatTexture(context)) { shaderBuilder.addDefine( - "USE_FLOAT_CLIPPING_PLANES_TEXTURE", + "USE_CLIPPING_PLANES_FLOAT_TEXTURE", undefined, ShaderDestination.FRAGMENT ); @@ -108,11 +107,11 @@ ModelClippingPlanesPipelineStage.process = function ( const uniformMap = { model_clippingPlanes: function () { - return model._clippingPlanes.texture; + return clippingPlanes.texture; }, model_clippingPlanesEdgeStyle: function () { - const style = Color.clone(model._clippingPlanes.edgeColor); - style.alpha = model._clippingPlanes.edgeWidth; + const style = Color.clone(clippingPlanes.edgeColor); + style.alpha = clippingPlanes.edgeWidth; return style; }, model_clippingPlanesMatrix: function () { diff --git a/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl b/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl index c56789f37d9b..b950df81daac 100644 --- a/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl +++ b/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl @@ -1,4 +1,4 @@ -#ifdef USE_FLOAT_CLIPPING_PLANES_TEXTURE +#ifdef USE_CLIPPING_PLANES_FLOAT_TEXTURE vec4 getClippingPlane( highp sampler2D packedClippingPlanes, int clippingPlaneNumber, diff --git a/Specs/Scene/ModelExperimental/ImageBasedLightingPipelineStageSpec.js b/Specs/Scene/ModelExperimental/ImageBasedLightingPipelineStageSpec.js new file mode 100644 index 000000000000..3586513267a1 --- /dev/null +++ b/Specs/Scene/ModelExperimental/ImageBasedLightingPipelineStageSpec.js @@ -0,0 +1,201 @@ +import { + Cartesian2, + Cartesian3, + ImageBasedLighting, + ImageBasedLightingPipelineStage, + Matrix3, + ShaderBuilder, +} from "../../../Source/Cesium.js"; +import ShaderBuilderTester from "../../ShaderBuilderTester.js"; + +describe("Scene/ModelExperimental/ImageBasedLightingPipelineStage", function () { + const mockFrameState = { + context: { + floatingPointTexture: true, + colorBufferFloat: true, + }, + }; + + it("configures the render resources for default image-based lighting", function () { + const imageBasedLighting = new ImageBasedLighting(); + const mockModel = { + imageBasedLighting: imageBasedLighting, + _iblReferenceFrameMatrix: Matrix3.clone(Matrix3.IDENTITY), + }; + + const renderResources = { + shaderBuilder: new ShaderBuilder(), + uniformMap: {}, + model: mockModel, + }; + const shaderBuilder = renderResources.shaderBuilder; + + ImageBasedLightingPipelineStage.process( + renderResources, + mockModel, + mockFrameState + ); + + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "USE_IBL_LIGHTING", + "USE_SUN_LUMINANCE", + ]); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform vec2 model_iblFactor;", + "uniform mat3 model_iblReferenceFrameMatrix;", + "uniform float model_luminanceAtZenith;", + ]); + + const uniformMap = renderResources.uniformMap; + expect( + Cartesian2.equals( + uniformMap.model_iblFactor(), + imageBasedLighting.imageBasedLightingFactor + ) + ).toBe(true); + + expect( + Matrix3.equals( + uniformMap.model_iblReferenceFrameMatrix(), + mockModel._iblReferenceFrameMatrix + ) + ).toBe(true); + + expect(uniformMap.model_luminanceAtZenith()).toEqual( + imageBasedLighting.luminanceAtZenith + ); + }); + + // These are dummy values, not meant to represent valid spherical harmonic coefficients. + const testCoefficients = [ + new Cartesian3(1, 1, 1), + new Cartesian3(2, 2, 2), + new Cartesian3(3, 3, 3), + new Cartesian3(4, 4, 4), + new Cartesian3(5, 5, 5), + new Cartesian3(6, 6, 6), + new Cartesian3(7, 7, 7), + new Cartesian3(8, 8, 8), + new Cartesian3(9, 9, 9), + ]; + + it("configures the render resources for spherical harmonics", function () { + const imageBasedLighting = new ImageBasedLighting({ + sphericalHarmonicCoefficients: testCoefficients, + }); + imageBasedLighting.luminanceAtZenith = undefined; + + const mockModel = { + imageBasedLighting: imageBasedLighting, + _iblReferenceFrameMatrix: Matrix3.clone(Matrix3.IDENTITY), + }; + + const renderResources = { + shaderBuilder: new ShaderBuilder(), + uniformMap: {}, + model: mockModel, + }; + const shaderBuilder = renderResources.shaderBuilder; + + ImageBasedLightingPipelineStage.process( + renderResources, + mockModel, + mockFrameState + ); + + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "USE_IBL_LIGHTING", + "DIFFUSE_IBL", + "CUSTOM_SPHERICAL_HARMONICS", + ]); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform vec2 model_iblFactor;", + "uniform mat3 model_iblReferenceFrameMatrix;", + "uniform vec3 model_sphericalHarmonicCoefficients[9];", + ]); + + const uniformMap = renderResources.uniformMap; + expect( + Cartesian2.equals( + uniformMap.model_iblFactor(), + imageBasedLighting.imageBasedLightingFactor + ) + ).toBe(true); + + expect( + Matrix3.equals( + uniformMap.model_iblReferenceFrameMatrix(), + mockModel._iblReferenceFrameMatrix + ) + ).toBe(true); + + expect(uniformMap.model_sphericalHarmonicCoefficients()).toBe( + testCoefficients + ); + }); + + it("configures the render resources for specular environment maps", function () { + const mockAtlas = { + texture: { + dimensions: {}, + }, + maximumMipmapLevel: 0, + ready: true, + }; + const imageBasedLighting = new ImageBasedLighting({ + specularEnvironmentMaps: "example.ktx2", + }); + imageBasedLighting.luminanceAtZenith = undefined; + imageBasedLighting._specularEnvironmentMapAtlas = mockAtlas; + + const mockModel = { + imageBasedLighting: imageBasedLighting, + _iblReferenceFrameMatrix: Matrix3.clone(Matrix3.IDENTITY), + }; + + const renderResources = { + shaderBuilder: new ShaderBuilder(), + uniformMap: {}, + model: mockModel, + }; + const shaderBuilder = renderResources.shaderBuilder; + + ImageBasedLightingPipelineStage.process( + renderResources, + mockModel, + mockFrameState + ); + + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "USE_IBL_LIGHTING", + "SPECULAR_IBL", + "CUSTOM_SPECULAR_IBL", + ]); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform vec2 model_iblFactor;", + "uniform mat3 model_iblReferenceFrameMatrix;", + "uniform sampler2D model_specularEnvironmentMaps;", + "uniform vec2 model_specularEnvironmentMapsSize;", + "uniform float model_specularEnvironmentMapsMaximumLOD;", + ]); + + const uniformMap = renderResources.uniformMap; + expect( + Cartesian2.equals( + uniformMap.model_iblFactor(), + imageBasedLighting.imageBasedLightingFactor + ) + ).toBe(true); + + expect( + Matrix3.equals( + uniformMap.model_iblReferenceFrameMatrix(), + mockModel._iblReferenceFrameMatrix + ) + ).toBe(true); + + expect(uniformMap.model_specularEnvironmentMaps()).toBeDefined(); + expect(uniformMap.model_specularEnvironmentMapsSize()).toBeDefined(); + expect(uniformMap.model_specularEnvironmentMapsMaximumLOD()).toBeDefined(); + }); +}); diff --git a/Specs/Scene/ModelExperimental/ModelClippingPlanesPipelineStageSpec.js b/Specs/Scene/ModelExperimental/ModelClippingPlanesPipelineStageSpec.js new file mode 100644 index 000000000000..299f9ba59b57 --- /dev/null +++ b/Specs/Scene/ModelExperimental/ModelClippingPlanesPipelineStageSpec.js @@ -0,0 +1,162 @@ +import { + Cartesian3, + ClippingPlane, + ClippingPlaneCollection, + Color, + Matrix4, + ModelClippingPlanesPipelineStage, + ShaderBuilder, +} from "../../../Source/Cesium.js"; +import ShaderBuilderTester from "../../ShaderBuilderTester.js"; + +describe("Scene/ModelExperimental/ModelClippingPlanesPipelineStage", function () { + let plane; + let clippingPlanes; + + beforeEach(function () { + plane = new ClippingPlane(Cartesian3.UNIT_X, 0.0); + clippingPlanes = new ClippingPlaneCollection({ + planes: [plane], + }); + clippingPlanes._clippingPlanesTexture = { + width: 1, + height: 1, + }; + }); + + it("configures the render resources for default clipping planes", function () { + const mockFrameState = { + context: {}, + }; + + const mockModel = { + clippingPlanes: clippingPlanes, + _clippingPlanesMatrix: Matrix4.clone(Matrix4.IDENTITY), + }; + + const renderResources = { + shaderBuilder: new ShaderBuilder(), + uniformMap: {}, + model: mockModel, + }; + const shaderBuilder = renderResources.shaderBuilder; + + ModelClippingPlanesPipelineStage.process( + renderResources, + mockModel, + mockFrameState + ); + + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_CLIPPING_PLANES", + "CLIPPING_PLANES_LENGTH 1", + "CLIPPING_PLANES_TEXTURE_WIDTH 1", + "CLIPPING_PLANES_TEXTURE_HEIGHT 1", + ]); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform sampler2D model_clippingPlanes;", + "uniform vec4 model_clippingPlanesEdgeStyle;", + "uniform mat4 model_clippingPlanesMatrix;", + ]); + + const uniformMap = renderResources.uniformMap; + + console.log("uho h"); + expect(uniformMap.model_clippingPlanes()).toBeDefined(); + + const edgeColor = clippingPlanes.edgeColor; + const expectedStyle = new Color( + edgeColor.r, + edgeColor.g, + edgeColor.b, + clippingPlanes.edgeWidth + ); + expect( + Color.equals(uniformMap.model_clippingPlanesEdgeStyle(), expectedStyle) + ).toBe(true); + + expect( + Matrix4.equals( + uniformMap.model_clippingPlanesMatrix(), + mockModel._clippingPlanesMatrix + ) + ).toBe(true); + }); + + it("configures the render resources for unioned clipping planes", function () { + const mockFrameState = { + context: {}, + }; + const mockModel = { + clippingPlanes: clippingPlanes, + _clippingPlanesMatrix: Matrix4.clone(Matrix4.IDENTITY), + }; + + clippingPlanes.unionClippingRegions = true; + + const renderResources = { + shaderBuilder: new ShaderBuilder(), + uniformMap: {}, + model: mockModel, + }; + const shaderBuilder = renderResources.shaderBuilder; + + ModelClippingPlanesPipelineStage.process( + renderResources, + mockModel, + mockFrameState + ); + + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_CLIPPING_PLANES", + "CLIPPING_PLANES_LENGTH 1", + "UNION_CLIPPING_REGIONS", + "CLIPPING_PLANES_TEXTURE_WIDTH 1", + "CLIPPING_PLANES_TEXTURE_HEIGHT 1", + ]); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform sampler2D model_clippingPlanes;", + "uniform vec4 model_clippingPlanesEdgeStyle;", + "uniform mat4 model_clippingPlanesMatrix;", + ]); + }); + + it("configures the render resources for float texture clipping planes", function () { + const mockFrameState = { + context: { + floatingPointTexture: true, + }, + }; + const mockModel = { + clippingPlanes: clippingPlanes, + _clippingPlanesMatrix: Matrix4.clone(Matrix4.IDENTITY), + }; + + const renderResources = { + shaderBuilder: new ShaderBuilder(), + uniformMap: {}, + model: mockModel, + }; + const shaderBuilder = renderResources.shaderBuilder; + + ModelClippingPlanesPipelineStage.process( + renderResources, + mockModel, + mockFrameState + ); + + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_CLIPPING_PLANES", + "CLIPPING_PLANES_LENGTH 1", + "USE_CLIPPING_PLANES_FLOAT_TEXTURE", + "CLIPPING_PLANES_TEXTURE_WIDTH 1", + "CLIPPING_PLANES_TEXTURE_HEIGHT 1", + ]); + + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform sampler2D model_clippingPlanes;", + "uniform vec4 model_clippingPlanesEdgeStyle;", + "uniform mat4 model_clippingPlanesMatrix;", + ]); + }); +}); From 4c73442c4e0ca97a27dbebea19c1984871b0fc6a Mon Sep 17 00:00:00 2001 From: Janine Liu Date: Wed, 30 Mar 2022 16:02:35 -0400 Subject: [PATCH 3/5] Pass clipping planes from tileset to model --- .../ModelExperimental3DTileContent.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Source/Scene/ModelExperimental/ModelExperimental3DTileContent.js b/Source/Scene/ModelExperimental/ModelExperimental3DTileContent.js index ffb13ea5c224..2f701b71cfd1 100644 --- a/Source/Scene/ModelExperimental/ModelExperimental3DTileContent.js +++ b/Source/Scene/ModelExperimental/ModelExperimental3DTileContent.js @@ -202,6 +202,27 @@ ModelExperimental3DTileContent.prototype.update = function ( model.showCreditsOnScreen = tileset.showCreditsOnScreen; model.splitDirection = tileset.splitDirection; + // Updating clipping planes requires more effort because of ownership checks + const tilesetClippingPlanes = tileset.clippingPlanes; + model.referenceMatrix = tileset.clippingPlanesOriginMatrix; + if (defined(tilesetClippingPlanes) && tile.clippingPlanesDirty) { + // Dereference the clipping planes from the model if they are irrelevant. + model._clippingPlanes = + tilesetClippingPlanes.enabled && tile._isClipped + ? tilesetClippingPlanes + : undefined; + } + + // If the model references a different ClippingPlaneCollection from the tileset, + // update the model to use the new ClippingPlaneCollection. + if ( + defined(tilesetClippingPlanes) && + defined(model._clippingPlanes) && + model._clippingPlanes !== tilesetClippingPlanes + ) { + model._clippingPlanes = tilesetClippingPlanes; + } + model.update(frameState); }; @@ -331,6 +352,7 @@ function makeModelOptions(tileset, tile, content, additionalOptions) { featureIdLabel: tileset.featureIdLabel, instanceFeatureIdLabel: tileset.instanceFeatureIdLabel, pointCloudShading: tileset.pointCloudShading, + clippingPlanes: tileset.clippingPlanes, backFaceCulling: tileset.backFaceCulling, shadows: tileset.shadows, showCreditsOnScreen: tileset.showCreditsOnScreen, From 94b0c63bf8254cca48dfe4adbe9157a593495d33 Mon Sep 17 00:00:00 2001 From: Janine Liu Date: Thu, 31 Mar 2022 15:33:42 -0400 Subject: [PATCH 4/5] Code and documentation tweaks --- Apps/Sandcastle/gallery/Terrain Clipping Planes.html | 2 -- CHANGES.md | 1 + Source/Scene/ClippingPlaneCollection.js | 2 +- Source/Scene/ModelExperimental/ModelExperimental.js | 2 +- .../ModelClippingPlanesStageFS.glsl | 12 ++++-------- .../ImageBasedLightingPipelineStageSpec.js | 5 +++++ .../ModelClippingPlanesPipelineStageSpec.js | 6 +++++- 7 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html index bee4ad44c24a..5533401a4bd9 100644 --- a/Apps/Sandcastle/gallery/Terrain Clipping Planes.html +++ b/Apps/Sandcastle/gallery/Terrain Clipping Planes.html @@ -290,8 +290,6 @@ }); return tileset.readyPromise .then(function () { - tileset.pointCloudShading.attenuation = true; - // Adjust height so tileset is in terrain const cartographic = Cesium.Cartographic.fromCartesian( tileset.boundingSphere.center diff --git a/CHANGES.md b/CHANGES.md index 5dac5ae228bf..060dbaa59021 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,6 +20,7 @@ - Added a 'renderable' property to 'Fog' to disable its visual rendering while preserving tiles culling at a distance - Refactored metadata API so `tileset.metadata` and `content.group.metadata` are more symmetric with `content.metadata` and `tile.metadata`. [#10224](https://github.com/CesiumGS/cesium/pull/10224) - Added support for `EXT_structural_metadata` property attributes in `CustomShader` [#10228](https://github.com/CesiumGS/cesium/pull/10228) +- Added clipping planes to `ModelExperimental`. [#10250](https://github.com/CesiumGS/cesium/pull/10250) ##### Fixes :wrench: diff --git a/Source/Scene/ClippingPlaneCollection.js b/Source/Scene/ClippingPlaneCollection.js index fbca054ec676..d16b971fb0af 100644 --- a/Source/Scene/ClippingPlaneCollection.js +++ b/Source/Scene/ClippingPlaneCollection.js @@ -246,7 +246,7 @@ Object.defineProperties(ClippingPlaneCollection.prototype, { * Returns a Number encapsulating the state for this ClippingPlaneCollection. * * Clipping mode is encoded in the sign of the number, which is just the plane count. - * Used for checking if shader regeneration is necessary. + * If this value changes, then shader regeneration is necessary. * * @memberof ClippingPlaneCollection.prototype * @returns {Number} A Number that describes the ClippingPlaneCollection's state. diff --git a/Source/Scene/ModelExperimental/ModelExperimental.js b/Source/Scene/ModelExperimental/ModelExperimental.js index 55c41016f46d..8963b717eccd 100644 --- a/Source/Scene/ModelExperimental/ModelExperimental.js +++ b/Source/Scene/ModelExperimental/ModelExperimental.js @@ -215,7 +215,7 @@ export default function ModelExperimental(options) { } else { this._clippingPlanes = clippingPlanes; } - this._clippingPlanesState = 0; // Used for checking if shaders need to be regenerated due to clipping plane changes. + this._clippingPlanesState = 0; // If this value changes, the shaders need to be regenerated. this._clippingPlanesMatrix = Matrix4.clone(Matrix4.IDENTITY); // Derived from reference matrix and the current view matrix this._lightColor = Cartesian3.clone(options.lightColor); diff --git a/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl b/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl index b950df81daac..5b1690a9774d 100644 --- a/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl +++ b/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl @@ -8,7 +8,7 @@ vec4 getClippingPlane( int pixX = clippingPlaneNumber - (pixY * CLIPPING_PLANES_TEXTURE_WIDTH); float pixelWidth = 1.0 / float(CLIPPING_PLANES_TEXTURE_WIDTH); float pixelHeight = 1.0 / float(CLIPPING_PLANES_TEXTURE_HEIGHT); - float u = (float(pixX) + 0.5) * pixelWidth;// sample from center of pixel + float u = (float(pixX) + 0.5) * pixelWidth; // sample from center of pixel float v = (float(pixY) + 0.5) * pixelHeight; vec4 plane = texture2D(packedClippingPlanes, vec2(u, v)); return czm_transformPlane(plane, transform); @@ -59,8 +59,7 @@ float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix) #ifdef UNION_CLIPPING_REGIONS clipAmount = czm_branchFreeTernary(i == 0, amount, min(amount, clipAmount)); if (amount <= 0.0) { - breakAndDiscard = true; - break; // HLSL compiler bug if we discard here: https://bugs.chromium.org/p/angleproject/issues/detail?id=1945#c6 + discard; } #else clipAmount = max(amount, clipAmount); @@ -68,15 +67,12 @@ float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix) #endif } - #ifdef UNION_CLIPPING_REGIONS - if (breakAndDiscard) { - discard; - } - #else + #ifndef UNION_CLIPPING_REGIONS if (clipped) { discard; } #endif + return clipAmount; } diff --git a/Specs/Scene/ModelExperimental/ImageBasedLightingPipelineStageSpec.js b/Specs/Scene/ModelExperimental/ImageBasedLightingPipelineStageSpec.js index 3586513267a1..f090c141493f 100644 --- a/Specs/Scene/ModelExperimental/ImageBasedLightingPipelineStageSpec.js +++ b/Specs/Scene/ModelExperimental/ImageBasedLightingPipelineStageSpec.js @@ -5,6 +5,7 @@ import { ImageBasedLightingPipelineStage, Matrix3, ShaderBuilder, + _shadersImageBasedLightingStageFS, } from "../../../Source/Cesium.js"; import ShaderBuilderTester from "../../ShaderBuilderTester.js"; @@ -46,6 +47,10 @@ describe("Scene/ModelExperimental/ImageBasedLightingPipelineStage", function () "uniform float model_luminanceAtZenith;", ]); + ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [ + _shadersImageBasedLightingStageFS, + ]); + const uniformMap = renderResources.uniformMap; expect( Cartesian2.equals( diff --git a/Specs/Scene/ModelExperimental/ModelClippingPlanesPipelineStageSpec.js b/Specs/Scene/ModelExperimental/ModelClippingPlanesPipelineStageSpec.js index 299f9ba59b57..aa654d68a686 100644 --- a/Specs/Scene/ModelExperimental/ModelClippingPlanesPipelineStageSpec.js +++ b/Specs/Scene/ModelExperimental/ModelClippingPlanesPipelineStageSpec.js @@ -6,6 +6,7 @@ import { Matrix4, ModelClippingPlanesPipelineStage, ShaderBuilder, + _shadersModelClippingPlanesStageFS, } from "../../../Source/Cesium.js"; import ShaderBuilderTester from "../../ShaderBuilderTester.js"; @@ -61,7 +62,6 @@ describe("Scene/ModelExperimental/ModelClippingPlanesPipelineStage", function () const uniformMap = renderResources.uniformMap; - console.log("uho h"); expect(uniformMap.model_clippingPlanes()).toBeDefined(); const edgeColor = clippingPlanes.edgeColor; @@ -81,6 +81,10 @@ describe("Scene/ModelExperimental/ModelClippingPlanesPipelineStage", function () mockModel._clippingPlanesMatrix ) ).toBe(true); + + ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [ + _shadersModelClippingPlanesStageFS, + ]); }); it("configures the render resources for unioned clipping planes", function () { From 7177228ab9859c062312d89c880f6b8651b7ce35 Mon Sep 17 00:00:00 2001 From: Janine Liu Date: Thu, 31 Mar 2022 15:46:19 -0400 Subject: [PATCH 5/5] Remove extraneous variable from shader --- Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl | 1 - 1 file changed, 1 deletion(-) diff --git a/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl b/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl index 5b1690a9774d..b09545bcf428 100644 --- a/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl +++ b/Source/Shaders/ModelExperimental/ModelClippingPlanesStageFS.glsl @@ -44,7 +44,6 @@ float clip(vec4 fragCoord, sampler2D clippingPlanes, mat4 clippingPlanesMatrix) #ifdef UNION_CLIPPING_REGIONS float clipAmount; // For union planes, we want to get the min distance. So we set the initial value to the first plane distance in the loop below. - bool breakAndDiscard = false; #else float clipAmount = 0.0; bool clipped = true;