Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to non-clustered omni light shadows #6998

Merged
merged 3 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions examples/src/examples/graphics/lights.example.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
1 change: 1 addition & 0 deletions src/platform/graphics/shader-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ class ShaderUtils {
precision ${precision} usampler2D;
precision ${precision} isampler2D;
precision ${precision} sampler2DShadow;
precision ${precision} samplerCubeShadow;
precision ${precision} sampler2DArray;
`;

Expand Down
14 changes: 5 additions & 9 deletions src/scene/light.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)) {
Expand All @@ -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;
Expand Down
49 changes: 34 additions & 15 deletions src/scene/renderer/shadow-map.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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, {
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}
Expand Down
23 changes: 4 additions & 19 deletions src/scene/renderer/shadow-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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];
Expand Down
2 changes: 0 additions & 2 deletions src/scene/shader-lib/chunks/chunks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -291,7 +290,6 @@ const shaderChunks = {
outputAlphaOpaquePS,
outputAlphaPremulPS,
outputTex2DPS,
packDepthPS,
sheenPS,
sheenGlossPS,
parallaxPS,
Expand Down
13 changes: 0 additions & 13 deletions src/scene/shader-lib/chunks/common/frag/packDepth.js

This file was deleted.

96 changes: 23 additions & 73 deletions src/scene/shader-lib/chunks/lit/frag/shadowStandard.js
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down
9 changes: 0 additions & 9 deletions src/scene/shader-lib/chunks/particle/frag/particle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
Loading