diff --git a/CHANGES.md b/CHANGES.md index 603e3b6d475e..f4041492a0e9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,7 @@ Change Log * Fixed an issue with constant `VertexArray` attributes not being set correctly. [#4995](https://github.com/AnalyticalGraphicsInc/cesium/pull/4995) * Add support for `Scene.pickPosition` in Columbus view and 2D. [#4990](https://github.com/AnalyticalGraphicsInc/cesium/pull/4990) * Added `Label.scaleByDistance` to control minimum/maximum label size based on distance from the camera. [#5019](https://github.com/AnalyticalGraphicsInc/cesium/pull/5019) +* Add support for depth picking translucent primitives when `Scene.pickTranslucentDepth` is `true`. [#4979](https://github.com/AnalyticalGraphicsInc/cesium/pull/4979) ### 1.30 - 2017-02-01 diff --git a/Source/Renderer/ShaderCache.js b/Source/Renderer/ShaderCache.js index d4ed90bd9f7d..d02724bdfedc 100644 --- a/Source/Renderer/ShaderCache.js +++ b/Source/Renderer/ShaderCache.js @@ -55,7 +55,7 @@ define([ * fragmentShaderSource : fs, * attributeLocations : attributeLocations * }); - * + * * @see ShaderCache#getShaderProgram */ ShaderCache.prototype.replaceShaderProgram = function(options) { @@ -103,7 +103,7 @@ define([ var keyword = vertexShaderText + fragmentShaderText + JSON.stringify(attributeLocations); var cachedShader; - if (this._shaders[keyword]) { + if (defined(this._shaders[keyword])) { cachedShader = this._shaders[keyword]; // No longer want to release this if it was previously released. @@ -125,6 +125,7 @@ define([ cache : this, shaderProgram : shaderProgram, keyword : keyword, + derivedKeywords : [], count : 0 }; @@ -138,14 +139,86 @@ define([ return cachedShader.shaderProgram; }; + ShaderCache.prototype.getDerivedShaderProgram = function(shaderProgram, keyword) { + var cachedShader = shaderProgram._cachedShader; + var derivedKeyword = keyword + cachedShader.keyword; + var cachedDerivedShader = this._shaders[derivedKeyword]; + if (!defined(cachedDerivedShader)) { + return undefined; + } + + return cachedDerivedShader.shaderProgram; + }; + + ShaderCache.prototype.createDerivedShaderProgram = function(shaderProgram, keyword, options) { + var cachedShader = shaderProgram._cachedShader; + var derivedKeyword = keyword + cachedShader.keyword; + + var vertexShaderSource = options.vertexShaderSource; + var fragmentShaderSource = options.fragmentShaderSource; + var attributeLocations = options.attributeLocations; + + if (typeof vertexShaderSource === 'string') { + vertexShaderSource = new ShaderSource({ + sources : [vertexShaderSource] + }); + } + + if (typeof fragmentShaderSource === 'string') { + fragmentShaderSource = new ShaderSource({ + sources : [fragmentShaderSource] + }); + } + + var vertexShaderText = vertexShaderSource.createCombinedVertexShader(); + var fragmentShaderText = fragmentShaderSource.createCombinedFragmentShader(); + + var context = this._context; + var derivedShaderProgram = new ShaderProgram({ + gl : context._gl, + logShaderCompilation : context.logShaderCompilation, + debugShaders : context.debugShaders, + vertexShaderSource : vertexShaderSource, + vertexShaderText : vertexShaderText, + fragmentShaderSource : fragmentShaderSource, + fragmentShaderText : fragmentShaderText, + attributeLocations : attributeLocations + }); + + var derivedCachedShader = { + cache : this, + shaderProgram : derivedShaderProgram, + keyword : derivedKeyword, + derivedKeywords : [], + count : 0 + }; + + cachedShader.derivedKeywords.push(keyword); + derivedShaderProgram._cachedShader = derivedCachedShader; + this._shaders[derivedKeyword] = derivedCachedShader; + return derivedShaderProgram; + }; + + function destroyShader(cache, cachedShader) { + var derivedKeywords = cachedShader.derivedKeywords; + var length = derivedKeywords.length; + for (var i = 0; i < length; ++i) { + var keyword = derivedKeywords[i] + cachedShader.keyword; + var derivedCachedShader = cache._shaders[keyword]; + destroyShader(cache, derivedCachedShader); + } + + delete cache._shaders[cachedShader.keyword]; + cachedShader.shaderProgram.finalDestroy(); + } + ShaderCache.prototype.destroyReleasedShaderPrograms = function() { var shadersToRelease = this._shadersToRelease; for ( var keyword in shadersToRelease) { if (shadersToRelease.hasOwnProperty(keyword)) { var cachedShader = shadersToRelease[keyword]; - delete this._shaders[cachedShader.keyword]; - cachedShader.shaderProgram.finalDestroy(); + destroyShader(this, cachedShader); --this._numberOfShaders; } } @@ -168,13 +241,11 @@ define([ ShaderCache.prototype.destroy = function() { var shaders = this._shaders; - - for ( var keyword in shaders) { + for (var keyword in shaders) { if (shaders.hasOwnProperty(keyword)) { shaders[keyword].shaderProgram.finalDestroy(); } } - return destroyObject(this); }; diff --git a/Source/Scene/FrameState.js b/Source/Scene/FrameState.js index 0d4278dc64a6..1431589182a0 100644 --- a/Source/Scene/FrameState.js +++ b/Source/Scene/FrameState.js @@ -117,7 +117,14 @@ define([ * @type {Boolean} * @default false */ - pick : false + pick : false, + + /** + * true if the primitive should update for a depth only pass, false otherwise. + * @type {Boolean} + * @default false + */ + depth : false }; /** diff --git a/Source/Scene/OIT.js b/Source/Scene/OIT.js index eba923512306..ac43dbcd8238 100644 --- a/Source/Scene/OIT.js +++ b/Source/Scene/OIT.js @@ -82,8 +82,6 @@ define([ this._translucentRenderStateCache = {}; this._alphaRenderStateCache = {}; - this._translucentShaderCache = {}; - this._alphaShaderCache = {}; this._compositeCommand = undefined; this._adjustTranslucentCommand = undefined; @@ -411,9 +409,8 @@ define([ ' float ai = czm_gl_FragColor.a;\n' + ' gl_FragColor = vec4(ai);\n'; - function getTranslucentShaderProgram(context, shaderProgram, cache, source) { - var id = shaderProgram.id; - var shader = cache[id]; + function getTranslucentShaderProgram(context, shaderProgram, keyword, source) { + var shader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword); if (!defined(shader)) { var attributeLocations = shaderProgram._attributeLocations; @@ -446,29 +443,26 @@ define([ source + '}\n'); - shader = ShaderProgram.fromCache({ - context : context, + shader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, { vertexShaderSource : shaderProgram.vertexShaderSource, fragmentShaderSource : fs, attributeLocations : attributeLocations }); - - cache[id] = shader; } return shader; } - function getTranslucentMRTShaderProgram(oit, context, shaderProgram) { - return getTranslucentShaderProgram(context, shaderProgram, oit._translucentShaderCache, mrtShaderSource); + function getTranslucentMRTShaderProgram(context, shaderProgram) { + return getTranslucentShaderProgram(context, shaderProgram, 'translucentMRT', mrtShaderSource); } - function getTranslucentColorShaderProgram(oit, context, shaderProgram) { - return getTranslucentShaderProgram(context, shaderProgram, oit._translucentShaderCache, colorShaderSource); + function getTranslucentColorShaderProgram(context, shaderProgram) { + return getTranslucentShaderProgram(context, shaderProgram, 'translucentMultipass', colorShaderSource); } - function getTranslucentAlphaShaderProgram(oit, context, shaderProgram) { - return getTranslucentShaderProgram(context, shaderProgram, oit._alphaShaderCache, alphaShaderSource); + function getTranslucentAlphaShaderProgram(context, shaderProgram) { + return getTranslucentShaderProgram(context, shaderProgram, 'alphaMultipass', alphaShaderSource); } OIT.prototype.createDerivedCommands = function(command, context, result) { @@ -487,7 +481,7 @@ define([ result.translucentCommand = DrawCommand.shallowClone(command, result.translucentCommand); if (!defined(translucentShader) || result.shaderProgramId !== command.shaderProgram.id) { - result.translucentCommand.shaderProgram = getTranslucentMRTShaderProgram(this, context, command.shaderProgram); + result.translucentCommand.shaderProgram = getTranslucentMRTShaderProgram(context, command.shaderProgram); result.translucentCommand.renderState = getTranslucentMRTRenderState(this, context, command.renderState); result.shaderProgramId = command.shaderProgram.id; } else { @@ -510,9 +504,9 @@ define([ result.alphaCommand = DrawCommand.shallowClone(command, result.alphaCommand); if (!defined(colorShader) || result.shaderProgramId !== command.shaderProgram.id) { - result.translucentCommand.shaderProgram = getTranslucentColorShaderProgram(this, context, command.shaderProgram); + result.translucentCommand.shaderProgram = getTranslucentColorShaderProgram(context, command.shaderProgram); result.translucentCommand.renderState = getTranslucentColorRenderState(this, context, command.renderState); - result.alphaCommand.shaderProgram = getTranslucentAlphaShaderProgram(this, context, command.shaderProgram); + result.alphaCommand.shaderProgram = getTranslucentAlphaShaderProgram(context, command.shaderProgram); result.alphaCommand.renderState = getTranslucentAlphaRenderState(this, context, command.renderState); result.shaderProgramId = command.shaderProgram.id; } else { @@ -639,23 +633,6 @@ define([ this._adjustAlphaCommand.shaderProgram = this._adjustAlphaCommand.shaderProgram && this._adjustAlphaCommand.shaderProgram.destroy(); } - var name; - var cache = this._translucentShaderCache; - for (name in cache) { - if (cache.hasOwnProperty(name) && defined(cache[name])) { - cache[name].destroy(); - } - } - this._translucentShaderCache = {}; - - cache = this._alphaShaderCache; - for (name in cache) { - if (cache.hasOwnProperty(name) && defined(cache[name])) { - cache[name].destroy(); - } - } - this._alphaShaderCache = {}; - return destroyObject(this); }; diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js index 1aa52b4d734e..e32dcc4afe31 100644 --- a/Source/Scene/Scene.js +++ b/Source/Scene/Scene.js @@ -28,6 +28,7 @@ define([ '../Core/Matrix4', '../Core/mergeSort', '../Core/Occluder', + '../Core/PixelFormat', '../Core/ShowGeometryInstanceAttribute', '../Core/Transforms', '../Renderer/ClearCommand', @@ -35,10 +36,14 @@ define([ '../Renderer/Context', '../Renderer/ContextLimits', '../Renderer/DrawCommand', + '../Renderer/Framebuffer', '../Renderer/Pass', '../Renderer/PassState', + '../Renderer/PixelDatatype', + '../Renderer/RenderState', '../Renderer/ShaderProgram', '../Renderer/ShaderSource', + '../Renderer/Texture', './Camera', './CreditDisplay', './CullingVolume', @@ -96,6 +101,7 @@ define([ Matrix4, mergeSort, Occluder, + PixelFormat, ShowGeometryInstanceAttribute, Transforms, ClearCommand, @@ -103,10 +109,14 @@ define([ Context, ContextLimits, DrawCommand, + Framebuffer, Pass, PassState, + PixelDatatype, + RenderState, ShaderProgram, ShaderSource, + Texture, Camera, CreditDisplay, CullingVolume, @@ -294,6 +304,12 @@ define([ this._pickDepths = []; this._debugGlobeDepths = []; + this._pickDepthPassState = undefined; + this._pickDepthFramebuffer = undefined; + this._pickDepthFramebufferWidth = undefined; + this._pickDepthFramebufferHeight = undefined; + this._depthOnlyRenderStateCache = {}; + this._transitioner = new SceneTransitioner(this); this._renderError = new Event(); @@ -548,6 +564,18 @@ define([ */ this.useDepthPicking = true; + /** + * When true, enables picking translucent geometry using the depth buffer. + * {@link Scene#useDepthPicking} must also be true to enable picking the depth buffer. + *

+ * There is a decrease in performance when enabled. There are extra draw calls to write depth for + * translucent geometry. + *

+ * @type {Boolean} + * @default false + */ + this.pickTranslucentDepth = false; + /** * The time in milliseconds to wait before checking if the camera has not moved and fire the cameraMoveEnd event. * @type {Number} @@ -1162,11 +1190,10 @@ define([ shadowsDirty = true; } - if (command.dirty) { + var derivedCommands = command.derivedCommands; + if (command.dirty && defined(derivedCommands)) { command.dirty = false; - var derivedCommands = command.derivedCommands; - if (shadowsEnabled && (command.receiveShadows || command.castShadows)) { derivedCommands.shadows = ShadowMap.createDerivedCommands(shadowMaps, lightShadowMaps, command, shadowsDirty, context, derivedCommands.shadows); } @@ -1180,6 +1207,8 @@ define([ derivedCommands.oit = oit.createDerivedCommands(command, context, derivedCommands.oit); } } + + derivedCommands.depth = createDepthOnlyDerivedCommand(scene, command, context, derivedCommands.depth); } } @@ -1203,6 +1232,7 @@ define([ function clearPasses(passes) { passes.render = false; passes.pick = false; + passes.depth = false; } function updateFrameState(scene, frameNumber, time) { @@ -1577,6 +1607,8 @@ define([ // Some commands, such as OIT derived commands, do not have derived shadow commands themselves // and instead shadowing is built-in. In this case execute the command regularly below. command.derivedCommands.shadows.receiveCommand.execute(context, passState); + } else if (scene.frameState.passes.depth && defined(command.derivedCommands.depth)) { + command.derivedCommands.depth.depthOnlyCommand.execute(context, passState); } else { command.execute(context, passState); } @@ -1704,7 +1736,7 @@ define([ var scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum(); var scratchOrthographicFrustum = new OrthographicFrustum(); - function executeCommands(scene, passState, picking) { + function executeCommands(scene, passState) { var camera = scene._camera; var context = scene.context; var us = context.uniformState; @@ -1729,6 +1761,9 @@ define([ us.updatePass(Pass.ENVIRONMENT); var useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D; + var passes = scene._frameState.passes; + var picking = passes.pick; + var depthOnly = passes.depth; var environmentState = scene._environmentState; // Do not render environment primitives during a pick pass since they do not generate picking commands. @@ -1877,10 +1912,11 @@ define([ commands.length = frustumCommands.indices[Pass.TRANSLUCENT]; executeTranslucentCommands(scene, executeCommand, passState, commands); - if (defined(globeDepth) && environmentState.useGlobeDepthFramebuffer && scene.useDepthPicking) { + if (defined(globeDepth) && (environmentState.useGlobeDepthFramebuffer || depthOnly) && scene.useDepthPicking) { // PERFORMANCE_IDEA: Use MRT to avoid the extra copy. + var depthStencilTexture = depthOnly ? passState.framebuffer.depthStencilTexture : globeDepth.framebuffer.depthStencilTexture; var pickDepth = getPickDepth(scene, index); - pickDepth.update(context, globeDepth.framebuffer.depthStencilTexture); + pickDepth.update(context, depthStencilTexture); pickDepth.executeCopyDepth(context, passState); } } @@ -1999,7 +2035,7 @@ define([ } } - function updateAndExecuteCommands(scene, passState, backgroundColor, picking) { + function updateAndExecuteCommands(scene, passState, backgroundColor) { var context = scene._context; var viewport = passState.viewport; @@ -2007,13 +2043,20 @@ define([ var frameState = scene._frameState; var camera = frameState.camera; var mode = frameState.mode; + var depthOnly = frameState.passes.depth; if (scene._useWebVR && mode !== SceneMode.SCENE2D) { - updatePrimitives(scene); + if (!depthOnly) { + updatePrimitives(scene); + } + createPotentiallyVisibleSet(scene); - updateAndClearFramebuffers(scene, passState, backgroundColor, picking); - executeComputeCommands(scene); - executeShadowMapCastCommands(scene); + updateAndClearFramebuffers(scene, passState, backgroundColor); + + if (!depthOnly) { + executeComputeCommands(scene); + executeShadowMapCastCommands(scene); + } // Based on Calculating Stereo pairs by Paul Bourke // http://paulbourke.net/stereographics/stereorender/ @@ -2037,14 +2080,14 @@ define([ Cartesian3.add(savedCamera.position, eyeTranslation, camera.position); camera.frustum.xOffset = offset; - executeCommands(scene, passState, picking); + executeCommands(scene, passState); viewport.x = passState.viewport.width; Cartesian3.subtract(savedCamera.position, eyeTranslation, camera.position); camera.frustum.xOffset = -offset; - executeCommands(scene, passState, picking); + executeCommands(scene, passState); Camera.clone(savedCamera, camera); } else { @@ -2054,9 +2097,9 @@ define([ viewport.height = context.drawingBufferHeight; if (mode !== SceneMode.SCENE2D || scene._mapMode2D === MapMode2D.ROTATE) { - executeCommandsInViewport(true, scene, passState, backgroundColor, picking); + executeCommandsInViewport(true, scene, passState, backgroundColor); } else { - execute2DViewportCommands(scene, passState, backgroundColor, picking); + execute2DViewportCommands(scene, passState, backgroundColor); } } } @@ -2069,7 +2112,7 @@ define([ var scratch2DViewportEyePoint = new Cartesian3(); var scratch2DViewportWindowCoords = new Cartesian3(); - function execute2DViewportCommands(scene, passState, backgroundColor, picking) { + function execute2DViewportCommands(scene, passState, backgroundColor) { var context = scene.context; var frameState = scene.frameState; var camera = scene.camera; @@ -2100,7 +2143,7 @@ define([ var viewportWidth = viewport.width; if (x === 0.0 || windowCoordinates.x <= 0.0 || windowCoordinates.x >= context.drawingBufferWidth) { - executeCommandsInViewport(true, scene, passState, backgroundColor, picking); + executeCommandsInViewport(true, scene, passState, backgroundColor); } else if (Math.abs(context.drawingBufferWidth * 0.5 - windowCoordinates.x) < 1.0) { viewport.width = windowCoordinates.x; @@ -2111,7 +2154,7 @@ define([ frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC); context.uniformState.update(frameState); - executeCommandsInViewport(true, scene, passState, backgroundColor, picking); + executeCommandsInViewport(true, scene, passState, backgroundColor); viewport.x = viewport.width; @@ -2123,7 +2166,7 @@ define([ frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC); context.uniformState.update(frameState); - executeCommandsInViewport(false, scene, passState, backgroundColor, picking); + executeCommandsInViewport(false, scene, passState, backgroundColor); } else if (windowCoordinates.x > context.drawingBufferWidth * 0.5) { viewport.width = windowCoordinates.x; @@ -2133,7 +2176,7 @@ define([ frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC); context.uniformState.update(frameState); - executeCommandsInViewport(true, scene, passState, backgroundColor, picking); + executeCommandsInViewport(true, scene, passState, backgroundColor); viewport.x += windowCoordinates.x; viewport.width = context.drawingBufferWidth - windowCoordinates.x; @@ -2146,7 +2189,7 @@ define([ frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC); context.uniformState.update(frameState); - executeCommandsInViewport(false, scene, passState, backgroundColor, picking); + executeCommandsInViewport(false, scene, passState, backgroundColor); } else { viewport.x = windowCoordinates.x; viewport.width = context.drawingBufferWidth - windowCoordinates.x; @@ -2157,7 +2200,7 @@ define([ frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC); context.uniformState.update(frameState); - executeCommandsInViewport(true, scene, passState, backgroundColor, picking); + executeCommandsInViewport(true, scene, passState, backgroundColor); viewport.x = 0; viewport.width = windowCoordinates.x; @@ -2170,7 +2213,7 @@ define([ frameState.cullingVolume = camera.frustum.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC); context.uniformState.update(frameState); - executeCommandsInViewport(false, scene, passState, backgroundColor, picking); + executeCommandsInViewport(false, scene, passState, backgroundColor); } camera._setTransform(transform); @@ -2181,21 +2224,28 @@ define([ viewport.width = viewportWidth; } - function executeCommandsInViewport(firstViewport, scene, passState, backgroundColor, picking) { - if (!firstViewport) { + function executeCommandsInViewport(firstViewport, scene, passState, backgroundColor) { + var depthOnly = scene.frameState.passes.depth; + + if (!firstViewport && !depthOnly) { scene.frameState.commandList.length = 0; } - updatePrimitives(scene); + if (!depthOnly) { + updatePrimitives(scene); + } + createPotentiallyVisibleSet(scene); if (firstViewport) { - updateAndClearFramebuffers(scene, passState, backgroundColor, picking); - executeComputeCommands(scene); - executeShadowMapCastCommands(scene); + updateAndClearFramebuffers(scene, passState, backgroundColor); + if (!depthOnly) { + executeComputeCommands(scene); + executeShadowMapCastCommands(scene); + } } - executeCommands(scene, passState, picking); + executeCommands(scene, passState); } function updateEnvironment(scene) { @@ -2305,10 +2355,12 @@ define([ } } - function updateAndClearFramebuffers(scene, passState, clearColor, picking) { + function updateAndClearFramebuffers(scene, passState, clearColor) { var context = scene._context; var environmentState = scene._environmentState; + var passes = scene._frameState.passes; + var picking = passes.pick; var useWebVR = scene._useWebVR && scene.mode !== SceneMode.SCENE2D; // Preserve the reference to the original framebuffer. @@ -2687,7 +2739,7 @@ define([ var passState = this._pickFramebuffer.begin(scratchRectangle); - updateAndExecuteCommands(this, passState, scratchColorZero, true); + updateAndExecuteCommands(this, passState, scratchColorZero); resolveFramebuffers(this, passState); var object = this._pickFramebuffer.end(scratchRectangle); @@ -2696,6 +2748,149 @@ define([ return object; }; + var fragDepthRegex = /\bgl_FragDepthEXT\b/; + var discardRegex = /\bdiscard\b/; + + function getDepthOnlyShaderProgram(context, shaderProgram) { + var shader = context.shaderCache.getDerivedShaderProgram(shaderProgram, 'depthOnly'); + if (!defined(shader)) { + var attributeLocations = shaderProgram._attributeLocations; + var fs = shaderProgram.fragmentShaderSource; + + var writesDepthOrDiscards = false; + var sources = fs.sources; + var length = sources.length; + for (var i = 0; i < length; ++i) { + if (fragDepthRegex.test(sources[i]) || discardRegex.test(sources[i])) { + writesDepthOrDiscards = true; + break; + } + } + + if (!writesDepthOrDiscards) { + fs = new ShaderSource({ + sources : ['void main() { gl_FragColor = vec4(1.0); }'] + }); + } + + shader = context.shaderCache.createDerivedShaderProgram(shaderProgram, 'depthOnly', { + vertexShaderSource : shaderProgram.vertexShaderSource, + fragmentShaderSource : fs, + attributeLocations : attributeLocations + }); + } + + return shader; + } + + function getDepthOnlyRenderState(scene, renderState) { + var cache = scene._depthOnlyRenderStateCache; + var depthOnlyState = cache[renderState.id]; + if (!defined(depthOnlyState)) { + var rs = RenderState.getState(renderState); + rs.depthMask = true; + rs.colorMask = { + red : false, + green : false, + blue : false, + alpha : false + }; + + depthOnlyState = RenderState.fromCache(rs); + cache[renderState.id] = depthOnlyState; + } + + return depthOnlyState; + } + + function createDepthOnlyDerivedCommand(scene, command, context, result) { + // For a depth only pass, we bind a framebuffer with only a depth attachment (no color attachments), + // do not write color, and write depth. If the fragment shader doesn't modify the fragment depth + // or discard, the driver can replace the fragment shader with a pass-through shader. We're unsure if this + // actually happens so we modify the shader to use a pass-through fragment shader. + + if (!defined(result)) { + result = {}; + } + + var shader; + var renderState; + if (defined(result.depthOnlyCommand)) { + shader = result.depthOnlyCommand.shaderProgram; + renderState = result.depthOnlyCommand.renderState; + } + + result.depthOnlyCommand = DrawCommand.shallowClone(command, result.depthOnlyCommand); + + if (!defined(shader) || result.shaderProgramId !== command.shaderProgram.id) { + result.depthOnlyCommand.shaderProgram = getDepthOnlyShaderProgram(context, command.shaderProgram); + result.depthOnlyCommand.renderState = getDepthOnlyRenderState(scene, command.renderState); + result.shaderProgramId = command.shaderProgram.id; + } else { + result.depthOnlyCommand.shaderProgram = shader; + result.depthOnlyCommand.renderState = renderState; + } + + return result; + } + + function renderTranslucentDepthForPick(scene, drawingBufferPosition) { + // PERFORMANCE_IDEA: render translucent only and merge with the previous frame + var context = scene._context; + var frameState = scene._frameState; + + clearPasses(frameState.passes); + frameState.passes.pick = true; + frameState.passes.depth = true; + frameState.cullingVolume = getPickCullingVolume(scene, drawingBufferPosition, 1, 1); + + var passState = scene._pickDepthPassState; + if (!defined(passState)) { + passState = scene._pickDepthPassState = new PassState(context); + passState.scissorTest = { + enabled : true, + rectangle : new BoundingRectangle() + }; + passState.viewport = new BoundingRectangle(); + } + + var width = context.drawingBufferWidth; + var height = context.drawingBufferHeight; + + var framebuffer = scene._pickDepthFramebuffer; + var pickDepthFBWidth = scene._pickDepthFramebufferWidth; + var pickDepthFBHeight = scene._pickDepthFramebufferHeight; + if (!defined(framebuffer) || pickDepthFBWidth !== width || pickDepthFBHeight !== height) { + scene._pickDepthFramebuffer = scene._pickDepthFramebuffer && scene._pickDepthFramebuffer.destroy(); + framebuffer = scene._pickDepthFramebuffer = new Framebuffer({ + context : context, + depthStencilTexture : new Texture({ + context : context, + width : width, + height : height, + pixelFormat : PixelFormat.DEPTH_STENCIL, + pixelDatatype : PixelDatatype.UNSIGNED_INT_24_8 + }) + }); + + scene._pickDepthFramebufferWidth = width; + scene._pickDepthFramebufferHeight = height; + } + + passState.framebuffer = framebuffer; + passState.viewport.width = width; + passState.viewport.height = height; + passState.scissorTest.rectangle.x = drawingBufferPosition.x; + passState.scissorTest.rectangle.y = height - drawingBufferPosition.y; + passState.scissorTest.rectangle.width = 1; + passState.scissorTest.rectangle.height = 1; + + updateAndExecuteCommands(scene, passState, scratchColorZero); + resolveFramebuffers(scene, passState); + + context.endFrame(); + } + var scratchPackedDepth = new Cartesian4(); var packedDepthScale = new Cartesian4(1.0, 1.0 / 255.0, 1.0 / 65025.0, 1.0 / 16581375.0); @@ -2703,6 +2898,10 @@ define([ * Returns the cartesian position reconstructed from the depth buffer and window position. * The returned position is in world coordinates. Used internally by camera functions to * prevent conversion to projected 2D coordinates and then back. + *

+ * Set {@link Scene#pickTranslucentDepth} to true to include the depth of + * translucent primitives; otherwise, this essentially picks through translucent primitives. + *

* * @private * @@ -2730,6 +2929,9 @@ define([ var uniformState = context.uniformState; var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(this, windowPosition, scratchPosition); + if (this.pickTranslucentDepth) { + renderTranslucentDepthForPick(this, drawingBufferPosition); + } drawingBufferPosition.y = this.drawingBufferHeight - drawingBufferPosition.y; var camera = this._camera; @@ -2798,6 +3000,10 @@ define([ * reconstructed in 3D and Columbus view. This is caused by the difference in the distribution * of depth values of perspective and orthographic projection. *

+ *

+ * Set {@link Scene#pickTranslucentDepth} to true to include the depth of + * translucent primitives; otherwise, this essentially picks through translucent primitives. + *

* * @param {Cartesian2} windowPosition Window coordinates to perform picking on. * @param {Cartesian3} [result] The object on which to restore the result. @@ -2991,6 +3197,7 @@ define([ this._screenSpaceCameraController = this._screenSpaceCameraController && this._screenSpaceCameraController.destroy(); this._deviceOrientationCameraController = this._deviceOrientationCameraController && !this._deviceOrientationCameraController.isDestroyed() && this._deviceOrientationCameraController.destroy(); this._pickFramebuffer = this._pickFramebuffer && this._pickFramebuffer.destroy(); + this._pickDepthFramebuffer = this._pickDepthFramebuffer && this._pickDepthFramebuffer.destroy(); this._primitives = this._primitives && this._primitives.destroy(); this._groundPrimitives = this._groundPrimitives && this._groundPrimitives.destroy(); this._globe = this._globe && this._globe.destroy(); diff --git a/Source/Scene/ShadowMap.js b/Source/Scene/ShadowMap.js index cbb634f9e011..64693c2f8cd5 100644 --- a/Source/Scene/ShadowMap.js +++ b/Source/Scene/ShadowMap.js @@ -1481,28 +1481,28 @@ define([ result.receiveShadows = false; if (!defined(castShader) || oldShaderId !== command.shaderProgram.id || shadowsDirty) { - if (defined(castShader)) { - castShader.destroy(); - } - var shaderProgram = command.shaderProgram; - var vertexShaderSource = shaderProgram.vertexShaderSource; - var fragmentShaderSource = shaderProgram.fragmentShaderSource; var isTerrain = command.pass === Pass.GLOBE; var isOpaque = command.pass !== Pass.TRANSLUCENT; var isPointLight = shadowMap._isPointLight; var usesDepthTexture= shadowMap._usesDepthTexture; - var castVS = ShadowMapShader.createShadowCastVertexShader(vertexShaderSource, isPointLight, isTerrain); - var castFS = ShadowMapShader.createShadowCastFragmentShader(fragmentShaderSource, isPointLight, usesDepthTexture, isOpaque); + var keyword = ShadowMapShader.getShadowCastShaderKeyword(isPointLight, isTerrain, usesDepthTexture, isOpaque); + castShader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword); + if (!defined(castShader)) { + var vertexShaderSource = shaderProgram.vertexShaderSource; + var fragmentShaderSource = shaderProgram.fragmentShaderSource; - castShader = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : castVS, - fragmentShaderSource : castFS, - attributeLocations : shaderProgram._attributeLocations - }); + var castVS = ShadowMapShader.createShadowCastVertexShader(vertexShaderSource, isPointLight, isTerrain); + var castFS = ShadowMapShader.createShadowCastFragmentShader(fragmentShaderSource, isPointLight, usesDepthTexture, isOpaque); + + castShader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, { + vertexShaderSource : castVS, + fragmentShaderSource : castFS, + attributeLocations : shaderProgram._attributeLocations + }); + } castRenderState = shadowMap._primitiveRenderState; if (isPointLight) { @@ -1582,20 +1582,19 @@ define([ var shaderDirty = result.receiveShaderProgramId !== command.shaderProgram.id; if (!defined(receiveShader) || shaderDirty || shadowsDirty || castShadowsDirty) { - if (defined(receiveShader)) { - receiveShader.destroy(); + var keyword = ShadowMapShader.getShadowReceiveShaderKeyword(lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal); + receiveShader = context.shaderCache.getDerivedShaderProgram(shaderProgram, keyword); + if (!defined(receiveShader)) { + var receiveVS = ShadowMapShader.createShadowReceiveVertexShader(vertexShaderSource, isTerrain, hasTerrainNormal); + var receiveFS = ShadowMapShader.createShadowReceiveFragmentShader(fragmentShaderSource, lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal); + + receiveShader = context.shaderCache.createDerivedShaderProgram(shaderProgram, keyword, { + vertexShaderSource : receiveVS, + fragmentShaderSource : receiveFS, + attributeLocations : shaderProgram._attributeLocations + }); } - var receiveVS = ShadowMapShader.createShadowReceiveVertexShader(vertexShaderSource, isTerrain, hasTerrainNormal); - var receiveFS = ShadowMapShader.createShadowReceiveFragmentShader(fragmentShaderSource, lightShadowMaps[0], command.castShadows, isTerrain, hasTerrainNormal); - - receiveShader = ShaderProgram.fromCache({ - context : context, - vertexShaderSource : receiveVS, - fragmentShaderSource : receiveFS, - attributeLocations : shaderProgram._attributeLocations - }); - receiveUniformMap = combineUniforms(lightShadowMaps[0], command.uniformMap, isTerrain); } diff --git a/Source/Scene/ShadowMapShader.js b/Source/Scene/ShadowMapShader.js index 555b61b8e0ad..41b2e00966b7 100644 --- a/Source/Scene/ShadowMapShader.js +++ b/Source/Scene/ShadowMapShader.js @@ -13,6 +13,10 @@ define([ function ShadowMapShader() { } + ShadowMapShader.getShadowCastShaderKeyword = function(isPointLight, isTerrain, usesDepthTexture, isOpaque) { + return 'castShadow ' + isPointLight + ' ' + isTerrain + ' ' + usesDepthTexture + ' ' + isOpaque; + }; + ShadowMapShader.createShadowCastVertexShader = function(vs, isPointLight, isTerrain) { var defines = vs.defines.slice(0); var sources = vs.sources.slice(0); @@ -106,6 +110,19 @@ define([ }); }; + ShadowMapShader.getShadowReceiveShaderKeyword = function(shadowMap, castShadows, isTerrain, hasTerrainNormal) { + var usesDepthTexture = shadowMap._usesDepthTexture; + var polygonOffsetSupported = shadowMap._polygonOffsetSupported; + var isPointLight = shadowMap._isPointLight; + var isSpotLight = shadowMap._isSpotLight; + var hasCascades = shadowMap._numberOfCascades > 1; + var debugCascadeColors = shadowMap.debugCascadeColors; + var softShadows = shadowMap.softShadows; + + return 'receiveShadow ' + usesDepthTexture + polygonOffsetSupported + isPointLight + isSpotLight + + hasCascades + debugCascadeColors + softShadows + castShadows + isTerrain + hasTerrainNormal; + }; + ShadowMapShader.createShadowReceiveVertexShader = function(vs, isTerrain, hasTerrainNormal) { var defines = vs.defines.slice(0); var sources = vs.sources.slice(0); diff --git a/Specs/Renderer/ShaderCacheSpec.js b/Specs/Renderer/ShaderCacheSpec.js index 953890a471d1..aa23202aa3b7 100644 --- a/Specs/Renderer/ShaderCacheSpec.js +++ b/Specs/Renderer/ShaderCacheSpec.js @@ -165,6 +165,69 @@ defineSuite([ cache.destroy(); }); + it('create derived shader program', function() { + var vs = 'attribute vec4 position; void main() { gl_Position = position; }'; + var fs = 'void main() { gl_FragColor = vec4(1.0); }'; + + var cache = new ShaderCache(context); + var sp = cache.getShaderProgram({ + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : { + position : 0 + } + }); + + var keyword = 'derived'; + var spDerived = cache.getDerivedShaderProgram(sp, keyword); + expect(spDerived).not.toBeDefined(); + + var fsDerived = 'void main() { gl_FragColor = vec4(vec3(1.0), 0.5); }'; + spDerived = cache.createDerivedShaderProgram(sp, keyword, { + vertexShaderSource : vs, + fragmentShaderSource : fsDerived, + attributeLocations : { + position : 0 + } + }); + expect(spDerived).toBeDefined(); + + cache.destroy(); + }); + + it('destroying a shader program destroys its derived shaders', function() { + var vs = 'attribute vec4 position; void main() { gl_Position = position; }'; + var fs = 'void main() { gl_FragColor = vec4(1.0); }'; + + var cache = new ShaderCache(context); + var sp = cache.getShaderProgram({ + vertexShaderSource : vs, + fragmentShaderSource : fs, + attributeLocations : { + position : 0 + } + }); + + var keyword = 'derived'; + var fsDerived = 'void main() { gl_FragColor = vec4(vec3(1.0), 0.5); }'; + var spDerived = cache.createDerivedShaderProgram(sp, keyword, { + vertexShaderSource : vs, + fragmentShaderSource : fsDerived, + attributeLocations : { + position : 0 + } + }); + expect(spDerived).toBeDefined(); + + sp.destroy(); + cache.destroyReleasedShaderPrograms(); + + expect(sp.isDestroyed()).toEqual(true); + expect(spDerived.isDestroyed()).toEqual(true); + + cache.destroy(); + }); + it('is destroyed', function() { var vs = 'attribute vec4 position; void main() { gl_Position = position; }'; var fs = 'void main() { gl_FragColor = vec4(1.0); }'; diff --git a/Specs/Scene/SceneSpec.js b/Specs/Scene/SceneSpec.js index 2d98e1a6302b..df8e6649e9d9 100644 --- a/Specs/Scene/SceneSpec.js +++ b/Specs/Scene/SceneSpec.js @@ -19,7 +19,9 @@ defineSuite([ 'Renderer/Framebuffer', 'Renderer/Pass', 'Renderer/PixelDatatype', + 'Renderer/RenderState', 'Renderer/ShaderProgram', + 'Renderer/ShaderSource', 'Renderer/Texture', 'Scene/Camera', 'Scene/EllipsoidSurfaceAppearance', @@ -55,7 +57,9 @@ defineSuite([ Framebuffer, Pass, PixelDatatype, + RenderState, ShaderProgram, + ShaderSource, Texture, Camera, EllipsoidSurfaceAppearance, @@ -74,9 +78,21 @@ defineSuite([ 'use strict'; var scene; + var simpleShaderProgram; + var simpleRenderState; beforeAll(function() { scene = createScene(); + simpleShaderProgram = ShaderProgram.fromCache({ + context : scene.context, + vertexShaderSource : new ShaderSource({ + sources : ['void main() { gl_Position = vec4(1.0); }'] + }), + fragmentShaderSource : new ShaderSource({ + sources : ['void main() { gl_FragColor = vec4(1.0); }'] + }) + }); + simpleRenderState = new RenderState(); }); afterEach(function() { @@ -199,6 +215,8 @@ defineSuite([ it('debugCommandFilter filters commands', function() { var c = new DrawCommand({ + shaderProgram : simpleShaderProgram, + renderState : simpleRenderState, pass : Pass.OPAQUE }); c.execute = function() {}; @@ -216,6 +234,8 @@ defineSuite([ it('debugCommandFilter does not filter commands', function() { var c = new DrawCommand({ + shaderProgram : simpleShaderProgram, + renderState : simpleRenderState, pass : Pass.OPAQUE }); c.execute = function() {}; @@ -233,6 +253,8 @@ defineSuite([ var center = Cartesian3.add(scene.camera.position, scene.camera.direction, new Cartesian3()); var c = new DrawCommand({ + shaderProgram : simpleShaderProgram, + renderState : simpleRenderState, pass : Pass.OPAQUE, debugShowBoundingVolume : true, boundingVolume : new BoundingSphere(center, radius) @@ -249,13 +271,9 @@ defineSuite([ it('debugShowCommands tints commands', function() { var c = new DrawCommand({ - pass : Pass.OPAQUE, - - shaderProgram : ShaderProgram.fromCache({ - context : scene.context, - vertexShaderSource : 'void main() { gl_Position = vec4(1.0); }', - fragmentShaderSource : 'void main() { gl_FragColor = vec4(1.0); }' - }) + shaderProgram : simpleShaderProgram, + renderState : simpleRenderState, + pass : Pass.OPAQUE }); c.execute = function() {}; @@ -753,6 +771,66 @@ defineSuite([ }); }); + it('pickPosition picks translucent geometry when pickTranslucentDepth is true', function() { + if (!scene.pickPositionSupported) { + return; + } + + var rectangle = Rectangle.fromDegrees(-100.0, 30.0, -90.0, 40.0); + scene.camera.setView({ + destination : rectangle + }); + + var canvas = scene.canvas; + var windowPosition = new Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2); + + var rectanglePrimitive = createRectangle(rectangle); + rectanglePrimitive.appearance.material.uniforms.color = new Color(1.0, 0.0, 0.0, 0.5); + + var primitives = scene.primitives; + primitives.add(rectanglePrimitive); + + scene.useDepthPicking = true; + scene.pickTranslucentDepth = false; + expect(scene).toRenderAndCall(function() { + var position = scene.pickPosition(windowPosition); + expect(position).not.toBeDefined(); + }); + + scene.pickTranslucentDepth = true; + expect(scene).toRenderAndCall(function() { + var position = scene.pickPosition(windowPosition); + expect(position).toBeDefined(); + }); + + var rectanglePrimitive2 = createRectangle(rectangle); + rectanglePrimitive2.appearance.material.uniforms.color = new Color(0.0, 1.0, 0.0, 0.5); + primitives.add(rectanglePrimitive2); + + expect(scene).toRenderAndCall(function() { + var position = scene.pickPosition(windowPosition); + expect(position).toBeDefined(); + + var commandList = scene.frameState.commandList; + expect(commandList.length).toEqual(2); + + var command1 = commandList[0]; + var command2 = commandList[1]; + + expect(command1.derivedCommands).toBeDefined(); + expect(command2.derivedCommands).toBeDefined(); + + expect(command1.derivedCommands.depth).toBeDefined(); + expect(command2.derivedCommands.depth).toBeDefined(); + + expect(command1.derivedCommands.depth.depthOnlyCommand).toBeDefined(); + expect(command2.derivedCommands.depth.depthOnlyCommand).toBeDefined(); + + expect(command1.derivedCommands.depth.depthOnlyCommand.shaderProgram).toEqual(command2.derivedCommands.depth.depthOnlyCommand.shaderProgram); + expect(command1.derivedCommands.depth.depthOnlyCommand.renderState).toEqual(command2.derivedCommands.depth.depthOnlyCommand.renderState); + }); + }); + it('pickPosition throws without windowPosition', function() { expect(function() { scene.pickPosition();