diff --git a/CHANGES.md b/CHANGES.md index 66eab04608e3..f3a4c3e761f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,6 +34,8 @@ Change Log * `TerrainProvider` now optionally exposes an `availability` property that can be used to query the terrain level that is available at a location or in a rectangle. Currently only `CesiumTerrainProvider` exposes this property. * Added `sampleTerrainMostDetailed` to sample the height of an array of positions using the best available terrain data at each point. This requires a `TerrainProvider` with the `availability` property. * Added 2D and Columbus View support for models using the RTC extension or whose vertices are in WGS84 coordinates. [#4922](https://github.com/AnalyticalGraphicsInc/cesium/pull/4922) +* Transparent parts of billboards, labels, and points no longer overwrite parts of the scene behind them. [#4886](https://github.com/AnalyticalGraphicsInc/cesium/pull/4886) + * Added `blendOption` property to `BillboardCollection`, `LabelCollection`, and `PointPrimitiveCollection`. The default is `BlendOption.OPAQUE_AND_TRANSLUCENT`; however, if all billboards, labels, or points are either completely opaque or completely translucent, `blendOption` can be changed to `BlendOption.OPAQUE` or `BlendOption.TRANSLUCENT`, respectively, to increase performance by up to 2x. ### 1.29 - 2017-01-02 diff --git a/Source/Scene/BillboardCollection.js b/Source/Scene/BillboardCollection.js index f20fb3afc4af..dae5d958ccee 100644 --- a/Source/Scene/BillboardCollection.js +++ b/Source/Scene/BillboardCollection.js @@ -28,6 +28,7 @@ define([ '../Shaders/BillboardCollectionVS', './Billboard', './BlendingState', + './BlendOption', './HeightReference', './HorizontalOrigin', './SceneMode', @@ -62,6 +63,7 @@ define([ BillboardCollectionVS, Billboard, BlendingState, + BlendOption, HeightReference, HorizontalOrigin, SceneMode, @@ -133,6 +135,9 @@ define([ * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each billboard from model to world coordinates. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. * @param {Scene} [options.scene] Must be passed in for billboards that use the height reference property or will be depth tested against the globe. + * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The billboard blending option. The default + * is used for rendering both opaque and translucent billboards. However, if either all of the billboards are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x. * * @performance For best performance, prefer a few collections, each with many billboards, to * many collections with only a few billboards each. Organize collections so that billboards @@ -168,9 +173,11 @@ define([ this._textureAtlasGUID = undefined; this._destroyTextureAtlas = true; this._sp = undefined; - this._rs = undefined; - this._vaf = undefined; + this._spTranslucent = undefined; this._spPick = undefined; + this._rsOpaque = undefined; + this._rsTranslucent = undefined; + this._vaf = undefined; this._billboards = []; this._billboardsToUpdate = []; @@ -268,6 +275,17 @@ define([ */ this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + /** + * The billboard blending option. The default is used for rendering both opaque and translucent billboards. + * However, if either all of the billboards are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve + * performance by up to 2x. + * @type {BlendOption} + * @default BlendOption.OPAQUE_AND_TRANSLUCENT + */ + this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT); + this._blendOption = undefined; + this._mode = SceneMode.SCENE3D; // The buffer usage for each attribute is determined based on the usage of the attribute over time. @@ -1427,97 +1445,201 @@ define([ } updateBoundingVolume(this, frameState, boundingVolume); - var va; - var vaLength; - var command; - var vs; - var fs; - var j; - - var commandList = frameState.commandList; + var blendOptionChanged = this._blendOption !== this.blendOption; + this._blendOption = this.blendOption; - if (pass.render) { - var colorList = this._colorCommands; + if (blendOptionChanged) { + this._rsOpaque = RenderState.fromCache({ + depthTest : { + enabled : true, + func : WebGLConstants.LEQUAL // Allows label glyphs and billboards to overlap. + }, + depthMask : true + }); - if (!defined(this._rs)) { - this._rs = RenderState.fromCache({ + if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + this._rsTranslucent = RenderState.fromCache({ depthTest : { enabled : true, func : WebGLConstants.LEQUAL // Allows label glyphs and billboards to overlap. }, + depthMask : false, blending : BlendingState.ALPHA_BLEND }); + } else { + this._rsTranslucent = undefined; } + } - if (!defined(this._sp) || - (this._shaderRotation !== this._compiledShaderRotation) || - (this._shaderAlignedAxis !== this._compiledShaderAlignedAxis) || - (this._shaderScaleByDistance !== this._compiledShaderScaleByDistance) || - (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistance) || - (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistance) || - (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayCondition)) { + if (blendOptionChanged || + (this._shaderRotation !== this._compiledShaderRotation) || + (this._shaderAlignedAxis !== this._compiledShaderAlignedAxis) || + (this._shaderScaleByDistance !== this._compiledShaderScaleByDistance) || + (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistance) || + (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistance) || + (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayCondition)) { - vs = new ShaderSource({ - sources : [BillboardCollectionVS] - }); - if (this._instanced) { - vs.defines.push('INSTANCED'); - } - if (this._shaderRotation) { - vs.defines.push('ROTATION'); - } - if (this._shaderAlignedAxis) { - vs.defines.push('ALIGNED_AXIS'); - } - if (this._shaderScaleByDistance) { - vs.defines.push('EYE_DISTANCE_SCALING'); - } - if (this._shaderTranslucencyByDistance) { - vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); - } - if (this._shaderPixelOffsetScaleByDistance) { - vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET'); - } - if (this._shaderDistanceDisplayCondition) { - vs.defines.push('DISTANCE_DISPLAY_CONDITION'); - } + vs = new ShaderSource({ + sources : [BillboardCollectionVS] + }); + if (this._instanced) { + vs.defines.push('INSTANCED'); + } + if (this._shaderRotation) { + vs.defines.push('ROTATION'); + } + if (this._shaderAlignedAxis) { + vs.defines.push('ALIGNED_AXIS'); + } + if (this._shaderScaleByDistance) { + vs.defines.push('EYE_DISTANCE_SCALING'); + } + if (this._shaderTranslucencyByDistance) { + vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); + } + if (this._shaderPixelOffsetScaleByDistance) { + vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET'); + } + if (this._shaderDistanceDisplayCondition) { + vs.defines.push('DISTANCE_DISPLAY_CONDITION'); + } + if (this._blendOption === BlendOption.OPAQUE || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + fs = new ShaderSource({ + defines : ['OPAQUE'], + sources : [BillboardCollectionFS] + }); this._sp = ShaderProgram.replaceCache({ context : context, shaderProgram : this._sp, vertexShaderSource : vs, - fragmentShaderSource : BillboardCollectionFS, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + } else { + this._sp = this._sp && this._sp.destroy(); + this._sp = undefined; + } + + if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + fs = new ShaderSource({ + defines : ['TRANSLUCENT'], + sources : [BillboardCollectionFS] + }); + this._spTranslucent = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._spTranslucent, + vertexShaderSource : vs, + fragmentShaderSource : fs, attributeLocations : attributeLocations }); + } else { + this._spTranslucent = this._spTranslucent && this._spTranslucent.destroy(); + this._spTranslucent = undefined; + } + + this._compiledShaderRotation = this._shaderRotation; + this._compiledShaderAlignedAxis = this._shaderAlignedAxis; + this._compiledShaderScaleByDistance = this._shaderScaleByDistance; + this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; + this._compiledShaderPixelOffsetScaleByDistance = this._shaderPixelOffsetScaleByDistance; + this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; + } + + if (!defined(this._spPick) || + (this._shaderRotation !== this._compiledShaderRotationPick) || + (this._shaderAlignedAxis !== this._compiledShaderAlignedAxisPick) || + (this._shaderScaleByDistance !== this._compiledShaderScaleByDistancePick) || + (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistancePick) || + (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistancePick) || + (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayConditionPick)) { + + vs = new ShaderSource({ + defines : ['RENDER_FOR_PICK'], + sources : [BillboardCollectionVS] + }); - this._compiledShaderRotation = this._shaderRotation; - this._compiledShaderAlignedAxis = this._shaderAlignedAxis; - this._compiledShaderScaleByDistance = this._shaderScaleByDistance; - this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; - this._compiledShaderPixelOffsetScaleByDistance = this._shaderPixelOffsetScaleByDistance; - this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; + if(this._instanced) { + vs.defines.push('INSTANCED'); } + if (this._shaderRotation) { + vs.defines.push('ROTATION'); + } + if (this._shaderAlignedAxis) { + vs.defines.push('ALIGNED_AXIS'); + } + if (this._shaderScaleByDistance) { + vs.defines.push('EYE_DISTANCE_SCALING'); + } + if (this._shaderTranslucencyByDistance) { + vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); + } + if (this._shaderPixelOffsetScaleByDistance) { + vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET'); + } + if (this._shaderDistanceDisplayCondition) { + vs.defines.push('DISTANCE_DISPLAY_CONDITION'); + } + + fs = new ShaderSource({ + defines : ['RENDER_FOR_PICK'], + sources : [BillboardCollectionFS] + }); + + this._spPick = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._spPick, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + this._compiledShaderRotationPick = this._shaderRotation; + this._compiledShaderAlignedAxisPick = this._shaderAlignedAxis; + this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance; + this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance; + this._compiledShaderPixelOffsetScaleByDistancePick = this._shaderPixelOffsetScaleByDistance; + this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition; + } + + var va; + var vaLength; + var command; + var vs; + var fs; + var j; + + var commandList = frameState.commandList; + + if (pass.render) { + var colorList = this._colorCommands; + + var opaque = this._blendOption === BlendOption.OPAQUE; + var opaqueAndTranslucent = this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT; va = this._vaf.va; vaLength = va.length; colorList.length = vaLength; - for (j = 0; j < vaLength; ++j) { + var totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength; + for (j = 0; j < totalLength; ++j) { command = colorList[j]; if (!defined(command)) { - command = colorList[j] = new DrawCommand({ - pass : Pass.OPAQUE, - owner : this - }); + command = colorList[j] = new DrawCommand(); } + var opaqueCommand = opaque || (opaqueAndTranslucent && j % 2 === 0); + + command.pass = opaqueCommand ? Pass.OPAQUE : Pass.TRANSLUCENT; + command.owner = this; + + var index = opaqueAndTranslucent ? Math.floor(j / 2.0) : j; command.boundingVolume = boundingVolume; command.modelMatrix = modelMatrix; - command.count = va[j].indicesCount; - command.shaderProgram = this._sp; + command.count = va[index].indicesCount; + command.shaderProgram = opaqueCommand ? this._sp : this._spTranslucent; command.uniformMap = this._uniforms; - command.vertexArray = va[j].va; - command.renderState = this._rs; + command.vertexArray = va[index].va; + command.renderState = opaqueCommand ? this._rsOpaque : this._rsTranslucent; command.debugShowBoundingVolume = this.debugShowBoundingVolume; if (this._instanced) { @@ -1532,61 +1654,6 @@ define([ if (picking) { var pickList = this._pickCommands; - if (!defined(this._spPick) || - (this._shaderRotation !== this._compiledShaderRotationPick) || - (this._shaderAlignedAxis !== this._compiledShaderAlignedAxisPick) || - (this._shaderScaleByDistance !== this._compiledShaderScaleByDistancePick) || - (this._shaderTranslucencyByDistance !== this._compiledShaderTranslucencyByDistancePick) || - (this._shaderPixelOffsetScaleByDistance !== this._compiledShaderPixelOffsetScaleByDistancePick) || - (this._shaderDistanceDisplayCondition !== this._compiledShaderDistanceDisplayConditionPick)) { - - vs = new ShaderSource({ - defines : ['RENDER_FOR_PICK'], - sources : [BillboardCollectionVS] - }); - - if(this._instanced) { - vs.defines.push('INSTANCED'); - } - if (this._shaderRotation) { - vs.defines.push('ROTATION'); - } - if (this._shaderAlignedAxis) { - vs.defines.push('ALIGNED_AXIS'); - } - if (this._shaderScaleByDistance) { - vs.defines.push('EYE_DISTANCE_SCALING'); - } - if (this._shaderTranslucencyByDistance) { - vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); - } - if (this._shaderPixelOffsetScaleByDistance) { - vs.defines.push('EYE_DISTANCE_PIXEL_OFFSET'); - } - if (this._shaderDistanceDisplayCondition) { - vs.defines.push('DISTANCE_DISPLAY_CONDITION'); - } - - fs = new ShaderSource({ - defines : ['RENDER_FOR_PICK'], - sources : [BillboardCollectionFS] - }); - - this._spPick = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._spPick, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - this._compiledShaderRotationPick = this._shaderRotation; - this._compiledShaderAlignedAxisPick = this._shaderAlignedAxis; - this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance; - this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance; - this._compiledShaderPixelOffsetScaleByDistancePick = this._shaderPixelOffsetScaleByDistance; - this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition; - } - va = this._vaf.va; vaLength = va.length; @@ -1606,7 +1673,7 @@ define([ command.shaderProgram = this._spPick; command.uniformMap = this._uniforms; command.vertexArray = va[j].va; - command.renderState = this._rs; + command.renderState = this._rsOpaque; if (this._instanced) { command.count = 6; @@ -1658,6 +1725,7 @@ define([ this._textureAtlas = this._destroyTextureAtlas && this._textureAtlas && this._textureAtlas.destroy(); this._sp = this._sp && this._sp.destroy(); + this._spTranslucent = this._spTranslucent && this._spTranslucent.destroy(); this._spPick = this._spPick && this._spPick.destroy(); this._vaf = this._vaf && this._vaf.destroy(); destroyBillboards(this._billboards); diff --git a/Source/Scene/BlendOption.js b/Source/Scene/BlendOption.js new file mode 100644 index 000000000000..4f2b7ef1702c --- /dev/null +++ b/Source/Scene/BlendOption.js @@ -0,0 +1,37 @@ +/*global define*/ +define([ + '../Core/freezeObject' +], function( + freezeObject) { + 'use strict'; + + /** + * Determines how opaque and translucent parts of billboards, points, and labels are blended with the scene. + * + * @exports BlendOption + */ + var BlendOption = { + /** + * The billboards, points, or labels in the collection are completely opaque. + * @type {Number} + * @constant + */ + OPAQUE : 0, + + /** + * The billboards, points, or labels in the collection are completely translucent. + * @type {Number} + * @constant + */ + TRANSLUCENT : 1, + + /** + * The billboards, points, or labels in the collection are both opaque and translucent. + * @type {Number} + * @constant + */ + OPAQUE_AND_TRANSLUCENT : 2 + }; + + return freezeObject(BlendOption); +}); diff --git a/Source/Scene/LabelCollection.js b/Source/Scene/LabelCollection.js index f7416ffc40d7..af953981629f 100644 --- a/Source/Scene/LabelCollection.js +++ b/Source/Scene/LabelCollection.js @@ -11,6 +11,7 @@ define([ '../Core/Matrix4', '../Core/writeTextToCanvas', './BillboardCollection', + './BlendOption', './HorizontalOrigin', './Label', './LabelStyle', @@ -28,6 +29,7 @@ define([ Matrix4, writeTextToCanvas, BillboardCollection, + BlendOption, HorizontalOrigin, Label, LabelStyle, @@ -440,6 +442,9 @@ define([ * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each label from model to world coordinates. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. * @param {Scene} [options.scene] Must be passed in for labels that use the height reference property or will be depth tested against the globe. + * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The label blending option. The default + * is used for rendering both opaque and translucent labels. However, if either all of the labels are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x. * * @performance For best performance, prefer a few collections, each with many labels, to * many collections with only a few labels each. Avoid having collections where some @@ -533,6 +538,16 @@ define([ * @default false */ this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + + /** + * The label blending option. The default is used for rendering both opaque and translucent labels. + * However, if either all of the labels are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve + * performance by up to 2x. + * @type {BlendOption} + * @default BlendOption.OPAQUE_AND_TRANSLUCENT + */ + this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT); } defineProperties(LabelCollection.prototype, { @@ -732,8 +747,10 @@ define([ billboardCollection.modelMatrix = this.modelMatrix; billboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume; + billboardCollection.blendOption = this.blendOption; backgroundBillboardCollection.modelMatrix = this.modelMatrix; backgroundBillboardCollection.debugShowBoundingVolume = this.debugShowBoundingVolume; + backgroundBillboardCollection.blendOption = this.blendOption; var context = frameState.context; diff --git a/Source/Scene/PointPrimitiveCollection.js b/Source/Scene/PointPrimitiveCollection.js index 52f0b5f42a80..ca1e50129ce4 100644 --- a/Source/Scene/PointPrimitiveCollection.js +++ b/Source/Scene/PointPrimitiveCollection.js @@ -24,6 +24,7 @@ define([ '../Shaders/PointPrimitiveCollectionFS', '../Shaders/PointPrimitiveCollectionVS', './BlendingState', + './BlendOption', './PointPrimitive', './SceneMode' ], function( @@ -51,6 +52,7 @@ define([ PointPrimitiveCollectionFS, PointPrimitiveCollectionVS, BlendingState, + BlendOption, PointPrimitive, SceneMode) { 'use strict'; @@ -87,6 +89,9 @@ define([ * @param {Object} [options] Object with the following properties: * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms each point from model to world coordinates. * @param {Boolean} [options.debugShowBoundingVolume=false] For debugging only. Determines if this primitive's commands' bounding spheres are shown. + * @param {BlendOption} [options.blendOption=BlendOption.OPAQUE_AND_TRANSLUCENT] The point blending option. The default + * is used for rendering both opaque and translucent points. However, if either all of the points are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve performance by up to 2x. * * @performance For best performance, prefer a few collections, each with many points, to * many collections with only a few points each. Organize collections so that points @@ -115,9 +120,11 @@ define([ options = defaultValue(options, defaultValue.EMPTY_OBJECT); this._sp = undefined; - this._rs = undefined; - this._vaf = undefined; + this._spTranslucent = undefined; this._spPick = undefined; + this._rsOpaque = undefined; + this._rsTranslucent = undefined; + this._vaf = undefined; this._pointPrimitives = []; this._pointPrimitivesToUpdate = []; @@ -197,6 +204,17 @@ define([ */ this.debugShowBoundingVolume = defaultValue(options.debugShowBoundingVolume, false); + /** + * The point blending option. The default is used for rendering both opaque and translucent points. + * However, if either all of the points are completely opaque or all are completely translucent, + * setting the technique to BillboardRenderTechnique.OPAQUE or BillboardRenderTechnique.TRANSLUCENT can improve + * performance by up to 2x. + * @type {BlendOption} + * @default BlendOption.OPAQUE_AND_TRANSLUCENT + */ + this.blendOption = defaultValue(options.blendOption, BlendOption.OPAQUE_AND_TRANSLUCENT); + this._blendOption = undefined; + this._mode = SceneMode.SCENE3D; this._maxTotalPointSize = 1; @@ -835,79 +853,166 @@ define([ } updateBoundingVolume(this, frameState, boundingVolume); - var va; - var vaLength; - var command; - var j; - var vs; - var fs; - - var commandList = frameState.commandList; + var blendOptionChanged = this._blendOption !== this.blendOption; + this._blendOption = this.blendOption; - if (pass.render) { - var colorList = this._colorCommands; + if (blendOptionChanged) { + this._rsOpaque = RenderState.fromCache({ + depthTest : { + enabled : true, + func : WebGLConstants.LEQUAL + }, + depthMask : true + }); - if (!defined(this._rs)) { - this._rs = RenderState.fromCache({ + if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + this._rsTranslucent = RenderState.fromCache({ depthTest : { enabled : true, func : WebGLConstants.LEQUAL }, + depthMask : false, blending : BlendingState.ALPHA_BLEND }); + } else { + this._rsTranslucent = undefined; } + } - if (!defined(this._sp) || - (this._shaderScaleByDistance && !this._compiledShaderScaleByDistance) || - (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistance) || - (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayCondition)) { + if (blendOptionChanged || + (this._shaderScaleByDistance && !this._compiledShaderScaleByDistance) || + (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistance) || + (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayCondition)) { - vs = new ShaderSource({ - sources : [PointPrimitiveCollectionVS] - }); - if (this._shaderScaleByDistance) { - vs.defines.push('EYE_DISTANCE_SCALING'); - } - if (this._shaderTranslucencyByDistance) { - vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); - } - if (this._shaderDistanceDisplayCondition) { - vs.defines.push('DISTANCE_DISPLAY_CONDITION'); - } + vs = new ShaderSource({ + sources : [PointPrimitiveCollectionVS] + }); + if (this._shaderScaleByDistance) { + vs.defines.push('EYE_DISTANCE_SCALING'); + } + if (this._shaderTranslucencyByDistance) { + vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); + } + if (this._shaderDistanceDisplayCondition) { + vs.defines.push('DISTANCE_DISPLAY_CONDITION'); + } + if (this._blendOption === BlendOption.OPAQUE || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + fs = new ShaderSource({ + defines : ['OPAQUE'], + sources : [PointPrimitiveCollectionFS] + }); this._sp = ShaderProgram.replaceCache({ context : context, shaderProgram : this._sp, vertexShaderSource : vs, - fragmentShaderSource : PointPrimitiveCollectionFS, + fragmentShaderSource : fs, attributeLocations : attributeLocations }); + } else { + this._sp = this._sp && this._sp.destroy(); + this._sp = undefined; + } - this._compiledShaderScaleByDistance = this._shaderScaleByDistance; - this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; - this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; + if (this._blendOption === BlendOption.TRANSLUCENT || this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT) { + fs = new ShaderSource({ + defines : ['TRANSLUCENT'], + sources : [PointPrimitiveCollectionFS] + }); + this._spTranslucent = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._spTranslucent, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + } else { + this._spTranslucent = this._spTranslucent && this._spTranslucent.destroy(); + this._spTranslucent = undefined; + } + + this._compiledShaderScaleByDistance = this._shaderScaleByDistance; + this._compiledShaderTranslucencyByDistance = this._shaderTranslucencyByDistance; + this._compiledShaderDistanceDisplayCondition = this._shaderDistanceDisplayCondition; + } + + if (!defined(this._spPick) || + (this._shaderScaleByDistance && !this._compiledShaderScaleByDistancePick) || + (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistancePick) || + (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayConditionPick)) { + + vs = new ShaderSource({ + defines : ['RENDER_FOR_PICK'], + sources : [PointPrimitiveCollectionVS] + }); + + if (this._shaderScaleByDistance) { + vs.defines.push('EYE_DISTANCE_SCALING'); + } + if (this._shaderTranslucencyByDistance) { + vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); } + if (this._shaderDistanceDisplayCondition) { + vs.defines.push('DISTANCE_DISPLAY_CONDITION'); + } + + fs = new ShaderSource({ + defines : ['RENDER_FOR_PICK'], + sources : [PointPrimitiveCollectionFS] + }); + + this._spPick = ShaderProgram.replaceCache({ + context : context, + shaderProgram : this._spPick, + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + + this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance; + this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance; + this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition; + } + + var va; + var vaLength; + var command; + var j; + var vs; + var fs; + + var commandList = frameState.commandList; + + if (pass.render) { + var colorList = this._colorCommands; + + var opaque = this._blendOption === BlendOption.OPAQUE; + var opaqueAndTranslucent = this._blendOption === BlendOption.OPAQUE_AND_TRANSLUCENT; va = this._vaf.va; vaLength = va.length; colorList.length = vaLength; - for (j = 0; j < vaLength; ++j) { + var totalLength = opaqueAndTranslucent ? vaLength * 2 : vaLength; + for (j = 0; j < totalLength; ++j) { + var opaqueCommand = opaque || (opaqueAndTranslucent && j % 2 === 0); + command = colorList[j]; if (!defined(command)) { - command = colorList[j] = new DrawCommand({ - primitiveType : PrimitiveType.POINTS, - pass : Pass.OPAQUE, - owner : this - }); + command = colorList[j] = new DrawCommand(); } + command.primitiveType = PrimitiveType.POINTS; + command.pass = opaqueCommand ? Pass.OPAQUE : Pass.TRANSLUCENT; + command.owner = this; + + var index = opaqueAndTranslucent ? Math.floor(j / 2.0) : j; command.boundingVolume = boundingVolume; command.modelMatrix = modelMatrix; - command.shaderProgram = this._sp; + command.shaderProgram = opaqueCommand ? this._sp : this._spTranslucent; command.uniformMap = this._uniforms; - command.vertexArray = va[j].va; - command.renderState = this._rs; + command.vertexArray = va[index].va; + command.renderState = opaqueCommand ? this._rsOpaque : this._rsTranslucent; command.debugShowBoundingVolume = this.debugShowBoundingVolume; commandList.push(command); @@ -917,44 +1022,6 @@ define([ if (picking) { var pickList = this._pickCommands; - if (!defined(this._spPick) || - (this._shaderScaleByDistance && !this._compiledShaderScaleByDistancePick) || - (this._shaderTranslucencyByDistance && !this._compiledShaderTranslucencyByDistancePick) || - (this._shaderDistanceDisplayCondition && !this._compiledShaderDistanceDisplayConditionPick)) { - - vs = new ShaderSource({ - defines : ['RENDER_FOR_PICK'], - sources : [PointPrimitiveCollectionVS] - }); - - if (this._shaderScaleByDistance) { - vs.defines.push('EYE_DISTANCE_SCALING'); - } - if (this._shaderTranslucencyByDistance) { - vs.defines.push('EYE_DISTANCE_TRANSLUCENCY'); - } - if (this._shaderDistanceDisplayCondition) { - vs.defines.push('DISTANCE_DISPLAY_CONDITION'); - } - - fs = new ShaderSource({ - defines : ['RENDER_FOR_PICK'], - sources : [PointPrimitiveCollectionFS] - }); - - this._spPick = ShaderProgram.replaceCache({ - context : context, - shaderProgram : this._spPick, - vertexShaderSource : vs, - fragmentShaderSource : fs, - attributeLocations : attributeLocations - }); - - this._compiledShaderScaleByDistancePick = this._shaderScaleByDistance; - this._compiledShaderTranslucencyByDistancePick = this._shaderTranslucencyByDistance; - this._compiledShaderDistanceDisplayConditionPick = this._shaderDistanceDisplayCondition; - } - va = this._vaf.va; vaLength = va.length; @@ -974,7 +1041,7 @@ define([ command.shaderProgram = this._spPick; command.uniformMap = this._uniforms; command.vertexArray = va[j].va; - command.renderState = this._rs; + command.renderState = this._rsOpaque; commandList.push(command); } @@ -1015,6 +1082,7 @@ define([ */ PointPrimitiveCollection.prototype.destroy = function() { this._sp = this._sp && this._sp.destroy(); + this._spTranslucent = this._spTranslucent && this._spTranslucent.destroy(); this._spPick = this._spPick && this._spPick.destroy(); this._vaf = this._vaf && this._vaf.destroy(); destroyPointPrimitives(this._pointPrimitives); diff --git a/Source/Shaders/BillboardCollectionFS.glsl b/Source/Shaders/BillboardCollectionFS.glsl index 1973342a2f64..e018862f5feb 100644 --- a/Source/Shaders/BillboardCollectionFS.glsl +++ b/Source/Shaders/BillboardCollectionFS.glsl @@ -15,13 +15,31 @@ void main() #else vec4 vertexColor = v_color; #endif - + vec4 color = texture2D(u_atlas, v_textureCoordinates) * vertexColor; - if (color.a == 0.0) + +// Fully transparent parts of the billboard are not pickable. +#ifdef RENDER_FOR_PICK + if (color.a < 0.005) // matches 0/255 and 1/255 + { + discard; + } +#else +// The billboard is rendered twice. The opaque pass discards translucent fragments +// and the translucent pass discards opaque fragments. +#ifdef OPAQUE + if (color.a < 0.995) // matches < 254/255 { discard; } - +#else + if (color.a >= 0.995) // matches 254/255 and 255/255 + { + discard; + } +#endif +#endif + #ifdef RENDER_FOR_PICK gl_FragColor = v_pickColor; #else diff --git a/Source/Shaders/PointPrimitiveCollectionFS.glsl b/Source/Shaders/PointPrimitiveCollectionFS.glsl index 3977cb799870..75906f28a735 100644 --- a/Source/Shaders/PointPrimitiveCollectionFS.glsl +++ b/Source/Shaders/PointPrimitiveCollectionFS.glsl @@ -18,10 +18,28 @@ void main() vec4 color = mix(v_outlineColor, v_color, innerAlpha); color.a *= wholeAlpha; - if (color.a < 0.005) + +// Fully transparent parts of the billboard are not pickable. +#ifdef RENDER_FOR_PICK + if (color.a < 0.005) // matches 0/255 and 1/255 + { + discard; + } +#else +// The billboard is rendered twice. The opaque pass discards translucent fragments +// and the translucent pass discards opaque fragments. +#ifdef OPAQUE + if (color.a < 0.995) // matches < 254/255 { discard; } +#else + if (color.a >= 0.995) // matches 254/255 and 255/255 + { + discard; + } +#endif +#endif #ifdef RENDER_FOR_PICK gl_FragColor = v_pickColor; diff --git a/Specs/Scene/BillboardCollectionSpec.js b/Specs/Scene/BillboardCollectionSpec.js index dc0c9b432c5a..205b0ed70656 100644 --- a/Specs/Scene/BillboardCollectionSpec.js +++ b/Specs/Scene/BillboardCollectionSpec.js @@ -13,6 +13,7 @@ defineSuite([ 'Core/NearFarScalar', 'Core/Rectangle', 'Scene/Billboard', + 'Scene/BlendOption', 'Scene/HeightReference', 'Scene/HorizontalOrigin', 'Scene/OrthographicFrustum', @@ -36,6 +37,7 @@ defineSuite([ NearFarScalar, Rectangle, Billboard, + BlendOption, HeightReference, HorizontalOrigin, OrthographicFrustum, @@ -230,6 +232,30 @@ defineSuite([ expect(billboards.isDestroyed()).toEqual(false); }); + it('renders billboard in multiple passes', function() { + billboards.add({ + position : Cartesian3.ZERO, + image : greenImage + }); + camera.position = new Cartesian3(2.0, 0.0, 0.0); + + var frameState = scene.frameState; + frameState.commandList.length = 0; + billboards.blendOption = BlendOption.OPAQUE_AND_TRANSLUCENT; + billboards.update(frameState); + expect(frameState.commandList.length).toEqual(2); + + frameState.commandList.length = 0; + billboards.blendOption = BlendOption.OPAQUE; + billboards.update(frameState); + expect(frameState.commandList.length).toEqual(1); + + frameState.commandList.length = 0; + billboards.blendOption = BlendOption.TRANSLUCENT; + billboards.update(frameState); + expect(frameState.commandList.length).toEqual(1); + }); + it('renders billboard with sizeInMeters', function() { billboards.add({ position : Cartesian3.ZERO, diff --git a/Specs/Scene/LabelCollectionSpec.js b/Specs/Scene/LabelCollectionSpec.js index a83fb94b3f52..50abe11e756e 100644 --- a/Specs/Scene/LabelCollectionSpec.js +++ b/Specs/Scene/LabelCollectionSpec.js @@ -11,6 +11,7 @@ defineSuite([ 'Core/Math', 'Core/NearFarScalar', 'Core/Rectangle', + 'Scene/BlendOption', 'Scene/Globe', 'Scene/HeightReference', 'Scene/HorizontalOrigin', @@ -31,6 +32,7 @@ defineSuite([ CesiumMath, NearFarScalar, Rectangle, + BlendOption, Globe, HeightReference, HorizontalOrigin, @@ -343,6 +345,32 @@ defineSuite([ expect(scene.renderForSpecs()[0]).toBeGreaterThan(10); }); + it('renders in multiple passes', function() { + labels.add({ + position : Cartesian3.ZERO, + text : 'x', + horizontalOrigin : HorizontalOrigin.CENTER, + verticalOrigin : VerticalOrigin.CENTER + }); + camera.position = new Cartesian3(2.0, 0.0, 0.0); + + var frameState = scene.frameState; + frameState.commandList.length = 0; + labels.blendOption = BlendOption.OPAQUE_AND_TRANSLUCENT; + labels.update(frameState); + expect(frameState.commandList.length).toEqual(2); + + frameState.commandList.length = 0; + labels.blendOption = BlendOption.OPAQUE; + labels.update(frameState); + expect(frameState.commandList.length).toEqual(1); + + frameState.commandList.length = 0; + labels.blendOption = BlendOption.TRANSLUCENT; + labels.update(frameState); + expect(frameState.commandList.length).toEqual(1); + }); + it('can render after adding a label', function() { labels.add({ position : Cartesian3.ZERO, diff --git a/Specs/Scene/PointPrimitiveCollectionSpec.js b/Specs/Scene/PointPrimitiveCollectionSpec.js index 521c33a6d4d4..978a1ced9842 100644 --- a/Specs/Scene/PointPrimitiveCollectionSpec.js +++ b/Specs/Scene/PointPrimitiveCollectionSpec.js @@ -10,6 +10,7 @@ defineSuite([ 'Core/Math', 'Core/NearFarScalar', 'Core/Rectangle', + 'Scene/BlendOption', 'Scene/PointPrimitive', 'Specs/createScene' ], function( @@ -23,6 +24,7 @@ defineSuite([ CesiumMath, NearFarScalar, Rectangle, + BlendOption, PointPrimitive, createScene) { 'use strict'; @@ -155,6 +157,30 @@ defineSuite([ expect(pointPrimitives.isDestroyed()).toEqual(false); }); + it('renders pointPrimitive in multiple passes', function() { + pointPrimitives.add({ + position : Cartesian3.ZERO, + color : Color.LIME + }); + camera.position = new Cartesian3(2.0, 0.0, 0.0); + + var frameState = scene.frameState; + frameState.commandList.length = 0; + pointPrimitives.blendOption = BlendOption.OPAQUE_AND_TRANSLUCENT; + pointPrimitives.update(frameState); + expect(frameState.commandList.length).toEqual(2); + + frameState.commandList.length = 0; + pointPrimitives.blendOption = BlendOption.OPAQUE; + pointPrimitives.update(frameState); + expect(frameState.commandList.length).toEqual(1); + + frameState.commandList.length = 0; + pointPrimitives.blendOption = BlendOption.TRANSLUCENT; + pointPrimitives.update(frameState); + expect(frameState.commandList.length).toEqual(1); + }); + it('disables pointPrimitive scaleByDistance', function() { var p = pointPrimitives.add({ scaleByDistance : new NearFarScalar(1.0, 3.0, 1.0e6, 0.0)