diff --git a/examples/src/examples/graphics/taa.example.mjs b/examples/src/examples/graphics/taa.example.mjs index 36f517414fd..df8c9e35c11 100644 --- a/examples/src/examples/graphics/taa.example.mjs +++ b/examples/src/examples/graphics/taa.example.mjs @@ -9,6 +9,7 @@ window.focus(); const assets = { orbit: new pc.Asset('script', 'script', { url: `${rootPath}/static/scripts/camera/orbit-camera.js` }), house: new pc.Asset('house', 'container', { url: `${rootPath}/static/assets/models/pbr-house.glb` }), + cube: new pc.Asset('cube', 'container', { url: `${rootPath}/static/assets/models/playcanvas-cube.glb` }), envatlas: new pc.Asset( 'env-atlas', 'texture', @@ -111,6 +112,10 @@ assetListLoader.load(() => { app.root.addChild(light); light.setLocalEulerAngles(40, 10, 0); + const cubeEntity = assets.cube.resource.instantiateRenderEntity(); + cubeEntity.setLocalScale(30, 30, 30); + app.root.addChild(cubeEntity); + // ------ Custom render passes set up ------ /** @type { CameraFrame } */ @@ -152,6 +157,13 @@ assetListLoader.load(() => { jitter: 1 } }); + + let time = 0; + app.on('update', (/** @type {number} */ dt) => { + time += dt; + cubeEntity.setLocalPosition(130 * Math.sin(time), 0, 130 * Math.cos(time)); + cubeEntity.rotate(50 * dt, 20 * dt, 30 * dt); + }); }); export { app }; diff --git a/src/extras/render-passes/render-pass-camera-frame.js b/src/extras/render-passes/render-pass-camera-frame.js index 5a83d8abb41..34ea9a27eac 100644 --- a/src/extras/render-passes/render-pass-camera-frame.js +++ b/src/extras/render-passes/render-pass-camera-frame.js @@ -1,8 +1,5 @@ import { LAYERID_SKYBOX, LAYERID_IMMEDIATE, TONEMAP_NONE, GAMMA_NONE } from '../../scene/constants.js'; -import { - ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR, FILTER_NEAREST, - PIXELFORMAT_DEPTH, PIXELFORMAT_DEPTHSTENCIL, PIXELFORMAT_R32F, PIXELFORMAT_RGBA8 -} from '../../platform/graphics/constants.js'; +import { ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js'; import { Texture } from '../../platform/graphics/texture.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; import { RenderPassColorGrab } from '../../scene/graphics/render-pass-color-grab.js'; @@ -100,7 +97,6 @@ class RenderPassCameraFrame extends RenderPass { reset() { this.sceneTexture = null; - this.sceneDepth = null; if (this.rt) { this.rt.destroyTextureBuffers(); @@ -213,28 +209,10 @@ class RenderPassCameraFrame extends RenderPass { addressV: ADDRESS_CLAMP_TO_EDGE }); - let depthFormat = options.stencil ? PIXELFORMAT_DEPTHSTENCIL : PIXELFORMAT_DEPTH; - if (options.prepassEnabled && device.isWebGPU && options.samples > 1) { - // on WebGPU the depth format cannot be resolved, so we need to use a float format in that case - // TODO: ideally we expose this using some option or similar public API to hide this implementation detail - depthFormat = PIXELFORMAT_R32F; - } - - this.sceneDepth = new Texture(device, { - name: 'SceneDepth', - width: 4, - height: 4, - format: depthFormat, - mipmaps: false, - minFilter: FILTER_NEAREST, - magFilter: FILTER_NEAREST, - addressU: ADDRESS_CLAMP_TO_EDGE, - addressV: ADDRESS_CLAMP_TO_EDGE - }); - this.rt = new RenderTarget({ colorBuffer: this.sceneTexture, - depthBuffer: this.sceneDepth, + // depthBuffer: this.sceneDepth, + depth: true, samples: options.samples, flipY: !!targetRenderTarget?.flipY // flipY is inherited from the target renderTarget }); @@ -286,11 +264,7 @@ class RenderPassCameraFrame extends RenderPass { const { app, device, cameraComponent } = this; const { scene, renderer } = app; - - // ssao & taa need resolved depth - const resolveDepth = this.options.ssaoType !== SSAOTYPE_NONE || this.options.taaEnabled; - - this.prePass = new RenderPassPrepass(device, scene, renderer, cameraComponent, this.sceneDepth, resolveDepth, this.sceneOptions, options.samples); + this.prePass = new RenderPassPrepass(device, scene, renderer, cameraComponent, this.sceneOptions); } } @@ -306,17 +280,6 @@ class RenderPassCameraFrame extends RenderPass { this.scenePass = new RenderPassForward(device, composition, scene, renderer); this.scenePass.init(this.rt, this.sceneOptions); - // if prepass is enabled, do not clear the depth buffer when rendering the scene, and preserve it - if (options.prepassEnabled) { - if (!options.stencil) { - // when stencil is used, the depth buffer might not be correct as the prepass does not - // handle stencil, so we need to clear it - in this case, depth prepass does not give - // us any benefit - this.scenePass.noDepthClear = true; - } - this.scenePass.depthStencilOps.storeDepth = true; - } - // layers this pass renders depend on the grab pass being used const lastLayerId = options.sceneColorMap ? options.lastGrabLayerId : options.lastSceneLayerId; const lastLayerIsTransparent = options.sceneColorMap ? options.lastGrabLayerIsTransparent : options.lastSceneLayerIsTransparent; @@ -359,9 +322,10 @@ class RenderPassCameraFrame extends RenderPass { } setupSsaoPass(options) { - const { camera, ssaoBlurEnabled, ssaoType } = options; + const { ssaoBlurEnabled, ssaoType } = options; + const { device, cameraComponent } = this; if (ssaoType !== SSAOTYPE_NONE) { - this.ssaoPass = new RenderPassSsao(this.device, this.sceneTexture, camera, ssaoBlurEnabled); + this.ssaoPass = new RenderPassSsao(device, this.sceneTexture, cameraComponent, ssaoBlurEnabled); } } diff --git a/src/extras/render-passes/render-pass-depth-aware-blur.js b/src/extras/render-passes/render-pass-depth-aware-blur.js index 4a426daea69..4e000ef01eb 100644 --- a/src/extras/render-passes/render-pass-depth-aware-blur.js +++ b/src/extras/render-passes/render-pass-depth-aware-blur.js @@ -1,5 +1,5 @@ import { RenderPassShaderQuad } from '../../scene/graphics/render-pass-shader-quad.js'; -import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js'; +import { ChunkUtils } from '../../scene/shader-lib/chunk-utils.js'; /** * Render pass implementation of a depth-aware bilateral blur filter. @@ -8,11 +8,13 @@ import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js'; * @ignore */ class RenderPassDepthAwareBlur extends RenderPassShaderQuad { - constructor(device, sourceTexture, horizontal) { + constructor(device, sourceTexture, cameraComponent, horizontal) { super(device); this.sourceTexture = sourceTexture; - this.shader = this.createQuadShader(`DepthAware${horizontal ? 'Horizontal' : 'Vertical'}BlurShader`, `${shaderChunks.screenDepthPS /* glsl */} + const screenDepth = ChunkUtils.getScreenDepthChunk(device, cameraComponent.shaderParams); + this.shader = this.createQuadShader(`DepthAware${horizontal ? 'Horizontal' : 'Vertical'}BlurShader`, + /* glsl */ `${screenDepth} ${horizontal ? '#define HORIZONTAL' : ''} @@ -27,17 +29,17 @@ class RenderPassDepthAwareBlur extends RenderPassShaderQuad { return fract(m.z * fract(dot(w, m.xy))); } - float bilateralWeight(in float depth, in float sampleDepth) { - float diff = (sampleDepth - depth); + mediump float bilateralWeight(in mediump float depth, in mediump float sampleDepth) { + mediump float diff = (sampleDepth - depth); return max(0.0, 1.0 - diff * diff); } void tap(inout float sum, inout float totalWeight, float weight, float depth, vec2 position) { - float color = texture2D(sourceTexture, position).r; - float textureDepth = -getLinearScreenDepth(position); + mediump float color = texture2D(sourceTexture, position).r; + mediump float textureDepth = -getLinearScreenDepth(position); - float bilateral = bilateralWeight(depth, textureDepth); + mediump float bilateral = bilateralWeight(depth, textureDepth); bilateral *= weight; sum += color * bilateral; @@ -48,13 +50,13 @@ class RenderPassDepthAwareBlur extends RenderPassShaderQuad { void main() { // handle the center pixel separately because it doesn't participate in bilateral filtering - float depth = -getLinearScreenDepth(uv0); - float totalWeight = 1.0; - float color = texture2D(sourceTexture, uv0 ).r; - float sum = color * totalWeight; + mediump float depth = -getLinearScreenDepth(uv0); + mediump float totalWeight = 1.0; + mediump float color = texture2D(sourceTexture, uv0 ).r; + mediump float sum = color * totalWeight; - for (int i = -filterSize; i <= filterSize; i++) { - float weight = 1.0; + for (mediump int i = -filterSize; i <= filterSize; i++) { + mediump float weight = 1.0; #ifdef HORIZONTAL vec2 offset = vec2(i, 0) * sourceInvResolution; @@ -65,7 +67,7 @@ class RenderPassDepthAwareBlur extends RenderPassShaderQuad { tap(sum, totalWeight, weight, depth, uv0 + offset); } - float ao = sum / totalWeight; + mediump float ao = sum / totalWeight; // simple dithering helps a lot (assumes 8 bits target) // this is most useful with high quality/large blurs diff --git a/src/extras/render-passes/render-pass-prepass.js b/src/extras/render-passes/render-pass-prepass.js index bb4a343924a..129ed776136 100644 --- a/src/extras/render-passes/render-pass-prepass.js +++ b/src/extras/render-passes/render-pass-prepass.js @@ -1,8 +1,8 @@ import { FILTER_NEAREST, - PIXELFORMAT_RGBA32F, - PIXELFORMAT_RGBA16F, - ADDRESS_CLAMP_TO_EDGE + ADDRESS_CLAMP_TO_EDGE, + PIXELFORMAT_R32F, + PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js'; import { Texture } from '../../platform/graphics/texture.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; @@ -10,8 +10,9 @@ import { RenderTarget } from '../../platform/graphics/render-target.js'; import { LAYERID_DEPTH, - SHADER_PREPASS_VELOCITY + SHADER_PREPASS } from '../../scene/constants.js'; +import { Color } from '../../core/math/color.js'; /** * @import { BindGroup } from '../../platform/graphics/bind-group.js' @@ -22,9 +23,6 @@ const tempMeshInstances = []; // uniform name of the depth texture const DEPTH_UNIFORM_NAME = 'uSceneDepthMap'; -// uniform name of the velocity texture -const VELOCITY_UNIFORM_NAME = 'uSceneVelocityMap'; - /** * A render pass which typically executes before the rendering of the main scene, and renders data * that is required for the main rendering pass (and also in following passes) into separate render @@ -38,24 +36,26 @@ class RenderPassPrepass extends RenderPass { viewBindGroups = []; /** @type {Texture} */ - velocityTexture; + linearDepthTexture; + + /** @type {Color} */ + linearDepthClearValue; - constructor(device, scene, renderer, camera, depthBuffer, resolveDepth, options, samples) { + constructor(device, scene, renderer, camera, options) { super(device); this.scene = scene; this.renderer = renderer; this.camera = camera; - this.samples = samples; - this.setupRenderTarget(depthBuffer, resolveDepth, options); + this.setupRenderTarget(options); } destroy() { super.destroy(); this.renderTarget?.destroy(); this.renderTarget = null; - this.velocityTexture?.destroy(); - this.velocityTexture = null; + this.linearDepthTexture?.destroy(); + this.linearDepthTexture = null; this.viewBindGroups.forEach((bg) => { bg.defaultUniformBuffer.destroy(); @@ -64,17 +64,16 @@ class RenderPassPrepass extends RenderPass { this.viewBindGroups.length = 0; } - setupRenderTarget(depthBuffer, resolveDepth, options) { + setupRenderTarget(options) { const { device } = this; - // TODO: only two channel texture is needed here, but that is not supported by WebGL - const velocityFormat = device.getRenderableHdrFormat([PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA16F]); - this.velocityTexture = new Texture(device, { - name: 'VelocityTexture', - width: 4, - height: 4, - format: velocityFormat, + const linearDepthFormat = device.textureFloatRenderable ? PIXELFORMAT_R32F : PIXELFORMAT_RGBA8; + this.linearDepthTexture = new Texture(device, { + name: 'SceneLinearDepthTexture', + width: 1, + height: 1, + format: linearDepthFormat, mipmaps: false, minFilter: FILTER_NEAREST, magFilter: FILTER_NEAREST, @@ -84,26 +83,30 @@ class RenderPassPrepass extends RenderPass { const renderTarget = new RenderTarget({ name: 'PrepassRT', - // colorBuffer: this.velocityTexture, - depthBuffer: depthBuffer, - samples: this.samples + colorBuffer: this.linearDepthTexture, + + // use depth buffer, but this can be discarded after the prepass as the depth is stored in the linearDepthTexture + depth: true, + + // always single sampled + samples: 1 }); + // scene depth will be linear + this.camera.shaderParams.sceneDepthMapLinear = true; + this.init(renderTarget, options); - this.depthStencilOps.clearStencil = true; - this.depthStencilOps.storeDepth = true; - if (resolveDepth) { - this.depthStencilOps.resolveDepth = true; - } + // clear color for the linear depth texture + this.linearDepthClearValue = new Color(linearDepthFormat === PIXELFORMAT_R32F ? + [1, 1, 1, 1] : // only R is used + [0.5, 0.0, 0.0, 0.0] // represents linear value of -1 encoded into RGBA8 + ); } after() { - - // Assign the depth to the uniform. Note that the depth buffer is still used as a render - // target in the following scene passes, and cannot be used as a texture inside those passes. - this.device.scope.resolve(DEPTH_UNIFORM_NAME).setValue(this.renderTarget.depthBuffer); - this.device.scope.resolve(VELOCITY_UNIFORM_NAME).setValue(this.velocityTexture); + // Assign the lienar depth texture to the uniform + this.device.scope.resolve(DEPTH_UNIFORM_NAME).setValue(this.linearDepthTexture); } execute() { @@ -139,7 +142,7 @@ class RenderPassPrepass extends RenderPass { } } - renderer.renderForwardLayer(camera, renderTarget, null, undefined, SHADER_PREPASS_VELOCITY, this.viewBindGroups, { + renderer.renderForwardLayer(camera, renderTarget, null, undefined, SHADER_PREPASS, this.viewBindGroups, { meshInstances: tempMeshInstances }); @@ -156,6 +159,7 @@ class RenderPassPrepass extends RenderPass { // depth clear value (1 or no clear) set up each frame const { camera } = this; this.setClearDepth(camera.clearDepthBuffer ? 1 : undefined); + this.setClearColor(camera.clearDepthBuffer ? this.linearDepthClearValue : undefined); } } diff --git a/src/extras/render-passes/render-pass-ssao.js b/src/extras/render-passes/render-pass-ssao.js index 2e5569c1b97..e03d4231151 100644 --- a/src/extras/render-passes/render-pass-ssao.js +++ b/src/extras/render-passes/render-pass-ssao.js @@ -3,7 +3,7 @@ import { ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_R8 } from '../../pla import { RenderTarget } from '../../platform/graphics/render-target.js'; import { Texture } from '../../platform/graphics/texture.js'; import { RenderPassShaderQuad } from '../../scene/graphics/render-pass-shader-quad.js'; -import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js'; +import { ChunkUtils } from '../../scene/shader-lib/chunk-utils.js'; import { RenderPassDepthAwareBlur } from './render-pass-depth-aware-blur.js'; const fs = /* glsl */` @@ -84,10 +84,10 @@ const fs = /* glsl */` #define PI (3.14159) - vec3 tapLocation(float i, const float noise) { - float offset = ((2.0 * PI) * 2.4) * noise; - float angle = ((i * uSampleCount.y) * uSpiralTurns) * (2.0 * PI) + offset; - float radius = (i + noise + 0.5) * uSampleCount.y; + mediump vec3 tapLocation(mediump float i, const mediump float noise) { + mediump float offset = ((2.0 * PI) * 2.4) * noise; + mediump float angle = ((i * uSampleCount.y) * uSpiralTurns) * (2.0 * PI) + offset; + mediump float radius = (i + noise + 0.5) * uSampleCount.y; return vec3(cos(angle), sin(angle), radius * radius); } @@ -103,8 +103,8 @@ const fs = /* glsl */` return mat2(t.x, t.y, -t.y, t.x); } - vec3 tapLocationFast(float i, vec2 p, const float noise) { - float radius = (i + noise + 0.5) * uSampleCount.y; + mediump vec3 tapLocationFast(mediump float i, mediump vec2 p, const mediump float noise) { + mediump float radius = (i + noise + 0.5) * uSampleCount.y; return vec3(p, radius * radius); } @@ -114,18 +114,18 @@ const fs = /* glsl */` uniform float uBias; uniform float uPeak2; - void computeAmbientOcclusionSAO(inout float occlusion, float i, float ssDiskRadius, - const highp vec2 uv, const highp vec3 origin, const vec3 normal, - const vec2 tapPosition, const float noise) { + void computeAmbientOcclusionSAO(inout mediump float occlusion, mediump float i, mediump float ssDiskRadius, + const highp vec2 uv, const highp vec3 origin, const mediump vec3 normal, + const mediump vec2 tapPosition, const float noise) { - vec3 tap = tapLocationFast(i, tapPosition, noise); + mediump vec3 tap = tapLocationFast(i, tapPosition, noise); - float ssRadius = max(1.0, tap.z * ssDiskRadius); // at least 1 pixel screen-space radius + mediump float ssRadius = max(1.0, tap.z * ssDiskRadius); // at least 1 pixel screen-space radius - vec2 uvSamplePos = uv + vec2(ssRadius * tap.xy) * uInvResolution; + mediump vec2 uvSamplePos = uv + vec2(ssRadius * tap.xy) * uInvResolution; // TODO: level is not used, but could be used with mip-mapped depth texture - float level = clamp(floor(log2(ssRadius)) - kLog2LodRate, 0.0, float(uMaxLevel)); + mediump float level = clamp(floor(log2(ssRadius)) - kLog2LodRate, 0.0, float(uMaxLevel)); highp float occlusionDepth = -getLinearScreenDepth(uvSamplePos); highp vec3 p = computeViewSpacePositionFromDepth(uvSamplePos, occlusionDepth); @@ -136,7 +136,7 @@ const fs = /* glsl */` // discard samples that are outside of the radius, preventing distant geometry to cast // shadows -- there are many functions that work and choosing one is an artistic decision. - float w = max(0.0, 1.0 - vv * uInvRadiusSquared); + mediump float w = max(0.0, 1.0 - vv * uInvRadiusSquared); w = w * w; // discard samples that are too close to the horizon to reduce shadows cast by geometry @@ -251,7 +251,8 @@ class RenderPassSsao extends RenderPassShaderQuad { this.cameraComponent = cameraComponent; // main SSAO render pass - this.shader = this.createQuadShader('SsaoShader', shaderChunks.screenDepthPS + fs); + const screenDepth = ChunkUtils.getScreenDepthChunk(device, cameraComponent.shaderParams); + this.shader = this.createQuadShader('SsaoShader', screenDepth + fs); const rt = this.createRenderTarget('SsaoFinalTexture'); this.ssaoTexture = rt.colorBuffer; @@ -269,13 +270,13 @@ class RenderPassSsao extends RenderPassShaderQuad { const blurRT = this.createRenderTarget('SsaoTempTexture'); - const blurPassHorizontal = new RenderPassDepthAwareBlur(device, rt.colorBuffer, true); + const blurPassHorizontal = new RenderPassDepthAwareBlur(device, rt.colorBuffer, cameraComponent, true); blurPassHorizontal.init(blurRT, { resizeSource: rt.colorBuffer }); blurPassHorizontal.setClearColor(clearColor); - const blurPassVertical = new RenderPassDepthAwareBlur(device, blurRT.colorBuffer, false); + const blurPassVertical = new RenderPassDepthAwareBlur(device, blurRT.colorBuffer, cameraComponent, false); blurPassVertical.init(rt, { resizeSource: rt.colorBuffer }); diff --git a/src/extras/render-passes/render-pass-taa.js b/src/extras/render-passes/render-pass-taa.js index b12b9bbea3d..87c22231b4a 100644 --- a/src/extras/render-passes/render-pass-taa.js +++ b/src/extras/render-passes/render-pass-taa.js @@ -6,9 +6,10 @@ import { Texture } from '../../platform/graphics/texture.js'; import { shaderChunks } from '../../scene/shader-lib/chunks/chunks.js'; import { RenderPassShaderQuad } from '../../scene/graphics/render-pass-shader-quad.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; +import { PROJECTION_ORTHOGRAPHIC } from '../../scene/constants.js'; +import { ChunkUtils } from '../../scene/shader-lib/chunk-utils.js'; const fs = /* glsl */ ` - uniform highp sampler2D uSceneDepthMap; uniform sampler2D sourceTexture; uniform sampler2D historyTexture; uniform mat4 matrix_viewProjectionPrevious; @@ -76,8 +77,8 @@ const fs = /* glsl */ ` // current frame vec4 srcColor = texture2D(sourceTexture, uv); - // current depth - float depth = texture2DLodEXT(uSceneDepthMap, uv, 0.0).r; + // current depth is in linear space, convert it to non-linear space + float depth = delinearizeDepth(texture2DLodEXT(uSceneDepthMap, uv, 0.0).r); // previous frame vec2 historyUv = reproject(uv0, depth); @@ -142,7 +143,8 @@ class RenderPassTAA extends RenderPassShaderQuad { const defines = /* glsl */` #define QUALITY_HIGH `; - const fsChunks = shaderChunks.sampleCatmullRomPS; + const screenDepth = ChunkUtils.getScreenDepthChunk(device, cameraComponent.shaderParams); + const fsChunks = shaderChunks.sampleCatmullRomPS + screenDepth; this.shader = this.createQuadShader('TaaResolveShader', defines + fsChunks + fs); const { scope } = device; @@ -153,6 +155,8 @@ class RenderPassTAA extends RenderPassShaderQuad { this.viewProjPrevId = scope.resolve('matrix_viewProjectionPrevious'); this.viewProjInvId = scope.resolve('matrix_viewProjectionInverse'); this.jittersId = scope.resolve('jitters'); + this.cameraParams = new Float32Array(4); + this.cameraParamsId = scope.resolve('camera_params'); this.setup(); } @@ -205,6 +209,13 @@ class RenderPassTAA extends RenderPassShaderQuad { this.viewProjPrevId.setValue(camera._viewProjPrevious.data); this.viewProjInvId.setValue(camera._viewProjInverse.data); this.jittersId.setValue(camera._jitters); + + const f = camera._farClip; + this.cameraParams[0] = 1 / f; + this.cameraParams[1] = f; + this.cameraParams[2] = camera._nearClip; + this.cameraParams[3] = camera.projection === PROJECTION_ORTHOGRAPHIC ? 1 : 0; + this.cameraParamsId.setValue(this.cameraParams); } // called when the parent render pass gets added to the frame graph diff --git a/src/index.js b/src/index.js index ed25fb9ce51..2012b906544 100644 --- a/src/index.js +++ b/src/index.js @@ -192,6 +192,7 @@ export { ProgramLibrary } from './scene/shader-lib/program-library.js'; export { shaderChunks } from './scene/shader-lib/chunks/chunks.js'; export { shaderChunksLightmapper } from './scene/shader-lib/chunks/chunks-lightmapper.js'; export { ChunkBuilder } from './scene/shader-lib/chunk-builder.js'; // used by shed +export { ChunkUtils } from './scene/shader-lib/chunk-utils.js'; // SCENE / SKY export { Sky } from './scene/skybox/sky.js'; diff --git a/src/scene/camera-shader-params.js b/src/scene/camera-shader-params.js index 9e2158bfa72..bf061a12313 100644 --- a/src/scene/camera-shader-params.js +++ b/src/scene/camera-shader-params.js @@ -22,6 +22,9 @@ class CameraShaderParams { /** @private */ _fog = FOG_NONE; + /** @private */ + _sceneDepthMapLinear = false; + /** * The hash of the rendering parameters, or undefined if the hash has not been computed yet. * @@ -30,6 +33,17 @@ class CameraShaderParams { */ _hash; + /** + * Content of this class relevant to shader generation, which is supplied as defines for the + * shader. + * + * @type {Map} + * @private + */ + _defines = new Map(); + + _definesDirty = true; + /** * The hash of the rendering parameters. * @@ -38,22 +52,30 @@ class CameraShaderParams { */ get hash() { if (this._hash === undefined) { - const key = `${this.gammaCorrection}_${this.toneMapping}_${this.srgbRenderTarget}_${this.fog}_${this.ssaoEnabled}`; + const key = `${this.gammaCorrection}_${this.toneMapping}_${this.srgbRenderTarget}_${this.fog}_${this.ssaoEnabled}_${this.sceneDepthMapLinear}`; this._hash = hashCode(key); } return this._hash; } - initDefaults() { - this._gammaCorrection = GAMMA_SRGB; - this._toneMapping = TONEMAP_LINEAR; - this._srgbRenderTarget = false; - this._ssaoEnabled = false; - this._fog = FOG_NONE; + get defines() { + + const defines = this._defines; + + if (this._definesDirty) { + this._definesDirty = false; + defines.clear(); + + // TODO: add defines for all members, and remove the code selection handling these in shader generators, to make it all automatic + + if (this._sceneDepthMapLinear) defines.set('SCENE_DEPTHMAP_LINEAR', true); + } + return defines; } markDirty() { this._hash = undefined; + this._definesDirty = true; } set fog(type) { @@ -112,6 +134,17 @@ class CameraShaderParams { return this._srgbRenderTarget; } + set sceneDepthMapLinear(value) { + if (this._sceneDepthMapLinear !== value) { + this._sceneDepthMapLinear = value; + this.markDirty(); + } + } + + get sceneDepthMapLinear() { + return this._sceneDepthMapLinear; + } + /** * Returns {@link GAMMA_SRGB} if the shader code needs to output gamma corrected color, otherwise * returns {@link GAMMA_NONE}. diff --git a/src/scene/constants.js b/src/scene/constants.js index de449251603..751dcab2e9e 100644 --- a/src/scene/constants.js +++ b/src/scene/constants.js @@ -697,7 +697,7 @@ export const MASK_BAKE = 4; */ export const SHADER_FORWARD = 0; -export const SHADER_PREPASS_VELOCITY = 1; +export const SHADER_PREPASS = 1; /** * Render RGBA-encoded depth value. diff --git a/src/scene/gsplat/gsplat-compressed-material.js b/src/scene/gsplat/gsplat-compressed-material.js index 00b0b64fec3..bba1e6cef7f 100644 --- a/src/scene/gsplat/gsplat-compressed-material.js +++ b/src/scene/gsplat/gsplat-compressed-material.js @@ -9,6 +9,7 @@ import { ShaderUtils } from '../../platform/graphics/shader-utils.js'; import { shaderChunks } from '../shader-lib/chunks/chunks.js'; import { ShaderGenerator } from '../shader-lib/programs/shader-generator.js'; import { ShaderPass } from '../shader-pass.js'; +import { getMaterialShaderDefines } from '../shader-lib/utils.js'; const splatCoreVS = /* glsl */ ` uniform mat4 matrix_model; @@ -380,8 +381,9 @@ const createGSplatCompressedMaterial = (options = {}) => { material.getShaderVariant = function (params) { + const { cameraShaderParams } = params; const programOptions = { - defines: material.defines, + defines: getMaterialShaderDefines(material, cameraShaderParams), pass: params.pass, gamma: params.cameraShaderParams.shaderOutputGamma, toneMapping: params.cameraShaderParams.toneMapping, diff --git a/src/scene/gsplat/gsplat-material.js b/src/scene/gsplat/gsplat-material.js index 666879bc340..9038bbe6161 100644 --- a/src/scene/gsplat/gsplat-material.js +++ b/src/scene/gsplat/gsplat-material.js @@ -3,6 +3,7 @@ import { ShaderProcessorOptions } from '../../platform/graphics/shader-processor import { BLEND_NONE, BLEND_NORMAL, DITHER_NONE } from '../constants.js'; import { ShaderMaterial } from '../materials/shader-material.js'; import { getProgramLibrary } from '../shader-lib/get-program-library.js'; +import { getMaterialShaderDefines } from '../shader-lib/utils.js'; import { gsplat } from './shader-generator-gsplat.js'; const splatMainVS = /* glsl */ ` @@ -110,11 +111,12 @@ const createGSplatMaterial = (options = {}) => { material.getShaderVariant = function (params) { + const { cameraShaderParams } = params; const programOptions = { - defines: material.defines, + defines: getMaterialShaderDefines(material, cameraShaderParams), pass: params.pass, - gamma: params.cameraShaderParams.shaderOutputGamma, - toneMapping: params.cameraShaderParams.toneMapping, + gamma: cameraShaderParams.shaderOutputGamma, + toneMapping: cameraShaderParams.toneMapping, vertex: options.vertex ?? splatMainVS, fragment: options.fragment ?? splatMainFS, dither: ditherEnum diff --git a/src/scene/materials/lit-material.js b/src/scene/materials/lit-material.js index 1c38bae1057..d91a9c581d5 100644 --- a/src/scene/materials/lit-material.js +++ b/src/scene/materials/lit-material.js @@ -5,6 +5,7 @@ import { LitMaterialOptions } from './lit-material-options.js'; import { LitMaterialOptionsBuilder } from './lit-material-options-builder.js'; import { getProgramLibrary } from '../shader-lib/get-program-library.js'; import { lit } from '../shader-lib/programs/lit.js'; +import { getMaterialShaderDefines } from '../shader-lib/utils.js'; const options = new LitMaterialOptions(); @@ -86,9 +87,11 @@ class LitMaterial extends Material { getShaderVariant(params) { + const { cameraShaderParams } = params; + options.usedUvs = this.usedUvs.slice(); options.shaderChunk = this.shaderChunk; - options.defines = this.defines; + options.defines = getMaterialShaderDefines(this, cameraShaderParams); LitMaterialOptionsBuilder.update(options.litOptions, this, params.scene, params.cameraShaderParams, params.objDefs, params.pass, params.sortedLights); const processingOptions = new ShaderProcessorOptions(params.viewUniformFormat, params.viewBindGroupFormat, params.vertexFormat); diff --git a/src/scene/materials/material.js b/src/scene/materials/material.js index 2fa53e926f2..fed6a50c00c 100644 --- a/src/scene/materials/material.js +++ b/src/scene/materials/material.js @@ -532,7 +532,7 @@ class Material { // defines this.defines.clear(); - source.defines.forEach(define => this.defines.add(define)); + source.defines.forEach((value, key) => this.defines.set(key, value)); return this; } diff --git a/src/scene/materials/shader-material.js b/src/scene/materials/shader-material.js index c01d7ec981b..7c6392d1490 100644 --- a/src/scene/materials/shader-material.js +++ b/src/scene/materials/shader-material.js @@ -2,6 +2,7 @@ import { ShaderProcessorOptions } from '../../platform/graphics/shader-processor import { SHADERDEF_INSTANCING, SHADERDEF_MORPH_NORMAL, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_TEXTURE_BASED_INT, SHADERDEF_SKIN } from '../constants.js'; import { getProgramLibrary } from '../shader-lib/get-program-library.js'; import { shaderGeneratorShader } from '../shader-lib/programs/shader-generator-shader.js'; +import { getMaterialShaderDefines } from '../shader-lib/utils.js'; import { Material } from './material.js'; /** @@ -96,9 +97,9 @@ class ShaderMaterial extends Material { getShaderVariant(params) { - const objDefs = params.objDefs; + const { objDefs, cameraShaderParams } = params; const options = { - defines: this.defines, + defines: getMaterialShaderDefines(this, cameraShaderParams), skin: (objDefs & SHADERDEF_SKIN) !== 0, useInstancing: (objDefs & SHADERDEF_INSTANCING) !== 0, useMorphPosition: (objDefs & SHADERDEF_MORPH_POSITION) !== 0, diff --git a/src/scene/materials/standard-material-options-builder.js b/src/scene/materials/standard-material-options-builder.js index 5d92dee9d99..1d0c45c4cba 100644 --- a/src/scene/materials/standard-material-options-builder.js +++ b/src/scene/materials/standard-material-options-builder.js @@ -6,7 +6,7 @@ import { BLEND_NONE, LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI, LIGHTTYPE_SPOT, MASK_AFFECT_DYNAMIC, - SHADER_PREPASS_VELOCITY, + SHADER_PREPASS, SHADERDEF_DIRLM, SHADERDEF_INSTANCING, SHADERDEF_LM, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_NORMAL, SHADERDEF_NOSHADOW, SHADERDEF_SCREENSPACE, SHADERDEF_SKIN, SHADERDEF_TANGENTS, SHADERDEF_UV0, SHADERDEF_UV1, SHADERDEF_VCOLOR, SHADERDEF_LMAMBIENT, TONEMAP_NONE, @@ -192,8 +192,9 @@ class StandardMaterialOptionsBuilder { _updateMinOptions(options, stdMat, pass) { // pre-pass uses the same dither setting as forward pass, otherwise shadow dither - const isPrepass = pass === SHADER_PREPASS_VELOCITY; + const isPrepass = pass === SHADER_PREPASS; options.litOptions.opacityShadowDither = isPrepass ? stdMat.opacityDither : stdMat.opacityShadowDither; + options.litOptions.linearDepth = isPrepass; options.litOptions.lights = []; } diff --git a/src/scene/materials/standard-material.js b/src/scene/materials/standard-material.js index 8190a86879d..5e6a83c4b5f 100644 --- a/src/scene/materials/standard-material.js +++ b/src/scene/materials/standard-material.js @@ -9,7 +9,7 @@ import { DITHER_NONE, FRESNEL_SCHLICK, SHADER_DEPTH, SHADER_PICK, - SHADER_PREPASS_VELOCITY, + SHADER_PREPASS, SPECOCC_AO } from '../constants.js'; import { ShaderPass } from '../shader-pass.js'; @@ -20,6 +20,7 @@ import { Material } from './material.js'; import { StandardMaterialOptionsBuilder } from './standard-material-options-builder.js'; import { standardMaterialCubemapParameters, standardMaterialTextureParameters } from './standard-material-parameters.js'; import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; +import { getMaterialShaderDefines } from '../shader-lib/utils.js'; /** * @import { BoundingBox } from '../../core/shape/bounding-box.js' @@ -857,9 +858,9 @@ class StandardMaterial extends Material { // Minimal options for Depth, Shadow and Prepass passes const shaderPassInfo = ShaderPass.get(device).getByIndex(pass); - const minimalOptions = pass === SHADER_DEPTH || pass === SHADER_PICK || pass === SHADER_PREPASS_VELOCITY || shaderPassInfo.isShadow; + const minimalOptions = pass === SHADER_DEPTH || pass === SHADER_PICK || pass === SHADER_PREPASS || shaderPassInfo.isShadow; let options = minimalOptions ? standard.optionsContextMin : standard.optionsContext; - options.defines = this.defines; + options.defines = getMaterialShaderDefines(this, cameraShaderParams); if (minimalOptions) { this.shaderOptBuilder.updateMinRef(options, scene, this, objDefs, pass, sortedLights); diff --git a/src/scene/particle-system/particle-material.js b/src/scene/particle-system/particle-material.js index cf8072f6bc4..ccd2827756a 100644 --- a/src/scene/particle-system/particle-material.js +++ b/src/scene/particle-system/particle-material.js @@ -9,6 +9,7 @@ import { import { getProgramLibrary } from '../shader-lib/get-program-library.js'; import { Material } from '../materials/material.js'; import { particle } from '../shader-lib/programs/particle.js'; +import { getMaterialShaderDefines } from '../shader-lib/utils.js'; /** * @import { ParticleEmitter } from './particle-emitter.js' @@ -39,7 +40,7 @@ class ParticleMaterial extends Material { const { device, scene, cameraShaderParams } = params; const { emitter } = this; const options = { - defines: this.defines, + defines: getMaterialShaderDefines(this, cameraShaderParams), pass: SHADER_FORWARD, useCpu: this.emitter.useCpu, normal: emitter.lighting ? ((emitter.normalMap !== null) ? 2 : 1) : 0, diff --git a/src/scene/shader-lib/chunk-utils.js b/src/scene/shader-lib/chunk-utils.js index 27db4f283bb..b74396e7658 100644 --- a/src/scene/shader-lib/chunk-utils.js +++ b/src/scene/shader-lib/chunk-utils.js @@ -1,3 +1,10 @@ +import { shaderChunks } from './chunks/chunks.js'; + +/** + * @import { CameraShaderParams } from '../camera-shader-params.js' + * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' + */ + const decodeTable = { 'linear': 'decodeLinear', 'srgb': 'decodeGamma', @@ -23,6 +30,23 @@ class ChunkUtils { static encodeFunc(encoding) { return encodeTable[encoding] || 'encodeGamma'; } + + /** + * Returns a screenDepth chunk configured for the given camera shader parameters. + * + * @param {GraphicsDevice} device - The graphics device. + * @param {CameraShaderParams} cameraShaderParams - The camera shader parameters. + * @returns {string} The screenDepth chunk. + * @ignore + */ + static getScreenDepthChunk(device, cameraShaderParams) { + + return ` + ${cameraShaderParams.sceneDepthMapLinear ? '#define SCENE_DEPTHMAP_LINEAR' : ''} + ${device.textureFloatRenderable ? '#define SCENE_DEPTHMAP_FLOAT' : ''} + ${shaderChunks.screenDepthPS} + `; + } } export { ChunkUtils }; diff --git a/src/scene/shader-lib/chunks/common/frag/screenDepth.js b/src/scene/shader-lib/chunks/common/frag/screenDepth.js index e0b41a9a521..9cb9b0cfec2 100644 --- a/src/scene/shader-lib/chunks/common/frag/screenDepth.js +++ b/src/scene/shader-lib/chunks/common/frag/screenDepth.js @@ -26,9 +26,36 @@ float linearizeDepth(float z) { } #endif // LINEARIZE_DEPTH +float delinearizeDepth(float linearDepth) { + if (camera_params.w == 0.0) { + return (camera_params.y * (camera_params.z - linearDepth)) / (linearDepth * (camera_params.z - camera_params.y)); + } else { + return (linearDepth - camera_params.z) / (camera_params.y - camera_params.z); + } +} + // Retrieves rendered linear camera depth by UV float getLinearScreenDepth(vec2 uv) { - return linearizeDepth(texture2D(uSceneDepthMap, uv).r); + #ifdef SCENE_DEPTHMAP_LINEAR + #ifdef SCENE_DEPTHMAP_FLOAT + return texture2D(uSceneDepthMap, uv).r; + #else + + ivec2 textureSize = textureSize(uSceneDepthMap, 0); + ivec2 texel = ivec2(uv * vec2(textureSize)); + vec4 data = texelFetch(uSceneDepthMap, texel, 0); + + uint intBits = + (uint(data.r * 255.0) << 24u) | + (uint(data.g * 255.0) << 16u) | + (uint(data.b * 255.0) << 8u) | + uint(data.a * 255.0); + + return uintBitsToFloat(intBits); + #endif + #else + return linearizeDepth(texture2D(uSceneDepthMap, uv).r); + #endif } #ifndef VERTEXSHADER diff --git a/src/scene/shader-lib/program-library.js b/src/scene/shader-lib/program-library.js index 54d99b50c74..9f782068c3a 100644 --- a/src/scene/shader-lib/program-library.js +++ b/src/scene/shader-lib/program-library.js @@ -2,7 +2,7 @@ import { Debug } from '../../core/debug.js'; import { hashCode } from '../../core/hash.js'; import { version, revision } from '../../core/core.js'; import { Shader } from '../../platform/graphics/shader.js'; -import { SHADER_FORWARD, SHADER_DEPTH, SHADER_PICK, SHADER_SHADOW, SHADER_PREPASS_VELOCITY } from '../constants.js'; +import { SHADER_FORWARD, SHADER_DEPTH, SHADER_PICK, SHADER_SHADOW, SHADER_PREPASS } from '../constants.js'; import { ShaderPass } from '../shader-pass.js'; import { StandardMaterialOptions } from '../materials/standard-material-options.js'; import { CameraShaderParams } from '../camera-shader-params.js'; @@ -282,7 +282,7 @@ class ProgramLibrary { _getDefaultStdMatOptions(pass) { const shaderPassInfo = ShaderPass.get(this._device).getByIndex(pass); - return (pass === SHADER_DEPTH || pass === SHADER_PICK || pass === SHADER_PREPASS_VELOCITY || shaderPassInfo.isShadow) ? + return (pass === SHADER_DEPTH || pass === SHADER_PICK || pass === SHADER_PREPASS || shaderPassInfo.isShadow) ? this._defaultStdMatOptionMin : this._defaultStdMatOption; } diff --git a/src/scene/shader-lib/programs/lit-shader-options.js b/src/scene/shader-lib/programs/lit-shader-options.js index 81c7d1aa85c..9ae498b601d 100644 --- a/src/scene/shader-lib/programs/lit-shader-options.js +++ b/src/scene/shader-lib/programs/lit-shader-options.js @@ -302,6 +302,13 @@ class LitShaderOptions { * @type {Object} */ userAttributes = {}; + + /** + * Make vLinearDepth available in the shader. + * + * @type {boolean} + */ + linearDepth = false; } export { LitShaderOptions }; diff --git a/src/scene/shader-lib/programs/lit-shader.js b/src/scene/shader-lib/programs/lit-shader.js index ee544502e48..f5ebb3907ee 100644 --- a/src/scene/shader-lib/programs/lit-shader.js +++ b/src/scene/shader-lib/programs/lit-shader.js @@ -13,7 +13,7 @@ import { SHADER_DEPTH, SHADER_PICK, SHADOW_PCF1, SHADOW_PCF3, SHADOW_PCF5, SHADOW_VSM8, SHADOW_VSM16, SHADOW_VSM32, SHADOW_PCSS, SPECOCC_AO, SPECOCC_GLOSSDEPENDENT, - SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED, shadowTypeToString, SHADER_PREPASS_VELOCITY + SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED, shadowTypeToString, SHADER_PREPASS } from '../../constants.js'; import { shaderChunks } from '../chunks/chunks.js'; import { ChunkUtils } from '../chunk-utils.js'; @@ -23,7 +23,6 @@ import { validateUserChunks } from '../chunks/chunk-validation.js'; import { ShaderUtils } from '../../../platform/graphics/shader-utils.js'; import { ChunkBuilder } from '../chunk-builder.js'; import { ShaderGenerator } from './shader-generator.js'; -import { Debug } from '../../../core/debug.js'; /** * @import { GraphicsDevice } from '../../../platform/graphics/graphics-device.js' @@ -49,7 +48,8 @@ const builtinVaryings = { vBinormalW: 'vec3', vObjectSpaceUpW: 'vec3', vUv0: 'vec2', - vUv1: 'vec2' + vUv1: 'vec2', + vLinearDepth: 'float' }; class LitShader { @@ -203,23 +203,19 @@ class LitShader { code = this._vsAddBaseCode(code, chunks, options); - codeBody += ' vPositionW = getWorldPosition();\n'; + codeBody += ' vPositionW = getWorldPosition();\n'; - if (this.options.pass === SHADER_DEPTH || this.options.pass === SHADER_PREPASS_VELOCITY) { - code += 'varying float vDepth;\n'; - code += '#ifndef VIEWMATRIX\n'; - code += '#define VIEWMATRIX\n'; - code += 'uniform mat4 matrix_view;\n'; - code += '#endif\n'; - code += '#ifndef CAMERAPLANES\n'; - code += '#define CAMERAPLANES\n'; - code += 'uniform vec4 camera_params;\n\n'; - code += '#endif\n'; - codeBody += ' vDepth = -(matrix_view * vec4(vPositionW,1.0)).z * camera_params.x;\n'; - } - - if (this.options.pass === SHADER_PREPASS_VELOCITY) { - Debug.warnOnce('SHADER_PREPASS_VELOCITY not implemented'); + if (this.options.linearDepth) { + codeDefines += ` + #ifndef VIEWMATRIX + #define VIEWMATRIX + uniform mat4 matrix_view; + #endif + `; + codeBody += ` + // linear depth from the worldPosition, see getLinearDepth + vLinearDepth = -(matrix_view * vec4(vPositionW, 1.0)).z; + `; } if (this.options.useInstancing) { @@ -397,10 +393,32 @@ class LitShader { return code; } - _fsGetPrePassVelocityCode() { + _fsGetPrePassCode() { + let code = this._fsGetBeginCode(); + code += this.varyings; + code += this.varyingDefines; + code += this.frontendDecl; + code += this.frontendCode; + code += ShaderGenerator.begin(); + code += this.frontendFunc; + code += this.device.textureFloatRenderable ? + // storing linear depth in a single F32 channel + ` + gl_FragColor = vec4(vLinearDepth, 1.0, 1.0, 1.0); + ` : + // storing linear depth float value in RGBA8 + ` + uint intBits = floatBitsToUint(vLinearDepth); + gl_FragColor = vec4( + float((intBits >> 24u) & 0xFFu) / 255.0, + float((intBits >> 16u) & 0xFFu) / 255.0, + float((intBits >> 8u) & 0xFFu) / 255.0, + float(intBits & 0xFFu) / 255.0 + ); + `; + code += ShaderGenerator.end(); - // till the velocity is implemented, just output the depth - return this._fsGetDepthPassCode(); + return code; } _fsGetShadowPassCode() { @@ -1494,8 +1512,8 @@ class LitShader { this.fshader = this._fsGetPickPassCode(); } else if (options.pass === SHADER_DEPTH) { this.fshader = this._fsGetDepthPassCode(); - } else if (options.pass === SHADER_PREPASS_VELOCITY) { - this.fshader = this._fsGetPrePassVelocityCode(); + } else if (options.pass === SHADER_PREPASS) { + this.fshader = this._fsGetPrePassCode(); } else if (this.shadowPass) { this.fshader = this._fsGetShadowPassCode(); } else if (options.customFragmentShader) { diff --git a/src/scene/shader-lib/programs/lit.js b/src/scene/shader-lib/programs/lit.js index 2c7ca6f920e..23cb55fa7ec 100644 --- a/src/scene/shader-lib/programs/lit.js +++ b/src/scene/shader-lib/programs/lit.js @@ -11,7 +11,8 @@ const dummyUvs = [0, 1, 2, 3, 4, 5, 6, 7]; class ShaderGeneratorLit extends ShaderGenerator { generateKey(options) { - const key = `lit${ + const definesHash = ShaderGenerator.definesHash(options.defines); + const key = `lit_${definesHash}_${ dummyUvs.map((dummy, index) => { return options.usedUvs[index] ? '1' : '0'; }).join('') diff --git a/src/scene/shader-lib/programs/particle.js b/src/scene/shader-lib/programs/particle.js index 6372a39f16e..1b7613b3961 100644 --- a/src/scene/shader-lib/programs/particle.js +++ b/src/scene/shader-lib/programs/particle.js @@ -5,7 +5,8 @@ import { ShaderGenerator } from './shader-generator.js'; class ShaderGeneratorParticle extends ShaderGenerator { generateKey(options) { - let key = 'particle'; + const definesHash = ShaderGenerator.definesHash(options.defines); + let key = `particle_${definesHash}_`; for (const prop in options) { if (options.hasOwnProperty(prop)) { key += options[prop]; @@ -107,7 +108,9 @@ class ShaderGeneratorParticle extends ShaderGenerator { return ShaderUtils.createDefinition(device, { name: 'ParticleShader', vertexCode: vshader, - fragmentCode: fshader + fragmentCode: fshader, + fragmentDefines: options.defines, + vertexDefines: options.defines }); } } diff --git a/src/scene/shader-lib/programs/skybox.js b/src/scene/shader-lib/programs/skybox.js index 7287b09b20c..8313921bc53 100644 --- a/src/scene/shader-lib/programs/skybox.js +++ b/src/scene/shader-lib/programs/skybox.js @@ -8,7 +8,8 @@ import { SKYTYPE_INFINITE } from '../../constants.js'; class ShaderGeneratorSkybox extends ShaderGenerator { generateKey(options) { - const sharedKey = `skybox-${options.type}-${options.encoding}-${options.gamma}-${options.toneMapping}-${options.skymesh}`; + const definesHash = ShaderGenerator.definesHash(options.defines); + const sharedKey = `skybox-${options.type}-${options.encoding}-${options.gamma}-${options.toneMapping}-${options.skymesh}_${definesHash}`; return sharedKey + (options.type === 'cubemap' ? `-${options.mip}` : ''); } diff --git a/src/scene/shader-lib/utils.js b/src/scene/shader-lib/utils.js index 61ded8cbc7a..7741592478a 100644 --- a/src/scene/shader-lib/utils.js +++ b/src/scene/shader-lib/utils.js @@ -9,6 +9,8 @@ import { SHADERLANGUAGE_WGSL } from '../../platform/graphics/constants.js'; /** * @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js' * @import { ShaderProcessorOptions } from '../../platform/graphics/shader-processor-options.js' + * @import { CameraShaderParams } from '../camera-shader-params.js' + * @import { Material } from '../materials/material.js' */ /** @@ -166,4 +168,21 @@ function processShader(shader, processingOptions) { return variant; } -export { createShader, createShaderFromCode, processShader }; +/** + * Create a map of defines for the shader for a material, rendered by a camera with the specified + * shader parameters. + * + * @param {Material} material - The material to create the shader defines for. + * @param {CameraShaderParams} cameraShaderParams - The camera shader parameters. + * @returns {Map} The map of shader defines. + * @ignore + */ +const getMaterialShaderDefines = (material, cameraShaderParams) => { + + // merge both maps, with camera shader params taking precedence + const defines = new Map(material.defines); + cameraShaderParams.defines.forEach((value, key) => defines.set(key, value)); + return defines; +}; + +export { createShader, createShaderFromCode, processShader, getMaterialShaderDefines }; diff --git a/src/scene/shader-pass.js b/src/scene/shader-pass.js index c80ee8d7d7a..a6034e71783 100644 --- a/src/scene/shader-pass.js +++ b/src/scene/shader-pass.js @@ -1,7 +1,7 @@ import { Debug } from '../core/debug.js'; import { DeviceCache } from '../platform/graphics/device-cache.js'; import { - SHADER_FORWARD, SHADER_DEPTH, SHADER_PICK, SHADER_SHADOW, SHADER_PREPASS_VELOCITY + SHADER_FORWARD, SHADER_DEPTH, SHADER_PICK, SHADER_SHADOW, SHADER_PREPASS } from './constants.js'; /** @@ -104,7 +104,7 @@ class ShaderPass { // add default passes in the required order, to match the constants add('forward', SHADER_FORWARD, { isForward: true }); - add('prepass', SHADER_PREPASS_VELOCITY); + add('prepass', SHADER_PREPASS); add('depth', SHADER_DEPTH); add('pick', SHADER_PICK); add('shadow', SHADER_SHADOW); diff --git a/src/scene/skybox/sky-mesh.js b/src/scene/skybox/sky-mesh.js index 4edcad216fd..90617eea73d 100644 --- a/src/scene/skybox/sky-mesh.js +++ b/src/scene/skybox/sky-mesh.js @@ -5,6 +5,7 @@ import { ShaderMaterial } from '../materials/shader-material.js'; import { MeshInstance } from '../mesh-instance.js'; import { getProgramLibrary } from '../shader-lib/get-program-library.js'; import { skybox } from '../shader-lib/programs/skybox.js'; +import { getMaterialShaderDefines } from '../shader-lib/utils.js'; import { SkyGeometry } from './sky-geometry.js'; /** @@ -41,7 +42,7 @@ class SkyMesh { const { scene, cameraShaderParams } = params; const options = { - defines: this.defines, + defines: getMaterialShaderDefines(this, cameraShaderParams), pass: params.pass, encoding: texture.encoding, gamma: cameraShaderParams.shaderOutputGamma,