playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
3 lines (2 loc) • 6.18 kB
TypeScript
declare const _default: "\n\nfn fractSinRand(uv: vec2f) -> f32 {\n let PI: f32 = 3.141592653589793;\n let a: f32 = 12.9898; let b: f32 = 78.233; let c: f32 = 43758.5453;\n let dt: f32 = dot(uv.xy, vec2f(a, b));\n let sn: f32 = dt % PI;\n return fract(sin(sn) * c);\n}\n\n// struct to hold precomputed constants and current state\nstruct VogelDiskData {\n invNumSamples: f32,\n initialAngle: f32,\n currentPointId: f32,\n}\n\n// prepare the Vogel disk constants and initialize the current state in the struct\nfn prepareDiskConstants(data: ptr<function, VogelDiskData>, sampleCount: i32, randomSeed: f32) {\n let pi2: f32 = 6.28318530718;\n data.invNumSamples = 1.0 / f32(sampleCount);\n data.initialAngle = randomSeed * pi2;\n data.currentPointId = 0.0;\n}\n\n\nfn generateDiskSample(data: ptr<function, VogelDiskData>) -> vec2f {\n let GOLDEN_ANGLE: f32 = 2.399963;\n let r: f32 = sqrt((data.currentPointId + 0.5) * data.invNumSamples);\n let theta: f32 = data.currentPointId * GOLDEN_ANGLE + data.initialAngle;\n\n let offset: vec2f = vec2f(cos(theta), sin(theta)) * pow(r, 1.33);\n\n data.currentPointId = data.currentPointId + 1.0;\n return offset;\n}\n\nfn PCSSFindBlocker(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, avgBlockerDepth: ptr<function, f32>, numBlockers: ptr<function, i32>,\n shadowCoords: vec2f, z: f32, shadowBlockerSamples: i32, searchWidthUv: f32, randomSeed: f32) {\n\n var diskData: VogelDiskData;\n prepareDiskConstants(&diskData, shadowBlockerSamples, randomSeed);\n\n var blockerSum: f32 = 0.0;\n var numBlockers_local: i32 = 0;\n\n for( var i: i32 = 0; i < shadowBlockerSamples; i = i + 1 ) {\n let diskUV: vec2f = generateDiskSample(&diskData);\n let sampleUV: vec2f = shadowCoords + diskUV * searchWidthUv;\n let shadowMapDepth: f32 = textureSampleLevel(shadowMap, shadowMapSampler, sampleUV, 0.0).r;\n if ( shadowMapDepth < z ) {\n blockerSum = blockerSum + shadowMapDepth;\n numBlockers_local = numBlockers_local + 1;\n }\n }\n *avgBlockerDepth = blockerSum / f32(numBlockers_local);\n *numBlockers = numBlockers_local;\n}\n\nfn PCSSFilter(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, uv: vec2f, receiverDepth: f32, shadowSamples: i32, filterRadius: f32, randomSeed: f32) -> f32 {\n\n var diskData: VogelDiskData;\n prepareDiskConstants(&diskData, shadowSamples, randomSeed);\n\n var sum: f32 = 0.0;\n for (var i: i32 = 0; i < shadowSamples; i = i + 1) {\n let offsetUV: vec2f = generateDiskSample(&diskData) * filterRadius;\n let depth: f32 = textureSampleLevel(shadowMap, shadowMapSampler, uv + offsetUV, 0.0).r;\n sum = sum + step(receiverDepth, depth);\n }\n return sum / f32(shadowSamples);\n}\n\nfn PCSSDirectional(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoords: vec3f, cameraParams: vec4f, softShadowParams: vec4f) -> f32 {\n\n let receiverDepth: f32 = shadowCoords.z;\n // for sampling against the shadow map we clamp so cleared cells (depth = 1) don't get\n // treated as blockers when the receiver sits outside the tightened cascade range.\n // worldDist further down uses the *unclamped* receiverDepth so it stays cascade-stable.\n let receiverDepthClamped: f32 = min(receiverDepth, 0.9999);\n let randomSeed: f32 = fractSinRand(pcPosition.xy);\n let shadowSamples: i32 = i32(softShadowParams.x);\n let shadowBlockerSamples: i32 = i32(softShadowParams.y);\n let penumbraSize: f32 = softShadowParams.z; // world-space \"area size of the light\"\n let penumbraFalloff: f32 = softShadowParams.w; // curve shape (>= 1)\n\n // World-space PCSS. cameraParams.x is the ortho radius (world half-extent) of the\n // directional shadow camera; cameraParams.y/z are far/near. Working in world units\n // makes softness invariant to how the shadow camera tightens its depth bounds per\n // light direction.\n let orthoRadius: f32 = cameraParams.x;\n let depthRange: f32 = cameraParams.y - cameraParams.z;\n let worldPerUv: f32 = 2.0 * orthoRadius;\n\n var filterRadius: f32;\n\n // contact hardening path\n if (shadowBlockerSamples > 0) {\n\n // search width bounds the largest possible penumbra (blocker at far end of depth range)\n let searchWidthUv: f32 = (penumbraSize * depthRange) / worldPerUv;\n\n var avgBlockerDepth: f32 = 0.0;\n var numBlockers: i32 = 0;\n PCSSFindBlocker(shadowMap, shadowMapSampler, &avgBlockerDepth, &numBlockers, shadowCoords.xy, receiverDepthClamped, shadowBlockerSamples, searchWidthUv, randomSeed);\n\n // early out when no blockers are present\n if (numBlockers < 1) {\n return 1.0;\n }\n\n // World-space PCSS with shape control. The saturating curve reaches penumbraSize*depthRange\n // when the blocker sits at the far end of the caster depth range; higher penumbraFalloff\n // makes the softening climb faster (so the same blocker distance produces a wider penumbra).\n // falloff=1 is the linear baseline.\n let worldDist: f32 = max((receiverDepth - avgBlockerDepth) * depthRange, 0.0);\n let t: f32 = clamp(worldDist / depthRange, 0.0, 1.0);\n let shape: f32 = 1.0 - pow(1.0 - t, penumbraFalloff);\n let penumbraWorld: f32 = shape * penumbraSize * depthRange;\n filterRadius = penumbraWorld / worldPerUv;\n\n } else {\n\n // constant filter size, no contact hardening \u2014 penumbraSize is the world-space radius\n filterRadius = penumbraSize / worldPerUv;\n }\n\n // filtering\n return PCSSFilter(shadowMap, shadowMapSampler, shadowCoords.xy, receiverDepthClamped, shadowSamples, filterRadius, randomSeed);\n}\n\nfn getShadowPCSS(shadowMap: texture_2d<f32>, shadowMapSampler: sampler, shadowCoord: vec3f, shadowParams: vec4f, cameraParams: vec4f, softShadowParams: vec4f, lightDir: vec3f) -> f32 { // lightDir unused? Kept param.\n return PCSSDirectional(shadowMap, shadowMapSampler, shadowCoord, cameraParams, softShadowParams);\n}\n";
export default _default;