UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

312 lines (311 loc) 11.7 kB
import { Color } from "../../core/math/color.js"; import { Vec3 } from "../../core/math/vec3.js"; import { ADDRESS_CLAMP_TO_EDGE, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_SRC_ALPHA, BLENDMODE_ZERO, FILTER_LINEAR, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F, PIXELFORMAT_RGBA8, SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from "../../platform/graphics/constants.js"; import { BlendState } from "../../platform/graphics/blend-state.js"; import { FramePass } from "../../platform/graphics/frame-pass.js"; 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 { shadowTypeInfo } from "../../scene/constants.js"; import { ShaderChunks } from "../../scene/shader-lib/shader-chunks.js"; import { ShaderUtils } from "../../scene/shader-lib/shader-utils.js"; import glslVolumetricFogPS from "../../scene/shader-lib/glsl/chunks/render-pass/frag/volumetricFog.js"; import wgslVolumetricFogPS from "../../scene/shader-lib/wgsl/chunks/render-pass/frag/volumetricFog.js"; import glslVolumetricFogCombinePS from "../../scene/shader-lib/glsl/chunks/render-pass/frag/volumetricFogCombine.js"; import wgslVolumetricFogCombinePS from "../../scene/shader-lib/wgsl/chunks/render-pass/frag/volumetricFogCombine.js"; const _tempDir = new Vec3(); class RenderPassVolumetricFog extends RenderPassShaderQuad { light = null; shadowsEnabled = false; lightRenderData = null; tint = new Color(1, 1, 1); density = 0.01; heightBase = 0; heightFalloff = 0.05; anisotropy = 0.6; intensity = 1; ambientColor = new Color(1, 1, 1); ambientIntensity = 0.02; maxDistance = 300; steps = 24; noiseOffset = 0; exposure = 1; _variantKey = null; constructor(device, cameraComponent) { super(device); this.cameraComponent = cameraComponent; const scope = device.scope; this.cameraPosId = scope.resolve("uFogCameraPos"); this.cameraFwdId = scope.resolve("uFogCameraFwd"); this.invViewId = scope.resolve("uFogInvView"); this.projScaleId = scope.resolve("uFogProjScale"); this.tintId = scope.resolve("uFogTint"); this.lightColorId = scope.resolve("uFogLightColor"); this.lightDirId = scope.resolve("uFogLightDir"); this.ambientId = scope.resolve("uFogAmbient"); this.fogParamsId = scope.resolve("uFogParams"); this.scatterParamsId = scope.resolve("uFogScatterParams"); this.shadowMapId = scope.resolve("uFogShadowMap"); this.shadowMatrixPaletteId = scope.resolve("uFogShadowMatrixPalette[0]"); this.shadowCascadeDistancesId = scope.resolve("uFogShadowCascadeDistances"); this.shadowParamsId = scope.resolve("uFogShadowParams"); this._cameraPos = new Float32Array(3); this._cameraFwd = new Float32Array(3); this._projScale = new Float32Array(2); this._tint = new Float32Array(3); this._lightColor = new Float32Array(3); this._lightDir = new Float32Array(3); this._ambient = new Float32Array(3); this._fogParams = new Float32Array(4); this._scatterParams = new Float32Array(4); this._shadowParams = new Float32Array(4); } updateShaderVariant(shadows, pcf) { const depthMapLinear = this.cameraComponent.shaderParams.sceneDepthMapLinear; const key = `${shadows}-${pcf}-${depthMapLinear}`; if (this._variantKey !== key) { this._variantKey = key; const defines = /* @__PURE__ */ new Map(); ShaderUtils.addScreenDepthChunkDefines(this.cameraComponent.shaderParams, defines); if (shadows) defines.set("FOG_SHADOWS", ""); if (pcf) defines.set("FOG_SHADOW_PCF", ""); this.shader = ShaderUtils.createShader(this.device, { uniqueName: `VolumetricFogShader-${key}`, attributes: { aPosition: SEMANTIC_POSITION }, vertexChunk: "quadVS", fragmentChunk: "volumetricFogPS", fragmentDefines: defines }); } } execute() { const { light } = this; const camera = this.cameraComponent.camera; const node = camera._node; this.invViewId.setValue(node.getWorldTransform().data); const pos = node.getPosition(); this._cameraPos[0] = pos.x; this._cameraPos[1] = pos.y; this._cameraPos[2] = pos.z; this.cameraPosId.setValue(this._cameraPos); const fwd = node.forward; this._cameraFwd[0] = fwd.x; this._cameraFwd[1] = fwd.y; this._cameraFwd[2] = fwd.z; this.cameraFwdId.setValue(this._cameraFwd); const projData = camera.projectionMatrix.data; this._projScale[0] = 1 / projData[0]; this._projScale[1] = 1 / projData[5]; this.projScaleId.setValue(this._projScale); const lightScale = this.intensity * this.exposure; if (light) { light._node.getWorldTransform().getY(_tempDir).normalize(); this._lightDir[0] = _tempDir.x; this._lightDir[1] = _tempDir.y; this._lightDir[2] = _tempDir.z; const color = light._colorLinear; this._lightColor[0] = color[0] * lightScale; this._lightColor[1] = color[1] * lightScale; this._lightColor[2] = color[2] * lightScale; } else { this._lightDir[0] = 0; this._lightDir[1] = 1; this._lightDir[2] = 0; this._lightColor[0] = 0; this._lightColor[1] = 0; this._lightColor[2] = 0; } this.lightDirId.setValue(this._lightDir); this.lightColorId.setValue(this._lightColor); if (this.shadowsEnabled && light && this.lightRenderData) { const lightRenderData = this.lightRenderData; const biases = light._getUniformBiasValues(lightRenderData); this.shadowMapId.setValue(lightRenderData.shadowBuffer); this.shadowMatrixPaletteId.setValue(light._shadowMatrixPalette); this.shadowCascadeDistancesId.setValue(light._shadowCascadeDistances); this._shadowParams[0] = light.numCascades; this._shadowParams[1] = biases.bias; this._shadowParams[2] = 0; this._shadowParams[3] = light.shadowDistance; this.shadowParamsId.setValue(this._shadowParams); } const { tint, ambientColor, ambientIntensity } = this; this._tint[0] = tint.r; this._tint[1] = tint.g; this._tint[2] = tint.b; this.tintId.setValue(this._tint); const ambientScale = ambientIntensity * this.exposure; this._ambient[0] = ambientColor.r * ambientScale; this._ambient[1] = ambientColor.g * ambientScale; this._ambient[2] = ambientColor.b * ambientScale; this.ambientId.setValue(this._ambient); this._fogParams[0] = this.density; this._fogParams[1] = this.heightBase; this._fogParams[2] = this.heightFalloff; this._fogParams[3] = this.maxDistance; this.fogParamsId.setValue(this._fogParams); this._scatterParams[0] = this.anisotropy; this._scatterParams[1] = this.steps; this._scatterParams[2] = this.noiseOffset; this._scatterParams[3] = light ? light.shadowIntensity : 1; this.scatterParamsId.setValue(this._scatterParams); super.execute(); } } class RenderPassVolumetricFogCombine extends RenderPassShaderQuad { constructor(device, cameraComponent, fogTexture) { super(device); this.fogTexture = fogTexture; const defines = /* @__PURE__ */ new Map(); ShaderUtils.addScreenDepthChunkDefines(cameraComponent.shaderParams, defines); this.shader = ShaderUtils.createShader(device, { uniqueName: `VolumetricFogCombineShader-${cameraComponent.shaderParams.sceneDepthMapLinear}`, attributes: { aPosition: SEMANTIC_POSITION }, vertexChunk: "quadVS", fragmentChunk: "volumetricFogCombinePS", fragmentDefines: defines }); this.blendState = new BlendState( true, BLENDEQUATION_ADD, BLENDMODE_ONE, BLENDMODE_SRC_ALPHA, BLENDEQUATION_ADD, BLENDMODE_ZERO, BLENDMODE_ONE ); this.fogTextureId = device.scope.resolve("uFogTexture"); this.fogTextureSizeId = device.scope.resolve("uFogTextureSize"); this._fogTextureSize = new Float32Array(4); } execute() { const { fogTexture } = this; this.fogTextureId.setValue(fogTexture); this._fogTextureSize[0] = fogTexture.width; this._fogTextureSize[1] = fogTexture.height; this._fogTextureSize[2] = 1 / fogTexture.width; this._fogTextureSize[3] = 1 / fogTexture.height; this.fogTextureSizeId.setValue(this._fogTextureSize); super.execute(); } } class FramePassVolumetricFog extends FramePass { light = null; tint = new Color(1, 1, 1); density = 0.01; heightBase = 0; heightFalloff = 0.05; anisotropy = 0.6; intensity = 1; ambientColor = new Color(1, 1, 1); ambientIntensity = 0.02; maxDistance = 300; steps = 24; temporalDither = false; _scale = 0.5; _frameIndex = 0; constructor(device, cameraComponent, sceneTexture, sceneRenderTarget) { super(device); this.cameraComponent = cameraComponent; ShaderChunks.get(device, SHADERLANGUAGE_GLSL).set("volumetricFogPS", glslVolumetricFogPS); ShaderChunks.get(device, SHADERLANGUAGE_WGSL).set("volumetricFogPS", wgslVolumetricFogPS); ShaderChunks.get(device, SHADERLANGUAGE_GLSL).set("volumetricFogCombinePS", glslVolumetricFogCombinePS); ShaderChunks.get(device, SHADERLANGUAGE_WGSL).set("volumetricFogCombinePS", wgslVolumetricFogCombinePS); const format = device.getRenderableHdrFormat([PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F], true, 1) ?? PIXELFORMAT_RGBA8; this.fogTexture = new Texture(device, { name: "VolumetricFogTexture", width: 4, height: 4, format, mipmaps: false, minFilter: FILTER_LINEAR, magFilter: FILTER_LINEAR, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE }); this.fogRenderTarget = new RenderTarget({ name: "VolumetricFogRT", colorBuffer: this.fogTexture, depth: false }); this.fogPass = new RenderPassVolumetricFog(device, cameraComponent); this.fogPass.init(this.fogRenderTarget, { resizeSource: sceneTexture, scaleX: this._scale, scaleY: this._scale }); this.fogPass.setClearColor(new Color(0, 0, 0, 1)); this.beforePasses.push(this.fogPass); this.combinePass = new RenderPassVolumetricFogCombine(device, cameraComponent, this.fogTexture); this.combinePass.init(sceneRenderTarget); this.beforePasses.push(this.combinePass); } destroy() { this.beforePasses.forEach((pass) => pass.destroy()); this.beforePasses.length = 0; this.fogPass = null; this.combinePass = null; if (this.fogRenderTarget) { this.fogRenderTarget.destroyTextureBuffers(); this.fogRenderTarget.destroy(); this.fogRenderTarget = null; this.fogTexture = null; } } set scale(value) { this._scale = value; this.fogPass.scaleX = value; this.fogPass.scaleY = value; } get scale() { return this._scale; } frameUpdate() { super.frameUpdate(); const { light, fogPass } = this; const camera = this.cameraComponent.camera; let shadows = false; let pcf = false; let lightRenderData = null; if (light && light.castShadows && light.shadowIntensity > 0) { lightRenderData = light.getRenderData(camera, 0); if (lightRenderData.shadowBuffer) { shadows = true; pcf = !!shadowTypeInfo.get(light._shadowType)?.pcf; } } fogPass.updateShaderVariant(shadows, pcf); fogPass.shadowsEnabled = shadows; fogPass.lightRenderData = shadows ? lightRenderData : null; fogPass.light = light; fogPass.tint.copy(this.tint); fogPass.density = this.density; fogPass.heightBase = this.heightBase; fogPass.heightFalloff = this.heightFalloff; fogPass.anisotropy = this.anisotropy; fogPass.intensity = this.intensity; fogPass.ambientColor.copy(this.ambientColor); fogPass.ambientIntensity = this.ambientIntensity; fogPass.maxDistance = this.maxDistance; fogPass.steps = Math.max(1, Math.floor(this.steps)); fogPass.exposure = this.cameraComponent.system.app.scene.exposure; this._frameIndex = (this._frameIndex + 1) % 16; fogPass.noiseOffset = this.temporalDither ? this._frameIndex * 0.618034 : 0; } } export { FramePassVolumetricFog };