Skip to content

Commit

Permalink
Merge pull request #10247 from CesiumGS/property-textures-take-two
Browse files Browse the repository at this point in the history
Add support for UINT8-based property textures in `CustomShader`
  • Loading branch information
lilleyse authored Mar 31, 2022
2 parents 6df4ebc + e653a16 commit e452c92
Show file tree
Hide file tree
Showing 13 changed files with 1,362 additions and 20 deletions.
130 changes: 130 additions & 0 deletions Apps/Sandcastle/gallery/Custom Shaders Property Textures.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
/>
<meta name="description" content="Create 3D models using glTF." />
<meta name="cesium-sandcastle-labels" content="3D Tiles Next" />
<title>Cesium Demo</title>
<script type="text/javascript" src="../Sandcastle-header.js"></script>
<script
type="text/javascript"
src="../../../Build/CesiumUnminified/Cesium.js"
nomodule
></script>
<script type="module" src="../load-cesium-es6.js"></script>
</head>
<body
class="sandcastle-loading"
data-sandcastle-bucket="bucket-requirejs.html"
>
<style>
@import url(../templates/bucket.css);
#toolbar {
background: rgba(42, 42, 42, 0.8);
padding: 4px;
border-radius: 4px;
}
#toolbar input {
vertical-align: middle;
padding-top: 2px;
padding-bottom: 2px;
}
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar"></div>
<script id="cesium_sandcastle_script">
function startup(Cesium) {
"use strict";
//Sandcastle_Begin
const viewer = new Cesium.Viewer("cesiumContainer", {
terrainProvider: Cesium.createWorldTerrain(),
});
const scene = viewer.scene;

scene.globe.depthTestAgainstTerrain = false;

// MAXAR OWT Muscatatuk photogrammetry dataset with property textures
// containing horizontal and vertical uncertainty
const tileset = viewer.scene.primitives.add(
new Cesium.Cesium3DTileset({
url: Cesium.IonResource.fromAssetId(905848),
})
);

viewer.zoomTo(tileset);

const shaders = {
NO_TEXTURE: undefined,
UNCERTAINTY_CE90: new Cesium.CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
int horizontalUncertainty = fsInput.metadata.r3dm_uncertainty_ce90sum;
material.diffuse = vec3(float(horizontalUncertainty) / 255.0);
}
`,
}),
UNCERTAINTY_LE90: new Cesium.CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
int verticalUncertainty = fsInput.metadata.r3dm_uncertainty_le90sum;
material.diffuse = vec3(float(verticalUncertainty) / 255.0);
}
`,
}),
// combined uncertainty
UNCERTAINTY: new Cesium.CustomShader({
fragmentShaderText: `
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
int uncertainty = fsInput.metadata.r3dm_uncertainty_ce90sum + fsInput.metadata.r3dm_uncertainty_le90sum;
material.diffuse = vec3(float(uncertainty) / 255.0);
}
`,
}),
};

Sandcastle.addToolbarMenu([
{
text: "Default View",
onselect: function () {
tileset.customShader = shaders.NO_TEXTURE;
},
},
{
text: "Horizontal Uncertainty",
onselect: function () {
tileset.customShader = shaders.UNCERTAINTY_CE90;
},
},
{
text: "Vertical Uncertainty",
onselect: function () {
tileset.customShader = shaders.UNCERTAINTY_LE90;
},
},
{
text: "Combined Uncertainty",
onselect: function () {
tileset.customShader = shaders.UNCERTAINTY;
},
},
]);

