UNPKG

@cesium/engine

Version:

CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.

393 lines (341 loc) 11.8 kB
import Cartesian3 from "../../Core/Cartesian3.js"; import Cartesian4 from "../../Core/Cartesian4.js"; import CesiumMath from "../../Core/Math.js"; import Cesium3DTileRefine from "../Cesium3DTileRefine.js"; import clone from "../../Core/clone.js"; import defined from "../../Core/defined.js"; import Matrix4 from "../../Core/Matrix4.js"; import ModelType from "./ModelType.js"; import ModelUtility from "./ModelUtility.js"; import OrthographicFrustum from "../../Core/OrthographicFrustum.js"; import Pass from "../../Renderer/Pass.js"; import PointCloudStylingStageVS from "../../Shaders/Model/PointCloudStylingStageVS.js"; import RuntimeError from "../../Core/RuntimeError.js"; import SceneMode from "../SceneMode.js"; import ShaderDestination from "../../Renderer/ShaderDestination.js"; import VertexAttributeSemantic from "../VertexAttributeSemantic.js"; const scratchUniform = new Cartesian4(); /** * The point cloud styling stage is responsible for applying color, * size, and show styles to point clouds at runtime. It also handles * point cloud shading provided by either the model or the tileset that * owns it. Point cloud shading is only applied if no point size style * is provided. * * @namespace PointCloudStylingPipelineStage * * @private */ const PointCloudStylingPipelineStage = { name: "PointCloudStylingPipelineStage", // Helps with debugging }; /** * Processes a primitive. If the model that owns it has a style, then * this stage modifies the following parts of the render resources: * <ul> * <li>adds the styling functions to the vertex shaders</li> * <li>adds a define to compute the position in world coordinates</li> * <li>adds a varying to compute point cloud color</li> * </ul> * * If the model has point cloud shading, then this stage modifies the following * part of the render resources: * <ul> * <li>adds vertex shader code to compute attenuation and update gl_PointSize</li> * <li>updates the uniform map to pass in point cloud parameters</li> * </ul> * * @param {PrimitiveRenderResources} renderResources The render resources for this primitive. * @param {ModelComponents.Primitive} primitive The primitive. * @param {FrameState} frameState The frame state. * * @private */ PointCloudStylingPipelineStage.process = function ( renderResources, primitive, frameState, ) { const shaderBuilder = renderResources.shaderBuilder; const model = renderResources.model; const style = model.style; // Point cloud styling will only be applied on the GPU if there is // no batch table. If a batch table exists, then: // - the property attribute will not be defined // - the model will be using a feature table const structuralMetadata = model.structuralMetadata; const propertyAttributes = defined(structuralMetadata) ? structuralMetadata.propertyAttributes : undefined; const hasFeatureTable = defined(model.featureTableId) && model.featureTables[model.featureTableId].featuresLength > 0; const hasBatchTable = !defined(propertyAttributes) && hasFeatureTable; if (defined(style) && !hasBatchTable) { const variableSubstitutionMap = getVariableSubstitutionMap(propertyAttributes); const shaderFunctionInfo = getStyleShaderFunctionInfo( style, variableSubstitutionMap, ); addShaderFunctionsAndDefines(shaderBuilder, shaderFunctionInfo); const propertyNames = getPropertyNames(shaderFunctionInfo); const usesNormalSemantic = propertyNames.indexOf("normalMC") >= 0; const hasNormals = ModelUtility.getAttributeBySemantic( primitive, VertexAttributeSemantic.NORMAL, ); if (usesNormalSemantic && !hasNormals) { throw new RuntimeError( "Style references the NORMAL semantic but the point cloud does not have normals", ); } shaderBuilder.addDefine( "COMPUTE_POSITION_WC_STYLE", undefined, ShaderDestination.VERTEX, ); // If the style is translucent, the alpha options must be adjusted. const styleTranslucent = shaderFunctionInfo.styleTranslucent; if (styleTranslucent) { renderResources.alphaOptions.pass = Pass.TRANSLUCENT; } } const pointCloudShading = model.pointCloudShading; if (pointCloudShading.attenuation) { shaderBuilder.addDefine( "HAS_POINT_CLOUD_ATTENUATION", undefined, ShaderDestination.VERTEX, ); } if (pointCloudShading.backFaceCulling) { shaderBuilder.addDefine( "HAS_POINT_CLOUD_BACK_FACE_CULLING", undefined, ShaderDestination.VERTEX, ); } let content; let is3DTiles; let usesAddRefinement; if (ModelType.is3DTiles(model.type)) { is3DTiles = true; content = model.content; usesAddRefinement = content.tile.refine === Cesium3DTileRefine.ADD; } shaderBuilder.addUniform( "vec4", "model_pointCloudParameters", ShaderDestination.VERTEX, ); shaderBuilder.addVertexLines(PointCloudStylingStageVS); const uniformMap = renderResources.uniformMap; uniformMap.model_pointCloudParameters = function () { const vec4 = scratchUniform; // Point size let defaultPointSize = 1.0; if (is3DTiles) { defaultPointSize = usesAddRefinement ? 5.0 : content.tileset.memoryAdjustedScreenSpaceError; } vec4.x = pointCloudShading.maximumAttenuation ?? defaultPointSize; vec4.x *= frameState.pixelRatio; // Geometric error const geometricError = getGeometricError( renderResources, primitive, pointCloudShading, content, ); vec4.y = geometricError * pointCloudShading.geometricErrorScale; const context = frameState.context; const frustum = frameState.camera.frustum; let depthMultiplier; // Attenuation is maximumAttenuation in 2D/ortho if ( frameState.mode === SceneMode.SCENE2D || frustum instanceof OrthographicFrustum ) { depthMultiplier = Number.POSITIVE_INFINITY; } else { depthMultiplier = context.drawingBufferHeight / frameState.camera.frustum.sseDenominator; } // Depth multiplier vec4.z = depthMultiplier; // Time if (is3DTiles) { vec4.w = content.tileset.timeSinceLoad; } return vec4; }; }; const scratchDimensions = new Cartesian3(); function getGeometricError( renderResources, primitive, pointCloudShading, content, ) { if (defined(content)) { const geometricError = content.tile.geometricError; if (geometricError > 0) { return geometricError; } } if (defined(pointCloudShading.baseResolution)) { return pointCloudShading.baseResolution; } const positionAttribute = ModelUtility.getAttributeBySemantic( primitive, VertexAttributeSemantic.POSITION, ); const pointsLength = positionAttribute.count; // Estimate the geometric error const nodeTransform = renderResources.runtimeNode.transform; let dimensions = Cartesian3.subtract( positionAttribute.max, positionAttribute.min, scratchDimensions, ); // dimensions is a vector, as it is a subtraction between two points dimensions = Matrix4.multiplyByPointAsVector( nodeTransform, dimensions, scratchDimensions, ); const volume = dimensions.x * dimensions.y * dimensions.z; const geometricErrorEstimate = CesiumMath.cbrt(volume / pointsLength); return geometricErrorEstimate; } const scratchShaderFunctionInfo = { colorStyleFunction: undefined, showStyleFunction: undefined, pointSizeStyleFunction: undefined, styleTranslucent: false, }; const builtinVariableSubstitutionMap = { POSITION: "attributes.positionMC", POSITION_ABSOLUTE: "v_positionWC", COLOR: "attributes.color_0", NORMAL: "attributes.normalMC", }; function getVariableSubstitutionMap(propertyAttributes) { const variableSubstitutionMap = clone(builtinVariableSubstitutionMap); if (!defined(propertyAttributes)) { return variableSubstitutionMap; } for (let i = 0; i < propertyAttributes.length; i++) { const propertyAttribute = propertyAttributes[i]; const properties = propertyAttribute.properties; for (const propertyId in properties) { // The property ID was already sanitized for GLSL by PntsLoader. if (properties.hasOwnProperty(propertyId)) { variableSubstitutionMap[propertyId] = `metadata.${propertyId}`; } } } return variableSubstitutionMap; } const parameterList = "ProcessedAttributes attributes, " + "Metadata metadata, " + "float tiles3d_tileset_time"; function getStyleShaderFunctionInfo(style, variableSubstitutionMap) { const info = scratchShaderFunctionInfo; const shaderState = { translucent: false, }; info.colorStyleFunction = style.getColorShaderFunction( `getColorFromStyle(${parameterList})`, variableSubstitutionMap, shaderState, ); info.showStyleFunction = style.getShowShaderFunction( `getShowFromStyle(${parameterList})`, variableSubstitutionMap, shaderState, ); info.pointSizeStyleFunction = style.getPointSizeShaderFunction( `getPointSizeFromStyle(${parameterList})`, variableSubstitutionMap, shaderState, ); info.styleTranslucent = defined(info.colorStyleFunction) && shaderState.translucent; return info; } function addShaderFunctionsAndDefines(shaderBuilder, shaderFunctionInfo) { const colorStyleFunction = shaderFunctionInfo.colorStyleFunction; if (defined(colorStyleFunction)) { shaderBuilder.addDefine( "HAS_POINT_CLOUD_COLOR_STYLE", undefined, ShaderDestination.BOTH, ); shaderBuilder.addVertexLines(colorStyleFunction); // The point cloud may not necessarily have a color attribute. // Use a custom varying to account for this. shaderBuilder.addVarying("vec4", "v_pointCloudColor"); } const showStyleFunction = shaderFunctionInfo.showStyleFunction; if (defined(showStyleFunction)) { shaderBuilder.addDefine( "HAS_POINT_CLOUD_SHOW_STYLE", undefined, ShaderDestination.BOTH, ); shaderBuilder.addVertexLines(showStyleFunction); shaderBuilder.addVarying("float", "v_pointCloudShow"); } const pointSizeStyleFunction = shaderFunctionInfo.pointSizeStyleFunction; if (defined(pointSizeStyleFunction)) { shaderBuilder.addDefine( "HAS_POINT_CLOUD_POINT_SIZE_STYLE", undefined, ShaderDestination.VERTEX, ); shaderBuilder.addVertexLines(pointSizeStyleFunction); } } /** * Gets all the built-in property names used by the given style * function. * * @param {Function} source The style function. * @param {string[]} propertyNames The array of property names to add to. * * @private */ function getBuiltinPropertyNames(source, propertyNames) { const regex = /attributes\.(\w+)/g; let matches = regex.exec(source); while (matches !== null) { const name = matches[1]; // Add the property name if it isn't already in the array. if (propertyNames.indexOf(name) === -1) { propertyNames.push(name); } matches = regex.exec(source); } } function getPropertyNames(shaderFunctionInfo) { const colorStyleFunction = shaderFunctionInfo.colorStyleFunction; const showStyleFunction = shaderFunctionInfo.showStyleFunction; const pointSizeStyleFunction = shaderFunctionInfo.pointSizeStyleFunction; // Get the properties in use by the style. const builtinPropertyNames = []; if (defined(colorStyleFunction)) { getBuiltinPropertyNames(colorStyleFunction, builtinPropertyNames); } if (defined(showStyleFunction)) { getBuiltinPropertyNames(showStyleFunction, builtinPropertyNames); } if (defined(pointSizeStyleFunction)) { getBuiltinPropertyNames(pointSizeStyleFunction, builtinPropertyNames); } return builtinPropertyNames; } export default PointCloudStylingPipelineStage;