import BoundingRectangle from "../Core/BoundingRectangle.js"; import Color from "../Core/Color.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; import PixelFormat from "../Core/PixelFormat.js"; import WebGLConstants from "../Core/WebGLConstants.js"; import ClearCommand from "../Renderer/ClearCommand.js"; import DrawCommand from "../Renderer/DrawCommand.js"; import FramebufferManager from "../Renderer/FramebufferManager.js"; import PixelDatatype from "../Renderer/PixelDatatype.js"; import RenderState from "../Renderer/RenderState.js"; import ShaderSource from "../Renderer/ShaderSource.js"; import Texture from "../Renderer/Texture.js"; import AdjustTranslucentFS from "../Shaders/AdjustTranslucentFS.js"; import CompositeOITFS from "../Shaders/CompositeOITFS.js"; import BlendEquation from "./BlendEquation.js"; import BlendFunction from "./BlendFunction.js"; /** * @private */ function OIT(context) { this._numSamples = 1; // We support multipass for the Chrome D3D9 backend and ES 2.0 on mobile. this._translucentMultipassSupport = false; this._translucentMRTSupport = false; const extensionsSupported = context.colorBufferFloat && context.depthTexture; this._translucentMRTSupport = context.drawBuffers && extensionsSupported; this._translucentMultipassSupport = !this._translucentMRTSupport && extensionsSupported; this._opaqueFBO = undefined; this._opaqueTexture = undefined; this._depthStencilTexture = undefined; this._accumulationTexture = undefined; this._translucentFBO = new FramebufferManager({ colorAttachmentsLength: this._translucentMRTSupport ? 2 : 1, createColorAttachments: false, createDepthAttachments: false, depth: true, }); this._alphaFBO = new FramebufferManager({ createColorAttachments: false, createDepthAttachments: false, depth: true, }); this._adjustTranslucentFBO = new FramebufferManager({ colorAttachmentsLength: this._translucentMRTSupport ? 2 : 1, createColorAttachments: false, }); this._adjustAlphaFBO = new FramebufferManager({ createColorAttachments: false, }); this._opaqueClearCommand = new ClearCommand({ color: new Color(0.0, 0.0, 0.0, 0.0), owner: this, }); this._translucentMRTClearCommand = new ClearCommand({ color: new Color(0.0, 0.0, 0.0, 1.0), owner: this, }); this._translucentMultipassClearCommand = new ClearCommand({ color: new Color(0.0, 0.0, 0.0, 0.0), owner: this, }); this._alphaClearCommand = new ClearCommand({ color: new Color(1.0, 1.0, 1.0, 1.0), owner: this, }); this._translucentRenderStateCache = {}; this._alphaRenderStateCache = {}; this._compositeCommand = undefined; this._adjustTranslucentCommand = undefined; this._adjustAlphaCommand = undefined; this._viewport = new BoundingRectangle(); this._rs = undefined; this._useScissorTest = false; this._scissorRectangle = undefined; this._useHDR = false; } function destroyTextures(oit) { oit._accumulationTexture = oit._accumulationTexture && !oit._accumulationTexture.isDestroyed() && oit._accumulationTexture.destroy(); oit._revealageTexture = oit._revealageTexture && !oit._revealageTexture.isDestroyed() && oit._revealageTexture.destroy(); } function destroyFramebuffers(oit) { oit._translucentFBO.destroy(); oit._alphaFBO.destroy(); oit._adjustTranslucentFBO.destroy(); oit._adjustAlphaFBO.destroy(); } function destroyResources(oit) { destroyTextures(oit); destroyFramebuffers(oit); } function updateTextures(oit, context, width, height) { destroyTextures(oit); oit._accumulationTexture = new Texture({ context: context, width: width, height: height, pixelFormat: PixelFormat.RGBA, pixelDatatype: PixelDatatype.FLOAT, }); // Use zeroed arraybuffer instead of null to initialize texture // to workaround Firefox. Only needed for the second color attachment. const source = new Float32Array(width * height * 4); oit._revealageTexture = new Texture({ context: context, pixelFormat: PixelFormat.RGBA, pixelDatatype: PixelDatatype.FLOAT, source: { arrayBufferView: source, width: width, height: height, }, flipY: false, }); } function updateFramebuffers(oit, context) { destroyFramebuffers(oit); const completeFBO = WebGLConstants.FRAMEBUFFER_COMPLETE; let supported = true; const width = oit._accumulationTexture.width; const height = oit._accumulationTexture.height; // if MRT is supported, attempt to make an FBO with multiple color attachments if (oit._translucentMRTSupport) { oit._translucentFBO.setColorTexture(oit._accumulationTexture, 0); oit._translucentFBO.setColorTexture(oit._revealageTexture, 1); oit._translucentFBO.setDepthStencilTexture(oit._depthStencilTexture); oit._translucentFBO.update(context, width, height); oit._adjustTranslucentFBO.setColorTexture(oit._accumulationTexture, 0); oit._adjustTranslucentFBO.setColorTexture(oit._revealageTexture, 1); oit._adjustTranslucentFBO.update(context, width, height); if ( oit._translucentFBO.status !== completeFBO || oit._adjustTranslucentFBO.status !== completeFBO ) { destroyFramebuffers(oit); oit._translucentMRTSupport = false; } } // either MRT isn't supported or FBO creation failed, attempt multipass if (!oit._translucentMRTSupport) { oit._translucentFBO.setColorTexture(oit._accumulationTexture); oit._translucentFBO.setDepthStencilTexture(oit._depthStencilTexture); oit._translucentFBO.update(context, width, height); oit._alphaFBO.setColorTexture(oit._revealageTexture); oit._alphaFBO.setDepthStencilTexture(oit._depthStencilTexture); oit._alphaFBO.update(context, width, height); oit._adjustTranslucentFBO.setColorTexture(oit._accumulationTexture); oit._adjustTranslucentFBO.update(context, width, height); oit._adjustAlphaFBO.setColorTexture(oit._revealageTexture); oit._adjustAlphaFBO.update(context, width, height); const translucentComplete = oit._translucentFBO.status === completeFBO; const alphaComplete = oit._alphaFBO.status === completeFBO; const adjustTranslucentComplete = oit._adjustTranslucentFBO.status === completeFBO; const adjustAlphaComplete = oit._adjustAlphaFBO.status === completeFBO; if ( !translucentComplete || !alphaComplete || !adjustTranslucentComplete || !adjustAlphaComplete ) { destroyResources(oit); oit._translucentMultipassSupport = false; supported = false; } } return supported; } OIT.prototype.update = function ( context, passState, framebuffer, useHDR, numSamples ) { if (!this.isSupported()) { return; } this._opaqueFBO = framebuffer; this._opaqueTexture = framebuffer.getColorTexture(0); this._depthStencilTexture = framebuffer.getDepthStencilTexture(); const width = this._opaqueTexture.width; const height = this._opaqueTexture.height; const accumulationTexture = this._accumulationTexture; const textureChanged = !defined(accumulationTexture) || accumulationTexture.width !== width || accumulationTexture.height !== height || useHDR !== this._useHDR; const samplesChanged = this._numSamples !== numSamples; if (textureChanged || samplesChanged) { this._numSamples = numSamples; updateTextures(this, context, width, height); } if ( !defined(this._translucentFBO.framebuffer) || textureChanged || samplesChanged ) { if (!updateFramebuffers(this, context)) { // framebuffer creation failed return; } } this._useHDR = useHDR; const that = this; let fs; let uniformMap; if (!defined(this._compositeCommand)) { fs = new ShaderSource({ sources: [CompositeOITFS], }); if (this._translucentMRTSupport) { fs.defines.push("MRT"); } uniformMap = { u_opaque: function () { return that._opaqueTexture; }, u_accumulation: function () { return that._accumulationTexture; }, u_revealage: function () { return that._revealageTexture; }, }; this._compositeCommand = context.createViewportQuadCommand(fs, { uniformMap: uniformMap, owner: this, }); } if (!defined(this._adjustTranslucentCommand)) { if (this._translucentMRTSupport) { fs = new ShaderSource({ defines: ["MRT"], sources: [AdjustTranslucentFS], }); uniformMap = { u_bgColor: function () { return that._translucentMRTClearCommand.color; }, u_depthTexture: function () { return that._depthStencilTexture; }, }; this._adjustTranslucentCommand = context.createViewportQuadCommand(fs, { uniformMap: uniformMap, owner: this, }); } else if (this._translucentMultipassSupport) { fs = new ShaderSource({ sources: [AdjustTranslucentFS], }); uniformMap = { u_bgColor: function () { return that._translucentMultipassClearCommand.color; }, u_depthTexture: function () { return that._depthStencilTexture; }, }; this._adjustTranslucentCommand = context.createViewportQuadCommand(fs, { uniformMap: uniformMap, owner: this, }); uniformMap = { u_bgColor: function () { return that._alphaClearCommand.color; }, u_depthTexture: function () { return that._depthStencilTexture; }, }; this._adjustAlphaCommand = context.createViewportQuadCommand(fs, { uniformMap: uniformMap, owner: this, }); } } this._viewport.width = width; this._viewport.height = height; const useScissorTest = !BoundingRectangle.equals( this._viewport, passState.viewport ); let updateScissor = useScissorTest !== this._useScissorTest; this._useScissorTest = useScissorTest; if (!BoundingRectangle.equals(this._scissorRectangle, passState.viewport)) { this._scissorRectangle = BoundingRectangle.clone( passState.viewport, this._scissorRectangle ); updateScissor = true; } if ( !defined(this._rs) || !BoundingRectangle.equals(this._viewport, this._rs.viewport) || updateScissor ) { this._rs = RenderState.fromCache({ viewport: this._viewport, scissorTest: { enabled: this._useScissorTest, rectangle: this._scissorRectangle, }, }); } if (defined(this._compositeCommand)) { this._compositeCommand.renderState = this._rs; } if (this._adjustTranslucentCommand) { this._adjustTranslucentCommand.renderState = this._rs; } if (defined(this._adjustAlphaCommand)) { this._adjustAlphaCommand.renderState = this._rs; } }; const translucentMRTBlend = { enabled: true, color: new Color(0.0, 0.0, 0.0, 0.0), equationRgb: BlendEquation.ADD, equationAlpha: BlendEquation.ADD, functionSourceRgb: BlendFunction.ONE, functionDestinationRgb: BlendFunction.ONE, functionSourceAlpha: BlendFunction.ZERO, functionDestinationAlpha: BlendFunction.ONE_MINUS_SOURCE_ALPHA, }; const translucentColorBlend = { enabled: true, color: new Color(0.0, 0.0, 0.0, 0.0), equationRgb: BlendEquation.ADD, equationAlpha: BlendEquation.ADD, functionSourceRgb: BlendFunction.ONE, functionDestinationRgb: BlendFunction.ONE, functionSourceAlpha: BlendFunction.ONE, functionDestinationAlpha: BlendFunction.ONE, }; const translucentAlphaBlend = { enabled: true, color: new Color(0.0, 0.0, 0.0, 0.0), equationRgb: BlendEquation.ADD, equationAlpha: BlendEquation.ADD, functionSourceRgb: BlendFunction.ZERO, functionDestinationRgb: BlendFunction.ONE_MINUS_SOURCE_ALPHA, functionSourceAlpha: BlendFunction.ZERO, functionDestinationAlpha: BlendFunction.ONE_MINUS_SOURCE_ALPHA, }; function getTranslucentRenderState( context, translucentBlending, cache, renderState ) { let translucentState = cache[renderState.id]; if (!defined(translucentState)) { const rs = RenderState.getState(renderState); rs.depthMask = false; rs.blending = translucentBlending; translucentState = RenderState.fromCache(rs); cache[renderState.id] = translucentState; } return translucentState; } function getTranslucentMRTRenderState(oit, context, renderState) { return getTranslucentRenderState( context, translucentMRTBlend, oit._translucentRenderStateCache, renderState ); } function getTranslucentColorRenderState(oit, context, renderState) { return getTranslucentRenderState( context, translucentColorBlend, oit._translucentRenderStateCache, renderState ); } function getTranslucentAlphaRenderState(oit, context, renderState) { return getTranslucentRenderState( context, translucentAlphaBlend, oit._alphaRenderStateCache, renderState ); } const mrtShaderSource = " vec3 Ci = czm_gl_FragColor.rgb * czm_gl_FragColor.a;\n" + " float ai = czm_gl_FragColor.a;\n" + " float wzi = czm_alphaWeight(ai);\n" + " gl_FragData[0] = vec4(Ci * wzi, ai);\n" + " gl_FragData[1] = vec4(ai * wzi);\n"; const colorShaderSource = " vec3 Ci = czm_gl_FragColor.rgb * czm_gl_FragColor.a;\n" + " float ai = czm_gl_FragColor.a;\n" + " float wzi = czm_alphaWeight(ai);\n" + " gl_FragColor = vec4(Ci, ai) * wzi;\n"; const alphaShaderSource = " float ai = czm_gl_FragColor.a;\n" + " gl_FragColor = vec4(ai);\n"; function getTranslucentShaderProgram(context, shaderProgram, keyword, source) { let shader = context.shaderCache.getDerivedShaderProgram( shaderProgram, keyword ); if (!defined(shader)) { const attributeLocations = shaderProgram._attributeLocations; const fs = shaderProgram.fragmentShaderSource.clone(); fs.sources = fs.sources.map(function (source) { source = ShaderSource.replaceMain(source, "czm_translucent_main"); source = source.replace(/gl_FragColor/g, "czm_gl_FragColor"); source = source.replace(/\bdiscard\b/g, "czm_discard = true"); source = source.replace(/czm_phong/g, "czm_translucentPhong"); return source; }); // Discarding the fragment in main is a workaround for ANGLE D3D9 // shader compilation errors. fs.sources.splice( 0, 0, `${ source.indexOf("gl_FragData") !== -1 ? "#extension GL_EXT_draw_buffers : enable \n" : "" }vec4 czm_gl_FragColor;\n` + `bool czm_discard = false;\n` ); fs.sources.push( `${ "void main()\n" + "{\n" + " czm_translucent_main();\n" + " if (czm_discard)\n" + " {\n" + " discard;\n" + " }\n" }${source}}\n` ); shader = context.shaderCache.createDerivedShaderProgram( shaderProgram, keyword, { vertexShaderSource: shaderProgram.vertexShaderSource, fragmentShaderSource: fs, attributeLocations: attributeLocations, } ); } return shader; } function getTranslucentMRTShaderProgram(context, shaderProgram) { return getTranslucentShaderProgram( context, shaderProgram, "translucentMRT", mrtShaderSource ); } function getTranslucentColorShaderProgram(context, shaderProgram) { return getTranslucentShaderProgram( context, shaderProgram, "translucentMultipass", colorShaderSource ); } function getTranslucentAlphaShaderProgram(context, shaderProgram) { return getTranslucentShaderProgram( context, shaderProgram, "alphaMultipass", alphaShaderSource ); } OIT.prototype.createDerivedCommands = function (command, context, result) { if (!defined(result)) { result = {}; } if (this._translucentMRTSupport) { let translucentShader; let translucentRenderState; if (defined(result.translucentCommand)) { translucentShader = result.translucentCommand.shaderProgram; translucentRenderState = result.translucentCommand.renderState; } result.translucentCommand = DrawCommand.shallowClone( command, result.translucentCommand ); if ( !defined(translucentShader) || result.shaderProgramId !== command.shaderProgram.id ) { result.translucentCommand.shaderProgram = getTranslucentMRTShaderProgram( context, command.shaderProgram ); result.translucentCommand.renderState = getTranslucentMRTRenderState( this, context, command.renderState ); result.shaderProgramId = command.shaderProgram.id; } else { result.translucentCommand.shaderProgram = translucentShader; result.translucentCommand.renderState = translucentRenderState; } } else { let colorShader; let colorRenderState; let alphaShader; let alphaRenderState; if (defined(result.translucentCommand)) { colorShader = result.translucentCommand.shaderProgram; colorRenderState = result.translucentCommand.renderState; alphaShader = result.alphaCommand.shaderProgram; alphaRenderState = result.alphaCommand.renderState; } result.translucentCommand = DrawCommand.shallowClone( command, result.translucentCommand ); result.alphaCommand = DrawCommand.shallowClone( command, result.alphaCommand ); if ( !defined(colorShader) || result.shaderProgramId !== command.shaderProgram.id ) { result.translucentCommand.shaderProgram = getTranslucentColorShaderProgram( context, command.shaderProgram ); result.translucentCommand.renderState = getTranslucentColorRenderState( this, context, command.renderState ); result.alphaCommand.shaderProgram = getTranslucentAlphaShaderProgram( context, command.shaderProgram ); result.alphaCommand.renderState = getTranslucentAlphaRenderState( this, context, command.renderState ); result.shaderProgramId = command.shaderProgram.id; } else { result.translucentCommand.shaderProgram = colorShader; result.translucentCommand.renderState = colorRenderState; result.alphaCommand.shaderProgram = alphaShader; result.alphaCommand.renderState = alphaRenderState; } } return result; }; function executeTranslucentCommandsSortedMultipass( oit, scene, executeFunction, passState, commands, invertClassification ) { let command; let derivedCommand; let j; const context = scene.context; const useLogDepth = scene.frameState.useLogDepth; const useHdr = scene._hdr; const framebuffer = passState.framebuffer; const length = commands.length; const lightShadowsEnabled = scene.frameState.shadowState.lightShadowsEnabled; passState.framebuffer = oit._adjustTranslucentFBO.framebuffer; oit._adjustTranslucentCommand.execute(context, passState); passState.framebuffer = oit._adjustAlphaFBO.framebuffer; oit._adjustAlphaCommand.execute(context, passState); const debugFramebuffer = oit._opaqueFBO.framebuffer; passState.framebuffer = oit._translucentFBO.framebuffer; for (j = 0; j < length; ++j) { command = commands[j]; command = useLogDepth ? command.derivedCommands.logDepth.command : command; command = useHdr ? command.derivedCommands.hdr.command : command; derivedCommand = lightShadowsEnabled && command.receiveShadows ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction( derivedCommand, scene, context, passState, debugFramebuffer ); } if (defined(invertClassification)) { command = invertClassification.unclassifiedCommand; derivedCommand = lightShadowsEnabled && command.receiveShadows ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction( derivedCommand, scene, context, passState, debugFramebuffer ); } passState.framebuffer = oit._alphaFBO.framebuffer; for (j = 0; j < length; ++j) { command = commands[j]; command = useLogDepth ? command.derivedCommands.logDepth.command : command; command = useHdr ? command.derivedCommands.hdr.command : command; derivedCommand = lightShadowsEnabled && command.receiveShadows ? command.derivedCommands.oit.shadows.alphaCommand : command.derivedCommands.oit.alphaCommand; executeFunction( derivedCommand, scene, context, passState, debugFramebuffer ); } if (defined(invertClassification)) { command = invertClassification.unclassifiedCommand; derivedCommand = lightShadowsEnabled && command.receiveShadows ? command.derivedCommands.oit.shadows.alphaCommand : command.derivedCommands.oit.alphaCommand; executeFunction( derivedCommand, scene, context, passState, debugFramebuffer ); } passState.framebuffer = framebuffer; } function executeTranslucentCommandsSortedMRT( oit, scene, executeFunction, passState, commands, invertClassification ) { const context = scene.context; const useLogDepth = scene.frameState.useLogDepth; const useHdr = scene._hdr; const framebuffer = passState.framebuffer; const length = commands.length; const lightShadowsEnabled = scene.frameState.shadowState.lightShadowsEnabled; passState.framebuffer = oit._adjustTranslucentFBO.framebuffer; oit._adjustTranslucentCommand.execute(context, passState); const debugFramebuffer = oit._opaqueFBO.framebuffer; passState.framebuffer = oit._translucentFBO.framebuffer; let command; let derivedCommand; for (let j = 0; j < length; ++j) { command = commands[j]; command = useLogDepth ? command.derivedCommands.logDepth.command : command; command = useHdr ? command.derivedCommands.hdr.command : command; derivedCommand = lightShadowsEnabled && command.receiveShadows ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction( derivedCommand, scene, context, passState, debugFramebuffer ); } if (defined(invertClassification)) { command = invertClassification.unclassifiedCommand; derivedCommand = lightShadowsEnabled && command.receiveShadows ? command.derivedCommands.oit.shadows.translucentCommand : command.derivedCommands.oit.translucentCommand; executeFunction( derivedCommand, scene, context, passState, debugFramebuffer ); } passState.framebuffer = framebuffer; } OIT.prototype.executeCommands = function ( scene, executeFunction, passState, commands, invertClassification ) { if (this._translucentMRTSupport) { executeTranslucentCommandsSortedMRT( this, scene, executeFunction, passState, commands, invertClassification ); return; } executeTranslucentCommandsSortedMultipass( this, scene, executeFunction, passState, commands, invertClassification ); }; OIT.prototype.execute = function (context, passState) { this._compositeCommand.execute(context, passState); }; OIT.prototype.clear = function (context, passState, clearColor) { const framebuffer = passState.framebuffer; passState.framebuffer = this._opaqueFBO.framebuffer; Color.clone(clearColor, this._opaqueClearCommand.color); this._opaqueClearCommand.execute(context, passState); passState.framebuffer = this._translucentFBO.framebuffer; const translucentClearCommand = this._translucentMRTSupport ? this._translucentMRTClearCommand : this._translucentMultipassClearCommand; translucentClearCommand.execute(context, passState); if (this._translucentMultipassSupport) { passState.framebuffer = this._alphaFBO.framebuffer; this._alphaClearCommand.execute(context, passState); } passState.framebuffer = framebuffer; }; OIT.prototype.isSupported = function () { return this._translucentMRTSupport || this._translucentMultipassSupport; }; OIT.prototype.isDestroyed = function () { return false; }; OIT.prototype.destroy = function () { destroyResources(this); if (defined(this._compositeCommand)) { this._compositeCommand.shaderProgram = this._compositeCommand.shaderProgram && this._compositeCommand.shaderProgram.destroy(); } if (defined(this._adjustTranslucentCommand)) { this._adjustTranslucentCommand.shaderProgram = this._adjustTranslucentCommand.shaderProgram && this._adjustTranslucentCommand.shaderProgram.destroy(); } if (defined(this._adjustAlphaCommand)) { this._adjustAlphaCommand.shaderProgram = this._adjustAlphaCommand.shaderProgram && this._adjustAlphaCommand.shaderProgram.destroy(); } return destroyObject(this); }; export default OIT;