diff --git a/examples/src/examples/graphics/clustered-lighting.example.mjs b/examples/src/examples/graphics/clustered-lighting.example.mjs index 4db6aac393e..2c03b6730d3 100644 --- a/examples/src/examples/graphics/clustered-lighting.example.mjs +++ b/examples/src/examples/graphics/clustered-lighting.example.mjs @@ -100,7 +100,6 @@ assetListLoader.load(() => { const cylinderMesh = pc.Mesh.fromGeometry(app.graphicsDevice, new pc.CylinderGeometry({ capSegments: 200 })); const cylinder = new pc.Entity(); cylinder.addComponent('render', { - material: material, meshInstances: [new pc.MeshInstance(cylinderMesh, material)], castShadows: true }); diff --git a/src/extras/render-passes/render-pass-bloom.js b/src/extras/render-passes/render-pass-bloom.js index ae91d86682b..2d382110b69 100644 --- a/src/extras/render-passes/render-pass-bloom.js +++ b/src/extras/render-passes/render-pass-bloom.js @@ -9,6 +9,10 @@ import { RenderPassDownsample } from './render-pass-downsample.js'; import { RenderPassUpsample } from './render-pass-upsample.js'; import { math } from '../../core/math/math.js'; +/** + * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' + */ + // based on https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom /** * Render pass implementation of HDR bloom effect. @@ -27,6 +31,12 @@ class RenderPassBloom extends RenderPass { renderTargets = []; + /** + * @param {GraphicsDevice} device - The graphics device. + * @param {Texture} sourceTexture - The source texture, usually at half the resolution of the + * render target getting blurred. + * @param {number} format - The texture format. + */ constructor(device, sourceTexture, format) { super(device); this._sourceTexture = sourceTexture; @@ -95,8 +105,7 @@ class RenderPassBloom extends RenderPass { let passSourceTexture = this._sourceTexture; for (let i = 0; i < numPasses; i++) { - const fast = i === 0; // fast box downscale for the first pass - const pass = new RenderPassDownsample(device, passSourceTexture, fast); + const pass = new RenderPassDownsample(device, passSourceTexture); const rt = this.renderTargets[i]; pass.init(rt, { resizeSource: passSourceTexture, @@ -130,24 +139,6 @@ class RenderPassBloom extends RenderPass { this.destroyRenderTargets(1); } - set sourceTexture(value) { - this._sourceTexture = value; - - if (this.beforePasses.length > 0) { - const firstPass = this.beforePasses[0]; - - // change resize source - firstPass.options.resizeSource = value; - - // change downsample source - firstPass.sourceTexture = value; - } - } - - get sourceTexture() { - return this._sourceTexture; - } - frameUpdate() { super.frameUpdate(); diff --git a/src/extras/render-passes/render-pass-camera-frame.js b/src/extras/render-passes/render-pass-camera-frame.js index 7e539c4c860..8db06c6b60d 100644 --- a/src/extras/render-passes/render-pass-camera-frame.js +++ b/src/extras/render-passes/render-pass-camera-frame.js @@ -13,6 +13,8 @@ import { RenderPassPrepass } from './render-pass-prepass.js'; import { RenderPassSsao } from './render-pass-ssao.js'; import { SSAOTYPE_COMBINE, SSAOTYPE_LIGHTING, SSAOTYPE_NONE } from './constants.js'; import { Debug } from '../../core/debug.js'; +import { RenderPassDownsample } from './render-pass-downsample.js'; +import { Color } from '../../core/math/color.js'; class CameraFrameOptions { formats; @@ -71,6 +73,8 @@ class RenderPassCameraFrame extends RenderPass { taaPass; + scenePassHalf; + _renderTargetScale = 1; /** @@ -96,6 +100,7 @@ class RenderPassCameraFrame extends RenderPass { reset() { this.sceneTexture = null; + this.sceneTextureHalf = null; if (this.rt) { this.rt.destroyTextureBuffers(); @@ -103,6 +108,12 @@ class RenderPassCameraFrame extends RenderPass { this.rt = null; } + if (this.rtHalf) { + this.rtHalf.destroyTextureBuffers(); + this.rtHalf.destroy(); + this.rtHalf = null; + } + // destroy all passes we created this.beforePasses.forEach(pass => pass.destroy()); this.beforePasses.length = 0; @@ -116,6 +127,7 @@ class RenderPassCameraFrame extends RenderPass { this.ssaoPass = null; this.taaPass = null; this.afterPass = null; + this.scenePassHalf = null; } sanitizeOptions(options) { @@ -180,6 +192,29 @@ class RenderPassCameraFrame extends RenderPass { } } + createRenderTarget(name, depth, stencil, samples, flipY) { + + const texture = new Texture(this.device, { + name: name, + width: 4, + height: 4, + format: this.hdrFormat, + mipmaps: false, + minFilter: FILTER_LINEAR, + magFilter: FILTER_LINEAR, + addressU: ADDRESS_CLAMP_TO_EDGE, + addressV: ADDRESS_CLAMP_TO_EDGE + }); + + return new RenderTarget({ + colorBuffer: texture, + depth: depth, + stencil: stencil, + samples: samples, + flipY: flipY + }); + } + setupRenderPasses(options) { const { device } = this; @@ -188,6 +223,12 @@ class RenderPassCameraFrame extends RenderPass { this.hdrFormat = device.getRenderableHdrFormat(options.formats, true, options.samples) || PIXELFORMAT_RGBA8; + // HDR bloom is not supported on RGBA8 format + this._bloomEnabled = options.bloomEnabled && this.hdrFormat !== PIXELFORMAT_RGBA8; + + // bloom needs half resolution scene texture + this._sceneHalfEnabled = this._bloomEnabled; + // camera renders in HDR mode (linear output, no tonemapping) cameraComponent.gammaCorrection = GAMMA_NONE; cameraComponent.toneMapping = TONEMAP_NONE; @@ -196,25 +237,15 @@ class RenderPassCameraFrame extends RenderPass { cameraComponent.shaderParams.ssaoEnabled = options.ssaoType === SSAOTYPE_LIGHTING; // create a render target to render the scene into - this.sceneTexture = new Texture(device, { - name: 'SceneColor', - width: 4, - height: 4, - format: this.hdrFormat, - mipmaps: false, - minFilter: FILTER_LINEAR, - magFilter: FILTER_LINEAR, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }); - - this.rt = new RenderTarget({ - colorBuffer: this.sceneTexture, - depth: true, - stencil: options.stencil, - samples: options.samples, - flipY: !!targetRenderTarget?.flipY // flipY is inherited from the target renderTarget - }); + const flipY = !!targetRenderTarget?.flipY; // flipY is inherited from the target renderTarget + this.rt = this.createRenderTarget('SceneColor', true, options.stencil, options.samples, flipY); + this.sceneTexture = this.rt.colorBuffer; + + // when half size scene color buffer is used + if (this._sceneHalfEnabled) { + this.rtHalf = this.createRenderTarget('SceneColorHalf', false, false, 1, flipY); + this.sceneTextureHalf = this.rtHalf.colorBuffer; + } this.sceneOptions = { resizeSource: targetRenderTarget, @@ -231,7 +262,7 @@ class RenderPassCameraFrame extends RenderPass { collectPasses() { // use these prepared render passes in the order they should be executed - return [this.prePass, this.ssaoPass, this.scenePass, this.colorGrabPass, this.scenePassTransparent, this.taaPass, this.bloomPass, this.composePass, this.afterPass]; + return [this.prePass, this.ssaoPass, this.scenePass, this.colorGrabPass, this.scenePassTransparent, this.taaPass, this.scenePassHalf, this.bloomPass, this.composePass, this.afterPass]; } createPasses(options) { @@ -248,8 +279,11 @@ class RenderPassCameraFrame extends RenderPass { // TAA const sceneTextureWithTaa = this.setupTaaPass(options); + // downscale to half resolution + this.setupSceneHalfPass(options, sceneTextureWithTaa); + // bloom - this.setupBloomPass(options, sceneTextureWithTaa); + this.setupBloomPass(options, this.sceneTextureHalf); // compose this.setupComposePass(options); @@ -328,9 +362,23 @@ class RenderPassCameraFrame extends RenderPass { } } + setupSceneHalfPass(options, sourceTexture) { + + if (this._sceneHalfEnabled) { + this.scenePassHalf = new RenderPassDownsample(this.device, this.sceneTexture, true); + this.scenePassHalf.name = 'RenderPassSceneHalf'; + this.scenePassHalf.init(this.rtHalf, { + resizeSource: sourceTexture, + scaleX: 0.5, + scaleY: 0.5 + }); + this.scenePassHalf.setClearColor(Color.BLACK); + } + } + setupBloomPass(options, inputTexture) { - // HDR bloom is not supported on RGBA8 format - if (options.bloomEnabled && this.hdrFormat !== PIXELFORMAT_RGBA8) { + + if (this._bloomEnabled) { // create a bloom pass, which generates bloom texture based on the provided texture this.bloomPass = new RenderPassBloom(this.device, inputTexture, this.hdrFormat); } @@ -387,9 +435,7 @@ class RenderPassCameraFrame extends RenderPass { // TAA history buffer is double buffered, assign the current one to the follow up passes. this.composePass.sceneTexture = sceneTexture; - if (this.options.bloomEnabled && this.bloomPass) { - this.bloomPass.sourceTexture = sceneTexture; - } + this.scenePassHalf?.setSourceTexture(sceneTexture); } } diff --git a/src/extras/render-passes/render-pass-downsample.js b/src/extras/render-passes/render-pass-downsample.js index e047ef099cb..8cd1d294748 100644 --- a/src/extras/render-passes/render-pass-downsample.js +++ b/src/extras/render-passes/render-pass-downsample.js @@ -61,6 +61,13 @@ class RenderPassDownsample extends RenderPassShaderQuad { this.sourceInvResolutionValue = new Float32Array(2); } + setSourceTexture(value) { + this._sourceTexture = value; + + // change resize source + this.options.resizeSource = value; + } + execute() { this.sourceTextureId.setValue(this.sourceTexture); diff --git a/src/platform/graphics/render-target.js b/src/platform/graphics/render-target.js index bf3c0558a9f..91a3008a53b 100644 --- a/src/platform/graphics/render-target.js +++ b/src/platform/graphics/render-target.js @@ -312,13 +312,13 @@ class RenderTarget { */ resize(width, height) { - if (this.mipLevel > 0) { - Debug.warn('Only render target rendering to mipLevel 0 can be resized, ignoring.', this); - return; - } - if (this.width !== width || this.height !== height) { + if (this.mipLevel > 0) { + Debug.warn('Only a render target rendering to mipLevel 0 can be resized, ignoring.', this); + return; + } + // release existing const device = this._device; this.destroyFrameBuffers();