Skip to content

Commit 1c555bd

Browse files
mvaligurskyMartin Valigurskywilleastcott
authored
WGSL implementation of VSM shadows (#7599)
* WGSL implementation of VSM shadows * Update src/scene/shader-lib/chunks-wgsl/lit/frag/blurVSM.js Co-authored-by: Will Eastcott <[email protected]> --------- Co-authored-by: Martin Valigursky <[email protected]> Co-authored-by: Will Eastcott <[email protected]>
1 parent 0bfee15 commit 1c555bd

File tree

9 files changed

+165
-20
lines changed

9 files changed

+165
-20
lines changed

src/scene/renderer/shadow-renderer.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { Color } from '../../core/math/color.js';
44
import { Mat4 } from '../../core/math/mat4.js';
55
import { Vec3 } from '../../core/math/vec3.js';
66
import { Vec4 } from '../../core/math/vec4.js';
7-
import { SHADERSTAGE_FRAGMENT, SHADERSTAGE_VERTEX, UNIFORMTYPE_MAT4, UNIFORM_BUFFER_DEFAULT_SLOT_NAME } from '../../platform/graphics/constants.js';
7+
import {
8+
SEMANTIC_POSITION, SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL, SHADERSTAGE_FRAGMENT, SHADERSTAGE_VERTEX,
9+
UNIFORMTYPE_MAT4, UNIFORM_BUFFER_DEFAULT_SLOT_NAME
10+
} from '../../platform/graphics/constants.js';
811
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js';
912
import { drawQuadWithShader } from '../graphics/quad-render-utils.js';
1013
import {
@@ -21,6 +24,7 @@ import { LightCamera } from './light-camera.js';
2124
import { UniformBufferFormat, UniformFormat } from '../../platform/graphics/uniform-buffer-format.js';
2225
import { BindUniformBufferFormat, BindGroupFormat } from '../../platform/graphics/bind-group-format.js';
2326
import { BlendState } from '../../platform/graphics/blend-state.js';
27+
import { shaderChunksWGSL } from '../shader-lib/chunks-wgsl/chunks-wgsl.js';
2428

2529
/**
2630
* @import { Camera } from '../camera.js'
@@ -88,7 +92,8 @@ class ShadowRenderer {
8892
this.sourceId = scope.resolve('source');
8993
this.pixelOffsetId = scope.resolve('pixelOffset');
9094
this.weightId = scope.resolve('weight[0]');
91-
this.blurVsmShaderCode = [shaderChunks.blurVSMPS, `#define GAUSS\n${shaderChunks.blurVSMPS}`];
95+
const chunks = this.device.isWebGPU ? shaderChunksWGSL : shaderChunks;
96+
this.blurVsmShaderCode = [chunks.blurVSMPS, `#define GAUSS\n${chunks.blurVSMPS}`];
9297

9398
// cache for vsm blur shaders
9499
this.blurVsmShader = [{}, {}];
@@ -490,11 +495,14 @@ class ShadowRenderer {
490495
if (!blurShader) {
491496
this.blurVsmWeights[filterSize] = gaussWeights(filterSize);
492497

493-
const blurVS = shaderChunks.fullscreenQuadVS;
494-
let blurFS = `#define SAMPLES ${filterSize}\n`;
498+
const chunks = this.device.isWebGPU ? shaderChunksWGSL : shaderChunks;
499+
const blurVS = chunks.fullscreenQuadVS;
500+
let blurFS = `#define {SAMPLES} ${filterSize}\n`;
495501
blurFS += this.blurVsmShaderCode[blurMode];
496502
const blurShaderName = `blurVsm${blurMode}${filterSize}`;
497-
blurShader = createShaderFromCode(this.device, blurVS, blurFS, blurShaderName);
503+
blurShader = createShaderFromCode(this.device, blurVS, blurFS, blurShaderName, { vertex_position: SEMANTIC_POSITION }, undefined, {
504+
shaderLanguage: this.device.isWebGPU ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL
505+
});
498506
cache[blurMode][filterSize] = blurShader;
499507
}
500508

src/scene/shader-lib/chunks-wgsl/chunks-wgsl.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import basePS from './lit/frag/base.js';
77
// import baseNineSlicedPS from './lit/frag/baseNineSliced.js';
88
// import baseNineSlicedTiledPS from './lit/frag/baseNineSlicedTiled.js';
99
import bayerPS from './common/frag/bayer.js';
10-
// import blurVSMPS from './lit/frag/blurVSM.js';
10+
import blurVSMPS from './lit/frag/blurVSM.js';
1111
import clearCoatPS from './standard/frag/clearCoat.js';
1212
import clearCoatGlossPS from './standard/frag/clearCoatGloss.js';
1313
import clearCoatNormalPS from './standard/frag/clearCoatNormal.js';
@@ -38,7 +38,7 @@ import falloffLinearPS from './lit/frag/falloffLinear.js';
3838
import fogPS from './common/frag/fog.js';
3939
import fresnelSchlickPS from './lit/frag/fresnelSchlick.js';
4040
// import fullscreenQuadPS from './common/frag/fullscreenQuad.js';
41-
// import fullscreenQuadVS from './common/vert/fullscreenQuad.js';
41+
import fullscreenQuadVS from './common/vert/fullscreenQuad.js';
4242
import gammaPS from './common/frag/gamma.js';
4343
import glossPS from './standard/frag/gloss.js';
4444
// import gsplatCenterVS from './gsplat/vert/gsplatCenter.js';
@@ -159,7 +159,7 @@ import reprojectVS from './internal/vert/reproject.js';
159159
// import sampleCatmullRomPS from './common/frag/sampleCatmullRom.js';
160160
// import screenDepthPS from './common/frag/screenDepth.js';
161161
import shadowCascadesPS from './lit/frag/lighting/shadowCascades.js';
162-
// import shadowEVSMPS from './lit/frag/lighting/shadowEVSM.js';
162+
import shadowEVSMPS from './lit/frag/lighting/shadowEVSM.js';
163163
import shadowPCF1PS from './lit/frag/lighting/shadowPCF1.js';
164164
import shadowPCF3PS from './lit/frag/lighting/shadowPCF3.js';
165165
import shadowPCF5PS from './lit/frag/lighting/shadowPCF5.js';
@@ -217,7 +217,7 @@ const shaderChunksWGSL = {
217217
// baseNineSlicedPS,
218218
// baseNineSlicedTiledPS,
219219
bayerPS,
220-
// blurVSMPS,
220+
blurVSMPS,
221221
clearCoatPS,
222222
clearCoatGlossPS,
223223
clearCoatNormalPS,
@@ -248,7 +248,7 @@ const shaderChunksWGSL = {
248248
fogPS,
249249
fresnelSchlickPS,
250250
// fullscreenQuadPS,
251-
// fullscreenQuadVS,
251+
fullscreenQuadVS,
252252
gammaPS,
253253
glossPS,
254254
// gsplatCenterVS,
@@ -370,7 +370,7 @@ const shaderChunksWGSL = {
370370
// sampleCatmullRomPS,
371371
// screenDepthPS,
372372
shadowCascadesPS,
373-
// shadowEVSMPS,
373+
shadowEVSMPS,
374374
shadowPCF1PS,
375375
shadowPCF3PS,
376376
shadowPCF5PS,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export default /* wgsl */`
2+
attribute vertex_position: vec2f;
3+
4+
varying vUv0: vec2f;
5+
6+
@vertex
7+
fn vertexMain(input: VertexInput) -> VertexOutput {
8+
var output: VertexOutput;
9+
output.position = vec4f(input.vertex_position, 0.5, 1.0);
10+
output.vUv0 = input.vertex_position.xy * 0.5 + vec2f(0.5);
11+
return output;
12+
}
13+
`;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
export default /* wgsl */`
2+
varying vUv0: vec2f;
3+
4+
var source: texture_2d<f32>;
5+
var sourceSampler: sampler;
6+
7+
#ifdef GAUSS
8+
uniform weight: array<f32, {SAMPLES}>;
9+
#endif
10+
uniform pixelOffset: vec2f;
11+
12+
@fragment
13+
fn fragmentMain(input: FragmentInput) -> FragmentOutput {
14+
var output: FragmentOutput;
15+
var moments: vec3f = vec3f(0.0);
16+
let uv: vec2f = input.vUv0 - uniform.pixelOffset * (f32({SAMPLES}) * 0.5);
17+
18+
for (var i: i32 = 0; i < {SAMPLES}; i = i + 1) {
19+
let c: vec4f = textureSample(source, sourceSampler, uv + uniform.pixelOffset * f32(i));
20+
21+
#ifdef GAUSS
22+
moments = moments + c.xyz * uniform.weight[i].element;
23+
#else
24+
moments = moments + c.xyz;
25+
#endif
26+
}
27+
28+
#ifndef GAUSS
29+
moments = moments * (1.0 / f32({SAMPLES}));
30+
#endif
31+
32+
output.color = vec4f(moments, 1.0);
33+
return output;
34+
}
35+
`;

src/scene/shader-lib/chunks-wgsl/lit/frag/lighting/lightDeclaration.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export default /* wgsl */`
8181
var light{i}_shadowMap: texture_depth_2d;
8282
var light{i}_shadowMapSampler: sampler_comparison;
8383
#else
84-
var light{i}_shadowMap texture_2D<f32>;
84+
var light{i}_shadowMap: texture_2d<f32>;
8585
var light{i}_shadowMapSampler: sampler;
8686
#endif
8787
#endif
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
export default /* wgsl */`
2+
3+
// ------ VSM Shared ------
4+
5+
fn linstep(a: f32, b: f32, v: f32) -> f32 {
6+
// WGSL saturate -> clamp
7+
return clamp((v - a) / (b - a), 0.0, 1.0);
8+
}
9+
10+
fn reduceLightBleeding(pMax: f32, amount: f32) -> f32 {
11+
// Remove the [0, amount] tail and linearly rescale (amount, 1].
12+
return linstep(amount, 1.0, pMax);
13+
}
14+
15+
fn chebyshevUpperBound(moments: vec2f, mean: f32, minVariance: f32, lightBleedingReduction: f32) -> f32 {
16+
// Compute variance
17+
var variance: f32 = moments.y - (moments.x * moments.x);
18+
variance = max(variance, minVariance);
19+
20+
// Compute probabilistic upper bound
21+
let d: f32 = mean - moments.x;
22+
var pMax: f32 = variance / (variance + (d * d));
23+
24+
pMax = reduceLightBleeding(pMax, lightBleedingReduction);
25+
26+
// One-tailed Chebyshev
27+
return select(pMax, 1.0, mean <= moments.x);
28+
}
29+
30+
fn calculateEVSM(moments_in: vec3f, Z_in: f32, vsmBias: f32, exponent: f32) -> f32 {
31+
let Z: f32 = 2.0 * Z_in - 1.0;
32+
let warpedDepth: f32 = exp(exponent * Z);
33+
34+
let moments: vec2f = moments_in.xy + vec2f(warpedDepth, warpedDepth*warpedDepth) * (1.0 - moments_in.z);
35+
36+
let VSMBias: f32 = vsmBias;//0.01 * 0.25;
37+
let depthScale: f32 = VSMBias * exponent * warpedDepth;
38+
let minVariance1: f32 = depthScale * depthScale;
39+
return chebyshevUpperBound(moments, warpedDepth, minVariance1, 0.1);
40+
}
41+
// ------ VSM 16 ------
42+
43+
fn VSM16(tex: texture_2d<f32>, texSampler: sampler, texCoords: vec2f, resolution: f32, Z: f32, vsmBias: f32, exponent: f32) -> f32 {
44+
let moments: vec3f = textureSampleLevel(tex, texSampler, texCoords, 0.0).xyz;
45+
return calculateEVSM(moments, Z, vsmBias, exponent);
46+
}
47+
48+
fn getShadowVSM16(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32) -> f32 {
49+
return VSM16(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);
50+
}
51+
52+
fn getShadowSpotVSM16(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32, lightDir: vec3f) -> f32 {
53+
let Z: f32 = length(lightDir) * shadowParams.w + shadowParams.z;
54+
return VSM16(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, Z, shadowParams.y, exponent);
55+
}
56+
57+
// ------ VSM 32 ------
58+
59+
fn VSM32(tex: texture_2d<f32>, texSampler: sampler, texCoords_in: vec2f, resolution: f32, Z: f32, vsmBias: f32, exponent: f32) -> f32 {
60+
61+
#ifdef CAPS_TEXTURE_FLOAT_FILTERABLE
62+
var moments: vec3f = textureSampleLevel(tex, texSampler, texCoords_in, 0.0).xyz;
63+
#else
64+
// manual bilinear filtering
65+
var pixelSize : f32 = 1.0 / resolution;
66+
let texCoords: vec2f = texCoords_in - vec2f(pixelSize);
67+
let s00: vec3f = textureSampleLevel(tex, texSampler, texCoords, 0.0).xyz;
68+
let s10: vec3f = textureSampleLevel(tex, texSampler, texCoords + vec2f(pixelSize, 0.0), 0.0).xyz;
69+
let s01: vec3f = textureSampleLevel(tex, texSampler, texCoords + vec2f(0.0, pixelSize), 0.0).xyz;
70+
let s11: vec3f = textureSampleLevel(tex, texSampler, texCoords + vec2f(pixelSize), 0.0).xyz;
71+
let fr: vec2f = fract(texCoords * resolution);
72+
let h0: vec3f = mix(s00, s10, fr.x);
73+
let h1: vec3f = mix(s01, s11, fr.x);
74+
var moments: vec3f = mix(h0, h1, fr.y);
75+
#endif
76+
77+
return calculateEVSM(moments, Z, vsmBias, exponent);
78+
}
79+
80+
fn getShadowVSM32(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32) -> f32 {
81+
return VSM32(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, shadowCoord.z, shadowParams.y, exponent);
82+
}
83+
84+
fn getShadowSpotVSM32(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, exponent: f32, lightDir: vec3f) -> f32 {
85+
let Z: f32 = length(lightDir) * shadowParams.w + shadowParams.z;
86+
return VSM32(shadowMap, shadowMapSampler, shadowCoord.xy, shadowParams.x, Z, shadowParams.y, exponent);
87+
}
88+
`;

src/scene/shader-lib/chunks/common/vert/fullscreenQuad.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ varying vec2 vUv0;
66
void main(void)
77
{
88
gl_Position = vec4(vertex_position, 0.5, 1.0);
9-
vUv0 = vertex_position.xy*0.5+0.5;
9+
vUv0 = vertex_position.xy * 0.5 + 0.5;
1010
}
1111
`;

src/scene/shader-lib/chunks/lit/frag/blurVSM.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,24 @@ uniform sampler2D source;
55
uniform vec2 pixelOffset;
66
77
#ifdef GAUSS
8-
uniform float weight[SAMPLES];
8+
uniform float weight[{SAMPLES}];
99
#endif
1010
1111
void main(void) {
1212
vec3 moments = vec3(0.0);
13-
vec2 uv = vUv0 - pixelOffset * (float(SAMPLES) * 0.5);
14-
for (int i=0; i<SAMPLES; i++) {
13+
vec2 uv = vUv0 - pixelOffset * (float({SAMPLES}) * 0.5);
14+
for (int i = 0; i < {SAMPLES}; i++) {
1515
vec4 c = texture2D(source, uv + pixelOffset * float(i));
1616
1717
#ifdef GAUSS
18-
moments += c.xyz * weight[i];
18+
moments += c.xyz * weight[i];
1919
#else
20-
moments += c.xyz;
20+
moments += c.xyz;
2121
#endif
2222
}
2323
2424
#ifndef GAUSS
25-
moments /= float(SAMPLES);
25+
moments *= 1.0 / float({SAMPLES});
2626
#endif
2727
2828
gl_FragColor = vec4(moments.x, moments.y, moments.z, 1.0);

src/scene/shader-lib/chunks/lit/frag/lighting/shadowEVSM.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ float getShadowVSM32(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowPar
8181
}
8282
8383
float getShadowSpotVSM32(TEXTURE_ACCEPT(shadowMap), vec3 shadowCoord, vec4 shadowParams, float exponent, vec3 lightDir) {
84-
return VSM32(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, length(lightDir) * shadowParams.w + shadowParams.z, shadowParams.y, exponent);
84+
float Z = length(lightDir) * shadowParams.w + shadowParams.z;
85+
return VSM32(TEXTURE_PASS(shadowMap), shadowCoord.xy, shadowParams.x, Z, shadowParams.y, exponent);
8586
}
8687
`;

0 commit comments

Comments
 (0)