#define SHADOW_SAMPLING_RUNTIME_PCF 0u #define SHADOW_SAMPLING_RUNTIME_EVSM 1u #define SHADOW_SAMPLING_RUNTIME_DPCF 2u #define SHADOW_SAMPLING_RUNTIME_PCSS 3u #define SHADOW_SAMPLING_PCF_HARD 0 #define SHADOW_SAMPLING_PCF_LOW 1 float ShadowSample_PCF_Hard(const mediump sampler2DArrayShadow map, const highp vec4 scissorNormalized, const uint layer, const highp vec4 shadowPosition) { highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); position.xy = clamp(position.xy, scissorNormalized.xy, scissorNormalized.zw); position.z = saturate(position.z); return texture(map, vec4(position.xy, layer, position.z)); } float ShadowSample_PCF_Low(const mediump sampler2DArrayShadow map, const highp vec4 scissorNormalized, const uint layer, const highp vec4 shadowPosition) { highp vec2 size = vec2(frameUniforms.shadowAtlasResolution.x); highp vec2 texelSize = vec2(frameUniforms.shadowAtlasResolution.y); highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); position.z = saturate(position.z); position.xy = clamp(position.xy, vec2(-1.0), vec2(2.0)); vec2 offset = vec2(0.5); highp vec2 uv = (position.xy * size) + offset; highp vec2 base = (floor(uv) - offset) * texelSize; highp vec2 st = fract(uv); vec2 uw = vec2(3.0 - 2.0 * st.x, 1.0 + 2.0 * st.x); vec2 vw = vec2(3.0 - 2.0 * st.y, 1.0 + 2.0 * st.y); highp vec2 u = vec2((2.0 - st.x) / uw.x - 1.0, st.x / uw.y + 1.0); highp vec2 v = vec2((2.0 - st.y) / vw.x - 1.0, st.y / vw.y + 1.0); u *= texelSize.x; v *= texelSize.y; float w0 = uw.x * vw.x; float w1 = uw.y * vw.x; float w2 = uw.x * vw.y; float w3 = uw.y * vw.y; highp vec2 uv0 = base + vec2(u.x, v.x); highp vec2 uv1 = base + vec2(u.y, v.x); highp vec2 uv2 = base + vec2(u.x, v.y); highp vec2 uv3 = base + vec2(u.y, v.y); uv0 = clamp(uv0, scissorNormalized.xy, scissorNormalized.zw); uv1 = clamp(uv1, scissorNormalized.xy, scissorNormalized.zw); uv2 = clamp(uv2, scissorNormalized.xy, scissorNormalized.zw); uv3 = clamp(uv3, scissorNormalized.xy, scissorNormalized.zw); float sum = 0.0; sum += w0 * texture(map, vec4(uv0, layer, position.z)); sum += w1 * texture(map, vec4(uv1, layer, position.z)); sum += w2 * texture(map, vec4(uv2, layer, position.z)); sum += w3 * texture(map, vec4(uv3, layer, position.z)); return sum * 0.0625; } float ShadowSample_PCF(const mediump sampler2DArray map, const highp vec4 scissorNormalized, const uint layer, const highp vec4 shadowPosition) { highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); position.xy = clamp(position.xy, scissorNormalized.xy, scissorNormalized.zw); position.z = saturate(position.z); highp float depth = textureLod(map, vec3(position.xy, layer), 0.0).r; return step(0.0, position.z - depth); } mediump vec2 poissonDisk[64] = vec2[]( vec2(0.511749, 0.547686), vec2(0.58929, 0.257224), vec2(0.165018, 0.57663), vec2(0.407692, 0.742285), vec2(0.707012, 0.646523), vec2(0.31463, 0.466825), vec2(0.801257, 0.485186), vec2(0.418136, 0.146517), vec2(0.579889, 0.0368284), vec2(0.79801, 0.140114), vec2(-0.0413185, 0.371455), vec2(-0.0529108, 0.627352), vec2(0.0821375, 0.882071), vec2(0.17308, 0.301207), vec2(-0.120452, 0.867216), vec2(0.371096, 0.916454), vec2(-0.178381, 0.146101), vec2(-0.276489, 0.550525), vec2(0.12542, 0.126643), vec2(-0.296654, 0.286879), vec2(0.261744, -0.00604975), vec2(-0.213417, 0.715776), vec2(0.425684, -0.153211), vec2(-0.480054, 0.321357), vec2(-0.0717878, -0.0250567), vec2(-0.328775, -0.169666), vec2(-0.394923, 0.130802), vec2(-0.553681, -0.176777), vec2(-0.722615, 0.120616), vec2(-0.693065, 0.309017), vec2(0.603193, 0.791471), vec2(-0.0754941, -0.297988), vec2(0.109303, -0.156472), vec2(0.260605, -0.280111), vec2(0.129731, -0.487954), vec2(-0.537315, 0.520494), vec2(-0.42758, 0.800607), vec2(0.77309, -0.0728102), vec2(0.908777, 0.328356), vec2(0.985341, 0.0759158), vec2(0.947536, -0.11837), vec2(-0.103315, -0.610747), vec2(0.337171, -0.584), vec2(0.210919, -0.720055), vec2(0.41894, -0.36769), vec2(-0.254228, -0.49368), vec2(-0.428562, -0.404037), vec2(-0.831732, -0.189615), vec2(-0.922642, 0.0888026), vec2(-0.865914, 0.427795), vec2(0.706117, -0.311662), vec2(0.545465, -0.520942), vec2(-0.695738, 0.664492), vec2(0.389421, -0.899007), vec2(0.48842, -0.708054), vec2(0.760298, -0.62735), vec2(-0.390788, -0.707388), vec2(-0.591046, -0.686721), vec2(-0.769903, -0.413775), vec2(-0.604457, -0.502571), vec2(-0.557234, 0.00451362), vec2(0.147572, -0.924353), vec2(-0.0662488, -0.892081), vec2(0.863832, -0.407206) ); const uint DPCF_SHADOW_TAP_COUNT = 12u; const uint PCSS_SHADOW_BLOCKER_SEARCH_TAP_COUNT = 16u; const uint PCSS_SHADOW_FILTER_TAP_COUNT = 16u; float hardenedKernel(float x) { x = 2.0 * x - 1.0; float s = sign(x); x = 1.0 - s * x; x = x * x * x; x = s - x * s; return 0.5 * x + 0.5; } highp vec2 computeReceiverPlaneDepthBias(const highp vec3 position) { highp vec3 duvz_dx = dFdx(position); highp vec3 duvz_dy = dFdy(position); highp vec2 dz_duv = inverse(transpose(mat2(duvz_dx.xy, duvz_dy.xy))) * vec2(duvz_dx.z, duvz_dy.z); return dz_duv; } mat2 getRandomRotationMatrix(highp vec2 fragCoord) { fragCoord += vec2(frameUniforms.temporalNoise); float randomAngle = interleavedGradientNoise(fragCoord) * (2.0 * PI); vec2 randomBase = vec2(cos(randomAngle), sin(randomAngle)); mat2 R = mat2(randomBase.x, randomBase.y, -randomBase.y, randomBase.x); return R; } float getPenumbraLs(const bool DIRECTIONAL, const int index, const highp float zLight) { float penumbra; if (DIRECTIONAL) { penumbra = shadowUniforms.shadows[index].bulbRadiusLs; } else { penumbra = shadowUniforms.shadows[index].bulbRadiusLs / zLight; } return penumbra; } float getPenumbraRatio(const bool DIRECTIONAL, const int index, float z_receiver, float z_blocker) { float penumbraRatio; if (DIRECTIONAL) { penumbraRatio = (z_blocker - z_receiver) / (1.0 - z_blocker); } else { float nearOverFarMinusNear = shadowUniforms.shadows[index].nearOverFarMinusNear; penumbraRatio = (nearOverFarMinusNear + z_blocker) / (nearOverFarMinusNear + z_receiver) - 1.0; } return penumbraRatio * frameUniforms.shadowPenumbraRatioScale; } void blockerSearchAndFilter(out float occludedCount, out float z_occSum, const mediump sampler2DArray map, const highp vec4 scissorNormalized, const highp vec2 uv, const float z_rec, const uint layer, const highp vec2 filterRadii, const mat2 R, const highp vec2 dz_duv, const uint tapCount) { occludedCount = 0.0; z_occSum = 0.0; for (uint i = 0u; i < tapCount; i++) { highp vec2 duv = R * (poissonDisk[i] * filterRadii); highp vec2 tc = clamp(uv + duv, scissorNormalized.xy, scissorNormalized.zw); float z_occ = textureLod(map, vec3(tc, layer), 0.0).r; float z_bias = dot(dz_duv, duv); float dz = z_occ - z_rec; float occluded = step(z_bias, dz); occludedCount += occluded; z_occSum += z_occ * occluded; } } float filterPCSS(const mediump sampler2DArray map, const highp vec4 scissorNormalized, const highp vec2 size, const highp vec2 uv, const float z_rec, const uint layer, const highp vec2 filterRadii, const mat2 R, const highp vec2 dz_duv, const uint tapCount) { float occludedCount = 0.0; for (uint i = 0u; i < tapCount; i++) { highp vec2 duv = R * (poissonDisk[i] * filterRadii); vec4 d; highp vec2 tc = clamp(uv + duv, scissorNormalized.xy, scissorNormalized.zw); highp vec2 st = tc.xy * size - 0.5; highp vec2 grad = fract(st); #if defined(FILAMENT_HAS_FEATURE_TEXTURE_GATHER) d = textureGather(map, vec3(tc, layer), 0); #else d[0] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(0, 1)).r; d[1] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(1, 1)).r; d[2] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(1, 0)).r; d[3] = texelFetchOffset(map, ivec3(st, layer), 0, ivec2(0, 0)).r; #endif float z_bias = dot(dz_duv, duv); vec4 dz = d - vec4(z_rec); vec4 pcf = step(z_bias, dz); occludedCount += mix(mix(pcf.w, pcf.z, grad.x), mix(pcf.x, pcf.y, grad.x), grad.y); } return occludedCount * (1.0 / float(tapCount)); } float ShadowSample_DPCF(const bool DIRECTIONAL, const mediump sampler2DArray map, const highp vec4 scissorNormalized, const uint layer, const int index, const highp vec4 shadowPosition, const highp float zLight) { highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); highp vec2 texelSize = vec2(1.0) / vec2(textureSize(map, 0)); highp vec2 dz_duv = computeReceiverPlaneDepthBias(position); float penumbra = getPenumbraLs(DIRECTIONAL, index, zLight); mat2 R = getRandomRotationMatrix(gl_FragCoord.xy); float occludedCount = 0.0; float z_occSum = 0.0; blockerSearchAndFilter(occludedCount, z_occSum, map, scissorNormalized, position.xy, position.z, layer, texelSize * penumbra, R, dz_duv, DPCF_SHADOW_TAP_COUNT); if (z_occSum == 0.0) { return 1.0; } float penumbraRatio = getPenumbraRatio(DIRECTIONAL, index, position.z, z_occSum / occludedCount); penumbraRatio = saturate(penumbraRatio); float percentageOccluded = occludedCount * (1.0 / float(DPCF_SHADOW_TAP_COUNT)); percentageOccluded = mix(hardenedKernel(percentageOccluded), percentageOccluded, penumbraRatio); return 1.0 - percentageOccluded; } float ShadowSample_PCSS(const bool DIRECTIONAL, const mediump sampler2DArray map, const highp vec4 scissorNormalized, const uint layer, const int index, const highp vec4 shadowPosition, const highp float zLight) { highp vec2 size = vec2(textureSize(map, 0)); highp vec2 texelSize = vec2(1.0) / size; highp vec3 position = shadowPosition.xyz * (1.0 / shadowPosition.w); highp vec2 dz_duv = computeReceiverPlaneDepthBias(position); float penumbra = getPenumbraLs(DIRECTIONAL, index, zLight); mat2 R = getRandomRotationMatrix(gl_FragCoord.xy); float occludedCount = 0.0; float z_occSum = 0.0; blockerSearchAndFilter(occludedCount, z_occSum, map, scissorNormalized, position.xy, position.z, layer, texelSize * penumbra, R, dz_duv, PCSS_SHADOW_BLOCKER_SEARCH_TAP_COUNT); if (z_occSum == 0.0) { return 1.0; } float penumbraRatio = getPenumbraRatio(DIRECTIONAL, index, position.z, z_occSum / occludedCount); float percentageOccluded = filterPCSS(map, scissorNormalized, size, position.xy, position.z, layer, texelSize * (penumbra * penumbraRatio), R, dz_duv, PCSS_SHADOW_FILTER_TAP_COUNT); return 1.0 - percentageOccluded; } float chebyshevUpperBound(const highp vec2 moments, const highp float depth, const highp float minVariance, const highp float lbrAmount) { if (depth <= moments.x) { return 1.0; } highp float variance = max(moments.y - (moments.x * moments.x), minVariance); highp float d = depth - moments.x; highp float p_max = variance / (variance + d * d); return saturate((p_max - lbrAmount) / (1.0 - lbrAmount)); } float evaluateEVSM(const bool ELVSM, float c, const highp vec4 moments, const highp float zReceiver, const highp vec2 dzduv, const highp vec2 texelSize) { const highp float EPSILON_MULTIPLIER = 0.002; float lbrAmount = frameUniforms.vsmLightBleedReduction; highp vec2 texel_dzduv = dzduv * texelSize; highp float dz2 = dot(texel_dzduv, texel_dzduv); highp float depth = zReceiver * 2.0 - 1.0; highp float pw = exp(c * depth); highp float epsilon = EPSILON_MULTIPLIER * (pw * pw); highp float dpwdz = 2.0 * c * pw; highp float pMinVariance = epsilon + 0.25 * (dpwdz * dpwdz) * dz2; float p = chebyshevUpperBound(moments.xy, pw, pMinVariance, lbrAmount); if (ELVSM) { highp float nw = -1.0 / pw; highp float epsilon = EPSILON_MULTIPLIER * (nw * nw); highp float dnwdz = 2.0 * c * nw; highp float nMinVariance = epsilon + 0.25 * (dnwdz * dnwdz) * dz2; float n = chebyshevUpperBound(moments.zw, nw, nMinVariance, lbrAmount); p = min(p, n); } return p; } float ShadowSample_VSM(const bool DIRECTIONAL, const highp sampler2DArray shadowMap, const highp vec4 scissorNormalized, const uint layer, const int index, const highp vec4 shadowPosition, const highp float zLight) { bool ELVSM = shadowUniforms.shadows[index].elvsm; float c = shadowUniforms.shadows[index].vsmExponent; highp vec2 texelSize = vec2(1.0) / vec2(textureSize(shadowMap, 0)); highp vec3 position = vec3(shadowPosition.xy * (1.0 / shadowPosition.w), shadowPosition.z); highp vec2 dzduv = computeReceiverPlaneDepthBias(position); position.xy = clamp(position.xy, scissorNormalized.xy, scissorNormalized.zw); highp vec4 moments = texture(shadowMap, vec3(position.xy, layer)); return evaluateEVSM(ELVSM, c, moments, position.z, dzduv, texelSize); } struct ScreenSpaceRay { highp vec3 ssRayStart; highp vec3 ssRayEnd; highp vec3 ssViewRayEnd; highp vec3 uvRayStart; highp vec3 uvRay; }; void initScreenSpaceRay(out ScreenSpaceRay ray, highp vec3 wsRayStart, vec3 wsRayDirection, float wsRayLength) { highp mat4 worldToClip = getClipFromWorldMatrix(); highp mat4 viewToClip = getClipFromViewMatrix(); highp vec3 wsRayEnd = wsRayStart + wsRayDirection * wsRayLength; highp vec4 csRayStart = worldToClip * vec4(wsRayStart, 1.0); highp vec4 csRayEnd = worldToClip * vec4(wsRayEnd, 1.0); highp vec4 csViewRayEnd = csRayStart + viewToClip * vec4(0.0, 0.0, wsRayLength, 0.0); ray.ssRayStart = csRayStart.xyz * (1.0 / csRayStart.w); ray.ssRayEnd = csRayEnd.xyz * (1.0 / csRayEnd.w); ray.ssViewRayEnd = csViewRayEnd.xyz * (1.0 / csViewRayEnd.w); highp vec3 uvRayEnd = vec3(ray.ssRayEnd.xy * 0.5 + 0.5, ray.ssRayEnd.z); ray.uvRayStart = vec3(ray.ssRayStart.xy * 0.5 + 0.5, ray.ssRayStart.z); ray.uvRay = uvRayEnd - ray.uvRayStart; } float screenSpaceContactShadow(vec3 lightDirection) { float occlusion = 0.0; int kStepCount = (frameUniforms.directionalShadows >> 8) & 0xFF; float kDistanceMax = frameUniforms.ssContactShadowDistance; ScreenSpaceRay rayData; initScreenSpaceRay(rayData, shading_position, lightDirection, kDistanceMax); highp float dt = 1.0 / float(kStepCount); highp float tolerance = abs(rayData.ssViewRayEnd.z - rayData.ssRayStart.z) * dt; float dither = interleavedGradientNoise(gl_FragCoord.xy) - 0.5; highp float t = dt * dither + dt; highp vec3 ray; for (int i = 0 ; i < kStepCount ; i++, t += dt) { ray = rayData.uvRayStart + rayData.uvRay * t; highp float z = textureLod(sampler0_structure, uvToRenderTargetUV(ray.xy), 0.0).r; highp float dz = z - ray.z; if (abs(tolerance - dz) < tolerance) { occlusion = 1.0; break; } } vec2 fade = max(12.0 * abs(ray.xy - 0.5) - 5.0, 0.0); occlusion *= saturate(1.0 - dot(fade, fade)); return occlusion; } #if defined(VARIANT_HAS_DIRECTIONAL_LIGHTING) highp vec4 getShadowPosition(const int cascade) { return getCascadeLightSpacePosition(cascade); } #endif #if defined(VARIANT_HAS_DYNAMIC_LIGHTING) highp vec4 getShadowPosition(const int index, const highp vec3 dir, const highp float zLight) { return getSpotLightSpacePosition(index, dir, zLight); } #endif int getPointLightFace(const highp vec3 r) { highp vec4 tc; highp float rx = abs(r.x); highp float ry = abs(r.y); highp float rz = abs(r.z); highp float d = max(rx, max(ry, rz)); if (d == rx) { return (r.x >= 0.0 ? 0 : 1); } else if (d == ry) { return (r.y >= 0.0 ? 2 : 3); } else { return (r.z >= 0.0 ? 4 : 5); } } #if defined(MATERIAL_HAS_SHADOW_STRENGTH) void applyShadowStrength(inout float visibility, float strength) { visibility = 1.0 - (1.0 - visibility) * strength; } #endif float shadow(const bool DIRECTIONAL, const mediump sampler2DArrayShadow shadowMap, const int index, highp vec4 shadowPosition, highp float zLight) { highp vec4 scissorNormalized = shadowUniforms.shadows[index].scissorNormalized; uint layer = shadowUniforms.shadows[index].layer; if (CONFIG_SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HARD) { return ShadowSample_PCF_Hard(shadowMap, scissorNormalized, layer, shadowPosition); } else if (CONFIG_SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW) { return ShadowSample_PCF_Low(shadowMap, scissorNormalized, layer, shadowPosition); } return 0.0; } float shadow(const bool DIRECTIONAL, const highp sampler2DArray shadowMap, const int index, highp vec4 shadowPosition, highp float zLight) { highp vec4 scissorNormalized = shadowUniforms.shadows[index].scissorNormalized; uint layer = shadowUniforms.shadows[index].layer; if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_EVSM) { return ShadowSample_VSM(DIRECTIONAL, shadowMap, scissorNormalized, layer, index, shadowPosition, zLight); } if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_DPCF) { return ShadowSample_DPCF(DIRECTIONAL, shadowMap, scissorNormalized, layer, index, shadowPosition, zLight); } if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_PCSS) { return ShadowSample_PCSS(DIRECTIONAL, shadowMap, scissorNormalized, layer, index, shadowPosition, zLight); } if (frameUniforms.shadowSamplingType == SHADOW_SAMPLING_RUNTIME_PCF) { return ShadowSample_PCF(shadowMap, scissorNormalized, layer, shadowPosition); } return 0.0; }