From 501de27ac580d296694dff530a5aa5c166fff13b Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 20 Dec 2017 15:59:49 -0500 Subject: [PATCH 01/13] add simple geometric error based point cloud attenuation --- Source/Scene/Cesium3DTileset.js | 83 ++++++++------- Source/Scene/PointAttenuationOptions.js | 50 +++++++++ Source/Scene/PointCloud3DTileContent.js | 78 +++++++++++++- Specs/Scene/PointAttenuationOptionsSpec.js | 26 +++++ Specs/Scene/PointCloud3DTileContentSpec.js | 112 +++++++++++++++++++++ 5 files changed, 308 insertions(+), 41 deletions(-) create mode 100644 Source/Scene/PointAttenuationOptions.js create mode 100644 Specs/Scene/PointAttenuationOptionsSpec.js diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index b05284ba5e83..ef3ea3ee4b88 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -1,41 +1,42 @@ define([ - '../Core/Cartesian2', - '../Core/Cartesian3', - '../Core/Cartographic', - '../Core/Check', - '../Core/defaultValue', - '../Core/defined', - '../Core/defineProperties', - '../Core/destroyObject', - '../Core/DeveloperError', - '../Core/DoublyLinkedList', - '../Core/Event', - '../Core/getBaseUri', - '../Core/getExtensionFromUri', - '../Core/isDataUri', - '../Core/joinUrls', - '../Core/JulianDate', - '../Core/loadJson', - '../Core/ManagedArray', - '../Core/Math', - '../Core/Matrix4', - '../Core/RuntimeError', - '../Renderer/ClearCommand', - '../Renderer/Pass', - '../ThirdParty/when', - './Axis', - './Cesium3DTile', - './Cesium3DTileColorBlendMode', - './Cesium3DTileOptimizations', - './Cesium3DTilesetStatistics', - './Cesium3DTilesetTraversal', - './Cesium3DTileStyleEngine', - './LabelCollection', - './SceneMode', - './ShadowMode', - './TileBoundingRegion', - './TileBoundingSphere', - './TileOrientedBoundingBox' + '../Core/Cartesian2', + '../Core/Cartesian3', + '../Core/Cartographic', + '../Core/Check', + '../Core/defaultValue', + '../Core/defined', + '../Core/defineProperties', + '../Core/destroyObject', + '../Core/DeveloperError', + '../Core/DoublyLinkedList', + '../Core/Event', + '../Core/getBaseUri', + '../Core/getExtensionFromUri', + '../Core/isDataUri', + '../Core/joinUrls', + '../Core/JulianDate', + '../Core/loadJson', + '../Core/ManagedArray', + '../Core/Math', + '../Core/Matrix4', + '../Core/RuntimeError', + '../Renderer/ClearCommand', + '../Renderer/Pass', + '../ThirdParty/when', + './Axis', + './Cesium3DTile', + './Cesium3DTileColorBlendMode', + './Cesium3DTileOptimizations', + './Cesium3DTilesetStatistics', + './Cesium3DTilesetTraversal', + './Cesium3DTileStyleEngine', + './LabelCollection', + './PointAttenuationOptions', + './SceneMode', + './ShadowMode', + './TileBoundingRegion', + './TileBoundingSphere', + './TileOrientedBoundingBox' ], function( Cartesian2, Cartesian3, @@ -69,6 +70,7 @@ define([ Cesium3DTilesetTraversal, Cesium3DTileStyleEngine, LabelCollection, + PointAttenuationOptions, SceneMode, ShadowMode, TileBoundingRegion, @@ -112,6 +114,7 @@ define([ * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. + * @param {Object} [options.pointAttenuationOptions] Options for constructing a PointAttenuationOptions object to control point attenuation based on geometric error. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. See {@link https://github.com/AnalyticalGraphicsInc/3d-tiles#spec-status} * @@ -317,6 +320,12 @@ define([ */ this.colorBlendAmount = 0.5; + /** + * Options for controlling point attenuation based on geometric error. + * @type {PointAttenuationOptions} + */ + this.pointAttenuationOptions = new PointAttenuationOptions(options.pointAttenuationOptions); + /** * The event fired to indicate progress of loading new tiles. This event is fired when a new tile * is requested, when a requested tile is finished downloading, and when a downloaded tile has been diff --git a/Source/Scene/PointAttenuationOptions.js b/Source/Scene/PointAttenuationOptions.js new file mode 100644 index 000000000000..e662dfad4bd3 --- /dev/null +++ b/Source/Scene/PointAttenuationOptions.js @@ -0,0 +1,50 @@ +define([ + '../Core/defaultValue' +], function( + defaultValue +) { + 'use strict'; + + /** + * Options for performing point attenuation based on geometric error when rendering + * pointclouds using 3D Tiles. + * + * @param {Object} [options] Object with the following properties: + * {Boolean} [options.geometricErrorAttenuation=false] Perform point attenuation based on geometric error. + * {Number} [options.geometricErrorScale=1.0] Scale to be applied to each tile's geometric error. + * {Number} [options.maximumAttenuation] Maximum attenuation in pixels. Defaults to the Cesium3DTileset's maximumScreenSpaceError. + * {Number} [options.baseResolution] Average base resolution for the dataset in meters. Substitute for Geometric Error when not available. + * @constructor + */ + function PointAttenuationOptions(options) { + var pointAttenuationOptions = defaultValue(options, {}); + + /** + * Perform point attenuation based on geometric error. + * @type {Boolean} + */ + this.geometricErrorAttenuation = defaultValue(pointAttenuationOptions.geometricErrorAttenuation, false); + + /** + * Scale to be applied to the geometric error before computing attenuation. + * @type {Number} + */ + this.geometricErrorScale = defaultValue(pointAttenuationOptions.geometricErrorScale, 1.0); + + /** + * Maximum point attenuation in pixels. If undefined, the Cesium3DTileset's maximumScreenSpaceError will be used. + * @type {Number} + */ + this.maximumAttenuation = pointAttenuationOptions.maximumAttenuation; + + /** + * Average base resolution for the dataset in meters. + * Used in place of geometric error when geometric error is 0. + * If undefined, an approximation will be computed for each tile that has geometric error of 0. + * @type {Number} + */ + this.baseResolution = pointAttenuationOptions.baseResolution; + } + + return PointAttenuationOptions; +}); diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index f961e4ead24a..f31eb2dbf065 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -2,6 +2,7 @@ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', + '../Core/Math', '../Core/Color', '../Core/combine', '../Core/ComponentDatatype', @@ -15,6 +16,7 @@ define([ '../Core/Matrix3', '../Core/Matrix4', '../Core/oneTimeWarning', + '../Core/OrthographicFrustum', '../Core/Plane', '../Core/PrimitiveType', '../Core/RuntimeError', @@ -38,6 +40,7 @@ define([ Cartesian2, Cartesian3, Cartesian4, + CesiumMath, Color, combine, ComponentDatatype, @@ -51,6 +54,7 @@ define([ Matrix3, Matrix4, oneTimeWarning, + OrthographicFrustum, Plane, PrimitiveType, RuntimeError, @@ -150,6 +154,14 @@ define([ */ this.featurePropertiesDirty = false; + // Options for geometric error based attenuation + this.geometricErrorAttenuation = false; + this._geometricErrorAttenuation = false; + this._geometricErrorScale = undefined; + this._maximumAttenuation = undefined; + this._baseResolution = undefined; + this._baseResolutionApproximation = undefined; + initialize(this, arrayBuffer, byteOffset); } @@ -448,9 +460,16 @@ define([ content._hasColors = defined(colors); content._hasNormals = defined(normals); content._hasBatchIds = defined(batchIds); + + // Compute an approximation for base resolution in case it isn't given + // Assume a uniform distribution of points in the bounding sphere around the tile. + var radius = content._tile.contentBoundingVolume.boundingSphere.radius; + var sphereVolume = (4.0 / 3.0) * CesiumMath.PI * radius * radius * radius; + content._baseResolutionApproximation = Math.cbrt(sphereVolume / pointsLength); } var scratchPointSizeAndTilesetTime = new Cartesian2(); + var scratchGeometricErrorAndDepthMultiplier = new Cartesian2(); var positionLocation = 0; var colorLocation = 1; @@ -523,7 +542,7 @@ define([ var uniformMap = { u_pointSizeAndTilesetTime : function() { - scratchPointSizeAndTilesetTime.x = content._pointSize; + scratchPointSizeAndTilesetTime.x = content._geometricErrorAttenuation ? content._maximumAttenuation : content._pointSize; scratchPointSizeAndTilesetTime.y = content._tileset.timeSinceLoad; return scratchPointSizeAndTilesetTime; }, @@ -549,6 +568,24 @@ define([ var style = Color.clone(clippingPlanes.edgeColor); style.alpha = clippingPlanes.edgeWidth; return style; + }, + u_geometricErrorAndDepthMultiplier : function() { + var geometricError = content.tile.geometricError; + if (geometricError === 0) { + geometricError = defined(content._baseResolution) ? content._baseResolution : content._baseResolutionApproximation; + } + var frustum = frameState.camera.frustum; + var 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; + } + + scratchGeometricErrorAndDepthMultiplier.x = geometricError * content._geometricErrorScale; + scratchGeometricErrorAndDepthMultiplier.y = depthMultiplier; + return scratchGeometricErrorAndDepthMultiplier; } }; @@ -816,6 +853,7 @@ define([ var backFaceCulling = content._backFaceCulling; var vertexArray = content._drawCommand.vertexArray; var clippingPlanes = content._tileset.clippingPlanes; + var geometricErrorAttenuation = content._geometricErrorAttenuation; var colorStyleFunction; var showStyleFunction; @@ -930,9 +968,17 @@ define([ 'varying vec4 v_color; \n' + 'uniform vec2 u_pointSizeAndTilesetTime; \n' + 'uniform vec4 u_constantColor; \n' + - 'uniform vec4 u_highlightColor; \n' + - 'float u_pointSize; \n' + - 'float u_tilesetTime; \n'; + 'uniform vec4 u_highlightColor; \n'; + if (geometricErrorAttenuation) { + vs += 'uniform vec2 u_geometricErrorAndDepthMultiplier; \n'; + } + vs += 'float u_pointSize; \n' + + 'float u_tilesetTime; \n'; + + if (geometricErrorAttenuation) { + vs += 'float u_geometricError; \n' + + 'float u_depthMultiplier; \n'; + } vs += attributeDeclarations; @@ -984,6 +1030,11 @@ define([ ' u_pointSize = u_pointSizeAndTilesetTime.x; \n' + ' u_tilesetTime = u_pointSizeAndTilesetTime.y; \n'; + if (geometricErrorAttenuation) { + vs += ' u_geometricError = u_geometricErrorAndDepthMultiplier.x; \n' + + ' u_depthMultiplier = u_geometricErrorAndDepthMultiplier.y; \n'; + } + if (usesColors) { if (isTranslucent) { vs += ' vec4 color = a_color; \n'; @@ -1030,6 +1081,12 @@ define([ if (hasPointSizeStyle) { vs += ' gl_PointSize = getPointSizeFromStyle(position, position_absolute, color, normal); \n'; + } else if (geometricErrorAttenuation) { + vs += ' vec3 positionWC = vec3(czm_model * vec4(position, 1.0)); \n' + + ' vec4 positionEC = czm_view * vec4(positionWC, 1.0); \n' + + ' float depth = -positionEC.z; \n' + + // compute SSE for this point + ' gl_PointSize = min((u_geometricError / depth) * u_depthMultiplier, u_pointSize); \n'; } else { vs += ' gl_PointSize = u_pointSize; \n'; } @@ -1255,6 +1312,19 @@ define([ createShaders(this, frameState, tileset.style); } + // Update attenuation + var pointAttenuationOptions = this._tileset.pointAttenuationOptions; + if (defined(pointAttenuationOptions)) { + this.geometricErrorAttenuation = pointAttenuationOptions.geometricErrorAttenuation; + this._geometricErrorScale = pointAttenuationOptions.geometricErrorScale; + this._maximumAttenuation = defined(pointAttenuationOptions.maximumAttenuation) ? pointAttenuationOptions.maximumAttenuation : tileset.maximumScreenSpaceError; + this._baseResolution = pointAttenuationOptions.baseResolution; + if (this.geometricErrorAttenuation !== this._geometricErrorAttenuation) { + this._geometricErrorAttenuation = this.geometricErrorAttenuation; + createShaders(this, frameState, tileset.style); + } + } + if (updateModelMatrix) { Matrix4.clone(modelMatrix, this._modelMatrix); if (defined(this._rtcCenter)) { diff --git a/Specs/Scene/PointAttenuationOptionsSpec.js b/Specs/Scene/PointAttenuationOptionsSpec.js new file mode 100644 index 000000000000..f34494842f5f --- /dev/null +++ b/Specs/Scene/PointAttenuationOptionsSpec.js @@ -0,0 +1,26 @@ +defineSuite([ + 'Scene/PointAttenuationOptions' + ], function( + PointAttenuationOptions + ) { + 'use strict'; + + it('creates expected instance from raw assignment and construction', function() { + var pointAttenuationOptions = new PointAttenuationOptions(); + expect(pointAttenuationOptions.geometricErrorAttenuation).toEqual(false); + expect(pointAttenuationOptions.geometricErrorScale).toEqual(1.0); + expect(pointAttenuationOptions.maximumAttenuation).not.toBeDefined(); + expect(pointAttenuationOptions.baseResolution).not.toBeDefined(); + + var options = { + geometricErrorScale : 2.0, + maximumAttenuation : 16, + baseResolution : 0.1 + }; + pointAttenuationOptions = new PointAttenuationOptions(options); + expect(pointAttenuationOptions.geometricErrorAttenuation).toEqual(false); + expect(pointAttenuationOptions.geometricErrorScale).toEqual(options.geometricErrorScale); + expect(pointAttenuationOptions.maximumAttenuation).toEqual(options.maximumAttenuation); + expect(pointAttenuationOptions.baseResolution).toEqual(options.baseResolution); + }); +}); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index 6ddee66a725a..6975f39999a7 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -14,6 +14,7 @@ defineSuite([ 'Scene/Cesium3DTileStyle', 'Scene/Expression', 'Specs/Cesium3DTilesTester', + 'Specs/createCanvas', 'Specs/createScene', 'ThirdParty/when' ], 'Scene/PointCloud3DTileContent', function( @@ -32,6 +33,7 @@ defineSuite([ Cesium3DTileStyle, Expression, Cesium3DTilesTester, + createCanvas, createScene, when) { 'use strict'; @@ -436,6 +438,116 @@ defineSuite([ }); }); + function countRenderedPixels(rgba) { + var pixelCount = rgba.length / 4; + var count = 0; + for (var i = 0; i < pixelCount; i++) { + var index = i * 4; + if (rgba[index] !== 0 || + rgba[index + 1] !== 0 || + rgba[index + 2] !== 0 || + rgba[index + 3] !== 255) { + count++; + } + } + return count; + } + + function rgbaToAscii(rgba) { + console.log(countRenderedPixels(rgba)); + for (var i = 0; i < 10; i++) { + var scanline = rgba.slice(i * 40, i * 40 + 40); + var lineString = i + ' '; + for (var j = 0; j < 40; j += 4) { + var isDrawn = scanline[j] !== 0; + lineString += isDrawn ? '[ ]' : ' _ '; + } + console.log(lineString); + } + } + + it('attenuates points based on geometric error', function() { + var scene = createScene({ + canvas : createCanvas(10, 10) + }); + var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); + scene.fxaa = false; + scene.camera.zoomIn(6); + + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + + var noAttenuationPixelCount = 0; + expect(scene).toRenderAndCall(function(rgba) { + noAttenuationPixelCount = countRenderedPixels(rgba); + }); + + // Activate attenuation + tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + tileset.pointAttenuationOptions.geometricErrorScale = 1.0; + tileset.pointAttenuationOptions.maximumAttenuation = undefined; + tileset.pointAttenuationOptions.baseResolution = undefined; + tileset.maximumScreenSpaceError = 16; + expect(scene).toRenderAndCall(function(rgba) { + expect(countRenderedPixels(rgba)).toBeGreaterThan(noAttenuationPixelCount); + }); + + // Adjust screen space error in tileset to modulate maximumAttenuation + tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + tileset.pointAttenuationOptions.geometricErrorScale = 1.0; + tileset.pointAttenuationOptions.maximumAttenuation = undefined; + tileset.pointAttenuationOptions.baseResolution = undefined; + tileset.maximumScreenSpaceError = 1; + expect(scene).toRenderAndCall(function(rgba) { + expect(countRenderedPixels(rgba)).toEqual(noAttenuationPixelCount); + }); + + // Adjust maximumAttenuation directly + tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + tileset.pointAttenuationOptions.geometricErrorScale = 1.0; + tileset.pointAttenuationOptions.maximumAttenuation = 1; + tileset.pointAttenuationOptions.baseResolution = undefined; + tileset.maximumScreenSpaceError = 16; + expect(scene).toRenderAndCall(function(rgba) { + expect(countRenderedPixels(rgba)).toEqual(noAttenuationPixelCount); + }); + + // Adjust baseResolution - pointCloudNoColorUrl is a single tile with GeometricError = 0 + tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + tileset.pointAttenuationOptions.geometricErrorScale = 1.0; + tileset.pointAttenuationOptions.maximumAttenuation = undefined; + tileset.pointAttenuationOptions.baseResolution = CesiumMath.EPSILON20; + tileset.maximumScreenSpaceError = 16; + expect(scene).toRenderAndCall(function(rgba) { + expect(countRenderedPixels(rgba)).toEqual(noAttenuationPixelCount); + }); + + // Adjust geometricErrorScale + tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + tileset.pointAttenuationOptions.geometricErrorScale = 0.0; + tileset.pointAttenuationOptions.maximumAttenuation = undefined; + tileset.pointAttenuationOptions.baseResolution = undefined; + tileset.maximumScreenSpaceError = 1; + expect(scene).toRenderAndCall(function(rgba) { + expect(countRenderedPixels(rgba)).toEqual(noAttenuationPixelCount); + }); + + // Works with 2D scenes + scene.morphTo2D(0); + tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + tileset.pointAttenuationOptions.geometricErrorScale = 1.0; + tileset.pointAttenuationOptions.maximumAttenuation = undefined; + tileset.pointAttenuationOptions.baseResolution = undefined; + tileset.maximumScreenSpaceError = 16; + expect(scene).toRenderAndCall(function(rgba) { + rgbaToAscii(rgba); + expect(countRenderedPixels(rgba)).toBeGreaterThan(noAttenuationPixelCount); + }); + + scene.destroyForSpecs(); + }); + }); + it('applies shader style', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudWithPerPointPropertiesUrl).then(function(tileset) { var content = tileset._root.content; From 095e152081619c7118d49f412f59006895dfb131 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 20 Dec 2017 20:55:22 -0500 Subject: [PATCH 02/13] added eye dome lighting for point clouds --- ...oud Attenuation and Eye Dome Lighting.html | 186 +++++++++ ...loud Attenuation and Eye Dome Lighting.jpg | Bin 0 -> 25647 bytes Source/Scene/Cesium3DTileset.js | 13 +- Source/Scene/PointAttenuationOptions.js | 21 + Source/Scene/PointCloudEyeDomeLighting.js | 395 ++++++++++++++++++ .../PointCloudEyeDomeLighting.glsl | 47 +++ Specs/Scene/PointAttenuationOptionsSpec.js | 10 +- Specs/Scene/PointCloud3DTileContentSpec.js | 14 - Specs/Scene/PointCloudEyeDomeLightingSpec.js | 89 ++++ 9 files changed, 759 insertions(+), 16 deletions(-) create mode 100644 Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.html create mode 100644 Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.jpg create mode 100644 Source/Scene/PointCloudEyeDomeLighting.js create mode 100644 Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl create mode 100644 Specs/Scene/PointCloudEyeDomeLightingSpec.js diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.html new file mode 100644 index 000000000000..f8db578d6bc4 --- /dev/null +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.html @@ -0,0 +1,186 @@ + + + + + + + + + Cesium Demo + + + + + + +
+

Loading...

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Maximum Screen Space Error + + +
Attenuation
Geometric Error Scale + + +
Maximum Attenuation + + +
Base Resolution + + +
Eye Dome Lighting
Eye Dome Lighting Strength + + +
Eye Dome Lighting Radius + + +
+
+ + + diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.jpg b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3ba34dc741ff3806b1b9c103efeaec048951e0ab GIT binary patch literal 25647 zcmeEtbyyrtv+v>%T!RI-;O_43?(Xgu2=49{+$}f+cemi~5+u0W-Q<<;JLi1Qea`*+ zzP;NsEx)R*t*)u*uIYJOeA@&dNs3B{0zg1O0OG(8;B5<8S;XDK6abKs0Z;+}0B8U- z2nqlUSONil03g@^h~H%Zz!n7O@3Jch#h(~ZAchM7JObc>*(byT0R9Iax;8WG7Y6_~ zFh?HY0-%7*;eh2LVE&bWzy7=b_gUJ4nn23H*38zz)(ilU23P>70R#XkfC0c3Uw?h%j*`WN{>=^qXJqk(@k@Q()m(ZD|%`2SJ^(6NADvkCw>JtG5sEZFZ^2pRwY zgBc6{cbPgq?splCg&qKikN;OW=Pz0YdiorYKV~y;Zveo*W-2iJzqHCo&%nmXz{tr+ zPr$&=$-uzLME@V#{+XHl($+ue9}WDYfqyjcj|TqH!2iD*c-w*~Fn4yg=cJ>vb)q#e zwlg%LHL|mzb2qT3W1yv{190=X+Zz~JnK%;|nwVMG@{pgkc9IiV81s;;vB=QN*bAAM zTZnr)nkae7DjRuP8F3hs^YOmpcIR}rv9~dCHXv}fv9@*Mbmt-e&72ch{?$xJ4g@(G zn{p}&i~b1#)_BPOl*P@>jn<8c*3Qw4j)8-NgN~k&j**cDh@f%uuyr{hN`o5uJ&(iH(V^vlH;IG5nH%fPmZBh||c? z#K76k@mKW^eo7_|zbk*#tSxx|J?6tr_e*SG<8S!y%Aa^{y8puL58%Ja_J5HVsI0#Q z{*UAEhp<2xr_e8HO@x7&_t%7vhJlfWkzV<)2_G#BJrg(Gf5QRQ^jpsVoh&B6AlrWy z=2y>etN25jUsX;yM+=}v4SwmKSJ}><#@fWxnVar^bp2BG??!b+6DK=sm*03c#y^q& z-uO3C<3Az)z4344zkn*X7S6m9wnpZEGX8Hpe^UrJI$Jn7|F?1dE#IF#ek#AXn6x;V_;kmI5MpC1cn~YCQbw{w#Fu=7PcnF1jYu=27k)+7w&ID z5uh$rO&pzmsqt5t;Li#HBQ3+9H2)6yo9>q#C|lT=0EYp1bsAXzwu%3?2>6r!e?b2S z6Ja0^XA4gg^fdD{}z>t6~k7nH1GaAE$+yfj|n0t8&_6(tW0SX-=MuPNP6!rv89I!-DP~F32 z5U0EQ1Qaaw;G|%F`A%r)?^wfzODLSgGlHiaae;~VJrt<_aNGv_p}hGVq7ri{iurUD z1XO@%W*agh%O%l>w>T8 zzgTQhtfL?%5XQhkaathI3Uk+Jr+fsjh_~2x>P9UMM_F^d`>-@aMZ(0Y7?#FLa~h)b zApm@wqeLr}BXz>WxQqlHkVGskKCHx|i~%PtA*F-u`SW|m`GGNUs75izOhyV?PKvg; z>ce=jO|PJ`gC=T>z!yUlQOE9P!{Bt+ine{q%4E)4Pn?sl=6>HlZ)rjWO_dKuYbeRV zSL_o-VA<9L_z5ja6u787-w0lW`(Mc_jsIAXU7=GNtsbx4@LId8aWMz;2R`jUdvAb5 zL8KyZkmk`f$N=hSTNYN;s9-pSk3qZ!WBpvBTI~eA*RR{Y8E69-Qv&pdG-!v134T)N9pd77>wim+>Zk3 z9TF%QQUwL91{Q-3qmO5i)9h)jaj{AE&=Wsh?&tqy=0BYq8)u0NFSw7a8;D>^w8Hr_IlRk5eAcC z_;Idm9&=Bxa#1wvae}tBigMv~-e_%)^>37&Y=+-O(=N6W-~_o5^E$|9TDU%cXXUg| zP-rozkFYkbRlz#oh+;zLf~jO7Q>+LeEl^VQ6f3LAvfXn^)uO`=;6Tv(VwVcvB8;X6 z`_tO%)U?c`U)JwHO3qY9$1J-LOKIMaC@GHcWLz5=jw>yAcOs%OnZa!svXqHA@i~Rp z`e%35IU=LjjT_sisur>LvY9q|++((3Wm1!7Y?%$yIWC!Y6iG^}>&3~;Ekwm^mP)x( zfX475Q5BE(1rzl&W)2==X*g6ec5E7t=`2#}Q!*5LbNRg6uB_Xvb%)V=+EWDX?aApu ziTaMrT;C*^qrMNZ#u)_9$yd+kI~_VY9!xr`0bK$G65y8~0|vr?`PU;n02t6?03cA% zAdykg2{DME2uP5K8Gs%F8t7F(AV8l6K5a8KX`dXdJDpiS>G3vAHq;$kA6V~kj4X{f z-Nl`8h&oou$Y0gb} zjPfQ;xJ-InPx#awV@QBKE`bO;+`6gY-`|!jS{p zG*zQDt-ILkh>wl!rjbZjIQSlG!{ih@o&sVQP^LtdFU@n4nYSKe>kShQ7%8ymD17}JTkHS29WPYYzPi;dakR%sK>cwB{RKE?OKdTtPpD^Zw5yWX3>Ovp8@sw z1lDqki!z~jZzzSi^4TG;g7y=?*C9i1d2NLzt52nyjsHfZJFI9gb?HYvePhkkV&afL0eLV|;0 zE5t^VtnUJYo9~B2etKwJx4UB`WHx1?-B+|C(hY-KUmNC?h$yD`Mv*C|YdYU{yn)khE&dG@CVHehN zbGV70eHP4}{!!wtrtuKj6wKImttI}-)nQU*jg@`XDnB#_#g&}qN5dUq51RY_M^R@l?mhfc~MpTYg&u*9RLLmy!OYJu@^EJ zeBIS$C`}m)yJHb})_WQxm)r`x=+A8c*u90^vh^Hgx>#bRLjC9WO4LTWDMtwu{P<&iexl*hCcYA7GGA8|Sr4JO-RT5^6gSce2- zBdEUtrcRO>*AKXAxuBkpt9Gj20OS>y6c1l2k65Gd-hKK8qXHd2%MyXZ;Fr$a>j1V` z^u?yF!5}Ji<~{rZl5-W~qSAWErExF?O+&ha7%pD)icV3s0cXcxyW0ary5fQ>IU{#? zmBcOF=h$a?vy_i?r^YU9v6jnC?y5a!g;K@);-IZ|jCIVswPS<@?@mtE&J$UC5re66 z3~JWP0$kntY=)*`GZf*XJKq2``B(9=-o6nhX*#4jn*GaCB67w&pPm!Ou&A^?Jh`Ew zHGHA1z@fBK0c}{avE@hXzlk}114P*-Wx1tdVZ92Jo0Bw$;e@yjmz@gKM!jSRCVWl^_lV_IcoRUYR#FKAqksUOdt5!AfV& zF4KNj5P4ZrJZ4T@JGBMX@%yNJNDexnVv7=qXuQ-=pW_R>6MqCw<3T~de`VlY{C5HY zMMeQbLM3DnAW)zOM`JV~;&%}IeJch|GEWZT#);F-s3+7;_BarPm|}PT zI5IIZ{goI^O--d6-5QcL;^JbLOhrY`Ohrva(y-oNw4ca z8My7!i9AJoq?nmXAbd0q0$043l4ZQKG*|PNwHj)GP2l@69X9ZS>xirQF-a(12VsO8 z+5yd46FkRA*l;7z;x&%8e?gCv`b&CByeCscbNpP!rSEig7CbT3>r)^=qAwL!J!38AKg3nO3e7Bs zQxu8Rp+;dca$5Jt%}BpggubR*gulE2a#@!AqSw3_FcOTW%uhz8JM)-)#e78>u&MR3 za9yQ@?XfB3ad{(mBdZ_YA${=O9S%1YWO%<*`#Q*%+jJs5BzNxO3Psh{GNno2Spigq+KXkw?n4mP-tV zEM~S8rmP;Q?+^_(gAtBQgu_$qi3=xrFXlX;VT2iuefZrt4kZR?xa7YK_qXK&t@2-n zi;4vH%W&xx9KeYL(F}f@EpXY)2Qp>Bj15l~AWJ4`OT&s5GHlu`O=2u?qZR1{8oRlYs8sMMb)(kyeSs^5Ng9UwQXKGT zY-1jCiP;(7o{=LdZ;I?QIQ@)3gWthb4eCfMs>tLsHkMMm?Yb2Oyl_mCHB4UKy{OyVTdOAu)rJwuFp(cANR54;l1>{ z{)l0mk+*RKHm(L%4He3hvDW=ZEUa+Bz2W>8GEr?&h)-);*V>^QwGSOktoYXo_`4`a z8jVqDXhS-2Pie7p>4PXA&rgKJ=PhzwaTsc@ts|jD$UL8l8Ip}P;u*Qn^v6iX)E=2% z;e8V{MMW-PQm$QhZaJ{va&z;7aF`JvqfJ%aP@dD6o)DJs(zHoMyIrJ5?^&&-hLi3o zbtfUU>(w_f6hia#CAJCAJJ1YO-ieBensO5plMD-BBB1EPb&`>h@|x#Ks_p8qoyay< z``;^wV1}J9h6zh6;X5jsnQe+ID`>N8gsx%X2?rAr1r0cxs;Bwl;5HlWTpJJ?N2Zw_ zF7?L67O;{T8(O0R;mGzqnio zV!@j3X(TizMMFpbt@HT23jz`$NM@rRrNG)h7c!81pws5Hd0X04A%}Ag=zYx#SlY6h zl|GTZZK^RBx(hLnEwoZ8j}4j+RixF^dDE7>(NZFLx2?&tJjW`f+PYF_+{i0CC3k1V{feEBMUwZwh2 zQYw9Q4(HmW{brcDqh85!DwTpm)GtG@=?(azrV%l24akZZEiSZli239+g2Qts+U=uW zELm&)O5BgyEpGs>f8<(4n7NnRMT zRAHkjUP<1UfhE70*iYx1$kLgr#|@Tnc-31axcP^!2t`7 zmC=(I)V(Dfmi3n!8!xb&_jFeMmd%stbFZ+(l-u^&DtyW1E={kA7OD%{4ccqV??pVB z_@|aXRboufh*eS}LW~NY&DVoq2Pl27vo}A6aG<|M&-Z)Q+xwLN1}L>-zpH+7<*J(E z8Fk%P%k4Lz;eQ%ATwFlN5?t>UJ%i{BOv*1SpKrNf>dToQcHm*wXrqL|9eG&*G>sNK zuk_nCyc{!L$Ofw@nQyH&Y3r@lnuQWNm@TU+w%A!`dLHX}jzy0YgAf}SiAb%^@Ygyg z)Z=L-2~z2yLb~oBRrb8T%w-gknt0}{oho|;t?7MRR%g7@kZ^H%gp=2y&g|uI&4Tho zHLM^`v~lU}B>6#LB{Jy4DIYyTu|`Gg(oxIit)eZbjB{}E(iNqsV?e|Cd&gc2PTKsET4~?N^wga);c>n__PgDV3Epj}elr{h z&Yp$~&UO|t1K2eg54tChLC4BkwkFcPvm+I@>f2^%YNMoH^Ru6-vIpT_P|lR!C0*>O zO8AsOM^x*E5$~uIE)UKHTi1?lUovG5Nm@5EXWYcA8Mc*`WVrO5^Y*{0S=h-b=7Z#m z@krWEwZcLcMUO8TSHYq z`7@O+S?m>UAG@!Oi)yz=+;t;W`O6@GRX=or2Mb(td*#JK8`(EX*0mKO{?S~g56)#y z`Dk&gvgicICvSitnTiX)1o17(n7$CizAlx4B|ZtfJqM#Q`3fcaXy<8-wsU8Q>%96H zu)Lx-K)d||B`14H=2htDh4W_oa4rtM!Z$$en1pOo({{;F3Fn%g<9PiCX(8QZ>iqtd z@f!?}Ky4$Z2$y^-<%34Ih&3(o95M5kR8ut!FppF2>9~rTN{+GTXV5x?1%{At3kd|} zk)Pg^WBCG3I$1PBKs&@D7u}!xRB&w^Xdj2^uw&4yR99{oS^R)G z+HGbWdXI+gRxb(v>1ocQ**l4P)~YNQE^1=p$%&6ygY~MeVHpY`OWq;}j8j4NXoeph zrmh9Yq_aZB?pu&*alh`O8O;xL$93bK#u6n{xowLFN?wZc*-f6Kp-RY%qjtiCt%LP#pR7(=;csmU&tU!u^fa%G$@^z3V zNfVK_3pe>WsN|ukQy!(VpQn;se%YB;s~FF74kOtOyyml5I{Y$AIbc|>!OO69`z^d# zon3yQb0>RQ@8HuLTO~jao`0$;G?$rLsRA1{^IUA#_LbRE_SDzayrgWVE6wT(UEC-*x< z?4!b1de)Olt-SSaVY)T$h+qS4OtO@v_Xua3qK-nos2RdO!^MStV2fEAmODxFre0T? znRVqisx?*U3%RJ~XZFC*Jisg!Y*OSqw@P(=z)+wKU0nLWXmcyfv^?^sl?RoRQ~Ytk ztVbI>In^?M%IkO)X`9g`7Y&0pf6|l0+ZmF(ez;pD@|hscgZIr(8hgv18+Jb(zgxRY zuFOd`kNT(u#&bN|cP7En67TBaR+$V1tv%tuhlaa8qF7qV8`e{kwE4Jh_M}#Mtu{wi zNK+x(eD9x1PmT_jPD>sCedNyPP%JzIh9|v!deuh?X%S0JW}-l8v$DvjEa|7lhMR~;@%@AXNGrKAe$t9`2;-eoPaLC4lG<;QlyA~*fk^AJG#JhHi&C;~8m1k8>>Mo_D-Zs~Yj5R|SzOfk3nGj&ut08&5 zuvuk}g(n|Fcb+7j z1r8smiv$}yJr>49c72Nm(0i8QyLyk>f(fVkY0=7 zZu=FOr8Ve$7a=3uTJf&aXIA1waf;0tJs#Mdld|p zR1bKoWZy}MWBMyaURj%5W8&z%d|#1-RgJmxNxfdd}0^!T`b4ByvcMF>VUtgoH6aDfw?*kn{_OrCD=qG(@jAW{Ka*FuiomN+u z52IyAivm50gfL4&oC8wAUGSygrHmp)akiaR*dReh0?Slsa;GOYc;7`i$FuR@_6u&{ zp9ilX4RSoJ;Q8pWh&DR87VK6HOz|u;7Q`(ihn*QS>KhqZpqaUkt|W`2oG?old6~@~ zJbw=rgu`Gv#hv2kWmfA%B@uY|+%anWc6qI#D3!o7Idzkjg7=q`79+>RWNh(Q*TKVv@R(H-(<&Ftl8JmDnDWN%ELu#+wpbxKyrw|+78+F;Xcp&@QW|3 z_#K_9Essp~w@?^DqlZ~69N_(Mqeo6@yg9^sCIdWSYe}KCN6eqDZz~w2>m9=-OLuYJ zPn}TV`}B2FapUbv*%fBd<+*&J{l

qWMO1P!$t1%^n|kC*ia)J4&J<+lQ#C1@aU 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 KK9tgX)c@I>l=m(G literal 0 HcmV?d00001 diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index ef3ea3ee4b88..0cc6a96433f3 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -32,6 +32,7 @@ define([ './Cesium3DTileStyleEngine', './LabelCollection', './PointAttenuationOptions', + './PointCloudEyeDomeLighting', './SceneMode', './ShadowMode', './TileBoundingRegion', @@ -71,6 +72,7 @@ define([ Cesium3DTileStyleEngine, LabelCollection, PointAttenuationOptions, + PointCloudEyeDomeLighting, SceneMode, ShadowMode, TileBoundingRegion, @@ -326,6 +328,8 @@ define([ */ this.pointAttenuationOptions = new PointAttenuationOptions(options.pointAttenuationOptions); + this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting(); + /** * The event fired to indicate progress of loading new tiles. This event is fired when a new tile * is requested, when a requested tile is finished downloading, and when a downloaded tile has been @@ -1476,6 +1480,7 @@ define([ } } var lengthAfterUpdate = commandList.length; + var addedCommandsLength = lengthAfterUpdate - lengthBeforeUpdate; tileset._backfaceCommands.trim(); @@ -1505,7 +1510,6 @@ define([ */ var backfaceCommands = tileset._backfaceCommands.values; - var addedCommandsLength = (lengthAfterUpdate - lengthBeforeUpdate); var backfaceCommandsLength = backfaceCommands.length; commandList.length += backfaceCommandsLength; @@ -1524,6 +1528,13 @@ define([ // Number of commands added by each update above statistics.numberOfCommands = (commandList.length - numberOfInitialCommands); + // Only run EDL if simple attenuation is on + if (tileset.pointAttenuationOptions.geometricErrorAttenuation && + tileset.pointAttenuationOptions.eyeDomeLighting && + (addedCommandsLength > 0)) { + tileset._pointCloudEyeDomeLighting.update(frameState, numberOfInitialCommands, tileset); + } + if (tileset.debugShowGeometricError || tileset.debugShowRenderingStatistics || tileset.debugShowMemoryUsage || tileset.debugShowUrl) { if (!defined(tileset._tileDebugLabels)) { tileset._tileDebugLabels = new LabelCollection(); diff --git a/Source/Scene/PointAttenuationOptions.js b/Source/Scene/PointAttenuationOptions.js index e662dfad4bd3..f5be3300a796 100644 --- a/Source/Scene/PointAttenuationOptions.js +++ b/Source/Scene/PointAttenuationOptions.js @@ -14,6 +14,9 @@ define([ * {Number} [options.geometricErrorScale=1.0] Scale to be applied to each tile's geometric error. * {Number} [options.maximumAttenuation] Maximum attenuation in pixels. Defaults to the Cesium3DTileset's maximumScreenSpaceError. * {Number} [options.baseResolution] Average base resolution for the dataset in meters. Substitute for Geometric Error when not available. + * {Boolean} [options.eyeDomeLighting=false] When true, use eye dome lighting when drawing with point attenuation. + * {Number} [options.eyeDomeLightingStrength=1.0] Increasing this value increases contrast on slopes and edges. + * {Number} [options.eyeDomeLightingRadius=1.0] Increase the thickness of contours from eye dome lighting. * @constructor */ function PointAttenuationOptions(options) { @@ -44,6 +47,24 @@ define([ * @type {Number} */ this.baseResolution = pointAttenuationOptions.baseResolution; + + /** + * Use eye dome lighting when drawing with point attenuation + * @type {Boolean} + */ + this.eyeDomeLighting = defaultValue(pointAttenuationOptions.eyeDomeLighting, false); + + /** + * Eye dome lighting strength (apparent contrast) + * @type {Number} + */ + this.eyeDomeLightingStrength = defaultValue(pointAttenuationOptions.eyeDomeLightingStrength, 1.0); + + /** + * Thickness of contours from eye dome lighting + * @type {Number} + */ + this.eyeDomeLightingRadius = defaultValue(pointAttenuationOptions.eyeDomeLightingRadius, 1.0); } return PointAttenuationOptions; diff --git a/Source/Scene/PointCloudEyeDomeLighting.js b/Source/Scene/PointCloudEyeDomeLighting.js new file mode 100644 index 000000000000..4b9677eadef2 --- /dev/null +++ b/Source/Scene/PointCloudEyeDomeLighting.js @@ -0,0 +1,395 @@ +define([ + '../Core/Cartesian2', + '../Core/Math', + '../Core/clone', + '../Core/Color', + '../Core/combine', + '../Core/ComponentDatatype', + '../Core/defined', + '../Core/destroyObject', + '../Core/FeatureDetection', + '../Core/Geometry', + '../Core/GeometryAttribute', + '../Core/PixelFormat', + '../Core/PrimitiveType', + '../Renderer/BufferUsage', + '../Renderer/ClearCommand', + '../Renderer/DrawCommand', + '../Renderer/Framebuffer', + '../Renderer/Pass', + '../Renderer/PixelDatatype', + '../Renderer/RenderState', + '../Renderer/Sampler', + '../Renderer/ShaderSource', + '../Renderer/ShaderProgram', + '../Renderer/Texture', + '../Renderer/TextureMagnificationFilter', + '../Renderer/TextureMinificationFilter', + '../Renderer/TextureWrap', + '../Renderer/VertexArray', + '../Scene/BlendEquation', + '../Scene/BlendFunction', + '../Scene/BlendingState', + '../Scene/StencilFunction', + '../Scene/StencilOperation', + '../Shaders/PostProcessFilters/PointCloudEyeDomeLighting' + ], function( + Cartesian2, + CesiumMath, + clone, + Color, + combine, + ComponentDatatype, + defined, + destroyObject, + FeatureDetection, + Geometry, + GeometryAttribute, + PixelFormat, + PrimitiveType, + BufferUsage, + ClearCommand, + DrawCommand, + Framebuffer, + Pass, + PixelDatatype, + RenderState, + Sampler, + ShaderSource, + ShaderProgram, + Texture, + TextureMagnificationFilter, + TextureMinificationFilter, + TextureWrap, + VertexArray, + BlendEquation, + BlendFunction, + BlendingState, + StencilFunction, + StencilOperation, + PointCloudEyeDomeLightingShader + ) { + 'use strict'; + + /** + * @private + */ + function PointCloudEyeDomeLighting() { + this._framebuffers = undefined; + this._colorTexture = undefined; // color gbuffer + this._ecAndLogDepthTexture = undefined; // depth gbuffer + this._depthTexture = undefined; // needed to write depth so camera based on depth works + this._drawCommands = undefined; + this._clearCommands = undefined; + + this._strength = 1.0; + this._radius = 1.0; + } + + function addConstants(sourceFS, constantName, value) { + var finalSource = sourceFS; + if (typeof(value) === 'boolean') { + if (value !== false) { + finalSource = '#define ' + constantName + '\n' + sourceFS; + } + } else { + finalSource = '#define ' + constantName + ' ' + value + '\n' + sourceFS; + } + return finalSource; + } + + function createSampler() { + return new Sampler({ + wrapS : TextureWrap.CLAMP_TO_EDGE, + wrapT : TextureWrap.CLAMP_TO_EDGE, + minificationFilter : TextureMinificationFilter.NEAREST, + magnificationFilter : TextureMagnificationFilter.NEAREST + }); + } + + function destroyFramebuffers(processor) { + var framebuffers = processor._framebuffers; + if (!defined(framebuffers)) { + return; + } + + processor._colorTexture.destroy(); + processor._ecAndLogDepthTexture.destroy(); + processor._depthTexture.destroy(); + for (var name in framebuffers) { + if (framebuffers.hasOwnProperty(name)) { + framebuffers[name].destroy(); + } + } + + processor._framebuffers = undefined; + processor._colorTexture = undefined; + processor._ecAndLogDepthTexture = undefined; + processor._depthTexture = undefined; + processor._drawCommands = undefined; + processor._clearCommands = undefined; + } + + function createFramebuffers(processor, context) { + var screenWidth = context.drawingBufferWidth; + var screenHeight = context.drawingBufferHeight; + + var colorTexture = new Texture({ + context : context, + width : screenWidth, + height : screenHeight, + pixelFormat : PixelFormat.RGBA, + // Firefox as of 57.02 throws FRAMEBUFFER_UNSUPPORTED 0x8CDD if this doesn't match what's in ecTexture + pixelDatatype : FeatureDetection.isFirefox() ? PixelDatatype.FLOAT : PixelDatatype.UNSIGNED_BYTE, + sampler : createSampler() + }); + + var ecTexture = new Texture({ + context : context, + width : screenWidth, + height : screenHeight, + pixelFormat : PixelFormat.RGBA, + pixelDatatype : PixelDatatype.FLOAT, + sampler : createSampler() + }); + + var depthTexture = new Texture({ + context : context, + width : screenWidth, + height : screenHeight, + pixelFormat : PixelFormat.DEPTH_COMPONENT, + pixelDatatype : PixelDatatype.UNSIGNED_INT, + sampler : createSampler() + }); + + processor._framebuffers = { + prior : new Framebuffer({ + context : context, + colorTextures : [ + colorTexture, + ecTexture + ], + depthTexture : depthTexture, + destroyAttachments : false + }) + }; + processor._colorTexture = colorTexture; + processor._ecAndLogDepthTexture = ecTexture; + processor._depthTexture = depthTexture; + } + + var edlStrengthAndRadiusScratch = new Cartesian2(); + + function createCommands(processor, context) { + processor._drawCommands = {}; + + var blendFS = addConstants(PointCloudEyeDomeLightingShader, 'epsilon8', CesiumMath.EPSILON8); + + var blendUniformMap = { + u_pointCloud_colorTexture : function() { + return processor._colorTexture; + }, + u_pointCloud_ecAndLogDepthTexture : function() { + return processor._ecAndLogDepthTexture; + }, + u_edlStrengthAndDistance : function() { + edlStrengthAndRadiusScratch.x = processor._strength; + edlStrengthAndRadiusScratch.y = processor._radius; + return edlStrengthAndRadiusScratch; + } + }; + + var blendRenderState = RenderState.fromCache({ + blending : BlendingState.ALPHA_BLEND, + depthMask : true, + depthTest : { + enabled : true + } + }); + + var blendCommand = context.createViewportQuadCommand(blendFS, { + uniformMap : blendUniformMap, + renderState : blendRenderState, + pass : Pass.CESIUM_3D_TILE, + owner : processor + }); + + // set up clear commands for all frame buffers + var framebuffers = processor._framebuffers; + var clearCommands = {}; + for (var name in framebuffers) { + if (framebuffers.hasOwnProperty(name)) { + clearCommands[name] = new ClearCommand({ + framebuffer : framebuffers[name], + color : new Color(0.0, 0.0, 0.0, 0.0), + depth : 1.0, + renderState : RenderState.fromCache(), + pass : Pass.CESIUM_3D_TILE, + owner : processor + }); + } + } + + processor._drawCommands.blendCommand = blendCommand; + processor._clearCommands = clearCommands; + } + + function createResources(processor, context, dirty) { + var screenWidth = context.drawingBufferWidth; + var screenHeight = context.drawingBufferHeight; + var colorTexture = processor._colorTexture; + var nowDirty = false; + var resized = defined(colorTexture) && + ((colorTexture.width !== screenWidth) || + (colorTexture.height !== screenHeight)); + + if (!defined(colorTexture) || resized || dirty) { + destroyFramebuffers(processor); + createFramebuffers(processor, context); + createCommands(processor, context); + nowDirty = true; + } + return nowDirty; + } + + function processingSupported(context) { + return context.floatingPointTexture && context.drawBuffers && context.fragmentDepth; + } + + function getECShaderProgram(context, shaderProgram) { + var shader = context.shaderCache.getDerivedShaderProgram(shaderProgram, 'EC'); + if (!defined(shader)) { + var attributeLocations = shaderProgram._attributeLocations; + + var vs = shaderProgram.vertexShaderSource.clone(); + var fs = shaderProgram.fragmentShaderSource.clone(); + + vs.sources = vs.sources.map(function(source) { + source = ShaderSource.replaceMain(source, 'czm_point_cloud_post_process_main'); + return source; + }); + + fs.sources = fs.sources.map(function(source) { + source = ShaderSource.replaceMain(source, 'czm_point_cloud_post_process_main'); + source = source.replace(/gl_FragColor/g, 'gl_FragData[0]'); + return source; + }); + + vs.sources.push( + 'varying vec3 v_positionECPS; \n' + + 'void main() \n' + + '{ \n' + + ' czm_point_cloud_post_process_main(); \n' + + ' v_positionECPS = (czm_inverseProjection * gl_Position).xyz; \n' + + '}'); + fs.sources.unshift('#extension GL_EXT_draw_buffers : enable \n'); + fs.sources.push( + 'varying vec3 v_positionECPS; \n' + + 'void main() \n' + + '{ \n' + + ' czm_point_cloud_post_process_main(); \n' + + // Write log base 2 depth to alpha for EDL + ' gl_FragData[1] = vec4(v_positionECPS, log2(-v_positionECPS.z)); \n' + + '}'); + + shader = context.shaderCache.createDerivedShaderProgram(shaderProgram, 'EC', { + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + } + + return shader; + } + + PointCloudEyeDomeLighting.prototype.update = function(frameState, commandStart, tileset) { + var passes = frameState.passes; + var isPick = (passes.pick && !passes.render); + if (!processingSupported(frameState.context) || isPick) { + return; + } + + this._strength = tileset.pointAttenuationOptions.eyeDomeLightingStrength; + this._radius = tileset.pointAttenuationOptions.eyeDomeLightingRadius; + + var dirty = false; + dirty |= createResources(this, frameState.context, dirty); + + // Hijack existing point commands to render into an offscreen FBO. + var i; + var commandList = frameState.commandList; + var commandEnd = commandList.length; + + for (i = commandStart; i < commandEnd; ++i) { + var command = commandList[i]; + if (command.primitiveType !== PrimitiveType.POINTS) { + continue; + } + var derivedCommand = command.derivedCommands.pointCloudProcessor; + if (!defined(derivedCommand) || command.dirty || dirty || + derivedCommand.framebuffer !== this._framebuffers.prior) { // Prevent crash when tiles out-of-view come in-view during context size change + derivedCommand = DrawCommand.shallowClone(command); + command.derivedCommands.pointCloudProcessor = derivedCommand; + + derivedCommand.framebuffer = this._framebuffers.prior; + derivedCommand.shaderProgram = getECShaderProgram(frameState.context, command.shaderProgram); + derivedCommand.castShadows = false; + derivedCommand.receiveShadows = false; + + var derivedCommandRenderState = clone(derivedCommand.renderState); + derivedCommand.renderState = RenderState.fromCache( + derivedCommandRenderState + ); + + derivedCommand.pass = Pass.CESIUM_3D_TILE; // Overrides translucent commands + } + + commandList[i] = derivedCommand; + } + + var clearCommands = this._clearCommands; + var blendCommand = this._drawCommands.blendCommand; + + // Blend EDL into the main FBO + commandList.push(blendCommand); + commandList.push(clearCommands.prior); + }; + + /** + * Returns true if this object was destroyed; otherwise, false. + *

+ * If this object was destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. + * + * @returns {Boolean} true if this object was destroyed; otherwise, false. + * + * @see PointCloudEyeDomeLighting#destroy + */ + PointCloudEyeDomeLighting.prototype.isDestroyed = function() { + return false; + }; + + /** + * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic + * release of WebGL resources, instead of relying on the garbage collector to destroy this object. + *

+ * Once an object is destroyed, it should not be used; calling any function other than + * isDestroyed will result in a {@link DeveloperError} exception. Therefore, + * assign the return value (undefined) to the object as done in the example. + * + * @returns {undefined} + * + * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. + * + * @example + * processor = processor && processor.destroy(); + * + * @see PointCloudEyeDomeLighting#isDestroyed + */ + PointCloudEyeDomeLighting.prototype.destroy = function() { + destroyFramebuffers(this); + return destroyObject(this); + }; + + return PointCloudEyeDomeLighting; +}); diff --git a/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl b/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl new file mode 100644 index 000000000000..06d85114f9e5 --- /dev/null +++ b/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl @@ -0,0 +1,47 @@ +#extension GL_EXT_frag_depth : enable + +uniform sampler2D u_pointCloud_colorTexture; +uniform sampler2D u_pointCloud_ecAndLogDepthTexture; +uniform vec2 u_edlStrengthAndDistance; +varying vec2 v_textureCoordinates; + +vec2 neighborContribution(float log2Depth, vec2 padding) +{ + vec2 depthAndLog2Depth = texture2D(u_pointCloud_ecAndLogDepthTexture, v_textureCoordinates + padding).zw; + if (depthAndLog2Depth.x == 0.0) // 0.0 is the clear value for the gbuffer + { + return vec2(0.0, 0.0); // throw out this sample + } else { + return vec2(max(0.0, log2Depth - depthAndLog2Depth.y), 1.0); + } +} + +void main() +{ + vec4 ecAlphaDepth = texture2D(u_pointCloud_ecAndLogDepthTexture, v_textureCoordinates); + if (length(ecAlphaDepth.xyz) < epsilon8) + { + discard; + } + else + { + vec4 color = texture2D(u_pointCloud_colorTexture, v_textureCoordinates); + + // sample from neighbors up, down, left, right + float padx = (1.0 / czm_viewport.z) * u_edlStrengthAndDistance.y; + float pady = (1.0 / czm_viewport.w) * u_edlStrengthAndDistance.y; + + vec2 responseAndCount = vec2(0.0, 0.0); + + responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(0, pady)); + responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(padx, 0)); + responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(0, -pady)); + responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(-padx, 0)); + + float response = responseAndCount.x / responseAndCount.y; + float shade = exp(-response * 300.0 * u_edlStrengthAndDistance.x); + color.rgb *= shade; + gl_FragColor = vec4(color); + gl_FragDepthEXT = czm_eyeToWindowCoordinates(vec4(ecAlphaDepth.xyz, 1.0)).z; + } +} diff --git a/Specs/Scene/PointAttenuationOptionsSpec.js b/Specs/Scene/PointAttenuationOptionsSpec.js index f34494842f5f..9b128f8c09c6 100644 --- a/Specs/Scene/PointAttenuationOptionsSpec.js +++ b/Specs/Scene/PointAttenuationOptionsSpec.js @@ -11,16 +11,24 @@ defineSuite([ expect(pointAttenuationOptions.geometricErrorScale).toEqual(1.0); expect(pointAttenuationOptions.maximumAttenuation).not.toBeDefined(); expect(pointAttenuationOptions.baseResolution).not.toBeDefined(); + expect(pointAttenuationOptions.eyeDomeLighting).toEqual(false); + expect(pointAttenuationOptions.eyeDomeLightingStrength).toEqual(1.0); + expect(pointAttenuationOptions.eyeDomeLightingRadius).toEqual(1.0); var options = { geometricErrorScale : 2.0, maximumAttenuation : 16, - baseResolution : 0.1 + baseResolution : 0.1, + eyeDomeLightingStrength : 0.1, + eyeDomeLightingRadius : 2.0 }; pointAttenuationOptions = new PointAttenuationOptions(options); expect(pointAttenuationOptions.geometricErrorAttenuation).toEqual(false); expect(pointAttenuationOptions.geometricErrorScale).toEqual(options.geometricErrorScale); expect(pointAttenuationOptions.maximumAttenuation).toEqual(options.maximumAttenuation); expect(pointAttenuationOptions.baseResolution).toEqual(options.baseResolution); + expect(pointAttenuationOptions.eyeDomeLighting).toEqual(false); + expect(pointAttenuationOptions.eyeDomeLightingStrength).toEqual(options.eyeDomeLightingStrength); + expect(pointAttenuationOptions.eyeDomeLightingRadius).toEqual(options.eyeDomeLightingRadius); }); }); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index 6975f39999a7..6ac6618b0719 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -453,19 +453,6 @@ defineSuite([ return count; } - function rgbaToAscii(rgba) { - console.log(countRenderedPixels(rgba)); - for (var i = 0; i < 10; i++) { - var scanline = rgba.slice(i * 40, i * 40 + 40); - var lineString = i + ' '; - for (var j = 0; j < 40; j += 4) { - var isDrawn = scanline[j] !== 0; - lineString += isDrawn ? '[ ]' : ' _ '; - } - console.log(lineString); - } - } - it('attenuates points based on geometric error', function() { var scene = createScene({ canvas : createCanvas(10, 10) @@ -540,7 +527,6 @@ defineSuite([ tileset.pointAttenuationOptions.baseResolution = undefined; tileset.maximumScreenSpaceError = 16; expect(scene).toRenderAndCall(function(rgba) { - rgbaToAscii(rgba); expect(countRenderedPixels(rgba)).toBeGreaterThan(noAttenuationPixelCount); }); diff --git a/Specs/Scene/PointCloudEyeDomeLightingSpec.js b/Specs/Scene/PointCloudEyeDomeLightingSpec.js new file mode 100644 index 000000000000..8ea9518bcb06 --- /dev/null +++ b/Specs/Scene/PointCloudEyeDomeLightingSpec.js @@ -0,0 +1,89 @@ +defineSuite([ + 'Core/Cartesian3', + 'Core/Color', + 'Core/defined', + 'Core/HeadingPitchRange', + 'Core/HeadingPitchRoll', + 'Core/Math', + 'Core/Transforms', + 'Core/PerspectiveFrustum', + 'Scene/PointCloud3DTileContent', + 'Specs/Cesium3DTilesTester', + 'Specs/createScene' +], 'Scene/PointCloudEyeDomeLighting', function( + Cartesian3, + Color, + defined, + HeadingPitchRange, + HeadingPitchRoll, + CesiumMath, + Transforms, + PerspectiveFrustum, + PointCloud3DTileContent, + Cesium3DTilesTester, + createScene) { + 'use strict'; + + var scene; + var centerLongitude = -1.31968; + var centerLatitude = 0.698874; + + var pointCloudNoColorUrl = './Data/Cesium3DTiles/PointCloud/PointCloudNoColor'; + + function setCamera(longitude, latitude) { + // Point the camera to the center of the tile + var center = Cartesian3.fromRadians(longitude, latitude, 5.0); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); + } + + beforeAll(function() { + scene = createScene(); + scene.frameState.passes.render = true; + }); + + afterAll(function() { + scene.destroyForSpecs(); + }); + + beforeEach(function() { + var camera = scene.camera; + camera.frustum = new PerspectiveFrustum(); + camera.frustum.aspectRatio = scene.drawingBufferWidth / scene.drawingBufferHeight; + camera.frustum.fov = CesiumMath.toRadians(60.0); + + setCamera(centerLongitude, centerLatitude); + }); + + afterEach(function() { + scene.primitives.removeAll(); + }); + + it('Adds a clear command and a post-processing draw call', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + tileset.pointAttenuationOptions.eyeDomeLighting = true; + + scene.renderForSpecs(); + var originalLength = scene.frameState.commandList.length; + + tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + scene.renderForSpecs(); + var newLength = scene.frameState.commandList.length; + expect(newLength).toEqual(originalLength + 2); + }); + }); + + it('Does not change commands for pick calls', function() { + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + tileset.pointAttenuationOptions.eyeDomeLighting = true; + + scene.pickForSpecs(); + var originalLength = scene.frameState.commandList.length; + + tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + scene.pickForSpecs(); + var newLength = scene.frameState.commandList.length; + expect(newLength).toEqual(originalLength); + }); + }); + +}, 'WebGL'); From dccefebeeb7f2f1c951312010a4e74bce8eab1ec Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Wed, 20 Dec 2017 21:09:42 -0500 Subject: [PATCH 03/13] updated changes.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 6228a839fc31..14f42ba54da7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,7 @@ Change Log * Fixed globe materials when `Globe.enableLighting` was `false`. [#6042](https://github.com/AnalyticalGraphicsInc/cesium/issues/6042) * Fixed shader compilation failure on pick when globe materials were enabled. [#6039](https://github.com/AnalyticalGraphicsInc/cesium/issues/6039) * Fixed crash when `invertClassification` was enabled, the invert color had an alpha less than `1.0`, and the window was resized. [#6046](https://github.com/AnalyticalGraphicsInc/cesium/issues/6046) +* Added geometric-error-based point cloud attenuation and eye dome lighting for point clouds using additive refinement. ### 1.40 - 2017-12-01 From aad2105966e756bc86ffff052531cd84af3415e1 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Thu, 21 Dec 2017 09:47:53 -0500 Subject: [PATCH 04/13] add webgl extension support check to spec and requirements blurb to doc --- Source/Scene/PointCloudEyeDomeLighting.js | 5 +++++ Specs/Scene/PointCloudEyeDomeLightingSpec.js | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/Source/Scene/PointCloudEyeDomeLighting.js b/Source/Scene/PointCloudEyeDomeLighting.js index 4b9677eadef2..0f6c8ce3d20c 100644 --- a/Source/Scene/PointCloudEyeDomeLighting.js +++ b/Source/Scene/PointCloudEyeDomeLighting.js @@ -72,6 +72,9 @@ define([ 'use strict'; /** + * Eye dome lighting. + * Requires support for EXT_frag_depth, OES_texture_float, and WEBGL_draw_buffers extensions in WebGL 1.0. + * * @private */ function PointCloudEyeDomeLighting() { @@ -256,6 +259,8 @@ define([ return context.floatingPointTexture && context.drawBuffers && context.fragmentDepth; } + PointCloudEyeDomeLighting.processingSupported = processingSupported; + function getECShaderProgram(context, shaderProgram) { var shader = context.shaderCache.getDerivedShaderProgram(shaderProgram, 'EC'); if (!defined(shader)) { diff --git a/Specs/Scene/PointCloudEyeDomeLightingSpec.js b/Specs/Scene/PointCloudEyeDomeLightingSpec.js index 8ea9518bcb06..526c4e3bdc31 100644 --- a/Specs/Scene/PointCloudEyeDomeLightingSpec.js +++ b/Specs/Scene/PointCloudEyeDomeLightingSpec.js @@ -8,6 +8,7 @@ defineSuite([ 'Core/Transforms', 'Core/PerspectiveFrustum', 'Scene/PointCloud3DTileContent', + 'Scene/PointCloudEyeDomeLighting', 'Specs/Cesium3DTilesTester', 'Specs/createScene' ], 'Scene/PointCloudEyeDomeLighting', function( @@ -20,6 +21,7 @@ defineSuite([ Transforms, PerspectiveFrustum, PointCloud3DTileContent, + PointCloudEyeDomeLighting, Cesium3DTilesTester, createScene) { 'use strict'; @@ -60,6 +62,10 @@ defineSuite([ it('Adds a clear command and a post-processing draw call', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + if (!PointCloudEyeDomeLighting.processingSupported(scene.frameState.context)) { + return; + } + tileset.pointAttenuationOptions.eyeDomeLighting = true; scene.renderForSpecs(); From 9659688df70062850e44b8d40828c7b03493a0e3 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Sat, 30 Dec 2017 09:29:07 -0500 Subject: [PATCH 05/13] PR comments --- ...html => 3D Tiles Point Cloud Shading.html} | 36 +-- ...g.jpg => 3D Tiles Point Cloud Shading.jpg} | Bin Source/Core/BoundingSphere.js | 11 + Source/Scene/Cesium3DTileset.js | 16 +- Source/Scene/PointCloud3DTileContent.js | 24 +- Source/Scene/PointCloudEyeDomeLighting.js | 24 +- ...tAttenuationOptions.js => PointShading.js} | 28 ++- .../PointCloudEyeDomeLighting.glsl | 24 +- Specs/Core/BoundingSphereSpec.js | 7 + Specs/Scene/PointAttenuationOptionsSpec.js | 34 --- Specs/Scene/PointCloud3DTileContentSpec.js | 206 ++++++++++++------ Specs/Scene/PointCloudEyeDomeLightingSpec.js | 12 +- Specs/Scene/PointShadingSpec.js | 34 +++ Specs/addDefaultMatchers.js | 49 +++++ 14 files changed, 330 insertions(+), 175 deletions(-) rename Apps/Sandcastle/gallery/{3D Tiles Point Cloud Attenuation and Eye Dome Lighting.html => 3D Tiles Point Cloud Shading.html} (81%) rename Apps/Sandcastle/gallery/{3D Tiles Point Cloud Attenuation and Eye Dome Lighting.jpg => 3D Tiles Point Cloud Shading.jpg} (100%) rename Source/Scene/{PointAttenuationOptions.js => PointShading.js} (68%) delete mode 100644 Specs/Scene/PointAttenuationOptionsSpec.js create mode 100644 Specs/Scene/PointShadingSpec.js diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html similarity index 81% rename from Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.html rename to Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html index f8db578d6bc4..3ef03272294d 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html @@ -101,16 +101,24 @@ url : url })); +// Set the initial camera view to look at Mt. St. Helens +var initialPosition = Cesium.Cartesian3.fromRadians(-2.1344873183780484, 0.8071380277370774, 5743.394497982162); +var initialOrientation = new Cesium.HeadingPitchRoll.fromDegrees(112.99596671210358, -21.34390550872461, 0.0716951918898415); +viewer.scene.camera.setView({ + destination: initialPosition, + orientation: initialOrientation, + endTransform: Cesium.Matrix4.IDENTITY +}); + tileset.readyPromise.then(function() { - var boundingSphere = tileset.boundingSphere; - viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.0, -1.0, boundingSphere.radius)); - viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY); - tileset.maximumScreenSpaceError = 16.0; + tileset.maximumScreenSpaceError = 8.0; + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.eyeDomeLighting = true; }); // The viewModel tracks the state of our mini application. var viewModel = { - maximumScreenSpaceError : 16.0, + maximumScreenSpaceError : 8.0, geometricErrorScale : 1.0, maximumAttenuation : 0, // Equivalent to undefined baseResolution : 0, // Equivalent to undefined @@ -138,40 +146,40 @@ Cesium.knockout.getObservable(viewModel, 'geometricErrorScale').subscribe( function(newValue) { - tileset.pointAttenuationOptions.geometricErrorScale = parseFloat(newValue); + tileset.pointShading.geometricErrorScale = parseFloat(newValue); } ); Cesium.knockout.getObservable(viewModel, 'maximumAttenuation').subscribe( function(newValue) { - tileset.pointAttenuationOptions.maximumAttenuation = checkZero(newValue); + tileset.pointShading.maximumAttenuation = checkZero(newValue); } ); Cesium.knockout.getObservable(viewModel, 'baseResolution').subscribe( function(newValue) { - tileset.pointAttenuationOptions.baseResolution = checkZero(newValue); + tileset.pointShading.baseResolution = checkZero(newValue); } ); Cesium.knockout.getObservable(viewModel, 'eyeDomeLightingStrength').subscribe( function(newValue) { - tileset.pointAttenuationOptions.eyeDomeLightingStrength = parseFloat(newValue); + tileset.pointShading.eyeDomeLightingStrength = parseFloat(newValue); } ); Cesium.knockout.getObservable(viewModel, 'eyeDomeLightingRadius').subscribe( function(newValue) { - tileset.pointAttenuationOptions.eyeDomeLightingRadius = parseFloat(newValue); + tileset.pointShading.eyeDomeLightingRadius = parseFloat(newValue); } ); -Sandcastle.addToggleButton('Enable Attenuation', false, function(checked) { - tileset.pointAttenuationOptions.geometricErrorAttenuation = checked; +Sandcastle.addToggleButton('Enable Attenuation', true, function(checked) { + tileset.pointShading.geometricErrorAttenuation = checked; }); -Sandcastle.addToggleButton('Enable Eye Dome Lighting', false, function(checked) { - tileset.pointAttenuationOptions.eyeDomeLighting = checked; +Sandcastle.addToggleButton('Enable Eye Dome Lighting', true, function(checked) { + tileset.pointShading.eyeDomeLighting = checked; }); //Sandcastle_End Sandcastle.finishedLoading(); diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.jpg b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.jpg similarity index 100% rename from Apps/Sandcastle/gallery/3D Tiles Point Cloud Attenuation and Eye Dome Lighting.jpg rename to Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.jpg diff --git a/Source/Core/BoundingSphere.js b/Source/Core/BoundingSphere.js index 7372c40134be..fe3e3adc46c1 100644 --- a/Source/Core/BoundingSphere.js +++ b/Source/Core/BoundingSphere.js @@ -1,6 +1,7 @@ define([ './Cartesian3', './Cartographic', + './Math', './Check', './defaultValue', './defined', @@ -14,6 +15,7 @@ define([ ], function( Cartesian3, Cartographic, + CesiumMath, Check, defaultValue, defined, @@ -1302,5 +1304,14 @@ define([ return BoundingSphere.clone(this, result); }; + /** + * Computes the radius of the BoundingSphere. + * @returns {Number} The radius of the BoundingSphere. + */ + BoundingSphere.prototype.volume = function() { + var radius = this.radius; + return (4.0 / 3.0) * CesiumMath.PI * radius * radius * radius; + }; + return BoundingSphere; }); diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 0cc6a96433f3..2c62cf568666 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -31,7 +31,7 @@ define([ './Cesium3DTilesetTraversal', './Cesium3DTileStyleEngine', './LabelCollection', - './PointAttenuationOptions', + './PointShading', './PointCloudEyeDomeLighting', './SceneMode', './ShadowMode', @@ -71,7 +71,7 @@ define([ Cesium3DTilesetTraversal, Cesium3DTileStyleEngine, LabelCollection, - PointAttenuationOptions, + PointShading, PointCloudEyeDomeLighting, SceneMode, ShadowMode, @@ -116,7 +116,7 @@ define([ * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. - * @param {Object} [options.pointAttenuationOptions] Options for constructing a PointAttenuationOptions object to control point attenuation based on geometric error. + * @param {Object} [options.pointShading] Options for constructing a PointShading object to control point attenuation based on geometric error and lighting. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. See {@link https://github.com/AnalyticalGraphicsInc/3d-tiles#spec-status} * @@ -323,10 +323,10 @@ define([ this.colorBlendAmount = 0.5; /** - * Options for controlling point attenuation based on geometric error. - * @type {PointAttenuationOptions} + * Options for controlling point size based on geometric error and eye dome lighting. + * @type {PointShading} */ - this.pointAttenuationOptions = new PointAttenuationOptions(options.pointAttenuationOptions); + this.pointShading = new PointShading(options.pointShading); this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting(); @@ -1529,8 +1529,8 @@ define([ statistics.numberOfCommands = (commandList.length - numberOfInitialCommands); // Only run EDL if simple attenuation is on - if (tileset.pointAttenuationOptions.geometricErrorAttenuation && - tileset.pointAttenuationOptions.eyeDomeLighting && + if (tileset.pointShading.geometricErrorAttenuation && + tileset.pointShading.eyeDomeLighting && (addedCommandsLength > 0)) { tileset._pointCloudEyeDomeLighting.update(frameState, numberOfInitialCommands, tileset); } diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index f31eb2dbf065..7d4aae195032 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -2,7 +2,6 @@ define([ '../Core/Cartesian2', '../Core/Cartesian3', '../Core/Cartesian4', - '../Core/Math', '../Core/Color', '../Core/combine', '../Core/ComponentDatatype', @@ -40,7 +39,6 @@ define([ Cartesian2, Cartesian3, Cartesian4, - CesiumMath, Color, combine, ComponentDatatype, @@ -461,10 +459,12 @@ define([ content._hasNormals = defined(normals); content._hasBatchIds = defined(batchIds); - // Compute an approximation for base resolution in case it isn't given - // Assume a uniform distribution of points in the bounding sphere around the tile. - var radius = content._tile.contentBoundingVolume.boundingSphere.radius; - var sphereVolume = (4.0 / 3.0) * CesiumMath.PI * radius * radius * radius; + // Compute an approximation for base resolution in case it isn't given. + // Assume a uniform distribution of points in cubical cells throughout the + // bounding sphere around the tile. + // Typical use case is leaves, where lower estimates of interpoint distance might + // lead to underattenuation. + var sphereVolume = content._tile.contentBoundingVolume.boundingSphere.volume(); content._baseResolutionApproximation = Math.cbrt(sphereVolume / pointsLength); } @@ -1313,12 +1313,12 @@ define([ } // Update attenuation - var pointAttenuationOptions = this._tileset.pointAttenuationOptions; - if (defined(pointAttenuationOptions)) { - this.geometricErrorAttenuation = pointAttenuationOptions.geometricErrorAttenuation; - this._geometricErrorScale = pointAttenuationOptions.geometricErrorScale; - this._maximumAttenuation = defined(pointAttenuationOptions.maximumAttenuation) ? pointAttenuationOptions.maximumAttenuation : tileset.maximumScreenSpaceError; - this._baseResolution = pointAttenuationOptions.baseResolution; + var pointShading = this._tileset.pointShading; + if (defined(pointShading)) { + this.geometricErrorAttenuation = pointShading.geometricErrorAttenuation; + this._geometricErrorScale = pointShading.geometricErrorScale; + this._maximumAttenuation = defined(pointShading.maximumAttenuation) ? pointShading.maximumAttenuation : tileset.maximumScreenSpaceError; + this._baseResolution = pointShading.baseResolution; if (this.geometricErrorAttenuation !== this._geometricErrorAttenuation) { this._geometricErrorAttenuation = this.geometricErrorAttenuation; createShaders(this, frameState, tileset.style); diff --git a/Source/Scene/PointCloudEyeDomeLighting.js b/Source/Scene/PointCloudEyeDomeLighting.js index 0f6c8ce3d20c..c96649dc4124 100644 --- a/Source/Scene/PointCloudEyeDomeLighting.js +++ b/Source/Scene/PointCloudEyeDomeLighting.js @@ -1,5 +1,5 @@ define([ - '../Core/Cartesian2', + '../Core/Cartesian3', '../Core/Math', '../Core/clone', '../Core/Color', @@ -34,7 +34,7 @@ define([ '../Scene/StencilOperation', '../Shaders/PostProcessFilters/PointCloudEyeDomeLighting' ], function( - Cartesian2, + Cartesian3, CesiumMath, clone, Color, @@ -181,7 +181,7 @@ define([ processor._depthTexture = depthTexture; } - var edlStrengthAndRadiusScratch = new Cartesian2(); + var distancesAndEdlStrengthScratch = new Cartesian3(); function createCommands(processor, context) { processor._drawCommands = {}; @@ -195,10 +195,11 @@ define([ u_pointCloud_ecAndLogDepthTexture : function() { return processor._ecAndLogDepthTexture; }, - u_edlStrengthAndDistance : function() { - edlStrengthAndRadiusScratch.x = processor._strength; - edlStrengthAndRadiusScratch.y = processor._radius; - return edlStrengthAndRadiusScratch; + u_distancesAndEdlStrength : function() { + distancesAndEdlStrengthScratch.x = processor._radius / context.drawingBufferWidth; + distancesAndEdlStrengthScratch.y = processor._radius / context.drawingBufferHeight; + distancesAndEdlStrengthScratch.z = processor._strength; + return distancesAndEdlStrengthScratch; } }; @@ -314,11 +315,10 @@ define([ return; } - this._strength = tileset.pointAttenuationOptions.eyeDomeLightingStrength; - this._radius = tileset.pointAttenuationOptions.eyeDomeLightingRadius; + this._strength = tileset.pointShading.eyeDomeLightingStrength; + this._radius = tileset.pointShading.eyeDomeLightingRadius; - var dirty = false; - dirty |= createResources(this, frameState.context, dirty); + var dirty = createResources(this, frameState.context, false); // Hijack existing point commands to render into an offscreen FBO. var i; @@ -332,7 +332,7 @@ define([ } var derivedCommand = command.derivedCommands.pointCloudProcessor; if (!defined(derivedCommand) || command.dirty || dirty || - derivedCommand.framebuffer !== this._framebuffers.prior) { // Prevent crash when tiles out-of-view come in-view during context size change + (derivedCommand.framebuffer !== this._framebuffers.prior)) { // Prevent crash when tiles out-of-view come in-view during context size change derivedCommand = DrawCommand.shallowClone(command); command.derivedCommands.pointCloudProcessor = derivedCommand; diff --git a/Source/Scene/PointAttenuationOptions.js b/Source/Scene/PointShading.js similarity index 68% rename from Source/Scene/PointAttenuationOptions.js rename to Source/Scene/PointShading.js index f5be3300a796..4be748b53147 100644 --- a/Source/Scene/PointAttenuationOptions.js +++ b/Source/Scene/PointShading.js @@ -19,26 +19,28 @@ define([ * {Number} [options.eyeDomeLightingRadius=1.0] Increase the thickness of contours from eye dome lighting. * @constructor */ - function PointAttenuationOptions(options) { - var pointAttenuationOptions = defaultValue(options, {}); + function PointShading(options) { + var pointShading = defaultValue(options, {}); /** * Perform point attenuation based on geometric error. * @type {Boolean} + * @default false */ - this.geometricErrorAttenuation = defaultValue(pointAttenuationOptions.geometricErrorAttenuation, false); + this.geometricErrorAttenuation = defaultValue(pointShading.geometricErrorAttenuation, false); /** * Scale to be applied to the geometric error before computing attenuation. * @type {Number} + * @default 1.0 */ - this.geometricErrorScale = defaultValue(pointAttenuationOptions.geometricErrorScale, 1.0); + this.geometricErrorScale = defaultValue(pointShading.geometricErrorScale, 1.0); /** * Maximum point attenuation in pixels. If undefined, the Cesium3DTileset's maximumScreenSpaceError will be used. * @type {Number} */ - this.maximumAttenuation = pointAttenuationOptions.maximumAttenuation; + this.maximumAttenuation = pointShading.maximumAttenuation; /** * Average base resolution for the dataset in meters. @@ -46,26 +48,32 @@ define([ * If undefined, an approximation will be computed for each tile that has geometric error of 0. * @type {Number} */ - this.baseResolution = pointAttenuationOptions.baseResolution; + this.baseResolution = pointShading.baseResolution; /** * Use eye dome lighting when drawing with point attenuation + * Requires support for EXT_frag_depth, OES_texture_float, and WEBGL_draw_buffers extensions in WebGL 1.0, + * otherwise eye dome lighting is ignored. + * * @type {Boolean} + * @default false */ - this.eyeDomeLighting = defaultValue(pointAttenuationOptions.eyeDomeLighting, false); + this.eyeDomeLighting = defaultValue(pointShading.eyeDomeLighting, false); /** * Eye dome lighting strength (apparent contrast) * @type {Number} + * @default 1.0 */ - this.eyeDomeLightingStrength = defaultValue(pointAttenuationOptions.eyeDomeLightingStrength, 1.0); + this.eyeDomeLightingStrength = defaultValue(pointShading.eyeDomeLightingStrength, 1.0); /** * Thickness of contours from eye dome lighting * @type {Number} + * @default 1.0 */ - this.eyeDomeLightingRadius = defaultValue(pointAttenuationOptions.eyeDomeLightingRadius, 1.0); + this.eyeDomeLightingRadius = defaultValue(pointShading.eyeDomeLightingRadius, 1.0); } - return PointAttenuationOptions; + return PointShading; }); diff --git a/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl b/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl index 06d85114f9e5..78b6ee8fdb80 100644 --- a/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl +++ b/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl @@ -2,7 +2,7 @@ uniform sampler2D u_pointCloud_colorTexture; uniform sampler2D u_pointCloud_ecAndLogDepthTexture; -uniform vec2 u_edlStrengthAndDistance; +uniform vec3 u_distancesAndEdlStrength; varying vec2 v_textureCoordinates; vec2 neighborContribution(float log2Depth, vec2 padding) @@ -10,8 +10,10 @@ vec2 neighborContribution(float log2Depth, vec2 padding) vec2 depthAndLog2Depth = texture2D(u_pointCloud_ecAndLogDepthTexture, v_textureCoordinates + padding).zw; if (depthAndLog2Depth.x == 0.0) // 0.0 is the clear value for the gbuffer { - return vec2(0.0, 0.0); // throw out this sample - } else { + return vec2(0.0); // throw out this sample + } + else + { return vec2(max(0.0, log2Depth - depthAndLog2Depth.y), 1.0); } } @@ -28,18 +30,18 @@ void main() vec4 color = texture2D(u_pointCloud_colorTexture, v_textureCoordinates); // sample from neighbors up, down, left, right - float padx = (1.0 / czm_viewport.z) * u_edlStrengthAndDistance.y; - float pady = (1.0 / czm_viewport.w) * u_edlStrengthAndDistance.y; + float distX = u_distancesAndEdlStrength.x; + float distY = u_distancesAndEdlStrength.y; - vec2 responseAndCount = vec2(0.0, 0.0); + vec2 responseAndCount = vec2(0.0); - responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(0, pady)); - responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(padx, 0)); - responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(0, -pady)); - responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(-padx, 0)); + responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(0, distY)); + responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(distX, 0)); + responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(0, -distY)); + responseAndCount += neighborContribution(ecAlphaDepth.a, vec2(-distX, 0)); float response = responseAndCount.x / responseAndCount.y; - float shade = exp(-response * 300.0 * u_edlStrengthAndDistance.x); + float shade = exp(-response * 300.0 * u_distancesAndEdlStrength.z); color.rgb *= shade; gl_FragColor = vec4(color); gl_FragDepthEXT = czm_eyeToWindowCoordinates(vec4(ecAlphaDepth.xyz, 1.0)).z; diff --git a/Specs/Core/BoundingSphereSpec.js b/Specs/Core/BoundingSphereSpec.js index ceca57c35302..1dcc7711dab7 100644 --- a/Specs/Core/BoundingSphereSpec.js +++ b/Specs/Core/BoundingSphereSpec.js @@ -906,5 +906,12 @@ defineSuite([ expectBoundingSphereToContainPoint(boundingSphere, point, projection); }); + it('computes the volume of a BoundingSphere', function() { + var sphere = new BoundingSphere(new Cartesian3(), 1.0); + var computedVolume = sphere.volume(); + var expectedVolume = (4.0 / 3.0) * CesiumMath.PI; + expect(computedVolume).toEqualEpsilon(expectedVolume, CesiumMath.EPSILON6); + }); + createPackableSpecs(BoundingSphere, new BoundingSphere(new Cartesian3(1.0, 2.0, 3.0), 4.0), [1.0, 2.0, 3.0, 4.0]); }); diff --git a/Specs/Scene/PointAttenuationOptionsSpec.js b/Specs/Scene/PointAttenuationOptionsSpec.js deleted file mode 100644 index 9b128f8c09c6..000000000000 --- a/Specs/Scene/PointAttenuationOptionsSpec.js +++ /dev/null @@ -1,34 +0,0 @@ -defineSuite([ - 'Scene/PointAttenuationOptions' - ], function( - PointAttenuationOptions - ) { - 'use strict'; - - it('creates expected instance from raw assignment and construction', function() { - var pointAttenuationOptions = new PointAttenuationOptions(); - expect(pointAttenuationOptions.geometricErrorAttenuation).toEqual(false); - expect(pointAttenuationOptions.geometricErrorScale).toEqual(1.0); - expect(pointAttenuationOptions.maximumAttenuation).not.toBeDefined(); - expect(pointAttenuationOptions.baseResolution).not.toBeDefined(); - expect(pointAttenuationOptions.eyeDomeLighting).toEqual(false); - expect(pointAttenuationOptions.eyeDomeLightingStrength).toEqual(1.0); - expect(pointAttenuationOptions.eyeDomeLightingRadius).toEqual(1.0); - - var options = { - geometricErrorScale : 2.0, - maximumAttenuation : 16, - baseResolution : 0.1, - eyeDomeLightingStrength : 0.1, - eyeDomeLightingRadius : 2.0 - }; - pointAttenuationOptions = new PointAttenuationOptions(options); - expect(pointAttenuationOptions.geometricErrorAttenuation).toEqual(false); - expect(pointAttenuationOptions.geometricErrorScale).toEqual(options.geometricErrorScale); - expect(pointAttenuationOptions.maximumAttenuation).toEqual(options.maximumAttenuation); - expect(pointAttenuationOptions.baseResolution).toEqual(options.baseResolution); - expect(pointAttenuationOptions.eyeDomeLighting).toEqual(false); - expect(pointAttenuationOptions.eyeDomeLightingStrength).toEqual(options.eyeDomeLightingStrength); - expect(pointAttenuationOptions.eyeDomeLightingRadius).toEqual(options.eyeDomeLightingRadius); - }); -}); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index 6ac6618b0719..d7b2a75db550 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -438,21 +438,6 @@ defineSuite([ }); }); - function countRenderedPixels(rgba) { - var pixelCount = rgba.length / 4; - var count = 0; - for (var i = 0; i < pixelCount; i++) { - var index = i * 4; - if (rgba[index] !== 0 || - rgba[index + 1] !== 0 || - rgba[index + 2] !== 0 || - rgba[index + 3] !== 255) { - count++; - } - } - return count; - } - it('attenuates points based on geometric error', function() { var scene = createScene({ canvas : createCanvas(10, 10) @@ -463,72 +448,157 @@ defineSuite([ scene.camera.zoomIn(6); return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + var noAttenuationPixelCount = 16; - var noAttenuationPixelCount = 0; - expect(scene).toRenderAndCall(function(rgba) { - noAttenuationPixelCount = countRenderedPixels(rgba); - }); - - // Activate attenuation - tileset.pointAttenuationOptions.geometricErrorAttenuation = true; - tileset.pointAttenuationOptions.geometricErrorScale = 1.0; - tileset.pointAttenuationOptions.maximumAttenuation = undefined; - tileset.pointAttenuationOptions.baseResolution = undefined; + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.geometricErrorScale = 1.0; + tileset.pointShading.maximumAttenuation = undefined; + tileset.pointShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 16; - expect(scene).toRenderAndCall(function(rgba) { - expect(countRenderedPixels(rgba)).toBeGreaterThan(noAttenuationPixelCount); - }); + expect(scene).toRenderPixelCountGreaterThan(noAttenuationPixelCount); - // Adjust screen space error in tileset to modulate maximumAttenuation - tileset.pointAttenuationOptions.geometricErrorAttenuation = true; - tileset.pointAttenuationOptions.geometricErrorScale = 1.0; - tileset.pointAttenuationOptions.maximumAttenuation = undefined; - tileset.pointAttenuationOptions.baseResolution = undefined; + scene.destroyForSpecs(); + }); + }); + + it('modulates attenuation using the tileset screen space error', function() { + var scene = createScene({ + canvas : createCanvas(10, 10) + }); + var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); + scene.fxaa = false; + scene.camera.zoomIn(6); + + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + var noAttenuationPixelCount = 16; + + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.geometricErrorScale = 1.0; + tileset.pointShading.maximumAttenuation = undefined; + tileset.pointShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 1; - expect(scene).toRenderAndCall(function(rgba) { - expect(countRenderedPixels(rgba)).toEqual(noAttenuationPixelCount); - }); + expect(scene).toRenderPixelCount(noAttenuationPixelCount); - // Adjust maximumAttenuation directly - tileset.pointAttenuationOptions.geometricErrorAttenuation = true; - tileset.pointAttenuationOptions.geometricErrorScale = 1.0; - tileset.pointAttenuationOptions.maximumAttenuation = 1; - tileset.pointAttenuationOptions.baseResolution = undefined; + scene.destroyForSpecs(); + }); + }); + + it('modulates attenuation using the maximumAttenuation parameter', function() { + var scene = createScene({ + canvas : createCanvas(10, 10) + }); + var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); + scene.fxaa = false; + scene.camera.zoomIn(6); + + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + var noAttenuationPixelCount = 16; + + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.geometricErrorScale = 1.0; + tileset.pointShading.maximumAttenuation = 1; + tileset.pointShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 16; - expect(scene).toRenderAndCall(function(rgba) { - expect(countRenderedPixels(rgba)).toEqual(noAttenuationPixelCount); - }); + expect(scene).toRenderPixelCount(noAttenuationPixelCount); - // Adjust baseResolution - pointCloudNoColorUrl is a single tile with GeometricError = 0 - tileset.pointAttenuationOptions.geometricErrorAttenuation = true; - tileset.pointAttenuationOptions.geometricErrorScale = 1.0; - tileset.pointAttenuationOptions.maximumAttenuation = undefined; - tileset.pointAttenuationOptions.baseResolution = CesiumMath.EPSILON20; + scene.destroyForSpecs(); + }); + }); + + it('modulates attenuation using the baseResolution parameter', function() { + var scene = createScene({ + canvas : createCanvas(10, 10) + }); + var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); + scene.fxaa = false; + scene.camera.zoomIn(6); + + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + var noAttenuationPixelCount = 16; + + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.geometricErrorScale = 1.0; + tileset.pointShading.maximumAttenuation = undefined; + tileset.pointShading.baseResolution = CesiumMath.EPSILON20; tileset.maximumScreenSpaceError = 16; - expect(scene).toRenderAndCall(function(rgba) { - expect(countRenderedPixels(rgba)).toEqual(noAttenuationPixelCount); - }); + expect(scene).toRenderPixelCount(noAttenuationPixelCount); + + scene.destroyForSpecs(); + }); + }); + + it('modulates attenuation using the baseResolution parameter', function() { + var scene = createScene({ + canvas : createCanvas(10, 10) + }); + var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); + scene.fxaa = false; + scene.camera.zoomIn(6); + + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + var noAttenuationPixelCount = 16; + + // pointCloudNoColorUrl is a single tile with GeometricError = 0, + // which results in default baseResolution being computed + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.geometricErrorScale = 1.0; + tileset.pointShading.maximumAttenuation = undefined; + tileset.pointShading.baseResolution = CesiumMath.EPSILON20; + tileset.maximumScreenSpaceError = 16; + expect(scene).toRenderPixelCount(noAttenuationPixelCount); + + scene.destroyForSpecs(); + }); + }); + + it('modulates attenuation using the geometricErrorScale parameter', function() { + var scene = createScene({ + canvas : createCanvas(10, 10) + }); + var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); + scene.fxaa = false; + scene.camera.zoomIn(6); - // Adjust geometricErrorScale - tileset.pointAttenuationOptions.geometricErrorAttenuation = true; - tileset.pointAttenuationOptions.geometricErrorScale = 0.0; - tileset.pointAttenuationOptions.maximumAttenuation = undefined; - tileset.pointAttenuationOptions.baseResolution = undefined; + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + var noAttenuationPixelCount = 16; + + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.geometricErrorScale = 0.0; + tileset.pointShading.maximumAttenuation = undefined; + tileset.pointShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 1; - expect(scene).toRenderAndCall(function(rgba) { - expect(countRenderedPixels(rgba)).toEqual(noAttenuationPixelCount); - }); + expect(scene).toRenderPixelCount(noAttenuationPixelCount); + + scene.destroyForSpecs(); + }); + }); + + + it('attenuates points based on geometric error in 2D', function() { + var scene = createScene({ + canvas : createCanvas(10, 10) + }); + var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); + scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); + scene.fxaa = false; + scene.camera.zoomIn(6); + + return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { + + var noAttenuationPixelCount = 16; - // Works with 2D scenes scene.morphTo2D(0); - tileset.pointAttenuationOptions.geometricErrorAttenuation = true; - tileset.pointAttenuationOptions.geometricErrorScale = 1.0; - tileset.pointAttenuationOptions.maximumAttenuation = undefined; - tileset.pointAttenuationOptions.baseResolution = undefined; + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.geometricErrorScale = 1.0; + tileset.pointShading.maximumAttenuation = undefined; + tileset.pointShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 16; - expect(scene).toRenderAndCall(function(rgba) { - expect(countRenderedPixels(rgba)).toBeGreaterThan(noAttenuationPixelCount); - }); + expect(scene).toRenderPixelCountGreaterThan(noAttenuationPixelCount); scene.destroyForSpecs(); }); diff --git a/Specs/Scene/PointCloudEyeDomeLightingSpec.js b/Specs/Scene/PointCloudEyeDomeLightingSpec.js index 526c4e3bdc31..eae1efe38ae0 100644 --- a/Specs/Scene/PointCloudEyeDomeLightingSpec.js +++ b/Specs/Scene/PointCloudEyeDomeLightingSpec.js @@ -60,32 +60,32 @@ defineSuite([ scene.primitives.removeAll(); }); - it('Adds a clear command and a post-processing draw call', function() { + it('adds a clear command and a post-processing draw call', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { if (!PointCloudEyeDomeLighting.processingSupported(scene.frameState.context)) { return; } - tileset.pointAttenuationOptions.eyeDomeLighting = true; + tileset.pointShading.eyeDomeLighting = true; scene.renderForSpecs(); var originalLength = scene.frameState.commandList.length; - tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + tileset.pointShading.geometricErrorAttenuation = true; scene.renderForSpecs(); var newLength = scene.frameState.commandList.length; expect(newLength).toEqual(originalLength + 2); }); }); - it('Does not change commands for pick calls', function() { + it('does not change commands for pick calls', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - tileset.pointAttenuationOptions.eyeDomeLighting = true; + tileset.pointShading.eyeDomeLighting = true; scene.pickForSpecs(); var originalLength = scene.frameState.commandList.length; - tileset.pointAttenuationOptions.geometricErrorAttenuation = true; + tileset.pointShading.geometricErrorAttenuation = true; scene.pickForSpecs(); var newLength = scene.frameState.commandList.length; expect(newLength).toEqual(originalLength); diff --git a/Specs/Scene/PointShadingSpec.js b/Specs/Scene/PointShadingSpec.js new file mode 100644 index 000000000000..5db6c5a5092c --- /dev/null +++ b/Specs/Scene/PointShadingSpec.js @@ -0,0 +1,34 @@ +defineSuite([ + 'Scene/PointShading' + ], function( + PointShading + ) { + 'use strict'; + + it('creates expected instance from raw assignment and construction', function() { + var pointShading = new PointShading(); + expect(pointShading.geometricErrorAttenuation).toEqual(false); + expect(pointShading.geometricErrorScale).toEqual(1.0); + expect(pointShading.maximumAttenuation).not.toBeDefined(); + expect(pointShading.baseResolution).not.toBeDefined(); + expect(pointShading.eyeDomeLighting).toEqual(false); + expect(pointShading.eyeDomeLightingStrength).toEqual(1.0); + expect(pointShading.eyeDomeLightingRadius).toEqual(1.0); + + var options = { + geometricErrorScale : 2.0, + maximumAttenuation : 16, + baseResolution : 0.1, + eyeDomeLightingStrength : 0.1, + eyeDomeLightingRadius : 2.0 + }; + pointShading = new PointShading(options); + expect(pointShading.geometricErrorAttenuation).toEqual(false); + expect(pointShading.geometricErrorScale).toEqual(options.geometricErrorScale); + expect(pointShading.maximumAttenuation).toEqual(options.maximumAttenuation); + expect(pointShading.baseResolution).toEqual(options.baseResolution); + expect(pointShading.eyeDomeLighting).toEqual(false); + expect(pointShading.eyeDomeLightingStrength).toEqual(options.eyeDomeLightingStrength); + expect(pointShading.eyeDomeLightingRadius).toEqual(options.eyeDomeLightingRadius); + }); +}); diff --git a/Specs/addDefaultMatchers.js b/Specs/addDefaultMatchers.js index 4ef3a85d986b..4cede2b9378c 100644 --- a/Specs/addDefaultMatchers.js +++ b/Specs/addDefaultMatchers.js @@ -241,6 +241,40 @@ define([ }; }, + toRenderPixelCount : function(util, customEqualityTesters) { + return { + compare : function(actual, expected) { + var actualRgba = renderAndReadPixels(actual); + + var webglStub = !!window.webglStub; + if (!webglStub) { + return {pass : countRenderedPixels(actualRgba) === expected}; + } + + return { + pass : true + }; + } + }; + }, + + toRenderPixelCountGreaterThan : function(util, customEqualityTesters) { + return { + compare : function(actual, expected) { + var actualRgba = renderAndReadPixels(actual); + + var webglStub = !!window.webglStub; + if (!webglStub) { + return {pass : countRenderedPixels(actualRgba) > expected}; + } + + return { + pass : true + }; + } + }; + }, + toRenderAndCall : function(util, customEqualityTesters) { return { compare : function(actual, expected) { @@ -418,6 +452,21 @@ define([ }; } + function countRenderedPixels(rgba) { + var pixelCount = rgba.length / 4; + var count = 0; + for (var i = 0; i < pixelCount; i++) { + var index = i * 4; + if (rgba[index] !== 0 || + rgba[index + 1] !== 0 || + rgba[index + 2] !== 0 || + rgba[index + 3] !== 255) { + count++; + } + } + return count; + } + function renderAndReadPixels(options) { var scene; From e754963da5d4c97e582100faac5498c7ad11dd1f Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 23 Jan 2018 11:01:48 -0500 Subject: [PATCH 06/13] added another tileset to point cloud shading sandcastle --- .../gallery/3D Tiles Point Cloud Shading.html | 140 ++++++++++++++---- 1 file changed, 111 insertions(+), 29 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html index 3ef03272294d..f09d4db398ed 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html @@ -38,6 +38,7 @@

Loading...

+ @@ -57,7 +58,7 @@ @@ -94,30 +95,19 @@ }); var scene = viewer.scene; +var tileset; -// Mt. St. Helens 3D Tileset generated from LAS provided by https://www.liblas.org/samples/ -var url = 'https://beta.cesium.com/api/assets/3523?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNDBlZDJhZS1jMDlmLTRiNWEtODhiMC1hMjExNGUzYmY5NzYiLCJpZCI6MjUsImlhdCI6MTQ4NTQ1OTAwMn0.PJQ7H5FsfSPBpQ2-_0jYNI4FnJBwuNpXnqfa7MZXFtc'; -var tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ - url : url -})); - -// Set the initial camera view to look at Mt. St. Helens -var initialPosition = Cesium.Cartesian3.fromRadians(-2.1344873183780484, 0.8071380277370774, 5743.394497982162); -var initialOrientation = new Cesium.HeadingPitchRoll.fromDegrees(112.99596671210358, -21.34390550872461, 0.0716951918898415); -viewer.scene.camera.setView({ - destination: initialPosition, - orientation: initialOrientation, - endTransform: Cesium.Matrix4.IDENTITY -}); - -tileset.readyPromise.then(function() { - tileset.maximumScreenSpaceError = 8.0; - tileset.pointShading.geometricErrorAttenuation = true; - tileset.pointShading.eyeDomeLighting = true; -}); +function reset() { + viewer.entities.removeAll(); + viewer.scene.primitives.removeAll(); + tileset = undefined; +} // The viewModel tracks the state of our mini application. +var pointClouds = ['St. Helens', 'Church']; var viewModel = { + exampleTypes : pointClouds, + currentExampleType : pointClouds[0], maximumScreenSpaceError : 8.0, geometricErrorScale : 1.0, maximumAttenuation : 0, // Equivalent to undefined @@ -126,11 +116,77 @@ eyeDomeLightingRadius : 1.0 }; +function tilesetToViewModel(tileset) { + var pointShading = tileset.pointShading; + viewModel.maximumScreenSpaceError = tileset.maximumScreenSpaceError; + viewModel.geometricErrorScale = pointShading.geometricErrorScale; + viewModel.maximumAttenuation = pointShading.maximumAttenuation ? pointShading.maximumAttenuation : 0; + viewModel.baseResolution = pointShading.baseResolution ? pointShading.baseResolution : 0; + viewModel.eyeDomeLightingStrength = pointShading.eyeDomeLightingStrength; + viewModel.eyeDomeLightingRadius = pointShading.eyeDomeLightingRadius; +} + +function loadStHelens() { + // Mt. St. Helens 3D Tileset generated from LAS provided by https://www.liblas.org/samples/ + // This tileset uses replacement refinement and has geometric error approximately equal to + // the average interpoint distance in each tile. + var url = 'https://beta.cesium.com/api/assets/3523?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNDBlZDJhZS1jMDlmLTRiNWEtODhiMC1hMjExNGUzYmY5NzYiLCJpZCI6MjUsImlhdCI6MTQ4NTQ1OTAwMn0.PJQ7H5FsfSPBpQ2-_0jYNI4FnJBwuNpXnqfa7MZXFtc'; + tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ + url : url + })); + + // Set the initial camera view to look at Mt. St. Helens + var initialPosition = Cesium.Cartesian3.fromRadians(-2.1344873183780484, 0.8071380277370774, 5743.394497982162); + var initialOrientation = new Cesium.HeadingPitchRoll.fromDegrees(112.99596671210358, -21.34390550872461, 0.0716951918898415); + viewer.scene.camera.setView({ + destination: initialPosition, + orientation: initialOrientation, + endTransform: Cesium.Matrix4.IDENTITY + }); + + tileset.readyPromise.then(function() { + tileset.maximumScreenSpaceError = 8.0; + tileset.pointShading.maximumAttenuation = undefined; // Will be based on maximumScreenSpaceError instead + tileset.pointShading.baseResolution = undefined; + tileset.pointShading.geometricErrorScale = 1.0; + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.eyeDomeLighting = true; + + tilesetToViewModel(tileset); + }); +} + +function loadChurch() { + // Point Cloud by Prof. Peter Allen, Columbia University Robotics Lab. Scanning by Alejandro Troccoli and Matei Ciocarlie. + // This tileset uses additive refinement and has geometric error based on the bounding box size for each tile. + var url = 'https://beta.cesium.com/api/assets/1460?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMzk2YzJiOS1jZGFmLTRlZmYtYmQ4MS00NTA3NjEwMzViZTkiLCJpZCI6NDQsImFzc2V0cyI6WzE0NjBdLCJpYXQiOjE0OTkyNjQ3NTV9.oWjvN52CRQ-dk3xtvD4e8ZnOHZhoWSpJLlw115mbQJM'; + tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ + url : url + })); + + viewer.zoomTo(tileset).otherwise(function(error) { + throw(error); + }); + + tileset.readyPromise.then(function() { + tileset.maximumScreenSpaceError = 1024.0; // For better performance, due to how this tileset treats geometric error. + tileset.pointShading.maximumAttenuation = 8.0; // Don't allow points larger than 8 pixels. + tileset.pointShading.baseResolution = 0.05; // Assume an original capture resolution of 5 centimeters between neighboring points. + tileset.pointShading.geometricErrorScale = 1.0; // Applies to both geometric error and the base resolution. + tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.eyeDomeLighting = true; + + tilesetToViewModel(tileset); + }); +} + function checkZero(newValue) { var newValueFloat = parseFloat(newValue); return (newValueFloat === 0.0) ? undefined : newValueFloat; } +loadStHelens(); + // Convert the viewModel members into knockout observables. Cesium.knockout.track(viewModel); @@ -138,49 +194,75 @@ var toolbar = document.getElementById('toolbar'); Cesium.knockout.applyBindings(viewModel, toolbar); +Cesium.knockout.getObservable(viewModel, 'currentExampleType').subscribe(function(newValue) { + reset(); + if (newValue === pointClouds[0]) { + loadStHelens(); + } else if (newValue === pointClouds[1]) { + loadChurch(); + } +}); + Cesium.knockout.getObservable(viewModel, 'maximumScreenSpaceError').subscribe( function(newValue) { - tileset.maximumScreenSpaceError = parseFloat(newValue); + if (Cesium.defined(tileset)) { + tileset.maximumScreenSpaceError = parseFloat(newValue); + } } ); Cesium.knockout.getObservable(viewModel, 'geometricErrorScale').subscribe( function(newValue) { - tileset.pointShading.geometricErrorScale = parseFloat(newValue); + if (Cesium.defined(tileset)) { + tileset.pointShading.geometricErrorScale = parseFloat(newValue); + } } ); Cesium.knockout.getObservable(viewModel, 'maximumAttenuation').subscribe( function(newValue) { - tileset.pointShading.maximumAttenuation = checkZero(newValue); + if (Cesium.defined(tileset)) { + tileset.pointShading.maximumAttenuation = checkZero(newValue); + } } ); Cesium.knockout.getObservable(viewModel, 'baseResolution').subscribe( function(newValue) { - tileset.pointShading.baseResolution = checkZero(newValue); + if (Cesium.defined(tileset)) { + tileset.pointShading.baseResolution = checkZero(newValue); + } } ); Cesium.knockout.getObservable(viewModel, 'eyeDomeLightingStrength').subscribe( function(newValue) { - tileset.pointShading.eyeDomeLightingStrength = parseFloat(newValue); + if (Cesium.defined(tileset)) { + tileset.pointShading.eyeDomeLightingStrength = parseFloat(newValue); + } } ); Cesium.knockout.getObservable(viewModel, 'eyeDomeLightingRadius').subscribe( function(newValue) { - tileset.pointShading.eyeDomeLightingRadius = parseFloat(newValue); + if (Cesium.defined(tileset)) { + tileset.pointShading.eyeDomeLightingRadius = parseFloat(newValue); + } } ); Sandcastle.addToggleButton('Enable Attenuation', true, function(checked) { - tileset.pointShading.geometricErrorAttenuation = checked; + if (Cesium.defined(tileset)) { + tileset.pointShading.geometricErrorAttenuation = checked; + } }); Sandcastle.addToggleButton('Enable Eye Dome Lighting', true, function(checked) { - tileset.pointShading.eyeDomeLighting = checked; + if (Cesium.defined(tileset)) { + tileset.pointShading.eyeDomeLighting = checked; + } }); + //Sandcastle_End Sandcastle.finishedLoading(); } From 9f9ebd10380345558da6d371f32cebfa453644d0 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 23 Jan 2018 13:09:30 -0500 Subject: [PATCH 07/13] added point cloud attenuation and EDL options to 3d tiles inspector --- .../gallery/3D Tiles Point Cloud Shading.html | 4 +- .../Cesium3DTilesInspector.js | 15 ++ .../Cesium3DTilesInspectorViewModel.js | 169 +++++++++++++++++- .../Cesium3DTilesInspectorViewModelSpec.js | 49 +++++ 4 files changed, 233 insertions(+), 4 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html index f09d4db398ed..ae5eb9b2cff0 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html @@ -90,9 +90,7 @@ function startup(Cesium) { 'use strict'; //Sandcastle_Begin -var viewer = new Cesium.Viewer('cesiumContainer', { - baseLayerPicker : false -}); +var viewer = new Cesium.Viewer('cesiumContainer'); var scene = viewer.scene; var tileset; diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js index 8407d7ea8918..3da27a44c4af 100644 --- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js +++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspector.js @@ -80,6 +80,21 @@ define([ displayPanelContents.appendChild(makeCheckbox('showContentBoundingVolumes', 'Content Volumes')); displayPanelContents.appendChild(makeCheckbox('showRequestVolumes', 'Request Volumes')); + displayPanelContents.appendChild(makeCheckbox('pointCloudShading', 'Point Cloud Shading')); + var pointCloudShadingContainer = document.createElement('div'); + pointCloudShadingContainer.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : pointCloudShading, "cesium-cesiumInspector-hide" : !pointCloudShading}'); + pointCloudShadingContainer.appendChild(makeRangeInput('geometricErrorScale', 0, 2, 0.01, 'Geometric Error Scale')); + pointCloudShadingContainer.appendChild(makeRangeInput('maximumAttenuation', 0, 32, 1, 'Maximum Attenuation')); + pointCloudShadingContainer.appendChild(makeRangeInput('baseResolution', 0, 1, 0.01, 'Base Resolution')); + pointCloudShadingContainer.appendChild(makeCheckbox('eyeDomeLighting', 'Eye Dome Lighting (EDL)')); + displayPanelContents.appendChild(pointCloudShadingContainer); + + var edlContainer = document.createElement('div'); + edlContainer.setAttribute('data-bind', 'css: {"cesium-cesiumInspector-show" : eyeDomeLighting, "cesium-cesiumInspector-hide" : !eyeDomeLighting}'); + edlContainer.appendChild(makeRangeInput('eyeDomeLightingStrength', 0, 2.0, 0.1, 'EDL Strength')); + edlContainer.appendChild(makeRangeInput('eyeDomeLightingRadius', 0, 4.0, 0.1, 'EDL Radius')); + pointCloudShadingContainer.appendChild(edlContainer); + updatePanelContents.appendChild(makeCheckbox('freezeFrame', 'Freeze Frame')); updatePanelContents.appendChild(makeCheckbox('dynamicScreenSpaceError', 'Dynamic Screen Space Error')); var sseContainer = document.createElement('div'); diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js index 6fff82c67a4a..ca3eb82ee7c8 100644 --- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js +++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js @@ -712,6 +712,162 @@ define([ } } }); + + var pointCloudShading = knockout.observable(); + knockout.defineProperty(this, 'pointCloudShading', { + get : function() { + return pointCloudShading(); + }, + set : function(value) { + pointCloudShading(value); + if (defined(that._tileset)) { + that._tileset.pointShading.geometricErrorAttenuation = value; + } + } + }); + /** + * Gets or sets the flag to enable point cloud shading. This property is observable. + * + * @type {Boolean} + * @default false + */ + this.pointCloudShading = false; + + var geometricErrorScale = knockout.observable(); + knockout.defineProperty(this, 'geometricErrorScale', { + get : function() { + return geometricErrorScale(); + }, + set : function(value) { + value = Number(value); + if (!isNaN(value)) { + geometricErrorScale(value); + if (defined(that._tileset)) { + that._tileset.pointShading.geometricErrorScale = value; + } + } + } + }); + /** + * Gets or sets the geometric error scale. This property is observable. + * + * @type {Number} + * @default 1.0 + */ + this.geometricErrorScale = 1.0; + + var maximumAttenuation = knockout.observable(); + knockout.defineProperty(this, 'maximumAttenuation', { + get : function() { + return maximumAttenuation(); + }, + set : function(value) { + value = Number(value); + if (!isNaN(value)) { + maximumAttenuation(value); + if (defined(that._tileset)) { + that._tileset.pointShading.maximumAttenuation = value === 0 ? undefined : value; + } + } + } + }); + /** + * Gets or sets the maximum attenuation. This property is observable. + * + * @type {Number} + * @default 0 + */ + this.maximumAttenuation = 0; + + var baseResolution = knockout.observable(); + knockout.defineProperty(this, 'baseResolution', { + get : function() { + return baseResolution(); + }, + set : function(value) { + value = Number(value); + if (!isNaN(value)) { + baseResolution(value); + if (defined(that._tileset)) { + that._tileset.pointShading.baseResolution = value === 0 ? undefined : value; + } + } + } + }); + /** + * Gets or sets the base resolution. This property is observable. + * + * @type {Number} + * @default 0 + */ + this.baseResolution = 0; + + var eyeDomeLighting = knockout.observable(); + knockout.defineProperty(this, 'eyeDomeLighting', { + get : function() { + return eyeDomeLighting(); + }, + set : function(value) { + eyeDomeLighting(value); + if (defined(that._tileset)) { + that._tileset.pointShading.eyeDomeLighting = value; + } + } + }); + /** + * Gets or sets the flag to enable eye dome lighting. This property is observable. + * + * @type {Boolean} + * @default false + */ + this.eyeDomeLighting = false; + + var eyeDomeLightingStrength = knockout.observable(); + knockout.defineProperty(this, 'eyeDomeLightingStrength', { + get : function() { + return eyeDomeLightingStrength(); + }, + set : function(value) { + value = Number(value); + if (!isNaN(value)) { + eyeDomeLightingStrength(value); + if (defined(that._tileset)) { + that._tileset.pointShading.eyeDomeLightingStrength = value; + } + } + } + }); + /** + * Gets or sets the eye dome lighting strength. This property is observable. + * + * @type {Number} + * @default 1.0 + */ + this.eyeDomeLightingStrength = 1.0; + + var eyeDomeLightingRadius = knockout.observable(); + knockout.defineProperty(this, 'eyeDomeLightingRadius', { + get : function() { + return eyeDomeLightingRadius(); + }, + set : function(value) { + value = Number(value); + if (!isNaN(value)) { + eyeDomeLightingRadius(value); + if (defined(that._tileset)) { + that._tileset.pointShading.eyeDomeLightingRadius = value; + } + } + } + }); + /** + * Gets or sets the eye dome lighting radius. This property is observable. + * + * @type {Number} + * @default 1.0 + */ + this.eyeDomeLightingRadius = 1.0; + /** * Gets or sets the pick state * @@ -851,7 +1007,8 @@ define([ this._definedProperties = ['properties', 'dynamicScreenSpaceError', 'colorBlendMode', 'picking', 'colorize', 'wireframe', 'showBoundingVolumes', 'showContentBoundingVolumes', 'showRequestVolumes', 'freezeFrame', 'maximumScreenSpaceError', 'dynamicScreenSpaceErrorDensity', 'baseScreenSpaceError', 'skipScreenSpaceErrorFactor', 'skipLevelOfDetail', 'skipLevels', 'immediatelyLoadDesiredLevelOfDetail', 'loadSiblings', 'dynamicScreenSpaceErrorDensitySliderValue', - 'dynamicScreenSpaceErrorFactor', 'pickActive', 'showOnlyPickedTileDebugLabel', 'showGeometricError', 'showRenderingStatistics', 'showMemoryUsage', 'showUrl']; + 'dynamicScreenSpaceErrorFactor', 'pickActive', 'showOnlyPickedTileDebugLabel', 'showGeometricError', 'showRenderingStatistics', 'showMemoryUsage', 'showUrl', + 'pointCloudShading', 'geometricErrorScale', 'maximumAttenuation', 'baseResolution', 'eyeDomeLighting', 'eyeDomeLightingStrength', 'eyeDomeLightingRadius']; this._removePostRenderEvent = scene.postRender.addEventListener(function() { that._update(); }); @@ -986,6 +1143,16 @@ define([ this.skipLevels = tileset.skipLevels; this.immediatelyLoadDesiredLevelOfDetail = tileset.immediatelyLoadDesiredLevelOfDetail; this.loadSiblings = tileset.loadSiblings; + + var pointShading = tileset.pointShading; + this.pointCloudShading = pointShading.geometricErrorAttenuation; + this.geometricErrorScale = pointShading.geometricErrorScale; + this.maximumAttenuation = pointShading.maximumAttenuation ? pointShading.maximumAttenuation: 0.0; + this.baseResolution = pointShading.baseResolution ? pointShading.baseResolution : 0.0; + this.eyeDomeLighting = pointShading.eyeDomeLighting; + this.eyeDomeLightingStrength = pointShading.eyeDomeLightingStrength; + this.eyeDomeLightingRadius = pointShading.eyeDomeLightingRadius; + } else { this._properties({}); } diff --git a/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js b/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js index 0806123ea098..06cd67542651 100644 --- a/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js +++ b/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js @@ -161,6 +161,55 @@ defineSuite([ viewModel.showUrl = false; expect(viewModel.tileset.debugShowUrl).toBe(false); }); + + it('pointCloudShading', function() { + viewModel.pointCloudShading = true; + expect(viewModel.tileset.pointShading.geometricErrorAttenuation).toBe(true); + viewModel.pointCloudShading = false; + expect(viewModel.tileset.pointShading.geometricErrorAttenuation).toBe(false); + }); + + it('geometricErrorScale', function() { + viewModel.geometricErrorScale = 1.0; + expect(viewModel.tileset.pointShading.geometricErrorScale).toBe(1.0); + viewModel.geometricErrorScale = 0.0; + expect(viewModel.tileset.pointShading.geometricErrorScale).toBe(0.0); + }); + + it('maximumAttenuation', function() { + viewModel.maximumAttenuation = 1.0; + expect(viewModel.tileset.pointShading.maximumAttenuation).toBe(1.0); + viewModel.maximumAttenuation = 0.0; + expect(viewModel.tileset.pointShading.maximumAttenuation).not.toBeDefined(); + }); + + it('baseResolution', function() { + viewModel.baseResolution = 1.0; + expect(viewModel.tileset.pointShading.baseResolution).toBe(1.0); + viewModel.baseResolution = 0.0; + expect(viewModel.tileset.pointShading.baseResolution).not.toBeDefined(); + }); + + it('eyeDomeLighting', function() { + viewModel.eyeDomeLighting = true; + expect(viewModel.tileset.pointShading.eyeDomeLighting).toBe(true); + viewModel.eyeDomeLighting = false; + expect(viewModel.tileset.pointShading.eyeDomeLighting).toBe(false); + }); + + it('eyeDomeLightingStrength', function() { + viewModel.eyeDomeLightingStrength = 1.0; + expect(viewModel.tileset.pointShading.eyeDomeLightingStrength).toBe(1.0); + viewModel.eyeDomeLightingStrength = 0.0; + expect(viewModel.tileset.pointShading.eyeDomeLightingStrength).toBe(0.0); + }); + + it('eyeDomeLightingRadius', function() { + viewModel.eyeDomeLightingRadius = 1.0; + expect(viewModel.tileset.pointShading.eyeDomeLightingRadius).toBe(1.0); + viewModel.eyeDomeLightingRadius = 0.0; + expect(viewModel.tileset.pointShading.eyeDomeLightingRadius).toBe(0.0); + }); }); describe('update options', function() { From 4c7231bd1e260832fefe4958c499223d7fae52d6 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 29 Jan 2018 09:59:00 -0500 Subject: [PATCH 08/13] geometricErrorAttenuation -> attenuation --- .../gallery/3D Tiles Point Cloud Shading.html | 6 +++--- Source/Core/BoundingSphere.js | 3 ++- Source/Scene/Cesium3DTileset.js | 2 +- Source/Scene/PointCloud3DTileContent.js | 8 ++++---- Source/Scene/PointShading.js | 4 ++-- .../Cesium3DTilesInspectorViewModel.js | 4 ++-- Specs/Scene/PointCloud3DTileContentSpec.js | 14 +++++++------- Specs/Scene/PointCloudEyeDomeLightingSpec.js | 4 ++-- Specs/Scene/PointShadingSpec.js | 4 ++-- .../Cesium3DTilesInspectorViewModelSpec.js | 4 ++-- 10 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html index ae5eb9b2cff0..d09e1a951f7f 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html @@ -147,7 +147,7 @@ tileset.pointShading.maximumAttenuation = undefined; // Will be based on maximumScreenSpaceError instead tileset.pointShading.baseResolution = undefined; tileset.pointShading.geometricErrorScale = 1.0; - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; tileset.pointShading.eyeDomeLighting = true; tilesetToViewModel(tileset); @@ -171,7 +171,7 @@ tileset.pointShading.maximumAttenuation = 8.0; // Don't allow points larger than 8 pixels. tileset.pointShading.baseResolution = 0.05; // Assume an original capture resolution of 5 centimeters between neighboring points. tileset.pointShading.geometricErrorScale = 1.0; // Applies to both geometric error and the base resolution. - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; tileset.pointShading.eyeDomeLighting = true; tilesetToViewModel(tileset); @@ -251,7 +251,7 @@ Sandcastle.addToggleButton('Enable Attenuation', true, function(checked) { if (Cesium.defined(tileset)) { - tileset.pointShading.geometricErrorAttenuation = checked; + tileset.pointShading.attenuation = checked; } }); diff --git a/Source/Core/BoundingSphere.js b/Source/Core/BoundingSphere.js index fe3e3adc46c1..a1549999b480 100644 --- a/Source/Core/BoundingSphere.js +++ b/Source/Core/BoundingSphere.js @@ -68,6 +68,7 @@ define([ var fromPointsMinBoxPt = new Cartesian3(); var fromPointsMaxBoxPt = new Cartesian3(); var fromPointsNaiveCenterScratch = new Cartesian3(); + var volumeConstant = (4.0 / 3.0) * CesiumMath.PI; /** * Computes a tight-fitting bounding sphere enclosing a list of 3D Cartesian points. @@ -1310,7 +1311,7 @@ define([ */ BoundingSphere.prototype.volume = function() { var radius = this.radius; - return (4.0 / 3.0) * CesiumMath.PI * radius * radius * radius; + return volumeConstant * radius * radius * radius; }; return BoundingSphere; diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 80f194ce3dc5..47c2a774470d 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -1694,7 +1694,7 @@ define([ statistics.numberOfCommands = (commandList.length - numberOfInitialCommands); // Only run EDL if simple attenuation is on - if (tileset.pointShading.geometricErrorAttenuation && + if (tileset.pointShading.attenuation && tileset.pointShading.eyeDomeLighting && (addedCommandsLength > 0)) { tileset._pointCloudEyeDomeLighting.update(frameState, numberOfInitialCommands, tileset); diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 14b0c3413299..0b67eb25609b 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -155,7 +155,7 @@ define([ this.featurePropertiesDirty = false; // Options for geometric error based attenuation - this.geometricErrorAttenuation = false; + this.attenuation = false; this._geometricErrorAttenuation = false; this._geometricErrorScale = undefined; this._maximumAttenuation = undefined; @@ -1316,12 +1316,12 @@ define([ // Update attenuation var pointShading = this._tileset.pointShading; if (defined(pointShading)) { - this.geometricErrorAttenuation = pointShading.geometricErrorAttenuation; + this.attenuation = pointShading.attenuation; this._geometricErrorScale = pointShading.geometricErrorScale; this._maximumAttenuation = defined(pointShading.maximumAttenuation) ? pointShading.maximumAttenuation : tileset.maximumScreenSpaceError; this._baseResolution = pointShading.baseResolution; - if (this.geometricErrorAttenuation !== this._geometricErrorAttenuation) { - this._geometricErrorAttenuation = this.geometricErrorAttenuation; + if (this.attenuation !== this._geometricErrorAttenuation) { + this._geometricErrorAttenuation = this.attenuation; createShaders(this, frameState, tileset.style); } } diff --git a/Source/Scene/PointShading.js b/Source/Scene/PointShading.js index 4be748b53147..2d70f4a9f57f 100644 --- a/Source/Scene/PointShading.js +++ b/Source/Scene/PointShading.js @@ -10,7 +10,7 @@ define([ * pointclouds using 3D Tiles. * * @param {Object} [options] Object with the following properties: - * {Boolean} [options.geometricErrorAttenuation=false] Perform point attenuation based on geometric error. + * {Boolean} [options.attenuation=false] Perform point attenuation based on geometric error. * {Number} [options.geometricErrorScale=1.0] Scale to be applied to each tile's geometric error. * {Number} [options.maximumAttenuation] Maximum attenuation in pixels. Defaults to the Cesium3DTileset's maximumScreenSpaceError. * {Number} [options.baseResolution] Average base resolution for the dataset in meters. Substitute for Geometric Error when not available. @@ -27,7 +27,7 @@ define([ * @type {Boolean} * @default false */ - this.geometricErrorAttenuation = defaultValue(pointShading.geometricErrorAttenuation, false); + this.attenuation = defaultValue(pointShading.attenuation, false); /** * Scale to be applied to the geometric error before computing attenuation. diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js index ca3eb82ee7c8..621f48d9d646 100644 --- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js +++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js @@ -721,7 +721,7 @@ define([ set : function(value) { pointCloudShading(value); if (defined(that._tileset)) { - that._tileset.pointShading.geometricErrorAttenuation = value; + that._tileset.pointShading.attenuation = value; } } }); @@ -1145,7 +1145,7 @@ define([ this.loadSiblings = tileset.loadSiblings; var pointShading = tileset.pointShading; - this.pointCloudShading = pointShading.geometricErrorAttenuation; + this.pointCloudShading = pointShading.attenuation; this.geometricErrorScale = pointShading.geometricErrorScale; this.maximumAttenuation = pointShading.maximumAttenuation ? pointShading.maximumAttenuation: 0.0; this.baseResolution = pointShading.baseResolution ? pointShading.baseResolution : 0.0; diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index 0458f74add00..606b1eb5e8c3 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -453,7 +453,7 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { var noAttenuationPixelCount = 16; - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; tileset.pointShading.geometricErrorScale = 1.0; tileset.pointShading.maximumAttenuation = undefined; tileset.pointShading.baseResolution = undefined; @@ -476,7 +476,7 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { var noAttenuationPixelCount = 16; - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; tileset.pointShading.geometricErrorScale = 1.0; tileset.pointShading.maximumAttenuation = undefined; tileset.pointShading.baseResolution = undefined; @@ -499,7 +499,7 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { var noAttenuationPixelCount = 16; - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; tileset.pointShading.geometricErrorScale = 1.0; tileset.pointShading.maximumAttenuation = 1; tileset.pointShading.baseResolution = undefined; @@ -522,7 +522,7 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { var noAttenuationPixelCount = 16; - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; tileset.pointShading.geometricErrorScale = 1.0; tileset.pointShading.maximumAttenuation = undefined; tileset.pointShading.baseResolution = CesiumMath.EPSILON20; @@ -547,7 +547,7 @@ defineSuite([ // pointCloudNoColorUrl is a single tile with GeometricError = 0, // which results in default baseResolution being computed - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; tileset.pointShading.geometricErrorScale = 1.0; tileset.pointShading.maximumAttenuation = undefined; tileset.pointShading.baseResolution = CesiumMath.EPSILON20; @@ -570,7 +570,7 @@ defineSuite([ return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { var noAttenuationPixelCount = 16; - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; tileset.pointShading.geometricErrorScale = 0.0; tileset.pointShading.maximumAttenuation = undefined; tileset.pointShading.baseResolution = undefined; @@ -596,7 +596,7 @@ defineSuite([ var noAttenuationPixelCount = 16; scene.morphTo2D(0); - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; tileset.pointShading.geometricErrorScale = 1.0; tileset.pointShading.maximumAttenuation = undefined; tileset.pointShading.baseResolution = undefined; diff --git a/Specs/Scene/PointCloudEyeDomeLightingSpec.js b/Specs/Scene/PointCloudEyeDomeLightingSpec.js index eae1efe38ae0..b87801796fc6 100644 --- a/Specs/Scene/PointCloudEyeDomeLightingSpec.js +++ b/Specs/Scene/PointCloudEyeDomeLightingSpec.js @@ -71,7 +71,7 @@ defineSuite([ scene.renderForSpecs(); var originalLength = scene.frameState.commandList.length; - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; scene.renderForSpecs(); var newLength = scene.frameState.commandList.length; expect(newLength).toEqual(originalLength + 2); @@ -85,7 +85,7 @@ defineSuite([ scene.pickForSpecs(); var originalLength = scene.frameState.commandList.length; - tileset.pointShading.geometricErrorAttenuation = true; + tileset.pointShading.attenuation = true; scene.pickForSpecs(); var newLength = scene.frameState.commandList.length; expect(newLength).toEqual(originalLength); diff --git a/Specs/Scene/PointShadingSpec.js b/Specs/Scene/PointShadingSpec.js index 5db6c5a5092c..f2f38b323e74 100644 --- a/Specs/Scene/PointShadingSpec.js +++ b/Specs/Scene/PointShadingSpec.js @@ -7,7 +7,7 @@ defineSuite([ it('creates expected instance from raw assignment and construction', function() { var pointShading = new PointShading(); - expect(pointShading.geometricErrorAttenuation).toEqual(false); + expect(pointShading.attenuation).toEqual(false); expect(pointShading.geometricErrorScale).toEqual(1.0); expect(pointShading.maximumAttenuation).not.toBeDefined(); expect(pointShading.baseResolution).not.toBeDefined(); @@ -23,7 +23,7 @@ defineSuite([ eyeDomeLightingRadius : 2.0 }; pointShading = new PointShading(options); - expect(pointShading.geometricErrorAttenuation).toEqual(false); + expect(pointShading.attenuation).toEqual(false); expect(pointShading.geometricErrorScale).toEqual(options.geometricErrorScale); expect(pointShading.maximumAttenuation).toEqual(options.maximumAttenuation); expect(pointShading.baseResolution).toEqual(options.baseResolution); diff --git a/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js b/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js index 06cd67542651..b90a16cf8c42 100644 --- a/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js +++ b/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js @@ -164,9 +164,9 @@ defineSuite([ it('pointCloudShading', function() { viewModel.pointCloudShading = true; - expect(viewModel.tileset.pointShading.geometricErrorAttenuation).toBe(true); + expect(viewModel.tileset.pointShading.attenuation).toBe(true); viewModel.pointCloudShading = false; - expect(viewModel.tileset.pointShading.geometricErrorAttenuation).toBe(false); + expect(viewModel.tileset.pointShading.attenuation).toBe(false); }); it('geometricErrorScale', function() { From 4e765ac9f51113b1171b584011ea897e19be7ef7 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 29 Jan 2018 11:25:35 -0500 Subject: [PATCH 09/13] change access token for mt. st. helens --- Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html index d09e1a951f7f..e9366e50a191 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html @@ -128,7 +128,7 @@ // Mt. St. Helens 3D Tileset generated from LAS provided by https://www.liblas.org/samples/ // This tileset uses replacement refinement and has geometric error approximately equal to // the average interpoint distance in each tile. - var url = 'https://beta.cesium.com/api/assets/3523?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIzNDBlZDJhZS1jMDlmLTRiNWEtODhiMC1hMjExNGUzYmY5NzYiLCJpZCI6MjUsImlhdCI6MTQ4NTQ1OTAwMn0.PJQ7H5FsfSPBpQ2-_0jYNI4FnJBwuNpXnqfa7MZXFtc'; + var url = 'https://beta.cesium.com/api/assets/3742?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxOGZjMjlhZC03MDE1LTQ3ZTAtODEyNy05YTU3M2MwYzQ0YmEiLCJpZCI6NDQsImFzc2V0cyI6WzM3NDJdLCJpYXQiOjE1MTcyNDI3NDJ9.TJAJctFXC1UyFMpxkA3cyKVAmnh72cLtfY1yKbaQsyk'; tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ url : url })); From 6041751c2e36165900c1a8d778dd30c9e5dc55c2 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 29 Jan 2018 16:54:31 -0500 Subject: [PATCH 10/13] PR comments --- .../gallery/3D Tiles Point Cloud Shading.html | 117 +++++------ CHANGES.md | 4 +- Source/Scene/Cesium3DTileset.js | 14 +- Source/Scene/PointCloud3DTileContent.js | 92 +++++---- Source/Scene/PointCloudEyeDomeLighting.js | 137 +++++-------- Source/Scene/PointCloudShading.js | 82 ++++++++ Source/Scene/PointShading.js | 79 -------- .../PointCloudEyeDomeLighting.glsl | 2 +- .../Cesium3DTilesInspectorViewModel.js | 30 +-- Specs/Scene/PointCloud3DTileContentSpec.js | 183 ++++++------------ Specs/Scene/PointCloudEyeDomeLightingSpec.js | 61 +++--- Specs/Scene/PointCloudShadingSpec.js | 33 ++++ Specs/Scene/PointShadingSpec.js | 34 ---- .../Cesium3DTilesInspectorViewModelSpec.js | 28 +-- Specs/addDefaultMatchers.js | 28 +-- 15 files changed, 408 insertions(+), 516 deletions(-) create mode 100644 Source/Scene/PointCloudShading.js delete mode 100644 Source/Scene/PointShading.js create mode 100644 Specs/Scene/PointCloudShadingSpec.js delete mode 100644 Specs/Scene/PointShadingSpec.js diff --git a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html index e9366e50a191..0ab4b44b86bc 100644 --- a/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html +++ b/Apps/Sandcastle/gallery/3D Tiles Point Cloud Shading.html @@ -93,12 +93,12 @@ var viewer = new Cesium.Viewer('cesiumContainer'); var scene = viewer.scene; -var tileset; +var viewModelTileset; function reset() { viewer.entities.removeAll(); viewer.scene.primitives.removeAll(); - tileset = undefined; + viewModelTileset = undefined; } // The viewModel tracks the state of our mini application. @@ -115,24 +115,18 @@ }; function tilesetToViewModel(tileset) { - var pointShading = tileset.pointShading; + viewModelTileset = tileset; + + var pointCloudShading = tileset.pointCloudShading; viewModel.maximumScreenSpaceError = tileset.maximumScreenSpaceError; - viewModel.geometricErrorScale = pointShading.geometricErrorScale; - viewModel.maximumAttenuation = pointShading.maximumAttenuation ? pointShading.maximumAttenuation : 0; - viewModel.baseResolution = pointShading.baseResolution ? pointShading.baseResolution : 0; - viewModel.eyeDomeLightingStrength = pointShading.eyeDomeLightingStrength; - viewModel.eyeDomeLightingRadius = pointShading.eyeDomeLightingRadius; + viewModel.geometricErrorScale = pointCloudShading.geometricErrorScale; + viewModel.maximumAttenuation = pointCloudShading.maximumAttenuation ? pointCloudShading.maximumAttenuation : 0; + viewModel.baseResolution = pointCloudShading.baseResolution ? pointCloudShading.baseResolution : 0; + viewModel.eyeDomeLightingStrength = pointCloudShading.eyeDomeLightingStrength; + viewModel.eyeDomeLightingRadius = pointCloudShading.eyeDomeLightingRadius; } function loadStHelens() { - // Mt. St. Helens 3D Tileset generated from LAS provided by https://www.liblas.org/samples/ - // This tileset uses replacement refinement and has geometric error approximately equal to - // the average interpoint distance in each tile. - var url = 'https://beta.cesium.com/api/assets/3742?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxOGZjMjlhZC03MDE1LTQ3ZTAtODEyNy05YTU3M2MwYzQ0YmEiLCJpZCI6NDQsImFzc2V0cyI6WzM3NDJdLCJpYXQiOjE1MTcyNDI3NDJ9.TJAJctFXC1UyFMpxkA3cyKVAmnh72cLtfY1yKbaQsyk'; - tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ - url : url - })); - // Set the initial camera view to look at Mt. St. Helens var initialPosition = Cesium.Cartesian3.fromRadians(-2.1344873183780484, 0.8071380277370774, 5743.394497982162); var initialOrientation = new Cesium.HeadingPitchRoll.fromDegrees(112.99596671210358, -21.34390550872461, 0.0716951918898415); @@ -142,40 +136,47 @@ endTransform: Cesium.Matrix4.IDENTITY }); - tileset.readyPromise.then(function() { - tileset.maximumScreenSpaceError = 8.0; - tileset.pointShading.maximumAttenuation = undefined; // Will be based on maximumScreenSpaceError instead - tileset.pointShading.baseResolution = undefined; - tileset.pointShading.geometricErrorScale = 1.0; - tileset.pointShading.attenuation = true; - tileset.pointShading.eyeDomeLighting = true; + // Mt. St. Helens 3D Tileset generated from LAS provided by https://www.liblas.org/samples/ + // This tileset uses replacement refinement and has geometric error approximately equal to + // the average interpoint distance in each tile. + Cesium.CesiumIon.create3DTileset(3742, { accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIxOGZjMjlhZC03MDE1LTQ3ZTAtODEyNy05YTU3M2MwYzQ0YmEiLCJpZCI6NDQsImFzc2V0cyI6WzM3NDJdLCJpYXQiOjE1MTcyNDI3NDJ9.TJAJctFXC1UyFMpxkA3cyKVAmnh72cLtfY1yKbaQsyk' }) + .then(function(tileset) { + viewer.scene.primitives.add(tileset); - tilesetToViewModel(tileset); - }); + tileset.maximumScreenSpaceError = 8.0; + tileset.pointCloudShading.maximumAttenuation = undefined; // Will be based on maximumScreenSpaceError instead + tileset.pointCloudShading.baseResolution = undefined; + tileset.pointCloudShading.geometricErrorScale = 1.0; + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.eyeDomeLighting = true; + + tilesetToViewModel(tileset); + }) + .otherwise(function(error) { + console.log(error); + }); } function loadChurch() { // Point Cloud by Prof. Peter Allen, Columbia University Robotics Lab. Scanning by Alejandro Troccoli and Matei Ciocarlie. // This tileset uses additive refinement and has geometric error based on the bounding box size for each tile. - var url = 'https://beta.cesium.com/api/assets/1460?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMzk2YzJiOS1jZGFmLTRlZmYtYmQ4MS00NTA3NjEwMzViZTkiLCJpZCI6NDQsImFzc2V0cyI6WzE0NjBdLCJpYXQiOjE0OTkyNjQ3NTV9.oWjvN52CRQ-dk3xtvD4e8ZnOHZhoWSpJLlw115mbQJM'; - tileset = scene.primitives.add(new Cesium.Cesium3DTileset({ - url : url - })); + Cesium.CesiumIon.create3DTileset(1460, { accessToken: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiIyMzk2YzJiOS1jZGFmLTRlZmYtYmQ4MS00NTA3NjEwMzViZTkiLCJpZCI6NDQsImFzc2V0cyI6WzE0NjBdLCJpYXQiOjE0OTkyNjQ3NTV9.oWjvN52CRQ-dk3xtvD4e8ZnOHZhoWSpJLlw115mbQJM' }) + .then(function(tileset) { + viewer.scene.primitives.add(tileset); - viewer.zoomTo(tileset).otherwise(function(error) { - throw(error); - }); - - tileset.readyPromise.then(function() { - tileset.maximumScreenSpaceError = 1024.0; // For better performance, due to how this tileset treats geometric error. - tileset.pointShading.maximumAttenuation = 8.0; // Don't allow points larger than 8 pixels. - tileset.pointShading.baseResolution = 0.05; // Assume an original capture resolution of 5 centimeters between neighboring points. - tileset.pointShading.geometricErrorScale = 1.0; // Applies to both geometric error and the base resolution. - tileset.pointShading.attenuation = true; - tileset.pointShading.eyeDomeLighting = true; + tileset.maximumScreenSpaceError = 1024.0; // For better performance, due to how this tileset treats geometric error. + tileset.pointCloudShading.maximumAttenuation = 8.0; // Don't allow points larger than 8 pixels. + tileset.pointCloudShading.baseResolution = 0.05; // Assume an original capture resolution of 5 centimeters between neighboring points. + tileset.pointCloudShading.geometricErrorScale = 1.0; // Applies to both geometric error and the base resolution. + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.eyeDomeLighting = true; - tilesetToViewModel(tileset); - }); + tilesetToViewModel(tileset); + viewer.zoomTo(tileset); + }) + .otherwise(function(error) { + console.log(error); + }); } function checkZero(newValue) { @@ -203,61 +204,61 @@ Cesium.knockout.getObservable(viewModel, 'maximumScreenSpaceError').subscribe( function(newValue) { - if (Cesium.defined(tileset)) { - tileset.maximumScreenSpaceError = parseFloat(newValue); + if (Cesium.defined(viewModelTileset)) { + viewModelTileset.maximumScreenSpaceError = parseFloat(newValue); } } ); Cesium.knockout.getObservable(viewModel, 'geometricErrorScale').subscribe( function(newValue) { - if (Cesium.defined(tileset)) { - tileset.pointShading.geometricErrorScale = parseFloat(newValue); + if (Cesium.defined(viewModelTileset)) { + viewModelTileset.pointCloudShading.geometricErrorScale = parseFloat(newValue); } } ); Cesium.knockout.getObservable(viewModel, 'maximumAttenuation').subscribe( function(newValue) { - if (Cesium.defined(tileset)) { - tileset.pointShading.maximumAttenuation = checkZero(newValue); + if (Cesium.defined(viewModelTileset)) { + viewModelTileset.pointCloudShading.maximumAttenuation = checkZero(newValue); } } ); Cesium.knockout.getObservable(viewModel, 'baseResolution').subscribe( function(newValue) { - if (Cesium.defined(tileset)) { - tileset.pointShading.baseResolution = checkZero(newValue); + if (Cesium.defined(viewModelTileset)) { + viewModelTileset.pointCloudShading.baseResolution = checkZero(newValue); } } ); Cesium.knockout.getObservable(viewModel, 'eyeDomeLightingStrength').subscribe( function(newValue) { - if (Cesium.defined(tileset)) { - tileset.pointShading.eyeDomeLightingStrength = parseFloat(newValue); + if (Cesium.defined(viewModelTileset)) { + viewModelTileset.pointCloudShading.eyeDomeLightingStrength = parseFloat(newValue); } } ); Cesium.knockout.getObservable(viewModel, 'eyeDomeLightingRadius').subscribe( function(newValue) { - if (Cesium.defined(tileset)) { - tileset.pointShading.eyeDomeLightingRadius = parseFloat(newValue); + if (Cesium.defined(viewModelTileset)) { + viewModelTileset.pointCloudShading.eyeDomeLightingRadius = parseFloat(newValue); } } ); Sandcastle.addToggleButton('Enable Attenuation', true, function(checked) { - if (Cesium.defined(tileset)) { - tileset.pointShading.attenuation = checked; + if (Cesium.defined(viewModelTileset)) { + viewModelTileset.pointCloudShading.attenuation = checked; } }); Sandcastle.addToggleButton('Enable Eye Dome Lighting', true, function(checked) { - if (Cesium.defined(tileset)) { - tileset.pointShading.eyeDomeLighting = checked; + if (Cesium.defined(viewModelTileset)) { + viewModelTileset.pointCloudShading.eyeDomeLighting = checked; } }); diff --git a/CHANGES.md b/CHANGES.md index 88a0395df060..43da8c3b0575 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -41,8 +41,8 @@ Change Log * Fixed camera movement and look functions for 2D mode [#5884](https://github.com/AnalyticalGraphicsInc/cesium/issues/5884) * Fixed discrepancy between default value used and commented value for default value for halfAxes of OrientedBoundingBox. [#6147](https://github.com/AnalyticalGraphicsInc/cesium/pull/6147) * Added `Cartographic.toCartesian` to convert from Cartographic to Cartesian3. [#6163](https://github.com/AnalyticalGraphicsInc/cesium/pull/6163) -* Added geometric-error-based point cloud attenuation and eye dome lighting for point clouds using additive refinement. -* Added volume computation to `BoundingSphere`. +* Added geometric-error-based point cloud attenuation and eye dome lighting for point clouds using replacement refinement. [#6069](https://github.com/AnalyticalGraphicsInc/cesium/pull/6069) +* Added `BoundingSphere.volume` for computing the volume of a `BoundingSphere`. [#6069](https://github.com/AnalyticalGraphicsInc/cesium/pull/6069) ### 1.41 - 2018-01-02 diff --git a/Source/Scene/Cesium3DTileset.js b/Source/Scene/Cesium3DTileset.js index 47c2a774470d..b67e5ad02032 100644 --- a/Source/Scene/Cesium3DTileset.js +++ b/Source/Scene/Cesium3DTileset.js @@ -36,7 +36,7 @@ define([ './Cesium3DTileStyleEngine', './ClassificationType', './LabelCollection', - './PointShading', + './PointCloudShading', './PointCloudEyeDomeLighting', './SceneMode', './ShadowMode', @@ -81,7 +81,7 @@ define([ Cesium3DTileStyleEngine, ClassificationType, LabelCollection, - PointShading, + PointCloudShading, PointCloudEyeDomeLighting, SceneMode, ShadowMode, @@ -128,7 +128,7 @@ define([ * @param {Boolean} [options.debugShowRenderingStatistics=false] For debugging only. When true, draws labels to indicate the number of commands, points, triangles and features for each tile. * @param {Boolean} [options.debugShowMemoryUsage=false] For debugging only. When true, draws labels to indicate the texture and geometry memory in megabytes used by each tile. * @param {Boolean} [options.debugShowUrl=false] For debugging only. When true, draws labels to indicate the url of each tile. - * @param {Object} [options.pointShading] Options for constructing a PointShading object to control point attenuation based on geometric error and lighting. + * @param {Object} [options.pointCloudShading] Options for constructing a {@link PointCloudShading} object to control point attenuation based on geometric error and lighting. * * @exception {DeveloperError} The tileset must be 3D Tiles version 0.0 or 1.0. See {@link https://github.com/AnalyticalGraphicsInc/3d-tiles#spec-status} * @@ -341,9 +341,9 @@ define([ /** * Options for controlling point size based on geometric error and eye dome lighting. - * @type {PointShading} + * @type {PointCloudShading} */ - this.pointShading = new PointShading(options.pointShading); + this.pointCloudShading = new PointCloudShading(options.pointCloudShading); this._pointCloudEyeDomeLighting = new PointCloudEyeDomeLighting(); @@ -1694,8 +1694,8 @@ define([ statistics.numberOfCommands = (commandList.length - numberOfInitialCommands); // Only run EDL if simple attenuation is on - if (tileset.pointShading.attenuation && - tileset.pointShading.eyeDomeLighting && + if (tileset.pointCloudShading.attenuation && + tileset.pointCloudShading.eyeDomeLighting && (addedCommandsLength > 0)) { tileset._pointCloudEyeDomeLighting.update(frameState, numberOfInitialCommands, tileset); } diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index 0b67eb25609b..ef1180c3a58a 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -155,8 +155,7 @@ define([ this.featurePropertiesDirty = false; // Options for geometric error based attenuation - this.attenuation = false; - this._geometricErrorAttenuation = false; + this._attenuation = false; this._geometricErrorScale = undefined; this._maximumAttenuation = undefined; this._baseResolution = undefined; @@ -470,8 +469,7 @@ define([ content._baseResolutionApproximation = Math.cbrt(sphereVolume / pointsLength); } - var scratchPointSizeAndTilesetTime = new Cartesian2(); - var scratchGeometricErrorAndDepthMultiplier = new Cartesian2(); + var scratchPointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier = new Cartesian4(); var positionLocation = 0; var colorLocation = 1; @@ -543,10 +541,30 @@ define([ } var uniformMap = { - u_pointSizeAndTilesetTime : function() { - scratchPointSizeAndTilesetTime.x = content._geometricErrorAttenuation ? content._maximumAttenuation : content._pointSize; - scratchPointSizeAndTilesetTime.y = content._tileset.timeSinceLoad; - return scratchPointSizeAndTilesetTime; + u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier : function() { + var scratch = scratchPointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier; + scratch.x = content._attenuation ? content._maximumAttenuation : content._pointSize; + scratch.y = content._tileset.timeSinceLoad; + + if (content._attenuation) { + var geometricError = content.tile.geometricError; + if (geometricError === 0) { + geometricError = defined(content._baseResolution) ? content._baseResolution : content._baseResolutionApproximation; + } + var frustum = frameState.camera.frustum; + var 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; + } + + scratch.z = geometricError * content._geometricErrorScale; + scratch.w = depthMultiplier; + } + + return scratch; }, u_highlightColor : function() { return content._highlightColor; @@ -570,24 +588,6 @@ define([ var style = Color.clone(clippingPlanes.edgeColor); style.alpha = clippingPlanes.edgeWidth; return style; - }, - u_geometricErrorAndDepthMultiplier : function() { - var geometricError = content.tile.geometricError; - if (geometricError === 0) { - geometricError = defined(content._baseResolution) ? content._baseResolution : content._baseResolutionApproximation; - } - var frustum = frameState.camera.frustum; - var 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; - } - - scratchGeometricErrorAndDepthMultiplier.x = geometricError * content._geometricErrorScale; - scratchGeometricErrorAndDepthMultiplier.y = depthMultiplier; - return scratchGeometricErrorAndDepthMultiplier; } }; @@ -855,7 +855,7 @@ define([ var backFaceCulling = content._backFaceCulling; var vertexArray = content._drawCommand.vertexArray; var clippingPlanes = content._tileset.clippingPlanes; - var geometricErrorAttenuation = content._geometricErrorAttenuation; + var attenuation = content._attenuation; var colorStyleFunction; var showStyleFunction; @@ -968,16 +968,13 @@ define([ var vs = 'attribute vec3 a_position; \n' + 'varying vec4 v_color; \n' + - 'uniform vec2 u_pointSizeAndTilesetTime; \n' + + 'uniform vec4 u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier; \n' + 'uniform vec4 u_constantColor; \n' + 'uniform vec4 u_highlightColor; \n'; - if (geometricErrorAttenuation) { - vs += 'uniform vec2 u_geometricErrorAndDepthMultiplier; \n'; - } vs += 'float u_pointSize; \n' + 'float u_tilesetTime; \n'; - if (geometricErrorAttenuation) { + if (attenuation) { vs += 'float u_geometricError; \n' + 'float u_depthMultiplier; \n'; } @@ -1029,12 +1026,12 @@ define([ vs += 'void main() \n' + '{ \n' + - ' u_pointSize = u_pointSizeAndTilesetTime.x; \n' + - ' u_tilesetTime = u_pointSizeAndTilesetTime.y; \n'; + ' u_pointSize = u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier.x; \n' + + ' u_tilesetTime = u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier.y; \n'; - if (geometricErrorAttenuation) { - vs += ' u_geometricError = u_geometricErrorAndDepthMultiplier.x; \n' + - ' u_depthMultiplier = u_geometricErrorAndDepthMultiplier.y; \n'; + if (attenuation) { + vs += ' u_geometricError = u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier.z; \n' + + ' u_depthMultiplier = u_pointSizeAndTilesetTimeAndGeometricErrorAndDepthMultiplier.w; \n'; } if (usesColors) { @@ -1083,9 +1080,8 @@ define([ if (hasPointSizeStyle) { vs += ' gl_PointSize = getPointSizeFromStyle(position, position_absolute, color, normal); \n'; - } else if (geometricErrorAttenuation) { - vs += ' vec3 positionWC = vec3(czm_model * vec4(position, 1.0)); \n' + - ' vec4 positionEC = czm_view * vec4(positionWC, 1.0); \n' + + } else if (attenuation) { + vs += ' vec4 positionEC = czm_modelView * vec4(position, 1.0); \n' + ' float depth = -positionEC.z; \n' + // compute SSE for this point ' gl_PointSize = min((u_geometricError / depth) * u_depthMultiplier, u_pointSize); \n'; @@ -1314,14 +1310,14 @@ define([ } // Update attenuation - var pointShading = this._tileset.pointShading; - if (defined(pointShading)) { - this.attenuation = pointShading.attenuation; - this._geometricErrorScale = pointShading.geometricErrorScale; - this._maximumAttenuation = defined(pointShading.maximumAttenuation) ? pointShading.maximumAttenuation : tileset.maximumScreenSpaceError; - this._baseResolution = pointShading.baseResolution; - if (this.attenuation !== this._geometricErrorAttenuation) { - this._geometricErrorAttenuation = this.attenuation; + var pointCloudShading = this._tileset.pointCloudShading; + if (defined(pointCloudShading)) { + var formerAttenuation = this._attenuation; + this._attenuation = pointCloudShading.attenuation; + this._geometricErrorScale = pointCloudShading.geometricErrorScale; + this._maximumAttenuation = defined(pointCloudShading.maximumAttenuation) ? pointCloudShading.maximumAttenuation : tileset.maximumScreenSpaceError; + this._baseResolution = pointCloudShading.baseResolution; + if (this._attenuation !== formerAttenuation) { createShaders(this, frameState, tileset.style); } } diff --git a/Source/Scene/PointCloudEyeDomeLighting.js b/Source/Scene/PointCloudEyeDomeLighting.js index c96649dc4124..5501af809177 100644 --- a/Source/Scene/PointCloudEyeDomeLighting.js +++ b/Source/Scene/PointCloudEyeDomeLighting.js @@ -1,6 +1,5 @@ define([ '../Core/Cartesian3', - '../Core/Math', '../Core/clone', '../Core/Color', '../Core/combine', @@ -35,7 +34,6 @@ define([ '../Shaders/PostProcessFilters/PointCloudEyeDomeLighting' ], function( Cartesian3, - CesiumMath, clone, Color, combine, @@ -78,29 +76,17 @@ define([ * @private */ function PointCloudEyeDomeLighting() { - this._framebuffers = undefined; + this._framebuffer = undefined; this._colorTexture = undefined; // color gbuffer this._ecAndLogDepthTexture = undefined; // depth gbuffer this._depthTexture = undefined; // needed to write depth so camera based on depth works - this._drawCommands = undefined; - this._clearCommands = undefined; + this._drawCommand = undefined; + this._clearCommand = undefined; this._strength = 1.0; this._radius = 1.0; } - function addConstants(sourceFS, constantName, value) { - var finalSource = sourceFS; - if (typeof(value) === 'boolean') { - if (value !== false) { - finalSource = '#define ' + constantName + '\n' + sourceFS; - } - } else { - finalSource = '#define ' + constantName + ' ' + value + '\n' + sourceFS; - } - return finalSource; - } - function createSampler() { return new Sampler({ wrapS : TextureWrap.CLAMP_TO_EDGE, @@ -110,30 +96,26 @@ define([ }); } - function destroyFramebuffers(processor) { - var framebuffers = processor._framebuffers; - if (!defined(framebuffers)) { + function destroyFramebuffer(processor) { + var framebuffer = processor._framebuffer; + if (!defined(framebuffer)) { return; } processor._colorTexture.destroy(); processor._ecAndLogDepthTexture.destroy(); processor._depthTexture.destroy(); - for (var name in framebuffers) { - if (framebuffers.hasOwnProperty(name)) { - framebuffers[name].destroy(); - } - } + framebuffer.destroy(); - processor._framebuffers = undefined; + processor._framebuffer = undefined; processor._colorTexture = undefined; processor._ecAndLogDepthTexture = undefined; processor._depthTexture = undefined; - processor._drawCommands = undefined; - processor._clearCommands = undefined; + processor._drawCommand = undefined; + processor._clearCommand = undefined; } - function createFramebuffers(processor, context) { + function createFramebuffer(processor, context) { var screenWidth = context.drawingBufferWidth; var screenHeight = context.drawingBufferHeight; @@ -165,17 +147,15 @@ define([ sampler : createSampler() }); - processor._framebuffers = { - prior : new Framebuffer({ - context : context, - colorTextures : [ - colorTexture, - ecTexture - ], - depthTexture : depthTexture, - destroyAttachments : false - }) - }; + processor._framebuffer = new Framebuffer({ + context : context, + colorTextures : [ + colorTexture, + ecTexture + ], + depthTexture : depthTexture, + destroyAttachments : false + }); processor._colorTexture = colorTexture; processor._ecAndLogDepthTexture = ecTexture; processor._depthTexture = depthTexture; @@ -184,9 +164,7 @@ define([ var distancesAndEdlStrengthScratch = new Cartesian3(); function createCommands(processor, context) { - processor._drawCommands = {}; - - var blendFS = addConstants(PointCloudEyeDomeLightingShader, 'epsilon8', CesiumMath.EPSILON8); + var blendFS = PointCloudEyeDomeLightingShader; var blendUniformMap = { u_pointCloud_colorTexture : function() { @@ -211,34 +189,24 @@ define([ } }); - var blendCommand = context.createViewportQuadCommand(blendFS, { + processor._drawCommand = context.createViewportQuadCommand(blendFS, { uniformMap : blendUniformMap, renderState : blendRenderState, pass : Pass.CESIUM_3D_TILE, owner : processor }); - // set up clear commands for all frame buffers - var framebuffers = processor._framebuffers; - var clearCommands = {}; - for (var name in framebuffers) { - if (framebuffers.hasOwnProperty(name)) { - clearCommands[name] = new ClearCommand({ - framebuffer : framebuffers[name], - color : new Color(0.0, 0.0, 0.0, 0.0), - depth : 1.0, - renderState : RenderState.fromCache(), - pass : Pass.CESIUM_3D_TILE, - owner : processor - }); - } - } - - processor._drawCommands.blendCommand = blendCommand; - processor._clearCommands = clearCommands; + processor._clearCommand = new ClearCommand({ + framebuffer : processor._framebuffer, + color : new Color(0.0, 0.0, 0.0, 0.0), + depth : 1.0, + renderState : RenderState.fromCache(), + pass : Pass.CESIUM_3D_TILE, + owner : processor + }); } - function createResources(processor, context, dirty) { + function createResources(processor, context) { var screenWidth = context.drawingBufferWidth; var screenHeight = context.drawingBufferHeight; var colorTexture = processor._colorTexture; @@ -247,20 +215,20 @@ define([ ((colorTexture.width !== screenWidth) || (colorTexture.height !== screenHeight)); - if (!defined(colorTexture) || resized || dirty) { - destroyFramebuffers(processor); - createFramebuffers(processor, context); + if (!defined(colorTexture) || resized) { + destroyFramebuffer(processor); + createFramebuffer(processor, context); createCommands(processor, context); nowDirty = true; } return nowDirty; } - function processingSupported(context) { + function isSupported(context) { return context.floatingPointTexture && context.drawBuffers && context.fragmentDepth; } - PointCloudEyeDomeLighting.processingSupported = processingSupported; + PointCloudEyeDomeLighting.isSupported = isSupported; function getECShaderProgram(context, shaderProgram) { var shader = context.shaderCache.getDerivedShaderProgram(shaderProgram, 'EC'); @@ -282,20 +250,20 @@ define([ }); vs.sources.push( - 'varying vec3 v_positionECPS; \n' + + 'varying vec3 v_positionEC; \n' + 'void main() \n' + '{ \n' + ' czm_point_cloud_post_process_main(); \n' + - ' v_positionECPS = (czm_inverseProjection * gl_Position).xyz; \n' + + ' v_positionEC = (czm_inverseProjection * gl_Position).xyz; \n' + '}'); fs.sources.unshift('#extension GL_EXT_draw_buffers : enable \n'); fs.sources.push( - 'varying vec3 v_positionECPS; \n' + + 'varying vec3 v_positionEC; \n' + 'void main() \n' + '{ \n' + ' czm_point_cloud_post_process_main(); \n' + // Write log base 2 depth to alpha for EDL - ' gl_FragData[1] = vec4(v_positionECPS, log2(-v_positionECPS.z)); \n' + + ' gl_FragData[1] = vec4(v_positionEC, log2(-v_positionEC.z)); \n' + '}'); shader = context.shaderCache.createDerivedShaderProgram(shaderProgram, 'EC', { @@ -311,14 +279,14 @@ define([ PointCloudEyeDomeLighting.prototype.update = function(frameState, commandStart, tileset) { var passes = frameState.passes; var isPick = (passes.pick && !passes.render); - if (!processingSupported(frameState.context) || isPick) { + if (!isSupported(frameState.context) || isPick) { return; } - this._strength = tileset.pointShading.eyeDomeLightingStrength; - this._radius = tileset.pointShading.eyeDomeLightingRadius; + this._strength = tileset.pointCloudShading.eyeDomeLightingStrength; + this._radius = tileset.pointCloudShading.eyeDomeLightingRadius; - var dirty = createResources(this, frameState.context, false); + var dirty = createResources(this, frameState.context); // Hijack existing point commands to render into an offscreen FBO. var i; @@ -332,32 +300,27 @@ define([ } var derivedCommand = command.derivedCommands.pointCloudProcessor; if (!defined(derivedCommand) || command.dirty || dirty || - (derivedCommand.framebuffer !== this._framebuffers.prior)) { // Prevent crash when tiles out-of-view come in-view during context size change + (derivedCommand.framebuffer !== this._framebuffer)) { // Prevent crash when tiles out-of-view come in-view during context size change derivedCommand = DrawCommand.shallowClone(command); command.derivedCommands.pointCloudProcessor = derivedCommand; - derivedCommand.framebuffer = this._framebuffers.prior; + derivedCommand.framebuffer = this._framebuffer; derivedCommand.shaderProgram = getECShaderProgram(frameState.context, command.shaderProgram); derivedCommand.castShadows = false; derivedCommand.receiveShadows = false; - var derivedCommandRenderState = clone(derivedCommand.renderState); - derivedCommand.renderState = RenderState.fromCache( - derivedCommandRenderState - ); - derivedCommand.pass = Pass.CESIUM_3D_TILE; // Overrides translucent commands } commandList[i] = derivedCommand; } - var clearCommands = this._clearCommands; - var blendCommand = this._drawCommands.blendCommand; + var clearCommand = this._clearCommand; + var blendCommand = this._drawCommand; // Blend EDL into the main FBO commandList.push(blendCommand); - commandList.push(clearCommands.prior); + commandList.push(clearCommand); }; /** @@ -392,7 +355,7 @@ define([ * @see PointCloudEyeDomeLighting#isDestroyed */ PointCloudEyeDomeLighting.prototype.destroy = function() { - destroyFramebuffers(this); + destroyFramebuffer(this); return destroyObject(this); }; diff --git a/Source/Scene/PointCloudShading.js b/Source/Scene/PointCloudShading.js new file mode 100644 index 000000000000..3c4552339980 --- /dev/null +++ b/Source/Scene/PointCloudShading.js @@ -0,0 +1,82 @@ +define([ + '../Core/defaultValue', + './PointCloudEyeDomeLighting' + ], function( + defaultValue, + PointCloudEyeDomeLighting) { + 'use strict'; + + /** + * Options for performing point attenuation based on geometric error when rendering + * point clouds using 3D Tiles. + * + * @param {Object} [options] Object with the following properties: + * @param {Boolean} [options.attenuation=false] Perform point attenuation based on geometric error. + * @param {Number} [options.geometricErrorScale=1.0] Scale to be applied to each tile's geometric error. + * @param {Number} [options.maximumAttenuation] Maximum attenuation in pixels. Defaults to the Cesium3DTileset's maximumScreenSpaceError. + * @param {Number} [options.baseResolution] Average base resolution for the dataset in meters. Substitute for Geometric Error when not available. + * @param {Boolean} [options.eyeDomeLighting=true] When true, use eye dome lighting when drawing with point attenuation. + * @param {Number} [options.eyeDomeLightingStrength=1.0] Increasing this value increases contrast on slopes and edges. + * @param {Number} [options.eyeDomeLightingRadius=1.0] Increase the thickness of contours from eye dome lighting. + * @constructor + */ + function PointCloudShading(options) { + var pointCloudShading = defaultValue(options, {}); + + /** + * Perform point attenuation based on geometric error. + * @type {Boolean} + * @default false + */ + this.attenuation = defaultValue(pointCloudShading.attenuation, false); + + /** + * Scale to be applied to the geometric error before computing attenuation. + * @type {Number} + * @default 1.0 + */ + this.geometricErrorScale = defaultValue(pointCloudShading.geometricErrorScale, 1.0); + + /** + * Maximum point attenuation in pixels. If undefined, the Cesium3DTileset's maximumScreenSpaceError will be used. + * @type {Number} + */ + this.maximumAttenuation = pointCloudShading.maximumAttenuation; + + /** + * Average base resolution for the dataset in meters. + * Used in place of geometric error when geometric error is 0. + * If undefined, an approximation will be computed for each tile that has geometric error of 0. + * @type {Number} + */ + this.baseResolution = pointCloudShading.baseResolution; + + /** + * Use eye dome lighting when drawing with point attenuation + * Requires support for EXT_frag_depth, OES_texture_float, and WEBGL_draw_buffers extensions in WebGL 1.0, + * otherwise eye dome lighting is ignored. + * + * @type {Boolean} + * @default true + */ + this.eyeDomeLighting = defaultValue(pointCloudShading.eyeDomeLighting, true); + + /** + * Eye dome lighting strength (apparent contrast) + * @type {Number} + * @default 1.0 + */ + this.eyeDomeLightingStrength = defaultValue(pointCloudShading.eyeDomeLightingStrength, 1.0); + + /** + * Thickness of contours from eye dome lighting + * @type {Number} + * @default 1.0 + */ + this.eyeDomeLightingRadius = defaultValue(pointCloudShading.eyeDomeLightingRadius, 1.0); + } + + PointCloudShading.isSupported = PointCloudEyeDomeLighting.isSupported; + + return PointCloudShading; +}); diff --git a/Source/Scene/PointShading.js b/Source/Scene/PointShading.js deleted file mode 100644 index 2d70f4a9f57f..000000000000 --- a/Source/Scene/PointShading.js +++ /dev/null @@ -1,79 +0,0 @@ -define([ - '../Core/defaultValue' -], function( - defaultValue -) { - 'use strict'; - - /** - * Options for performing point attenuation based on geometric error when rendering - * pointclouds using 3D Tiles. - * - * @param {Object} [options] Object with the following properties: - * {Boolean} [options.attenuation=false] Perform point attenuation based on geometric error. - * {Number} [options.geometricErrorScale=1.0] Scale to be applied to each tile's geometric error. - * {Number} [options.maximumAttenuation] Maximum attenuation in pixels. Defaults to the Cesium3DTileset's maximumScreenSpaceError. - * {Number} [options.baseResolution] Average base resolution for the dataset in meters. Substitute for Geometric Error when not available. - * {Boolean} [options.eyeDomeLighting=false] When true, use eye dome lighting when drawing with point attenuation. - * {Number} [options.eyeDomeLightingStrength=1.0] Increasing this value increases contrast on slopes and edges. - * {Number} [options.eyeDomeLightingRadius=1.0] Increase the thickness of contours from eye dome lighting. - * @constructor - */ - function PointShading(options) { - var pointShading = defaultValue(options, {}); - - /** - * Perform point attenuation based on geometric error. - * @type {Boolean} - * @default false - */ - this.attenuation = defaultValue(pointShading.attenuation, false); - - /** - * Scale to be applied to the geometric error before computing attenuation. - * @type {Number} - * @default 1.0 - */ - this.geometricErrorScale = defaultValue(pointShading.geometricErrorScale, 1.0); - - /** - * Maximum point attenuation in pixels. If undefined, the Cesium3DTileset's maximumScreenSpaceError will be used. - * @type {Number} - */ - this.maximumAttenuation = pointShading.maximumAttenuation; - - /** - * Average base resolution for the dataset in meters. - * Used in place of geometric error when geometric error is 0. - * If undefined, an approximation will be computed for each tile that has geometric error of 0. - * @type {Number} - */ - this.baseResolution = pointShading.baseResolution; - - /** - * Use eye dome lighting when drawing with point attenuation - * Requires support for EXT_frag_depth, OES_texture_float, and WEBGL_draw_buffers extensions in WebGL 1.0, - * otherwise eye dome lighting is ignored. - * - * @type {Boolean} - * @default false - */ - this.eyeDomeLighting = defaultValue(pointShading.eyeDomeLighting, false); - - /** - * Eye dome lighting strength (apparent contrast) - * @type {Number} - * @default 1.0 - */ - this.eyeDomeLightingStrength = defaultValue(pointShading.eyeDomeLightingStrength, 1.0); - - /** - * Thickness of contours from eye dome lighting - * @type {Number} - * @default 1.0 - */ - this.eyeDomeLightingRadius = defaultValue(pointShading.eyeDomeLightingRadius, 1.0); - } - - return PointShading; -}); diff --git a/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl b/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl index 78b6ee8fdb80..9a6f86d448f5 100644 --- a/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl +++ b/Source/Shaders/PostProcessFilters/PointCloudEyeDomeLighting.glsl @@ -21,7 +21,7 @@ vec2 neighborContribution(float log2Depth, vec2 padding) void main() { vec4 ecAlphaDepth = texture2D(u_pointCloud_ecAndLogDepthTexture, v_textureCoordinates); - if (length(ecAlphaDepth.xyz) < epsilon8) + if (length(ecAlphaDepth.xyz) < czm_epsilon7) { discard; } diff --git a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js index 621f48d9d646..c614552ded65 100644 --- a/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js +++ b/Source/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModel.js @@ -721,7 +721,7 @@ define([ set : function(value) { pointCloudShading(value); if (defined(that._tileset)) { - that._tileset.pointShading.attenuation = value; + that._tileset.pointCloudShading.attenuation = value; } } }); @@ -743,7 +743,7 @@ define([ if (!isNaN(value)) { geometricErrorScale(value); if (defined(that._tileset)) { - that._tileset.pointShading.geometricErrorScale = value; + that._tileset.pointCloudShading.geometricErrorScale = value; } } } @@ -766,7 +766,7 @@ define([ if (!isNaN(value)) { maximumAttenuation(value); if (defined(that._tileset)) { - that._tileset.pointShading.maximumAttenuation = value === 0 ? undefined : value; + that._tileset.pointCloudShading.maximumAttenuation = value === 0 ? undefined : value; } } } @@ -789,7 +789,7 @@ define([ if (!isNaN(value)) { baseResolution(value); if (defined(that._tileset)) { - that._tileset.pointShading.baseResolution = value === 0 ? undefined : value; + that._tileset.pointCloudShading.baseResolution = value === 0 ? undefined : value; } } } @@ -810,7 +810,7 @@ define([ set : function(value) { eyeDomeLighting(value); if (defined(that._tileset)) { - that._tileset.pointShading.eyeDomeLighting = value; + that._tileset.pointCloudShading.eyeDomeLighting = value; } } }); @@ -832,7 +832,7 @@ define([ if (!isNaN(value)) { eyeDomeLightingStrength(value); if (defined(that._tileset)) { - that._tileset.pointShading.eyeDomeLightingStrength = value; + that._tileset.pointCloudShading.eyeDomeLightingStrength = value; } } } @@ -855,7 +855,7 @@ define([ if (!isNaN(value)) { eyeDomeLightingRadius(value); if (defined(that._tileset)) { - that._tileset.pointShading.eyeDomeLightingRadius = value; + that._tileset.pointCloudShading.eyeDomeLightingRadius = value; } } } @@ -1144,14 +1144,14 @@ define([ this.immediatelyLoadDesiredLevelOfDetail = tileset.immediatelyLoadDesiredLevelOfDetail; this.loadSiblings = tileset.loadSiblings; - var pointShading = tileset.pointShading; - this.pointCloudShading = pointShading.attenuation; - this.geometricErrorScale = pointShading.geometricErrorScale; - this.maximumAttenuation = pointShading.maximumAttenuation ? pointShading.maximumAttenuation: 0.0; - this.baseResolution = pointShading.baseResolution ? pointShading.baseResolution : 0.0; - this.eyeDomeLighting = pointShading.eyeDomeLighting; - this.eyeDomeLightingStrength = pointShading.eyeDomeLightingStrength; - this.eyeDomeLightingRadius = pointShading.eyeDomeLightingRadius; + var pointCloudShading = tileset.pointCloudShading; + this.pointCloudShading = pointCloudShading.attenuation; + this.geometricErrorScale = pointCloudShading.geometricErrorScale; + this.maximumAttenuation = pointCloudShading.maximumAttenuation ? pointCloudShading.maximumAttenuation: 0.0; + this.baseResolution = pointCloudShading.baseResolution ? pointCloudShading.baseResolution : 0.0; + this.eyeDomeLighting = pointCloudShading.eyeDomeLighting; + this.eyeDomeLightingStrength = pointCloudShading.eyeDomeLightingStrength; + this.eyeDomeLightingRadius = pointCloudShading.eyeDomeLightingRadius; } else { this._properties({}); diff --git a/Specs/Scene/PointCloud3DTileContentSpec.js b/Specs/Scene/PointCloud3DTileContentSpec.js index 606b1eb5e8c3..d7fdad687e6b 100644 --- a/Specs/Scene/PointCloud3DTileContentSpec.js +++ b/Specs/Scene/PointCloud3DTileContentSpec.js @@ -441,7 +441,8 @@ defineSuite([ }); }); - it('attenuates points based on geometric error', function() { + var noAttenuationPixelCount = 16; + function attenuationTest(postLoadCallback) { var scene = createScene({ canvas : createCanvas(10, 10) }); @@ -451,159 +452,103 @@ defineSuite([ scene.camera.zoomIn(6); return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - var noAttenuationPixelCount = 16; + tileset.pointCloudShading.eyeDomeLighting = false; + postLoadCallback(scene, tileset); + scene.destroyForSpecs(); + }); + } - tileset.pointShading.attenuation = true; - tileset.pointShading.geometricErrorScale = 1.0; - tileset.pointShading.maximumAttenuation = undefined; - tileset.pointShading.baseResolution = undefined; + it('attenuates points based on geometric error', function() { + return attenuationTest(function(scene, tileset) { + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.geometricErrorScale = 1.0; + tileset.pointCloudShading.maximumAttenuation = undefined; + tileset.pointCloudShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 16; - expect(scene).toRenderPixelCountGreaterThan(noAttenuationPixelCount); - - scene.destroyForSpecs(); + expect(scene).toRenderPixelCountAndCall(function(pixelCount) { + expect(pixelCount).toBeGreaterThan(noAttenuationPixelCount); + }); }); }); it('modulates attenuation using the tileset screen space error', function() { - var scene = createScene({ - canvas : createCanvas(10, 10) - }); - var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); - scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); - scene.fxaa = false; - scene.camera.zoomIn(6); - - return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - var noAttenuationPixelCount = 16; - - tileset.pointShading.attenuation = true; - tileset.pointShading.geometricErrorScale = 1.0; - tileset.pointShading.maximumAttenuation = undefined; - tileset.pointShading.baseResolution = undefined; + return attenuationTest(function(scene, tileset) { + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.geometricErrorScale = 1.0; + tileset.pointCloudShading.maximumAttenuation = undefined; + tileset.pointCloudShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 1; - expect(scene).toRenderPixelCount(noAttenuationPixelCount); - - scene.destroyForSpecs(); + expect(scene).toRenderPixelCountAndCall(function(pixelCount) { + expect(pixelCount).toEqual(noAttenuationPixelCount); + }); }); }); it('modulates attenuation using the maximumAttenuation parameter', function() { - var scene = createScene({ - canvas : createCanvas(10, 10) - }); - var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); - scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); - scene.fxaa = false; - scene.camera.zoomIn(6); - - return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - var noAttenuationPixelCount = 16; - - tileset.pointShading.attenuation = true; - tileset.pointShading.geometricErrorScale = 1.0; - tileset.pointShading.maximumAttenuation = 1; - tileset.pointShading.baseResolution = undefined; + return attenuationTest(function(scene, tileset) { + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.geometricErrorScale = 1.0; + tileset.pointCloudShading.maximumAttenuation = 1; + tileset.pointCloudShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 16; - expect(scene).toRenderPixelCount(noAttenuationPixelCount); - - scene.destroyForSpecs(); + expect(scene).toRenderPixelCountAndCall(function(pixelCount) { + expect(pixelCount).toEqual(noAttenuationPixelCount); + }); }); }); it('modulates attenuation using the baseResolution parameter', function() { - var scene = createScene({ - canvas : createCanvas(10, 10) - }); - var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); - scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); - scene.fxaa = false; - scene.camera.zoomIn(6); - - return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - var noAttenuationPixelCount = 16; - - tileset.pointShading.attenuation = true; - tileset.pointShading.geometricErrorScale = 1.0; - tileset.pointShading.maximumAttenuation = undefined; - tileset.pointShading.baseResolution = CesiumMath.EPSILON20; + return attenuationTest(function(scene, tileset) { + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.geometricErrorScale = 1.0; + tileset.pointCloudShading.maximumAttenuation = undefined; + tileset.pointCloudShading.baseResolution = CesiumMath.EPSILON20; tileset.maximumScreenSpaceError = 16; - expect(scene).toRenderPixelCount(noAttenuationPixelCount); - - scene.destroyForSpecs(); + expect(scene).toRenderPixelCountAndCall(function(pixelCount) { + expect(pixelCount).toEqual(noAttenuationPixelCount); + }); }); }); it('modulates attenuation using the baseResolution parameter', function() { - var scene = createScene({ - canvas : createCanvas(10, 10) - }); - var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); - scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); - scene.fxaa = false; - scene.camera.zoomIn(6); - - return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - var noAttenuationPixelCount = 16; - + return attenuationTest(function(scene, tileset) { // pointCloudNoColorUrl is a single tile with GeometricError = 0, // which results in default baseResolution being computed - tileset.pointShading.attenuation = true; - tileset.pointShading.geometricErrorScale = 1.0; - tileset.pointShading.maximumAttenuation = undefined; - tileset.pointShading.baseResolution = CesiumMath.EPSILON20; + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.geometricErrorScale = 1.0; + tileset.pointCloudShading.maximumAttenuation = undefined; + tileset.pointCloudShading.baseResolution = CesiumMath.EPSILON20; tileset.maximumScreenSpaceError = 16; - expect(scene).toRenderPixelCount(noAttenuationPixelCount); - - scene.destroyForSpecs(); + expect(scene).toRenderPixelCountAndCall(function(pixelCount) { + expect(pixelCount).toEqual(noAttenuationPixelCount); + }); }); }); it('modulates attenuation using the geometricErrorScale parameter', function() { - var scene = createScene({ - canvas : createCanvas(10, 10) - }); - var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); - scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); - scene.fxaa = false; - scene.camera.zoomIn(6); - - return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - var noAttenuationPixelCount = 16; - - tileset.pointShading.attenuation = true; - tileset.pointShading.geometricErrorScale = 0.0; - tileset.pointShading.maximumAttenuation = undefined; - tileset.pointShading.baseResolution = undefined; + return attenuationTest(function(scene, tileset) { + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.geometricErrorScale = 0.0; + tileset.pointCloudShading.maximumAttenuation = undefined; + tileset.pointCloudShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 1; - expect(scene).toRenderPixelCount(noAttenuationPixelCount); - - scene.destroyForSpecs(); + expect(scene).toRenderPixelCountAndCall(function(pixelCount) { + expect(pixelCount).toEqual(noAttenuationPixelCount); + }); }); }); - it('attenuates points based on geometric error in 2D', function() { - var scene = createScene({ - canvas : createCanvas(10, 10) - }); - var center = new Cartesian3.fromRadians(centerLongitude, centerLatitude, 5.0); - scene.camera.lookAt(center, new HeadingPitchRange(0.0, -1.57, 5.0)); - scene.fxaa = false; - scene.camera.zoomIn(6); - - return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - - var noAttenuationPixelCount = 16; - + return attenuationTest(function(scene, tileset) { scene.morphTo2D(0); - tileset.pointShading.attenuation = true; - tileset.pointShading.geometricErrorScale = 1.0; - tileset.pointShading.maximumAttenuation = undefined; - tileset.pointShading.baseResolution = undefined; + tileset.pointCloudShading.attenuation = true; + tileset.pointCloudShading.geometricErrorScale = 1.0; + tileset.pointCloudShading.maximumAttenuation = undefined; + tileset.pointCloudShading.baseResolution = undefined; tileset.maximumScreenSpaceError = 16; - expect(scene).toRenderPixelCountGreaterThan(noAttenuationPixelCount); - - scene.destroyForSpecs(); + expect(scene).toRenderPixelCountAndCall(function(pixelCount) { + expect(pixelCount).toBeGreaterThan(noAttenuationPixelCount); + }); }); }); diff --git a/Specs/Scene/PointCloudEyeDomeLightingSpec.js b/Specs/Scene/PointCloudEyeDomeLightingSpec.js index b87801796fc6..f2d33a541e44 100644 --- a/Specs/Scene/PointCloudEyeDomeLightingSpec.js +++ b/Specs/Scene/PointCloudEyeDomeLightingSpec.js @@ -1,29 +1,29 @@ defineSuite([ - 'Core/Cartesian3', - 'Core/Color', - 'Core/defined', - 'Core/HeadingPitchRange', - 'Core/HeadingPitchRoll', - 'Core/Math', - 'Core/Transforms', - 'Core/PerspectiveFrustum', - 'Scene/PointCloud3DTileContent', - 'Scene/PointCloudEyeDomeLighting', - 'Specs/Cesium3DTilesTester', - 'Specs/createScene' -], 'Scene/PointCloudEyeDomeLighting', function( - Cartesian3, - Color, - defined, - HeadingPitchRange, - HeadingPitchRoll, - CesiumMath, - Transforms, - PerspectiveFrustum, - PointCloud3DTileContent, - PointCloudEyeDomeLighting, - Cesium3DTilesTester, - createScene) { + 'Core/Cartesian3', + 'Core/Color', + 'Core/defined', + 'Core/HeadingPitchRange', + 'Core/HeadingPitchRoll', + 'Core/Math', + 'Core/Transforms', + 'Core/PerspectiveFrustum', + 'Scene/PointCloud3DTileContent', + 'Scene/PointCloudEyeDomeLighting', + 'Specs/Cesium3DTilesTester', + 'Specs/createScene' + ], 'Scene/PointCloudEyeDomeLighting', function( + Cartesian3, + Color, + defined, + HeadingPitchRange, + HeadingPitchRoll, + CesiumMath, + Transforms, + PerspectiveFrustum, + PointCloud3DTileContent, + PointCloudEyeDomeLighting, + Cesium3DTilesTester, + createScene) { 'use strict'; var scene; @@ -40,7 +40,6 @@ defineSuite([ beforeAll(function() { scene = createScene(); - scene.frameState.passes.render = true; }); afterAll(function() { @@ -62,16 +61,16 @@ defineSuite([ it('adds a clear command and a post-processing draw call', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - if (!PointCloudEyeDomeLighting.processingSupported(scene.frameState.context)) { + if (!PointCloudEyeDomeLighting.isSupported(scene.frameState.context)) { return; } - tileset.pointShading.eyeDomeLighting = true; + tileset.pointCloudShading.eyeDomeLighting = true; scene.renderForSpecs(); var originalLength = scene.frameState.commandList.length; - tileset.pointShading.attenuation = true; + tileset.pointCloudShading.attenuation = true; scene.renderForSpecs(); var newLength = scene.frameState.commandList.length; expect(newLength).toEqual(originalLength + 2); @@ -80,12 +79,12 @@ defineSuite([ it('does not change commands for pick calls', function() { return Cesium3DTilesTester.loadTileset(scene, pointCloudNoColorUrl).then(function(tileset) { - tileset.pointShading.eyeDomeLighting = true; + tileset.pointCloudShading.eyeDomeLighting = true; scene.pickForSpecs(); var originalLength = scene.frameState.commandList.length; - tileset.pointShading.attenuation = true; + tileset.pointCloudShading.attenuation = true; scene.pickForSpecs(); var newLength = scene.frameState.commandList.length; expect(newLength).toEqual(originalLength); diff --git a/Specs/Scene/PointCloudShadingSpec.js b/Specs/Scene/PointCloudShadingSpec.js new file mode 100644 index 000000000000..c4d2f36a139d --- /dev/null +++ b/Specs/Scene/PointCloudShadingSpec.js @@ -0,0 +1,33 @@ +defineSuite([ + 'Scene/PointCloudShading' + ], function( + PointCloudShading) { + 'use strict'; + + it('creates expected instance from raw assignment and construction', function() { + var pointCloudShading = new PointCloudShading(); + expect(pointCloudShading.attenuation).toEqual(false); + expect(pointCloudShading.geometricErrorScale).toEqual(1.0); + expect(pointCloudShading.maximumAttenuation).not.toBeDefined(); + expect(pointCloudShading.baseResolution).not.toBeDefined(); + expect(pointCloudShading.eyeDomeLighting).toEqual(true); + expect(pointCloudShading.eyeDomeLightingStrength).toEqual(1.0); + expect(pointCloudShading.eyeDomeLightingRadius).toEqual(1.0); + + var options = { + geometricErrorScale : 2.0, + maximumAttenuation : 16, + baseResolution : 0.1, + eyeDomeLightingStrength : 0.1, + eyeDomeLightingRadius : 2.0 + }; + pointCloudShading = new PointCloudShading(options); + expect(pointCloudShading.attenuation).toEqual(false); + expect(pointCloudShading.geometricErrorScale).toEqual(options.geometricErrorScale); + expect(pointCloudShading.maximumAttenuation).toEqual(options.maximumAttenuation); + expect(pointCloudShading.baseResolution).toEqual(options.baseResolution); + expect(pointCloudShading.eyeDomeLighting).toEqual(true); + expect(pointCloudShading.eyeDomeLightingStrength).toEqual(options.eyeDomeLightingStrength); + expect(pointCloudShading.eyeDomeLightingRadius).toEqual(options.eyeDomeLightingRadius); + }); +}); diff --git a/Specs/Scene/PointShadingSpec.js b/Specs/Scene/PointShadingSpec.js deleted file mode 100644 index f2f38b323e74..000000000000 --- a/Specs/Scene/PointShadingSpec.js +++ /dev/null @@ -1,34 +0,0 @@ -defineSuite([ - 'Scene/PointShading' - ], function( - PointShading - ) { - 'use strict'; - - it('creates expected instance from raw assignment and construction', function() { - var pointShading = new PointShading(); - expect(pointShading.attenuation).toEqual(false); - expect(pointShading.geometricErrorScale).toEqual(1.0); - expect(pointShading.maximumAttenuation).not.toBeDefined(); - expect(pointShading.baseResolution).not.toBeDefined(); - expect(pointShading.eyeDomeLighting).toEqual(false); - expect(pointShading.eyeDomeLightingStrength).toEqual(1.0); - expect(pointShading.eyeDomeLightingRadius).toEqual(1.0); - - var options = { - geometricErrorScale : 2.0, - maximumAttenuation : 16, - baseResolution : 0.1, - eyeDomeLightingStrength : 0.1, - eyeDomeLightingRadius : 2.0 - }; - pointShading = new PointShading(options); - expect(pointShading.attenuation).toEqual(false); - expect(pointShading.geometricErrorScale).toEqual(options.geometricErrorScale); - expect(pointShading.maximumAttenuation).toEqual(options.maximumAttenuation); - expect(pointShading.baseResolution).toEqual(options.baseResolution); - expect(pointShading.eyeDomeLighting).toEqual(false); - expect(pointShading.eyeDomeLightingStrength).toEqual(options.eyeDomeLightingStrength); - expect(pointShading.eyeDomeLightingRadius).toEqual(options.eyeDomeLightingRadius); - }); -}); diff --git a/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js b/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js index b90a16cf8c42..3b0294b3e648 100644 --- a/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js +++ b/Specs/Widgets/Cesium3DTilesInspector/Cesium3DTilesInspectorViewModelSpec.js @@ -164,51 +164,51 @@ defineSuite([ it('pointCloudShading', function() { viewModel.pointCloudShading = true; - expect(viewModel.tileset.pointShading.attenuation).toBe(true); + expect(viewModel.tileset.pointCloudShading.attenuation).toBe(true); viewModel.pointCloudShading = false; - expect(viewModel.tileset.pointShading.attenuation).toBe(false); + expect(viewModel.tileset.pointCloudShading.attenuation).toBe(false); }); it('geometricErrorScale', function() { viewModel.geometricErrorScale = 1.0; - expect(viewModel.tileset.pointShading.geometricErrorScale).toBe(1.0); + expect(viewModel.tileset.pointCloudShading.geometricErrorScale).toBe(1.0); viewModel.geometricErrorScale = 0.0; - expect(viewModel.tileset.pointShading.geometricErrorScale).toBe(0.0); + expect(viewModel.tileset.pointCloudShading.geometricErrorScale).toBe(0.0); }); it('maximumAttenuation', function() { viewModel.maximumAttenuation = 1.0; - expect(viewModel.tileset.pointShading.maximumAttenuation).toBe(1.0); + expect(viewModel.tileset.pointCloudShading.maximumAttenuation).toBe(1.0); viewModel.maximumAttenuation = 0.0; - expect(viewModel.tileset.pointShading.maximumAttenuation).not.toBeDefined(); + expect(viewModel.tileset.pointCloudShading.maximumAttenuation).not.toBeDefined(); }); it('baseResolution', function() { viewModel.baseResolution = 1.0; - expect(viewModel.tileset.pointShading.baseResolution).toBe(1.0); + expect(viewModel.tileset.pointCloudShading.baseResolution).toBe(1.0); viewModel.baseResolution = 0.0; - expect(viewModel.tileset.pointShading.baseResolution).not.toBeDefined(); + expect(viewModel.tileset.pointCloudShading.baseResolution).not.toBeDefined(); }); it('eyeDomeLighting', function() { viewModel.eyeDomeLighting = true; - expect(viewModel.tileset.pointShading.eyeDomeLighting).toBe(true); + expect(viewModel.tileset.pointCloudShading.eyeDomeLighting).toBe(true); viewModel.eyeDomeLighting = false; - expect(viewModel.tileset.pointShading.eyeDomeLighting).toBe(false); + expect(viewModel.tileset.pointCloudShading.eyeDomeLighting).toBe(false); }); it('eyeDomeLightingStrength', function() { viewModel.eyeDomeLightingStrength = 1.0; - expect(viewModel.tileset.pointShading.eyeDomeLightingStrength).toBe(1.0); + expect(viewModel.tileset.pointCloudShading.eyeDomeLightingStrength).toBe(1.0); viewModel.eyeDomeLightingStrength = 0.0; - expect(viewModel.tileset.pointShading.eyeDomeLightingStrength).toBe(0.0); + expect(viewModel.tileset.pointCloudShading.eyeDomeLightingStrength).toBe(0.0); }); it('eyeDomeLightingRadius', function() { viewModel.eyeDomeLightingRadius = 1.0; - expect(viewModel.tileset.pointShading.eyeDomeLightingRadius).toBe(1.0); + expect(viewModel.tileset.pointCloudShading.eyeDomeLightingRadius).toBe(1.0); viewModel.eyeDomeLightingRadius = 0.0; - expect(viewModel.tileset.pointShading.eyeDomeLightingRadius).toBe(0.0); + expect(viewModel.tileset.pointCloudShading.eyeDomeLightingRadius).toBe(0.0); }); }); diff --git a/Specs/addDefaultMatchers.js b/Specs/addDefaultMatchers.js index 4cede2b9378c..775b3f3e996e 100644 --- a/Specs/addDefaultMatchers.js +++ b/Specs/addDefaultMatchers.js @@ -241,31 +241,17 @@ define([ }; }, - toRenderPixelCount : function(util, customEqualityTesters) { - return { - compare : function(actual, expected) { - var actualRgba = renderAndReadPixels(actual); - - var webglStub = !!window.webglStub; - if (!webglStub) { - return {pass : countRenderedPixels(actualRgba) === expected}; - } - - return { - pass : true - }; - } - }; - }, - - toRenderPixelCountGreaterThan : function(util, customEqualityTesters) { + toRenderAndCall : function(util, customEqualityTesters) { return { compare : function(actual, expected) { var actualRgba = renderAndReadPixels(actual); var webglStub = !!window.webglStub; if (!webglStub) { - return {pass : countRenderedPixels(actualRgba) > expected}; + // The callback may have expectations that fail, which still makes the + // spec fail, as we desired, even though this matcher sets pass to true. + var callback = expected; + callback(actualRgba); } return { @@ -275,7 +261,7 @@ define([ }; }, - toRenderAndCall : function(util, customEqualityTesters) { + toRenderPixelCountAndCall : function(util, customEqualityTesters) { return { compare : function(actual, expected) { var actualRgba = renderAndReadPixels(actual); @@ -285,7 +271,7 @@ define([ // The callback may have expectations that fail, which still makes the // spec fail, as we desired, even though this matcher sets pass to true. var callback = expected; - callback(actualRgba); + callback(countRenderedPixels(actualRgba)); } return { From 314355c96dd0cfc2251c006f7e9daae8643cb003 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Mon, 29 Jan 2018 17:47:19 -0500 Subject: [PATCH 11/13] add check for points with color, fix for points with normal lighting --- Source/Scene/PointCloud3DTileContent.js | 2 +- Source/Scene/PointCloudEyeDomeLighting.js | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Source/Scene/PointCloud3DTileContent.js b/Source/Scene/PointCloud3DTileContent.js index ef1180c3a58a..653d1c0e7c1b 100644 --- a/Source/Scene/PointCloud3DTileContent.js +++ b/Source/Scene/PointCloud3DTileContent.js @@ -1095,7 +1095,7 @@ define([ vs += ' normal = czm_normal * normal; \n' + ' float diffuseStrength = czm_getLambertDiffuse(czm_sunDirectionEC, normal); \n' + ' diffuseStrength = max(diffuseStrength, 0.4); \n' + // Apply some ambient lighting - ' color *= diffuseStrength; \n'; + ' color.xyz *= diffuseStrength; \n'; } vs += ' v_color = color; \n' + diff --git a/Source/Scene/PointCloudEyeDomeLighting.js b/Source/Scene/PointCloudEyeDomeLighting.js index 5501af809177..880b1775ac08 100644 --- a/Source/Scene/PointCloudEyeDomeLighting.js +++ b/Source/Scene/PointCloudEyeDomeLighting.js @@ -70,7 +70,7 @@ define([ 'use strict'; /** - * Eye dome lighting. + * Eye dome lighting. Does not support points with per-point translucency, but does allow translucent styling against the globe. * Requires support for EXT_frag_depth, OES_texture_float, and WEBGL_draw_buffers extensions in WebGL 1.0. * * @private @@ -295,7 +295,7 @@ define([ for (i = commandStart; i < commandEnd; ++i) { var command = commandList[i]; - if (command.primitiveType !== PrimitiveType.POINTS) { + if (command.primitiveType !== PrimitiveType.POINTS || command.pass === Pass.TRANSLUCENT) { continue; } var derivedCommand = command.derivedCommands.pointCloudProcessor; @@ -308,8 +308,6 @@ define([ derivedCommand.shaderProgram = getECShaderProgram(frameState.context, command.shaderProgram); derivedCommand.castShadows = false; derivedCommand.receiveShadows = false; - - derivedCommand.pass = Pass.CESIUM_3D_TILE; // Overrides translucent commands } commandList[i] = derivedCommand; From c4a3f6bb32603088680cf28c47cc8e0a95618922 Mon Sep 17 00:00:00 2001 From: Kangning Li Date: Tue, 30 Jan 2018 12:09:20 -0500 Subject: [PATCH 12/13] comments for PointCloudShading.isSupported --- Source/Scene/PointCloudShading.js | 10 +++++++++- Specs/Scene/PointCloudShadingSpec.js | 14 ++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Source/Scene/PointCloudShading.js b/Source/Scene/PointCloudShading.js index 3c4552339980..d51503f20ccd 100644 --- a/Source/Scene/PointCloudShading.js +++ b/Source/Scene/PointCloudShading.js @@ -76,7 +76,15 @@ define([ this.eyeDomeLightingRadius = defaultValue(pointCloudShading.eyeDomeLightingRadius, 1.0); } - PointCloudShading.isSupported = PointCloudEyeDomeLighting.isSupported; + /** + * Determines if point cloud shading is supported. + * + * @param {Scene} scene The scene. + * @returns {Boolean} true if point cloud shading is supported; otherwise, returns false + */ + PointCloudShading.isSupported = function(scene) { + return PointCloudEyeDomeLighting.isSupported(scene.context); + }; return PointCloudShading; }); diff --git a/Specs/Scene/PointCloudShadingSpec.js b/Specs/Scene/PointCloudShadingSpec.js index c4d2f36a139d..a08282804630 100644 --- a/Specs/Scene/PointCloudShadingSpec.js +++ b/Specs/Scene/PointCloudShadingSpec.js @@ -1,7 +1,9 @@ defineSuite([ - 'Scene/PointCloudShading' + 'Scene/PointCloudShading', + 'Specs/createScene' ], function( - PointCloudShading) { + PointCloudShading, + createScene) { 'use strict'; it('creates expected instance from raw assignment and construction', function() { @@ -30,4 +32,12 @@ defineSuite([ expect(pointCloudShading.eyeDomeLightingStrength).toEqual(options.eyeDomeLightingStrength); expect(pointCloudShading.eyeDomeLightingRadius).toEqual(options.eyeDomeLightingRadius); }); + + it('provides a method for checking if point cloud shading is supported', function() { + var scene = createScene(); + var context = scene.context; + var expectedSupport = context.floatingPointTexture && context.drawBuffers && context.fragmentDepth; + expect(PointCloudShading.isSupported(scene)).toEqual(expectedSupport); + scene.destroyForSpecs(); + }); }); From 4400531fd50a5f427203b3772bc7d08f4e0fb513 Mon Sep 17 00:00:00 2001 From: Sean Lilley Date: Tue, 30 Jan 2018 12:53:41 -0500 Subject: [PATCH 13/13] Update PointCloudShading.js --- Source/Scene/PointCloudShading.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Scene/PointCloudShading.js b/Source/Scene/PointCloudShading.js index d51503f20ccd..9d160e27bf4f 100644 --- a/Source/Scene/PointCloudShading.js +++ b/Source/Scene/PointCloudShading.js @@ -80,7 +80,7 @@ define([ * Determines if point cloud shading is supported. * * @param {Scene} scene The scene. - * @returns {Boolean} true if point cloud shading is supported; otherwise, returns false + * @returns {Boolean} true if point cloud shading is supported; otherwise, returns false */ PointCloudShading.isSupported = function(scene) { return PointCloudEyeDomeLighting.isSupported(scene.context);
Maximum Screen Space Error
Maximum Attenuation - +