Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add clipping planes to ModelExperimental #10250

Merged
merged 6 commits into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Apps/Sandcastle/gallery/Terrain Clipping Planes.html
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- 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 partial support for `EXT_structural_metadata` property textures in `CustomShader` [#10247](https://github.com/CesiumGS/cesium/pull/10247)
- Added clipping planes to `ModelExperimental`. [#10250](https://github.com/CesiumGS/cesium/pull/10250)

##### Fixes :wrench:

Expand Down
2 changes: 1 addition & 1 deletion Source/Scene/ClippingPlaneCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
125 changes: 125 additions & 0 deletions Source/Scene/ModelExperimental/ModelClippingPlanesPipelineStage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import Cartesian2 from "../../Core/Cartesian2.js";
j9liu marked this conversation as resolved.
Show resolved Hide resolved
import ClippingPlaneCollection from "../ClippingPlaneCollection.js";
import combine from "../../Core/combine.js";
import Color from "../../Core/Color.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:
*
* <ul>
* <li>adds a define to the fragment shader to indicate that the model has clipping planes</li>
* <li>adds the defines to the fragment shader for parameters related to clipping planes, such as the number of planes</li>
* <li>adds a function to the fragment shader to apply the clipping planes to the model's base color</li>
* <li>adds the uniforms for the fragment shader for the clipping plane texture and matrix</li>
*</ul>
*
* @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_CLIPPING_PLANES_FLOAT_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 clippingPlanes.texture;
},
model_clippingPlanesEdgeStyle: function () {
const style = Color.clone(clippingPlanes.edgeColor);
style.alpha = clippingPlanes.edgeWidth;
return style;
},
model_clippingPlanesMatrix: function () {
return model._clippingPlanesMatrix;
},
};

renderResources.uniformMap = combine(uniformMap, renderResources.uniformMap);
};

export default ModelClippingPlanesPipelineStage;
103 changes: 100 additions & 3 deletions Source/Scene/ModelExperimental/ModelExperimental.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 <code>undefined</code> 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.
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Comment on lines +212 to +217
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@j9liu, @lilleyse and I have been discussing this offline, not sure the best way we want to handle this. It would be nice to make clipping planes shareable, but that might require putting more responsibility on the user since it's hard to tell when to destroy the GPU resources.

See also #6599 (comment).

Not sure if this discussion has to hold up this PR (any changes could come later). just wanted to write it down somewhere.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We decided that it would be better to handle this with some sort of reference count mechanism. I'll write up the discussion in a separate issue and link it here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yesterday afternoon I opened #10257 for this.

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

this._imageBasedLighting = defined(options.imageBasedLighting)
Expand Down Expand Up @@ -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 <code>undefined</code> the scene's light color is used instead.
* <p>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1232,6 +1300,21 @@ function getScale(model, frameState) {
: scale;
}

/**
* Gets whether or not clipping planes are enabled for this model.
*
* @returns {Boolean} <code>true</code> if clipping planes are enabled for this model, <code>false</code>.
* @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.
* <br /><br />
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 <code>undefined</code> 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.
Expand Down Expand Up @@ -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,
Expand Down
22 changes: 22 additions & 0 deletions Source/Scene/ModelExperimental/ModelExperimental3DTileContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};

Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions Source/Scene/ModelExperimental/ModelExperimentalSceneGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand Down
Loading