diff --git a/examples/src/examples/graphics/lights.example.mjs b/examples/src/examples/graphics/lights.example.mjs index 1f2c7afa4e2..d4c8503aa31 100644 --- a/examples/src/examples/graphics/lights.example.mjs +++ b/examples/src/examples/graphics/lights.example.mjs @@ -182,6 +182,10 @@ assetListLoader.load(() => { type: 'omni', color: pc.Color.YELLOW, castShadows: true, + shadowBias: 0.05, + normalOffsetBias: 0.03, + shadowType: pc.SHADOW_PCF3, + shadowResolution: 256, range: 111, cookieAsset: cubemapAsset, cookieChannel: 'rgb' diff --git a/src/platform/graphics/shader-utils.js b/src/platform/graphics/shader-utils.js index e3622da2e37..8124cc006d7 100644 --- a/src/platform/graphics/shader-utils.js +++ b/src/platform/graphics/shader-utils.js @@ -196,6 +196,7 @@ class ShaderUtils { precision ${precision} usampler2D; precision ${precision} isampler2D; precision ${precision} sampler2DShadow; + precision ${precision} samplerCubeShadow; precision ${precision} sampler2DArray; `; diff --git a/src/scene/light.js b/src/scene/light.js index f002103576f..d089fb039bc 100644 --- a/src/scene/light.js +++ b/src/scene/light.js @@ -104,12 +104,7 @@ class LightRenderData { get shadowBuffer() { const rt = this.shadowCamera.renderTarget; if (rt) { - const light = this.light; - if (light._type === LIGHTTYPE_OMNI) { - return rt.colorBuffer; - } - - return light._isPcf ? rt.depthBuffer : rt.colorBuffer; + return this.light._isPcf ? rt.depthBuffer : rt.colorBuffer; } return null; @@ -390,9 +385,10 @@ class Light { const device = this.device; - if (this._type === LIGHTTYPE_OMNI && value !== SHADOW_PCF3 && value !== SHADOW_PCSS) { + // omni light supports PCF1, PCF3 and PCSS only + if (this._type === LIGHTTYPE_OMNI && value !== SHADOW_PCF1 && value !== SHADOW_PCF3 && value !== SHADOW_PCSS) { value = SHADOW_PCF3; - } // VSM or HW PCF for omni lights is not supported yet + } // fallback from vsm32 to vsm16 if (value === SHADOW_VSM32 && (!device.textureFloatRenderable || !device.textureFloatFilterable)) { @@ -404,7 +400,7 @@ class Light { value = SHADOW_VSM8; } - this._isVsm = value >= SHADOW_VSM8 && value <= SHADOW_VSM32; + this._isVsm = value === SHADOW_VSM8 || value === SHADOW_VSM16 || value === SHADOW_VSM32; this._isPcf = value === SHADOW_PCF1 || value === SHADOW_PCF3 || value === SHADOW_PCF5; this._shadowType = value; diff --git a/src/scene/renderer/shadow-map.js b/src/scene/renderer/shadow-map.js index cb08ba0fad3..f07f604a675 100644 --- a/src/scene/renderer/shadow-map.js +++ b/src/scene/renderer/shadow-map.js @@ -45,14 +45,12 @@ class ShadowMap { this.renderTargets.length = 0; } - static getShadowFormat(device, shadowType) { + static getShadowFormat(shadowType) { if (shadowType === SHADOW_VSM32) { return PIXELFORMAT_RGBA32F; } else if (shadowType === SHADOW_VSM16) { return PIXELFORMAT_RGBA16F; - } else if (shadowType === SHADOW_PCF5) { - return PIXELFORMAT_DEPTH; - } else if (shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3) { + } else if (shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3 || shadowType === SHADOW_PCF5) { return PIXELFORMAT_DEPTH; } else if (shadowType === SHADOW_PCSS) { return PIXELFORMAT_R32F; @@ -96,7 +94,7 @@ class ShadowMap { static create2dMap(device, size, shadowType) { - const format = this.getShadowFormat(device, shadowType); + const format = this.getShadowFormat(shadowType); const filter = this.getShadowFiltering(device, shadowType); const texture = new Texture(device, { @@ -115,7 +113,7 @@ class ShadowMap { }); let target = null; - if (shadowType === SHADOW_PCF5 || shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3) { + if (shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3 || shadowType === SHADOW_PCF5) { // enable hardware PCF when sampling the depth texture texture.compareOnRead = true; @@ -143,7 +141,10 @@ class ShadowMap { static createCubemap(device, size, shadowType) { - const format = shadowType === SHADOW_PCSS ? PIXELFORMAT_R32F : PIXELFORMAT_RGBA8; + const isPcss = shadowType === SHADOW_PCSS; + const format = this.getShadowFormat(shadowType); + const filter = isPcss ? FILTER_NEAREST : FILTER_LINEAR; + const cubemap = new Texture(device, { // #if _PROFILER profilerHint: TEXHINT_SHADOWMAP, @@ -153,21 +154,39 @@ class ShadowMap { height: size, cubemap: true, mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, + minFilter: filter, + magFilter: filter, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE, name: 'ShadowMapCube' }); + // enable hardware PCF when sampling the depth texture + if (!isPcss) { + cubemap.compareOnRead = true; + cubemap.compareFunc = FUNC_LESS; + } + const targets = []; for (let i = 0; i < 6; i++) { - const target = new RenderTarget({ - colorBuffer: cubemap, - face: i, - depth: true - }); - targets.push(target); + + if (isPcss) { + + // color and depth buffer + targets.push(new RenderTarget({ + colorBuffer: cubemap, + face: i, + depth: true + })); + + } else { + + // depth buffer only + targets.push(new RenderTarget({ + depthBuffer: cubemap, + face: i + })); + } } return new ShadowMap(cubemap, targets); } diff --git a/src/scene/renderer/shadow-renderer.js b/src/scene/renderer/shadow-renderer.js index f121b13a5aa..a9654c09790 100644 --- a/src/scene/renderer/shadow-renderer.js +++ b/src/scene/renderer/shadow-renderer.js @@ -131,19 +131,11 @@ class ShadowRenderer { shadowCam.clearDepthBuffer = true; shadowCam.clearStencilBuffer = false; - return shadowCam; - } - - static setShadowCameraSettings(shadowCam, device, shadowType, type, isClustered) { - - // normal omni shadows on webgl2 encode depth in RGBA8 and do manual PCF sampling - // clustered omni shadows on webgl2 use depth format and hardware PCF sampling - let hwPcf = shadowType === SHADOW_PCF5 || shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3; - if (type === LIGHTTYPE_OMNI && !isClustered) { - hwPcf = false; - } - + // clear color buffer only when using it + const hwPcf = shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3 || shadowType === SHADOW_PCF5; shadowCam.clearColorBuffer = !hwPcf; + + return shadowCam; } _cullShadowCastersInternal(meshInstances, visible, camera) { @@ -391,16 +383,9 @@ class ShadowRenderer { prepareFace(light, camera, face) { const type = light._type; - const shadowType = light._shadowType; - const isClustered = this.renderer.scene.clusteredLightingEnabled; - const lightRenderData = this.getLightRenderData(light, camera, face); const shadowCam = lightRenderData.shadowCamera; - // camera clear setting - // Note: when clustered lighting is the only lighting type, this code can be moved to createShadowCamera function - ShadowRenderer.setShadowCameraSettings(shadowCam, this.device, shadowType, type, isClustered); - // assign render target for the face const renderTargetIndex = type === LIGHTTYPE_DIRECTIONAL ? 0 : face; shadowCam.renderTarget = light._shadowMap.renderTargets[renderTargetIndex]; diff --git a/src/scene/shader-lib/chunks/chunks.js b/src/scene/shader-lib/chunks/chunks.js index 5069f449d58..4f5b50dbcb0 100644 --- a/src/scene/shader-lib/chunks/chunks.js +++ b/src/scene/shader-lib/chunks/chunks.js @@ -89,7 +89,6 @@ import outputAlphaPS from './lit/frag/outputAlpha.js'; import outputAlphaOpaquePS from './lit/frag/outputAlphaOpaque.js'; import outputAlphaPremulPS from './lit/frag/outputAlphaPremul.js'; import outputTex2DPS from './common/frag/outputTex2D.js'; -import packDepthPS from './common/frag/packDepth.js'; import sheenPS from './standard/frag/sheen.js'; import sheenGlossPS from './standard/frag/sheenGloss.js'; import parallaxPS from './standard/frag/parallax.js'; @@ -291,7 +290,6 @@ const shaderChunks = { outputAlphaOpaquePS, outputAlphaPremulPS, outputTex2DPS, - packDepthPS, sheenPS, sheenGlossPS, parallaxPS, diff --git a/src/scene/shader-lib/chunks/common/frag/packDepth.js b/src/scene/shader-lib/chunks/common/frag/packDepth.js deleted file mode 100644 index 52ee7d6d0a5..00000000000 --- a/src/scene/shader-lib/chunks/common/frag/packDepth.js +++ /dev/null @@ -1,13 +0,0 @@ -export default /* glsl */` -// Packing a float in GLSL with multiplication and mod -// http://blog.gradientstudios.com/2012/08/23/shadow-map-improvement -vec4 packFloat(float depth) { - const vec4 bit_shift = vec4(256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0); - const vec4 bit_mask = vec4(0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0); - - // combination of mod and multiplication and division works better - vec4 res = mod(depth * bit_shift * vec4(255), vec4(256) ) / vec4(255); - res -= res.xxyz * bit_mask; - return res; -} -`; diff --git a/src/scene/shader-lib/chunks/lit/frag/shadowStandard.js b/src/scene/shader-lib/chunks/lit/frag/shadowStandard.js index e7697ff212a..24fd188bfce 100644 --- a/src/scene/shader-lib/chunks/lit/frag/shadowStandard.js +++ b/src/scene/shader-lib/chunks/lit/frag/shadowStandard.js @@ -1,17 +1,5 @@ export default /* glsl */` -vec3 lessThan2(vec3 a, vec3 b) { - return clamp((b - a)*1000.0, 0.0, 1.0); // softer version -} - -#ifndef UNPACKFLOAT -#define UNPACKFLOAT - float unpackFloat(vec4 rgbaDepth) { - const vec4 bitShift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0); - return dot(rgbaDepth, bitShift); - } -#endif - -// ----- Direct/Spot Sampling ----- +// ----- Directional/Spot Sampling ----- float _getShadowPCF3x3(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec3 shadowParams) { float z = shadowCoord.z; @@ -72,70 +60,32 @@ float getShadowSpotPCF1x1(SHADOWMAP_ACCEPT(shadowMap), vec3 shadowCoord, vec4 sh #ifndef WEBGPU -float _getShadowPoint(samplerCube shadowMap, vec4 shadowParams, vec3 dir) { +float getShadowPointPCF3x3(samplerCubeShadow shadowMap, vec4 shadowParams, vec3 dir) { + + // Calculate shadow depth from the light direction + float shadowZ = length(dir) * shadowParams.w + shadowParams.z; + // offset + float z = 1.0 / float(textureSize(shadowMap, 0)); vec3 tc = normalize(dir); - vec3 tcAbs = abs(tc); - - vec4 dirX = vec4(1,0,0, tc.x); - vec4 dirY = vec4(0,1,0, tc.y); - float majorAxisLength = tc.z; - if ((tcAbs.x > tcAbs.y) && (tcAbs.x > tcAbs.z)) { - dirX = vec4(0,0,1, tc.z); - dirY = vec4(0,1,0, tc.y); - majorAxisLength = tc.x; - } else if ((tcAbs.y > tcAbs.x) && (tcAbs.y > tcAbs.z)) { - dirX = vec4(1,0,0, tc.x); - dirY = vec4(0,0,1, tc.z); - majorAxisLength = tc.y; - } - - float shadowParamsInFaceSpace = ((1.0/shadowParams.x) * 2.0) * abs(majorAxisLength); - - vec3 xoffset = (dirX.xyz * shadowParamsInFaceSpace); - vec3 yoffset = (dirY.xyz * shadowParamsInFaceSpace); - vec3 dx0 = -xoffset; - vec3 dy0 = -yoffset; - vec3 dx1 = xoffset; - vec3 dy1 = yoffset; - - mat3 shadowKernel; - mat3 depthKernel; - - depthKernel[0][0] = unpackFloat(textureCube(shadowMap, tc + dx0 + dy0)); - depthKernel[0][1] = unpackFloat(textureCube(shadowMap, tc + dx0)); - depthKernel[0][2] = unpackFloat(textureCube(shadowMap, tc + dx0 + dy1)); - depthKernel[1][0] = unpackFloat(textureCube(shadowMap, tc + dy0)); - depthKernel[1][1] = unpackFloat(textureCube(shadowMap, tc)); - depthKernel[1][2] = unpackFloat(textureCube(shadowMap, tc + dy1)); - depthKernel[2][0] = unpackFloat(textureCube(shadowMap, tc + dx1 + dy0)); - depthKernel[2][1] = unpackFloat(textureCube(shadowMap, tc + dx1)); - depthKernel[2][2] = unpackFloat(textureCube(shadowMap, tc + dx1 + dy1)); - - vec3 shadowZ = vec3(length(dir) * shadowParams.w + shadowParams.z); - - shadowKernel[0] = vec3(lessThan2(depthKernel[0], shadowZ)); - shadowKernel[1] = vec3(lessThan2(depthKernel[1], shadowZ)); - shadowKernel[2] = vec3(lessThan2(depthKernel[2], shadowZ)); - - vec2 uv = (vec2(dirX.w, dirY.w) / abs(majorAxisLength)) * 0.5; - - vec2 fractionalCoord = fract( uv * shadowParams.x ); - - shadowKernel[0] = mix(shadowKernel[0], shadowKernel[1], fractionalCoord.x); - shadowKernel[1] = mix(shadowKernel[1], shadowKernel[2], fractionalCoord.x); - - vec4 shadowValues; - shadowValues.x = mix(shadowKernel[0][0], shadowKernel[0][1], fractionalCoord.y); - shadowValues.y = mix(shadowKernel[0][1], shadowKernel[0][2], fractionalCoord.y); - shadowValues.z = mix(shadowKernel[1][0], shadowKernel[1][1], fractionalCoord.y); - shadowValues.w = mix(shadowKernel[1][1], shadowKernel[1][2], fractionalCoord.y); - - return 1.0 - dot( shadowValues, vec4( 1.0 ) ) * 0.25; + + // average 4 samples - not a strict 3x3 PCF but that's tricky with cubemaps + mediump vec4 shadows; + shadows.x = texture(shadowMap, vec4(tc + vec3( z, z, z), shadowZ)); + shadows.y = texture(shadowMap, vec4(tc + vec3(-z,-z, z), shadowZ)); + shadows.z = texture(shadowMap, vec4(tc + vec3(-z, z,-z), shadowZ)); + shadows.w = texture(shadowMap, vec4(tc + vec3( z,-z,-z), shadowZ)); + + return dot(shadows, vec4(0.25)); +} + +float getShadowPointPCF1x1(samplerCubeShadow shadowMap, vec3 shadowCoord, vec4 shadowParams, vec3 lightDir) { + float shadowZ = length(lightDir) * shadowParams.w + shadowParams.z; + return texture(shadowMap, vec4(lightDir, shadowZ)); } -float getShadowPointPCF3x3(samplerCube shadowMap, vec3 shadowCoord, vec4 shadowParams, vec3 lightDir) { - return _getShadowPoint(shadowMap, shadowParams, lightDir); +float getShadowPointPCF3x3(samplerCubeShadow shadowMap, vec3 shadowCoord, vec4 shadowParams, vec3 lightDir) { + return getShadowPointPCF3x3(shadowMap, shadowParams, lightDir); } #endif diff --git a/src/scene/shader-lib/chunks/particle/frag/particle.js b/src/scene/shader-lib/chunks/particle/frag/particle.js index 809b1b71beb..51c2d09ee37 100644 --- a/src/scene/shader-lib/chunks/particle/frag/particle.js +++ b/src/scene/shader-lib/chunks/particle/frag/particle.js @@ -18,15 +18,6 @@ float saturate(float x) { return clamp(x, 0.0, 1.0); } -#ifndef UNPACKFLOAT -#define UNPACKFLOAT -float unpackFloat(vec4 rgbaDepth) { - const vec4 bitShift = vec4(1.0 / (256.0 * 256.0 * 256.0), 1.0 / (256.0 * 256.0), 1.0 / 256.0, 1.0); - float depth = dot(rgbaDepth, bitShift); - return depth; -} -#endif - void main(void) { vec4 tex = texture2D(colorMap, vec2(texCoordsAlphaLife.x, 1.0 - texCoordsAlphaLife.y)); vec4 ramp = texture2D(colorParam, vec2(texCoordsAlphaLife.w, 0.0)); diff --git a/src/scene/shader-lib/programs/lit-shader.js b/src/scene/shader-lib/programs/lit-shader.js index 770717d1dde..ee544502e48 100644 --- a/src/scene/shader-lib/programs/lit-shader.js +++ b/src/scene/shader-lib/programs/lit-shader.js @@ -384,18 +384,14 @@ class LitShader { } _fsGetDepthPassCode() { - const chunks = this.chunks; - let code = this._fsGetBeginCode(); - code += 'varying float vDepth;\n'; code += this.varyings; code += this.varyingDefines; - code += chunks.packDepthPS; code += this.frontendDecl; code += this.frontendCode; code += ShaderGenerator.begin(); code += this.frontendFunc; - code += ' gl_FragColor = packFloat(vDepth);\n'; + code += ' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n'; code += ShaderGenerator.end(); return code; @@ -440,10 +436,7 @@ class LitShader { code += this.frontendDecl; code += this.frontendCode; - const usePackedDepth = (lightType === LIGHTTYPE_OMNI && shadowType !== SHADOW_PCSS && !options.clusteredLightingEnabled); - if (usePackedDepth) { - code += chunks.packDepthPS; - } else if (shadowType === SHADOW_VSM8) { + if (shadowType === SHADOW_VSM8) { code += 'vec2 encodeFloatRG( float v ) {\n'; code += ' vec2 enc = vec2(1.0, 255.0) * v;\n'; code += ' enc = fract(enc);\n'; @@ -477,9 +470,7 @@ class LitShader { hasModifiedDepth = true; } - if (usePackedDepth) { - code += ' gl_FragColor = packFloat(depth);\n'; - } else if (!isVsm) { + if (!isVsm) { const exportR32 = shadowType === SHADOW_PCSS; if (exportR32) { @@ -616,13 +607,9 @@ class LitShader { shadowedDirectionalLightUsed = true; } if (lightType === LIGHTTYPE_OMNI) { - decl.append(`uniform samplerCube light${i}_shadowMap;`); + decl.append(`uniform ${light._isPcf ? 'samplerCubeShadow' : 'samplerCube'} light${i}_shadowMap;`); } else { - if (light._isPcf) { - decl.append(`uniform sampler2DShadow light${i}_shadowMap;`); - } else { - decl.append(`uniform sampler2D light${i}_shadowMap;`); - } + decl.append(`uniform ${light._isPcf ? 'sampler2DShadow' : 'sampler2D'} light${i}_shadowMap;`); } numShadowLights++; shadowTypeUsed[light._shadowType] = true;