//Sandcastle_End
Sandcastle.finishedLoading();
}
if (typeof Cesium !== "undefined") {
window.startupCalled = true;
startup(Cesium);
}
</script>
</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 partial support for `EXT_structural_metadata` property textures in `CustomShader` [#10247](https://github.com/CesiumGS/cesium/pull/10247)

##### Fixes :wrench:

Expand Down
5 changes: 3 additions & 2 deletions Documentation/CustomShaderGuide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,10 +564,11 @@ model from the
glTF extension (or the older
[`EXT_feature_metadata`](https://github.com/CesiumGS/glTF/tree/3d-tiles-next/extensions/2.0/Vendor/EXT_feature_metadata) extension).

The following types of metadata are currently supported. We plan to add more
in the near future:
The following types of metadata are currently supported:

- property attributes from the `EXT_structural_metadata` glTF extension.
- property textures from the `EXT_structural_metadata` glTF extension. Only
types with `componentType: UINT8` are currently supported.

Regardless of the source of metadata, the properties are collected into a single
struct by property ID. For example, if the metadata class looked like this:
Expand Down
125 changes: 123 additions & 2 deletions Source/Scene/ModelExperimental/MetadataPipelineStage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ import MetadataStageFS from "../../Shaders/ModelExperimental/MetadataStageFS.js"
import MetadataStageVS from "../../Shaders/ModelExperimental/MetadataStageVS.js";
import ModelExperimentalUtility from "./ModelExperimentalUtility.js";

/**
* The metadata pipeline stage processes metadata properties from
* EXT_structural_metadata and inserts them into a struct in the shader.
* This struct will be used by {@link CustomShaderPipelineStage} to allow the
* user to access metadata using {@link CustomShader}
*
* @namespace MetadataPipelineStage
*
* @private
*/
const MetadataPipelineStage = {};
MetadataPipelineStage.name = "MetadataPipelineStage";

Expand All @@ -20,6 +30,21 @@ MetadataPipelineStage.FUNCTION_ID_SET_METADATA_VARYINGS = "setMetadataVaryings";
MetadataPipelineStage.FUNCTION_SIGNATURE_SET_METADATA_VARYINGS =
"void setMetadataVaryings()";

/**
* Process a primitive. This modifies the following parts of the render
* resources:
* <ul>
* <li>Adds a Metadata struct to the shader</li>
* <li>If the primitive has structural metadata, properties are added to the Metadata struct</li>
* <li>dynamic functions are added to the shader to initialize the metadata properties</li>
* <li>Adds uniforms for property textures to the uniform map as needed</li>
* <li>Adds uniforms for offset/scale to the uniform map as needed</li>
* </ul>
* @param {PrimitiveRenderResources} renderResources The render resources for the primitive
* @param {ModelComponents.Primitive} primitive The primitive to be rendered
* @param {FrameState} frameState The frame state
* @private
*/
MetadataPipelineStage.process = function (
renderResources,
primitive,
Expand All @@ -38,6 +63,7 @@ MetadataPipelineStage.process = function (
}

processPropertyAttributes(renderResources, primitive, structuralMetadata);
processPropertyTextures(renderResources, structuralMetadata);
};

function declareStructsAndFunctions(shaderBuilder) {
Expand Down Expand Up @@ -173,6 +199,100 @@ function addPropertyAttributeProperty(
);
}

function processPropertyTextures(renderResources, structuralMetadata) {
const propertyTextures = structuralMetadata.propertyTextures;

if (!defined(propertyTextures)) {
return;
}

for (let i = 0; i < propertyTextures.length; i++) {
const propertyTexture = propertyTextures[i];

const properties = propertyTexture.properties;
for (const propertyId in properties) {
if (properties.hasOwnProperty(propertyId)) {
const property = properties[propertyId];
if (property.isGpuCompatible()) {
addPropertyTextureProperty(renderResources, propertyId, property);
}
}
}
}
}

function addPropertyTextureProperty(renderResources, propertyId, property) {
// Property texture properties may share the same physical texture, so only
// add the texture uniform the first time we encounter it.
const textureReader = property.textureReader;
const textureIndex = textureReader.index;
const textureUniformName = `u_propertyTexture_${textureIndex}`;
if (!renderResources.uniformMap.hasOwnProperty(textureUniformName)) {
addPropertyTextureUniform(
renderResources,
textureUniformName,
textureReader
);
}

const metadataVariable = sanitizeGlslIdentifier(propertyId);
const glslType = property.getGlslType();

const shaderBuilder = renderResources.shaderBuilder;
shaderBuilder.addStructField(
MetadataPipelineStage.STRUCT_ID_METADATA_FS,
glslType,
metadataVariable
);

const texCoord = textureReader.texCoord;
const texCoordVariable = `attributes.texCoord_${texCoord}`;
const channels = textureReader.channels;
let unpackedValue = `texture2D(${textureUniformName}, ${texCoordVariable}).${channels}`;

// Some types need an unpacking step or two. For example, since texture reads
// are always normalized, UINT8 (not normalized) properties need to be
// un-normalized in the shader.
unpackedValue = property.unpackInShader(unpackedValue);

// handle offset/scale transform. This wraps the GLSL expression with
// the czm_valueTransform() call.
if (property.hasValueTransform) {
unpackedValue = addValueTransformUniforms(unpackedValue, {
renderResources: renderResources,
glslType: glslType,
metadataVariable: metadataVariable,
shaderDestination: ShaderDestination.FRAGMENT,
offset: property.offset,
scale: property.scale,
});
}

const initializationLine = `metadata.${metadataVariable} = ${unpackedValue};`;
shaderBuilder.addFunctionLines(
MetadataPipelineStage.FUNCTION_ID_INITIALIZE_METADATA_FS,
[initializationLine]
);
}

function addPropertyTextureUniform(
renderResources,
uniformName,
textureReader
) {
const shaderBuilder = renderResources.shaderBuilder;
shaderBuilder.addUniform(
"sampler2D",
uniformName,
ShaderDestination.FRAGMENT
);

const uniformMap = renderResources.uniformMap;
uniformMap[uniformName] = function () {
return textureReader.texture;
};
}

function addValueTransformUniforms(valueExpression, options) {
const metadataVariable = options.metadataVariable;
const offsetUniformName = `u_${metadataVariable}_offset`;
Expand All @@ -181,8 +301,9 @@ function addValueTransformUniforms(valueExpression, options) {
const renderResources = options.renderResources;
const shaderBuilder = renderResources.shaderBuilder;
const glslType = options.glslType;
shaderBuilder.addUniform(glslType, offsetUniformName, ShaderDestination.BOTH);
shaderBuilder.addUniform(glslType, scaleUniformName, ShaderDestination.BOTH);
const shaderDestination = options.shaderDestination;
shaderBuilder.addUniform(glslType, offsetUniformName, shaderDestination);
shaderBuilder.addUniform(glslType, scaleUniformName, shaderDestination);

const uniformMap = renderResources.uniformMap;
uniformMap[offsetUniformName] = function () {
Expand Down
15 changes: 15 additions & 0 deletions Source/Scene/PropertyTexture.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ Object.defineProperties(PropertyTexture.prototype, {
},
},

/**
* The properties in this property texture.
*
* @memberof PropertyTexture.prototype
*
* @type {PropertyTextureProperty}
* @readonly
* @private
*/
properties: {
get: function () {
return this._properties;
},
},

/**
* Extras in the JSON object.
*
Expand Down
Loading

0 comments on commit e452c92

Please sign in to comment.