diff --git a/src/framework/lightmapper/lightmap-filters.js b/src/framework/lightmapper/lightmap-filters.js index d529bdad399..60386f783f6 100644 --- a/src/framework/lightmapper/lightmap-filters.js +++ b/src/framework/lightmapper/lightmap-filters.js @@ -17,7 +17,7 @@ class LightmapFilters { this.pixelOffset = new Float32Array(2); // denoise is optional and gets created only when needed - this.shaderDenoise = null; + this.shaderDenoise = []; this.sigmas = null; this.constantSigmas = null; this.kernel = null; @@ -35,10 +35,13 @@ class LightmapFilters { this.constantPixelOffset.setValue(this.pixelOffset); } - prepareDenoise(filterRange, filterSmoothness) { + prepareDenoise(filterRange, filterSmoothness, bakeHDR) { - if (!this.shaderDenoise) { - this.shaderDenoise = createShaderFromCode(this.device, shaderChunks.fullscreenQuadVS, shaderChunksLightmapper.bilateralDeNoisePS, 'lmBilateralDeNoise'); + const index = bakeHDR ? 0 : 1; + if (!this.shaderDenoise[index]) { + const name = `lmBilateralDeNoise-${bakeHDR ? 'hdr' : 'rgbm'}`; + const define = bakeHDR ? '#define HDR\n' : ''; + this.shaderDenoise[index] = createShaderFromCode(this.device, shaderChunks.fullscreenQuadVS, define + shaderChunksLightmapper.bilateralDeNoisePS, name); this.sigmas = new Float32Array(2); this.constantSigmas = this.device.scope.resolve('sigmas'); this.constantKernel = this.device.scope.resolve('kernel[0]'); @@ -52,6 +55,21 @@ class LightmapFilters { this.evaluateDenoiseUniforms(filterRange, filterSmoothness); } + getDenoise(bakeHDR) { + const index = bakeHDR ? 0 : 1; + return this.shaderDenoise[index]; + } + + getDilate(device, bakeHDR) { + const index = bakeHDR ? 0 : 1; + if (!this.shaderDilate[index]) { + const name = `lmDilate-${bakeHDR ? 'hdr' : 'rgbm'}`; + const define = bakeHDR ? '#define HDR\n' : ''; + this.shaderDilate[index] = createShaderFromCode(device, shaderChunks.fullscreenQuadVS, define + shaderChunksLightmapper.dilatePS, name); + } + return this.shaderDilate[index]; + } + evaluateDenoiseUniforms(filterRange, filterSmoothness) { function normpdf(x, sigma) { diff --git a/src/framework/lightmapper/lightmapper.js b/src/framework/lightmapper/lightmapper.js index 4b3147b3d44..96e806ca6a5 100644 --- a/src/framework/lightmapper/lightmapper.js +++ b/src/framework/lightmapper/lightmapper.js @@ -129,6 +129,8 @@ class Lightmapper { initBake(device) { + this.bakeHDR = this.scene.lightmapPixelFormat !== PIXELFORMAT_RGBA8; + // only initialize one time if (!this._initCalled) { this._initCalled = true; @@ -251,7 +253,7 @@ class Lightmapper { } else { material.ambient = new Color(0, 0, 0); // don't bake ambient } - material.chunks.basePS = shaderChunks.basePS + (scene.lightmapPixelFormat === PIXELFORMAT_RGBA8 ? '\n#define LIGHTMAP_RGBM\n' : ''); + material.chunks.basePS = shaderChunks.basePS + (this.bakeHDR ? '' : '\n#define LIGHTMAP_RGBM\n'); material.chunks.endPS = bakeLmEndChunk; material.lightMap = this.blackTex; } else { @@ -299,7 +301,7 @@ class Lightmapper { height: size, format: this.scene.lightmapPixelFormat, mipmaps: false, - type: this.scene.lightmapPixelFormat === PIXELFORMAT_RGBA8 ? TEXTURETYPE_RGBM : TEXTURETYPE_DEFAULT, + type: this.bakeHDR ? TEXTURETYPE_DEFAULT : TEXTURETYPE_RGBM, minFilter: FILTER_NEAREST, magFilter: FILTER_NEAREST, addressU: ADDRESS_CLAMP_TO_EDGE, @@ -876,12 +878,14 @@ class Lightmapper { postprocessTextures(device, bakeNodes, passCount) { const numDilates2x = 1; // 1 or 2 dilates (depending on filter being enabled) - const dilateShader = this.lightmapFilters.shaderDilate; + const dilateShader = this.lightmapFilters.getDilate(device, this.bakeHDR); + let denoiseShader; // bilateral denoise filter - runs as a first pass, before dilate const filterLightmap = this.scene.lightmapFilterEnabled; if (filterLightmap) { - this.lightmapFilters.prepareDenoise(this.scene.lightmapFilterRange, this.scene.lightmapFilterSmoothness); + this.lightmapFilters.prepareDenoise(this.scene.lightmapFilterRange, this.scene.lightmapFilterSmoothness, this.bakeHDR); + denoiseShader = this.lightmapFilters.getDenoise(this.bakeHDR); } device.setBlendState(BlendState.NOBLEND); @@ -908,7 +912,7 @@ class Lightmapper { this.lightmapFilters.setSourceTexture(lightmap); const bilateralFilterEnabled = filterLightmap && pass === 0 && i === 0; - drawQuadWithShader(device, tempRT, bilateralFilterEnabled ? this.lightmapFilters.shaderDenoise : dilateShader); + drawQuadWithShader(device, tempRT, bilateralFilterEnabled ? denoiseShader : dilateShader); this.lightmapFilters.setSourceTexture(tempTex); drawQuadWithShader(device, nodeRT, dilateShader); diff --git a/src/scene/shader-lib/chunks/lightmapper/frag/bilateralDeNoise.js b/src/scene/shader-lib/chunks/lightmapper/frag/bilateralDeNoise.js index 5d8f6391e05..6f05cee1ac9 100644 --- a/src/scene/shader-lib/chunks/lightmapper/frag/bilateralDeNoise.js +++ b/src/scene/shader-lib/chunks/lightmapper/frag/bilateralDeNoise.js @@ -33,6 +33,22 @@ vec4 encodeRGBM(vec3 color) { // modified RGBM return encoded; } +vec3 decode(vec4 pixel) { + #if HDR + return pixel.rgb; + #else + return decodeRGBM(pixel); + #endif +} + +bool isUsed(vec4 pixel) { + #if HDR + return any(greaterThan(pixel.rgb, vec3(0.0))); + #else + return pixel.a > 0.0; + #endif +} + // filter size #define MSIZE 15 @@ -45,13 +61,13 @@ uniform float kernel[MSIZE]; void main(void) { - vec4 pixelRgbm = texture2DLod(source, vUv0, 0.0); + vec4 pixel = texture2DLod(source, vUv0, 0.0); // lightmap specific optimization - skip pixels that were not baked // this also allows dilate filter that work on the output of this to work correctly, as it depends on .a being zero // to dilate, which the following blur filter would otherwise modify - if (pixelRgbm.a <= 0.0) { - gl_FragColor = pixelRgbm; + if (!isUsed(pixel)) { + gl_FragColor = pixel; return ; } @@ -61,9 +77,9 @@ void main(void) { // domain sigma - controls blurriness based on a pixel similarity (to preserve edges) float bSigma = sigmas.y; - vec3 pixelHdr = decodeRGBM(pixelRgbm); + vec3 pixelHdr = decode(pixel); vec3 accumulatedHdr = vec3(0.0); - float accumulatedFactor = 0.0; + float accumulatedFactor = 0.000001; // avoid division by zero // read out the texels const int kSize = (MSIZE-1)/2; @@ -72,11 +88,11 @@ void main(void) { // sample the pixel with offset vec2 coord = vUv0 + vec2(float(i), float(j)) * pixelOffset; - vec4 rgbm = texture2DLod(source, coord, 0.0); + vec4 pix = texture2DLod(source, coord, 0.0); // lightmap - only use baked pixels - if (rgbm.a > 0.0) { - vec3 hdr = decodeRGBM(rgbm); + if (isUsed(pix)) { + vec3 hdr = decode(pix); // bilateral factors float factor = kernel[kSize + j] * kernel[kSize + i]; @@ -89,6 +105,12 @@ void main(void) { } } - gl_FragColor = encodeRGBM(accumulatedHdr / accumulatedFactor); + vec3 finalHDR = accumulatedHdr / accumulatedFactor; + + #if HDR + gl_FragColor = vec4(finalHDR, 1.0); + #else + gl_FragColor = encodeRGBM(finalHDR); + #endif } `; diff --git a/src/scene/shader-lib/chunks/lightmapper/frag/dilate.js b/src/scene/shader-lib/chunks/lightmapper/frag/dilate.js index 34dfb029995..4ef1044b745 100644 --- a/src/scene/shader-lib/chunks/lightmapper/frag/dilate.js +++ b/src/scene/shader-lib/chunks/lightmapper/frag/dilate.js @@ -5,16 +5,24 @@ varying vec2 vUv0; uniform sampler2D source; uniform vec2 pixelOffset; +bool isUsed(vec4 pixel) { + #if HDR + return any(greaterThan(pixel.rgb, vec3(0.0))); + #else + return pixel.a > 0.0; + #endif +} + void main(void) { vec4 c = texture2DLod(source, vUv0, 0.0); - c = c.a>0.0? c : texture2DLod(source, vUv0 - pixelOffset, 0.0); - c = c.a>0.0? c : texture2DLod(source, vUv0 + vec2(0, -pixelOffset.y), 0.0); - c = c.a>0.0? c : texture2DLod(source, vUv0 + vec2(pixelOffset.x, -pixelOffset.y), 0.0); - c = c.a>0.0? c : texture2DLod(source, vUv0 + vec2(-pixelOffset.x, 0), 0.0); - c = c.a>0.0? c : texture2DLod(source, vUv0 + vec2(pixelOffset.x, 0), 0.0); - c = c.a>0.0? c : texture2DLod(source, vUv0 + vec2(-pixelOffset.x, pixelOffset.y), 0.0); - c = c.a>0.0? c : texture2DLod(source, vUv0 + vec2(0, pixelOffset.y), 0.0); - c = c.a>0.0? c : texture2DLod(source, vUv0 + pixelOffset, 0.0); + c = isUsed(c) ? c : texture2DLod(source, vUv0 - pixelOffset, 0.0); + c = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(0, -pixelOffset.y), 0.0); + c = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(pixelOffset.x, -pixelOffset.y), 0.0); + c = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(-pixelOffset.x, 0), 0.0); + c = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(pixelOffset.x, 0), 0.0); + c = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(-pixelOffset.x, pixelOffset.y), 0.0); + c = isUsed(c) ? c : texture2DLod(source, vUv0 + vec2(0, pixelOffset.y), 0.0); + c = isUsed(c) ? c : texture2DLod(source, vUv0 + pixelOffset, 0.0); gl_FragColor = c; } `;