255 lines
10 KiB
GLSL
255 lines
10 KiB
GLSL
//------------------------------------------------------------------------------
|
|
// Shadowing configuration
|
|
//------------------------------------------------------------------------------
|
|
|
|
#define SHADOW_SAMPLING_PCF_HARD 0
|
|
#define SHADOW_SAMPLING_PCF_LOW 1
|
|
#define SHADOW_SAMPLING_PCF_MEDIUM 2
|
|
#define SHADOW_SAMPLING_PCF_HIGH 3
|
|
|
|
#define SHADOW_SAMPLING_ERROR_DISABLED 0
|
|
#define SHADOW_SAMPLING_ERROR_ENABLED 1
|
|
|
|
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED 0
|
|
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS_ENABLED 1
|
|
|
|
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS_MIN_SAMPLING_METHOD SHADOW_SAMPLING_PCF_MEDIUM
|
|
|
|
#ifdef TARGET_MOBILE
|
|
#define SHADOW_SAMPLING_METHOD SHADOW_SAMPLING_PCF_LOW
|
|
#define SHADOW_SAMPLING_ERROR SHADOW_SAMPLING_ERROR_DISABLED
|
|
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED
|
|
#else
|
|
#define SHADOW_SAMPLING_METHOD SHADOW_SAMPLING_PCF_LOW
|
|
#define SHADOW_SAMPLING_ERROR SHADOW_SAMPLING_ERROR_DISABLED
|
|
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED
|
|
#endif
|
|
|
|
#if SHADOW_SAMPLING_ERROR == SHADOW_SAMPLING_ERROR_ENABLED
|
|
#undef SHADOW_RECEIVER_PLANE_DEPTH_BIAS
|
|
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_ENABLED
|
|
#elif SHADOW_SAMPLING_METHOD < SHADOW_RECEIVER_PLANE_DEPTH_BIAS_MIN_SAMPLING_METHOD
|
|
#undef SHADOW_RECEIVER_PLANE_DEPTH_BIAS
|
|
#define SHADOW_RECEIVER_PLANE_DEPTH_BIAS SHADOW_RECEIVER_PLANE_DEPTH_BIAS_DISABLED
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Shadow sampling methods
|
|
//------------------------------------------------------------------------------
|
|
|
|
vec2 computeReceiverPlaneDepthBias(const vec3 position) {
|
|
// see: GDC '06: Shadow Mapping: GPU-based Tips and Techniques
|
|
vec2 bias;
|
|
#if SHADOW_RECEIVER_PLANE_DEPTH_BIAS == SHADOW_RECEIVER_PLANE_DEPTH_BIAS_ENABLED
|
|
vec3 du = dFdx(position);
|
|
vec3 dv = dFdy(position);
|
|
|
|
// Chain rule we use:
|
|
// | du.x du.y |^-T | dv.y -du.y |T | dv.y -dv.x |
|
|
// D * | dv.x dv.y | = | -dv.x du.x | = | -du.y du.x |
|
|
|
|
bias = inverse(mat2(du.xy, dv.xy)) * vec2(du.z, dv.z);
|
|
#else
|
|
bias = vec2(0.0);
|
|
#endif
|
|
return bias;
|
|
}
|
|
|
|
float samplingBias(float depth, const vec2 rpdb, const vec2 texelSize) {
|
|
#if SHADOW_SAMPLING_ERROR == SHADOW_SAMPLING_ERROR_ENABLED
|
|
// note: if filtering is set to NEAREST, the 2.0 factor below can be changed to 1.0
|
|
float samplingError = min(2.0 * dot(texelSize, abs(rpdb)), 0.01);
|
|
depth -= samplingError;
|
|
#endif
|
|
return depth;
|
|
}
|
|
|
|
float sampleDepth(const lowp sampler2DShadow map, vec2 base, vec2 dudv, float depth, vec2 rpdb) {
|
|
#if SHADOW_RECEIVER_PLANE_DEPTH_BIAS == SHADOW_RECEIVER_PLANE_DEPTH_BIAS_ENABLED
|
|
#if SHADOW_SAMPLING_METHOD >= SHADOW_RECEIVER_PLANE_DEPTH_BIAS_MIN_SAMPLING_METHOD
|
|
depth += dot(dudv, rpdb);
|
|
#endif
|
|
#endif
|
|
// depth must be clamped to support floating-point depth formats. This is to avoid comparing a
|
|
// value from the depth texture (which is never greater than 1.0) with a greater-than-one
|
|
// comparison value (which is possible with floating-point formats).
|
|
return texture(map, vec3(base + dudv, clamp(depth, 0.0, 1.0)));
|
|
}
|
|
|
|
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HARD
|
|
float ShadowSample_Hard(const lowp sampler2DShadow map, const vec2 size, const vec3 position) {
|
|
vec2 rpdb = computeReceiverPlaneDepthBias(position);
|
|
float depth = samplingBias(position.z, rpdb, vec2(1.0) / size);
|
|
return texture(map, vec3(position.xy, clamp(depth, 0.0, 1.0)));
|
|
}
|
|
#endif
|
|
|
|
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW
|
|
float ShadowSample_PCF_Low(const lowp sampler2DShadow map, const vec2 size, vec3 position) {
|
|
// Castaño, 2013, "Shadow Mapping Summary Part 1"
|
|
vec2 texelSize = vec2(1.0) / size;
|
|
|
|
// clamp position to avoid overflows below, which cause some GPUs to abort
|
|
position.xy = clamp(position.xy, vec2(-1.0), vec2(2.0));
|
|
|
|
vec2 offset = vec2(0.5);
|
|
vec2 uv = (position.xy * size) + offset;
|
|
vec2 base = (floor(uv) - offset) * texelSize;
|
|
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);
|
|
|
|
vec2 u = vec2((2.0 - st.x) / uw.x - 1.0, st.x / uw.y + 1.0);
|
|
vec2 v = vec2((2.0 - st.y) / vw.x - 1.0, st.y / vw.y + 1.0);
|
|
|
|
u *= texelSize.x;
|
|
v *= texelSize.y;
|
|
|
|
vec2 rpdb = computeReceiverPlaneDepthBias(position);
|
|
|
|
float depth = samplingBias(position.z, rpdb, texelSize);
|
|
float sum = 0.0;
|
|
|
|
sum += uw.x * vw.x * sampleDepth(map, base, vec2(u.x, v.x), depth, rpdb);
|
|
sum += uw.y * vw.x * sampleDepth(map, base, vec2(u.y, v.x), depth, rpdb);
|
|
|
|
sum += uw.x * vw.y * sampleDepth(map, base, vec2(u.x, v.y), depth, rpdb);
|
|
sum += uw.y * vw.y * sampleDepth(map, base, vec2(u.y, v.y), depth, rpdb);
|
|
|
|
return sum * (1.0 / 16.0);
|
|
}
|
|
#endif
|
|
|
|
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_MEDIUM
|
|
float ShadowSample_PCF_Medium(const lowp sampler2DShadow map, const vec2 size, vec3 position) {
|
|
// Castaño, 2013, "Shadow Mapping Summary Part 1"
|
|
vec2 texelSize = vec2(1.0) / size;
|
|
|
|
// clamp position to avoid overflows below, which cause some GPUs to abort
|
|
position.xy = clamp(position.xy, vec2(-1.0), vec2(2.0));
|
|
|
|
vec2 offset = vec2(0.5);
|
|
vec2 uv = (position.xy * size) + offset;
|
|
vec2 base = (floor(uv) - offset) * texelSize;
|
|
vec2 st = fract(uv);
|
|
|
|
vec3 uw = vec3(4.0 - 3.0 * st.x, 7.0, 1.0 + 3.0 * st.x);
|
|
vec3 vw = vec3(4.0 - 3.0 * st.y, 7.0, 1.0 + 3.0 * st.y);
|
|
|
|
vec3 u = vec3((3.0 - 2.0 * st.x) / uw.x - 2.0, (3.0 + st.x) / uw.y, st.x / uw.z + 2.0);
|
|
vec3 v = vec3((3.0 - 2.0 * st.y) / vw.x - 2.0, (3.0 + st.y) / vw.y, st.y / vw.z + 2.0);
|
|
|
|
u *= texelSize.x;
|
|
v *= texelSize.y;
|
|
|
|
vec2 rpdb = computeReceiverPlaneDepthBias(position);
|
|
|
|
float depth = samplingBias(position.z, rpdb, texelSize);
|
|
float sum = 0.0;
|
|
|
|
sum += uw.x * vw.x * sampleDepth(map, base, vec2(u.x, v.x), depth, rpdb);
|
|
sum += uw.y * vw.x * sampleDepth(map, base, vec2(u.y, v.x), depth, rpdb);
|
|
sum += uw.z * vw.x * sampleDepth(map, base, vec2(u.z, v.x), depth, rpdb);
|
|
|
|
sum += uw.x * vw.y * sampleDepth(map, base, vec2(u.x, v.y), depth, rpdb);
|
|
sum += uw.y * vw.y * sampleDepth(map, base, vec2(u.y, v.y), depth, rpdb);
|
|
sum += uw.z * vw.y * sampleDepth(map, base, vec2(u.z, v.y), depth, rpdb);
|
|
|
|
sum += uw.x * vw.z * sampleDepth(map, base, vec2(u.x, v.z), depth, rpdb);
|
|
sum += uw.y * vw.z * sampleDepth(map, base, vec2(u.y, v.z), depth, rpdb);
|
|
sum += uw.z * vw.z * sampleDepth(map, base, vec2(u.z, v.z), depth, rpdb);
|
|
|
|
return sum * (1.0 / 144.0);
|
|
}
|
|
#endif
|
|
|
|
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HIGH
|
|
float ShadowSample_PCF_High(const lowp sampler2DShadow map, const vec2 size, vec3 position) {
|
|
// Castaño, 2013, "Shadow Mapping Summary Part 1"
|
|
vec2 texelSize = vec2(1.0) / size;
|
|
|
|
// clamp position to avoid overflows below, which cause some GPUs to abort
|
|
position.xy = clamp(position.xy, vec2(-1.0), vec2(2.0));
|
|
|
|
vec2 offset = vec2(0.5);
|
|
vec2 uv = (position.xy * size) + offset;
|
|
vec2 base = (floor(uv) - offset) * texelSize;
|
|
vec2 st = fract(uv);
|
|
|
|
vec4 uw = vec4(
|
|
5.0 * st.x - 6.0,
|
|
11.0 * st.x - 28.0,
|
|
-(11.0 * st.x + 17.0),
|
|
-(5.0 * st.x + 1.0));
|
|
vec4 vw = vec4(
|
|
5.0 * st.y - 6.0,
|
|
11.0 * st.y - 28.0,
|
|
-(11.0 * st.y + 17.0),
|
|
-(5.0 * st.y + 1.0));
|
|
|
|
vec4 u = vec4(
|
|
(4.0 * st.x - 5.0) / uw.x - 3.0,
|
|
(4.0 * st.x - 16.0) / uw.y - 1.0,
|
|
-(7.0 * st.x + 5.0) / uw.z + 1.0,
|
|
-st.x / uw.w + 3.0);
|
|
vec4 v = vec4(
|
|
(4.0 * st.y - 5.0) / vw.x - 3.0,
|
|
(4.0 * st.y - 16.0) / vw.y - 1.0,
|
|
-(7.0 * st.y + 5.0) / vw.z + 1.0,
|
|
-st.y / vw.w + 3.0);
|
|
|
|
u *= texelSize.x;
|
|
v *= texelSize.y;
|
|
|
|
vec2 rpdb = computeReceiverPlaneDepthBias(position);
|
|
|
|
float depth = samplingBias(position.z, rpdb, texelSize);
|
|
float sum = 0.0;
|
|
|
|
sum += uw.x * vw.x * sampleDepth(map, base, vec2(u.x, v.x), depth, rpdb);
|
|
sum += uw.y * vw.x * sampleDepth(map, base, vec2(u.y, v.x), depth, rpdb);
|
|
sum += uw.z * vw.x * sampleDepth(map, base, vec2(u.z, v.x), depth, rpdb);
|
|
sum += uw.w * vw.x * sampleDepth(map, base, vec2(u.w, v.x), depth, rpdb);
|
|
|
|
sum += uw.x * vw.y * sampleDepth(map, base, vec2(u.x, v.y), depth, rpdb);
|
|
sum += uw.y * vw.y * sampleDepth(map, base, vec2(u.y, v.y), depth, rpdb);
|
|
sum += uw.z * vw.y * sampleDepth(map, base, vec2(u.z, v.y), depth, rpdb);
|
|
sum += uw.w * vw.y * sampleDepth(map, base, vec2(u.w, v.y), depth, rpdb);
|
|
|
|
sum += uw.x * vw.z * sampleDepth(map, base, vec2(u.x, v.z), depth, rpdb);
|
|
sum += uw.y * vw.z * sampleDepth(map, base, vec2(u.y, v.z), depth, rpdb);
|
|
sum += uw.z * vw.z * sampleDepth(map, base, vec2(u.z, v.z), depth, rpdb);
|
|
sum += uw.w * vw.z * sampleDepth(map, base, vec2(u.w, v.z), depth, rpdb);
|
|
|
|
sum += uw.x * vw.w * sampleDepth(map, base, vec2(u.x, v.w), depth, rpdb);
|
|
sum += uw.y * vw.w * sampleDepth(map, base, vec2(u.y, v.w), depth, rpdb);
|
|
sum += uw.z * vw.w * sampleDepth(map, base, vec2(u.z, v.w), depth, rpdb);
|
|
sum += uw.w * vw.w * sampleDepth(map, base, vec2(u.w, v.w), depth, rpdb);
|
|
|
|
return sum * (1.0 / 2704.0);
|
|
}
|
|
#endif
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Shadow sampling dispatch
|
|
//------------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Samples the light visibility at the specified position in light (shadow)
|
|
* space. The output is a filtered visibility factor that can be used to multiply
|
|
* the light intensity.
|
|
*/
|
|
float shadow(const lowp sampler2DShadow shadowMap, const vec3 shadowPosition) {
|
|
vec2 size = vec2(textureSize(shadowMap, 0));
|
|
#if SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HARD
|
|
return ShadowSample_Hard(shadowMap, size, shadowPosition);
|
|
#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_LOW
|
|
return ShadowSample_PCF_Low(shadowMap, size, shadowPosition);
|
|
#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_MEDIUM
|
|
return ShadowSample_PCF_Medium(shadowMap, size, shadowPosition);
|
|
#elif SHADOW_SAMPLING_METHOD == SHADOW_SAMPLING_PCF_HIGH
|
|
return ShadowSample_PCF_High(shadowMap, size, shadowPosition);
|
|
#endif
|
|
}
|