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

Tileset back face culling options #8981

Merged
merged 4 commits into from
Jun 28, 2020
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
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

##### Additions :tada:

- Added `backFaceCulling` property to `Cesium3DTileset` and `Model` to support viewing the underside or interior of a tileset or model. [#8981](https://github.com/CesiumGS/cesium/pull/8981)
- Added `Ellipsoid.surfaceArea` for computing the approximate surface area of a rectangle on the surface of an ellipsoid. [#8986](https://github.com/CesiumGS/cesium/pull/8986)
- Added support for PolylineVolume in CZML. [#8841](https://github.com/CesiumGS/cesium/pull/8841)
- Added `Color.toCssHexString` for getting the CSS hex string equivalent for a color. [#8987](https://github.com/CesiumGS/cesium/pull/8987)
Expand Down
2 changes: 2 additions & 0 deletions Source/Scene/Batched3DModel3DTileContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,7 @@ function initialize(content, arrayBuffer, byteOffset) {
luminanceAtZenith: tileset.luminanceAtZenith,
sphericalHarmonicCoefficients: tileset.sphericalHarmonicCoefficients,
specularEnvironmentMaps: tileset.specularEnvironmentMaps,
backFaceCulling: tileset.backFaceCulling,
});
content._model.readyPromise.then(function (model) {
model.activeAnimations.addAll({
Expand Down Expand Up @@ -539,6 +540,7 @@ Batched3DModel3DTileContent.prototype.update = function (tileset, frameState) {
this._model.luminanceAtZenith = this._tileset.luminanceAtZenith;
this._model.sphericalHarmonicCoefficients = this._tileset.sphericalHarmonicCoefficients;
this._model.specularEnvironmentMaps = this._tileset.specularEnvironmentMaps;
this._model.backFaceCulling = this._tileset.backFaceCulling;
this._model.debugWireframe = this._tileset.debugWireframe;

// Update clipping planes
Expand Down
10 changes: 10 additions & 0 deletions Source/Scene/Cesium3DTileset.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ import TileOrientedBoundingBox from "./TileOrientedBoundingBox.js";
* @param {Number} [options.luminanceAtZenith=0.2] The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map.
* @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting.
* @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps.
* @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled.
* @param {String} [options.debugHeatmapTilePropertyName] The tile variable to colorize as a heatmap. All rendered tiles will be colorized relative to each other's specified variable value.
* @param {Boolean} [options.debugFreezeFrame=false] For debugging only. Determines if only the tiles from last frame should be used for rendering.
* @param {Boolean} [options.debugColorizeTiles=false] For debugging only. When true, assigns a random color to each tile.
Expand Down Expand Up @@ -750,6 +751,15 @@ function Cesium3DTileset(options) {
*/
this.specularEnvironmentMaps = options.specularEnvironmentMaps;

/**
* Whether to cull back-facing geometry. When true, back face culling is determined
* by the glTF material's doubleSided property; when false, back face culling is disabled.
*
* @type {Boolean}
* @default true
*/
this.backFaceCulling = defaultValue(options.backFaceCulling, true);

/**
* This property is for debugging only; it is not optimized for production use.
* <p>
Expand Down
2 changes: 2 additions & 0 deletions Source/Scene/Instanced3DModel3DTileContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ function initialize(content, arrayBuffer, byteOffset) {
luminanceAtZenith: tileset.luminanceAtZenith,
sphericalHarmonicCoefficients: tileset.sphericalHarmonicCoefficients,
specularEnvironmentMaps: tileset.specularEnvironmentMaps,
backFaceCulling: tileset.backFaceCulling,
};

if (gltfFormat === 0) {
Expand Down Expand Up @@ -615,6 +616,7 @@ Instanced3DModel3DTileContent.prototype.update = function (
this._modelInstanceCollection.luminanceAtZenith = this._tileset.luminanceAtZenith;
this._modelInstanceCollection.sphericalHarmonicCoefficients = this._tileset.sphericalHarmonicCoefficients;
this._modelInstanceCollection.specularEnvironmentMaps = this._tileset.specularEnvironmentMaps;
this._modelInstanceCollection.backFaceCulling = this._tileset.backFaceCulling;
this._modelInstanceCollection.debugWireframe = this._tileset.debugWireframe;

var model = this._modelInstanceCollection._model;
Expand Down
80 changes: 74 additions & 6 deletions Source/Scene/Model.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ var uriToGuid = {};
* @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting.
* @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps.
* @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
* @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 {@link Model#color} is translucent or {@link Model#silhouetteSize} is greater than 0.0.
*
* @see Model.fromGltf
*
Expand Down Expand Up @@ -493,6 +494,18 @@ function Model(options) {
// to the root tile.
this.clippingPlanesOriginMatrix = undefined;

/**
* 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 {@link Model#color} is
* translucent or {@link Model#silhouetteSize} is greater than 0.0.
*
* @type {Boolean}
*
* @default true
*/
this.backFaceCulling = defaultValue(options.backFaceCulling, true);

/**
* This property is for debugging only; it is not for production use nor is it optimized.
* <p>
Expand Down Expand Up @@ -1371,6 +1384,7 @@ function containsGltfMagic(uint8Array) {
* @param {ClippingPlaneCollection} [options.clippingPlanes] The {@link ClippingPlaneCollection} used to selectively disable rendering the model.
* @param {Boolean} [options.dequantizeInShader=true] Determines if a {@link https://github.com/google/draco|Draco} encoded model is dequantized on the GPU. This decreases total memory usage for encoded models.
* @param {Credit|String} [options.credit] A credit for the model, which is displayed on the canvas.
* @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 {@link Model#color} is translucent or {@link Model#silhouetteSize} is greater than 0.0.
*
* @returns {Model} The newly created model.
*
Expand Down Expand Up @@ -3947,6 +3961,9 @@ function createCommand(model, gltfNode, runtimeNode, context, scene3DOnly) {
// Generated on demand when color alpha is less than 1.0
translucentCommand: undefined,
translucentCommand2D: undefined,
// Generated on demand when back face culling is false
disableCullingCommand: undefined,
disableCullingCommand2D: undefined,
// For updating node commands on shader reconstruction
programId: programId,
};
Expand Down Expand Up @@ -4516,6 +4533,44 @@ function updateColor(model, frameState, forceDerive) {
}
}

function getDisableCullingRenderState(renderState) {
var rs = clone(renderState, true);
rs.cull.enabled = false;
return RenderState.fromCache(rs);
}

function deriveDisableCullingCommand(command) {
var disableCullingCommand = DrawCommand.shallowClone(command);
disableCullingCommand.renderState = getDisableCullingRenderState(
command.renderState
);
return disableCullingCommand;
}

function updateBackFaceCulling(model, frameState, forceDerive) {
var scene3DOnly = frameState.scene3DOnly;
var backFaceCulling = model.backFaceCulling;
if (!backFaceCulling) {
var nodeCommands = model._nodeCommands;
var length = nodeCommands.length;
if (!defined(nodeCommands[0].disableCullingCommand) || forceDerive) {
for (var i = 0; i < length; ++i) {
var nodeCommand = nodeCommands[i];
var command = nodeCommand.command;
nodeCommand.disableCullingCommand = deriveDisableCullingCommand(
command
);
if (!scene3DOnly) {
var command2D = nodeCommand.command2D;
nodeCommand.disableCullingCommand2D = deriveDisableCullingCommand(
command2D
);
}
}
}
}
}

function getProgramId(model, program) {
var programs = model._rendererResources.programs;
for (var id in programs) {
Expand Down Expand Up @@ -5347,6 +5402,7 @@ Model.prototype.update = function (frameState) {
var silhouette = hasSilhouette(this, frameState);
var translucent = isTranslucent(this);
var invisible = isInvisible(this);
var backFaceCulling = this.backFaceCulling;
var displayConditionPassed = defined(this.distanceDisplayCondition)
? distanceDisplayConditionVisible(this, frameState)
: true;
Expand Down Expand Up @@ -5491,6 +5547,7 @@ Model.prototype.update = function (frameState) {
regenerateShaders(this, frameState);
} else {
updateColor(this, frameState, false);
updateBackFaceCulling(this, frameState, false);
updateSilhouette(this, frameState, false);
}
}
Expand Down Expand Up @@ -5525,19 +5582,29 @@ Model.prototype.update = function (frameState) {
for (i = 0; i < length; ++i) {
nc = nodeCommands[i];
if (nc.show) {
var command = translucent ? nc.translucentCommand : nc.command;
command = silhouette ? nc.silhouetteModelCommand : command;
var command = nc.command;
if (silhouette) {
command = nc.silhouetteModelCommand;
} else if (translucent) {
command = nc.translucentCommand;
} else if (!backFaceCulling) {
command = nc.disableCullingCommand;
}
commandList.push(command);
boundingVolume = nc.command.boundingVolume;
if (
frameState.mode === SceneMode.SCENE2D &&
(boundingVolume.center.y + boundingVolume.radius > idl2D ||
boundingVolume.center.y - boundingVolume.radius < idl2D)
) {
var command2D = translucent
? nc.translucentCommand2D
: nc.command2D;
command2D = silhouette ? nc.silhouetteModelCommand2D : command2D;
var command2D = nc.command2D;
if (silhouette) {
command2D = nc.silhouetteModelCommand2D;
} else if (translucent) {
command2D = nc.translucentCommand2D;
} else if (!backFaceCulling) {
command2D = nc.disableCullingCommand2D;
}
commandList.push(command2D);
}
}
Expand Down Expand Up @@ -5664,6 +5731,7 @@ function regenerateShaders(model, frameState) {

// Force update silhouette commands/shaders
updateColor(model, frameState, true);
updateBackFaceCulling(model, frameState, true);
updateSilhouette(model, frameState, true);
}

Expand Down
72 changes: 60 additions & 12 deletions Source/Scene/ModelInstanceCollection.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import Buffer from "../Renderer/Buffer.js";
import BufferUsage from "../Renderer/BufferUsage.js";
import DrawCommand from "../Renderer/DrawCommand.js";
import Pass from "../Renderer/Pass.js";
import RenderState from "../Renderer/RenderState.js";
import ShaderSource from "../Renderer/ShaderSource.js";
import ForEach from "../ThirdParty/GltfPipeline/ForEach.js";
import when from "../ThirdParty/when.js";
Expand Down Expand Up @@ -62,6 +63,7 @@ var LoadState = {
* @param {Number} [options.luminanceAtZenith=0.2] The sun's luminance at the zenith in kilo candela per meter squared to use for this model's procedural environment map.
* @param {Cartesian3[]} [options.sphericalHarmonicCoefficients] The third order spherical harmonic coefficients used for the diffuse color of image-based lighting.
* @param {String} [options.specularEnvironmentMaps] A URL to a KTX file that contains a cube map of the specular lighting and the convoluted specular mipmaps.
* @param {Boolean} [options.backFaceCulling=true] Whether to cull back-facing geometry. When true, back face culling is determined by the glTF material's doubleSided property; when false, back face culling is disabled.
* @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Draws the bounding sphere for the collection.
* @param {Boolean} [options.debugWireframe=false] For debugging only. Draws the instances in wireframe.
*
Expand Down Expand Up @@ -114,6 +116,9 @@ function ModelInstanceCollection(options) {
this._drawCommands = [];
this._modelCommands = undefined;

this._renderStates = undefined;
this._disableCullingRenderStates = undefined;

this._boundingSphere = createBoundingSphere(this);
this._center = Cartesian3.clone(this._boundingSphere.center);
this._rtcTransform = new Matrix4();
Expand Down Expand Up @@ -157,6 +162,8 @@ function ModelInstanceCollection(options) {
this.luminanceAtZenith = options.luminanceAtZenith;
this.sphericalHarmonicCoefficients = options.sphericalHarmonicCoefficients;
this.specularEnvironmentMaps = options.specularEnvironmentMaps;
this.backFaceCulling = defaultValue(options.backFaceCulling, true);
this._backFaceCulling = this.backFaceCulling;
}

Object.defineProperties(ModelInstanceCollection.prototype, {
Expand Down Expand Up @@ -787,8 +794,8 @@ function createModel(collection, context) {
}
}

function updateWireframe(collection) {
if (collection._debugWireframe !== collection.debugWireframe) {
function updateWireframe(collection, force) {
if (collection._debugWireframe !== collection.debugWireframe || force) {
collection._debugWireframe = collection.debugWireframe;

// This assumes the original primitive was TRIANGLES and that the triangles
Expand All @@ -803,9 +810,45 @@ function updateWireframe(collection) {
}
}
}
function updateShowBoundingVolume(collection) {

function getDisableCullingRenderState(renderState) {
var rs = clone(renderState, true);
rs.cull.enabled = false;
return RenderState.fromCache(rs);
}

function updateBackFaceCulling(collection, force) {
if (collection._backFaceCulling !== collection.backFaceCulling || force) {
collection._backFaceCulling = collection.backFaceCulling;

var commands = collection._drawCommands;
var length = commands.length;
var i;

if (!defined(collection._disableCullingRenderStates)) {
collection._disableCullingRenderStates = new Array(length);
collection._renderStates = new Array(length);
for (i = 0; i < length; ++i) {
var renderState = commands[i].renderState;
var derivedRenderState = getDisableCullingRenderState(renderState);
collection._disableCullingRenderStates[i] = derivedRenderState;
collection._renderStates[i] = renderState;
}
}

for (i = 0; i < length; ++i) {
commands[i].renderState = collection._backFaceCulling
? collection._renderStates[i]
: collection._disableCullingRenderStates[i];
}
}
}

function updateShowBoundingVolume(collection, force) {
if (
collection.debugShowBoundingVolume !== collection._debugShowBoundingVolume
collection.debugShowBoundingVolume !==
collection._debugShowBoundingVolume ||
force
) {
collection._debugShowBoundingVolume = collection.debugShowBoundingVolume;

Expand Down Expand Up @@ -939,13 +982,16 @@ function commandsDirty(model) {
var nodeCommands = model._nodeCommands;
var length = nodeCommands.length;

var commandsDirty = false;

for (var i = 0; i < length; i++) {
var nc = nodeCommands[i];
if (nc.command.dirty) {
return true;
nc.command.dirty = false;
commandsDirty = true;
}
}
return false;
return commandsDirty;
}

function generateModelCommands(modelInstanceCollection, instancingSupported) {
Expand All @@ -960,8 +1006,8 @@ function generateModelCommands(modelInstanceCollection, instancingSupported) {
}
}

function updateShadows(collection) {
if (collection.shadows !== collection._shadows) {
function updateShadows(collection, force) {
if (collection.shadows !== collection._shadows || force) {
collection._shadows = collection.shadows;

var castShadows = ShadowMode.castShadows(collection.shadows);
Expand Down Expand Up @@ -1067,7 +1113,8 @@ ModelInstanceCollection.prototype.update = function (frameState) {
}

// If the model was set to rebuild shaders during update, rebuild instanced commands.
if (commandsDirty(model)) {
var modelCommandsDirty = commandsDirty(model);
if (modelCommandsDirty) {
generateModelCommands(this, instancingSupported);
}

Expand All @@ -1081,9 +1128,10 @@ ModelInstanceCollection.prototype.update = function (frameState) {
updateCommandsNonInstanced(this);
}

updateShadows(this);
updateWireframe(this);
updateShowBoundingVolume(this);
updateShadows(this, modelCommandsDirty);
updateWireframe(this, modelCommandsDirty);
updateBackFaceCulling(this, modelCommandsDirty);
updateShowBoundingVolume(this, modelCommandsDirty);

var passes = frameState.passes;
if (!passes.render && !passes.pick) {
Expand Down
Loading