/***************************************************************************** The Dark Mod GPL Source Code This file is part of the The Dark Mod Source Code, originally based on the Doom 3 GPL Source Code as published in 2011. The Dark Mod Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. For details, see LICENSE.TXT. Project: The Dark Mod (http://www.thedarkmod.com/) ******************************************************************************/ #version 140 #pragma tdm_include "tdm_interaction.fs.glsl" uniform bool u_shadows; uniform int u_softShadowsQuality; uniform float u_softShadowsRadius; uniform vec2 u_renderResolution; uniform vec2 u_softShadowsSamples[150]; uniform usampler2D u_stencilTexture; uniform sampler2D u_depthTexture; in vec3 var_WorldLightDir; out vec4 FragColor; //returns eye Z coordinate with reversed sign (monotonically increasing with depth) float depthToZ(float depth) { float clipZ = 2.0 * depth - 1.0; float A = u_projectionMatrix[2].z; float B = u_projectionMatrix[3].z; return B / (A + clipZ); } void StencilSoftShadow() { vec2 texSize = u_renderResolution; vec2 pixSize = vec2(1.0, 1.0) / texSize; vec2 baseTC = gl_FragCoord.xy * pixSize; float StLevel = 129.0; float StTex = float(texture( u_stencilTexture, baseTC ).r); float stencil = clamp( StLevel - StTex, 0., 1.); float sumWeight = 1.; float LightDist = min(length(lightDir), 1e3); // crutch ! //radius of light source float lightRadius = u_softShadowsRadius; //radius of one-point penumbra at the consided point (in world coordinates) //note that proper formula is: lightRadius * (LightDist - OcclDist) / OcclDist; float blurRadiusWorld = lightRadius * LightDist / 66.6666; //TODO: revert?! //project direction to light onto surface vec3 normal = var_TangentBitangentNormalMatrix[2]; vec3 alongDirW = normalize(lightDir - dot(lightDir, normal) * normal); //get orthogonal direction on surface vec3 orthoDirW = cross(normal, alongDirW); //multiply the two axes by penumbra radius alongDirW *= blurRadiusWorld / max(NdotL, 0.2); //penumbra is longer by (1/cos(a)) in light direction orthoDirW *= blurRadiusWorld; //convert both vectors into clip space (get only X and Y components) mat4 modelViewProjectionMatrix = u_projectionMatrix*u_modelViewMatrix; vec2 alongDir = (mat3(modelViewProjectionMatrix) * alongDirW).xy; vec2 orthoDir = (mat3(modelViewProjectionMatrix) * orthoDirW).xy; //now also get W component from multiplication by gl_ModelViewProjectionMatrix vec3 mvpRow3 = vec3(modelViewProjectionMatrix[0][3], modelViewProjectionMatrix[1][3], modelViewProjectionMatrix[2][3]); float along_w = dot(mvpRow3, alongDirW); float ortho_w = dot(mvpRow3, orthoDirW); //this is perspective correction: it is necessary because W component in clip space also varies //if you remove it and look horizontally parallel to a wall, then vertical shadow boundaries on this wall won't be blurred vec2 thisNdc = (2 * baseTC - vec2(1)); alongDir -= thisNdc * along_w; orthoDir -= thisNdc * ortho_w; //divide by clip W to get NDC coords (screen coords are half of them) alongDir *= gl_FragCoord.w / 2; orthoDir *= gl_FragCoord.w / 2; //Note: if you want to check the math just above, consider how screen position changes when a point moves in specified direction: // F(t) = divideByW(gl_ModelViewProjectionMatrix * (var_Position + dir_world * t)).xy //the converted vector must be equal to the derivative by parameter: // dir_screen = dF/dt (0) //(here [dir_world, dir_screen] are either [alongDirW, alongDir] or [orthoDirW, orthoDir]) //estimate the length of spot ellipse vectors (in pixels) float lenX = length(alongDir * texSize); float lenY = length(orthoDir * texSize); //make sure vectors are sufficiently sampled float avgSampleDistInPixels = 2 * max(1e-3 * texSize.y, 1.0); float oversize = max(lenX, lenY) / (avgSampleDistInPixels * sqrt(0.0 + u_softShadowsQuality)); if (oversize > 1) { alongDir /= oversize; orthoDir /= oversize; } //compute partial derivatives of eye -Z by screen X and Y (normalized) float Z00 = depthToZ(gl_FragCoord.z); vec2 dzdxy = vec2(dFdx(Z00), dFdy(Z00)); //rescale to derivatives by texture coordinates (not pixels) dzdxy *= texSize; //compute Z derivatives on a theoretical wall visible under 45-degree angle vec2 tanFovHalf = vec2(1.0 / u_projectionMatrix[0][0], 1.0 / u_projectionMatrix[1][1]); vec2 canonDerivs = 2.0 * Z00 * tanFovHalf; for( int i = 0; i < u_softShadowsQuality; i++ ) { vec2 delta = u_softShadowsSamples[i].x * alongDir + u_softShadowsSamples[i].y * orthoDir; vec2 StTc = baseTC + delta; float Zdiff = depthToZ(texture(u_depthTexture, StTc).r) - Z00; float tangentZdiff = dot(dzdxy, delta); float deg45diff = dot(canonDerivs, abs(delta)); float weight = float(abs(Zdiff - tangentZdiff) <= abs(tangentZdiff) * 0.5 + deg45diff * 0.2); float StTex = float(texture( u_stencilTexture, StTc ).r); stencil += clamp( StLevel - StTex, 0., 1. ) * weight; sumWeight += weight; } FragColor.rgb *= stencil / sumWeight; /* //for debug only: vec2 StTc = baseTC + vec2(1, 0) * 1e-2; StTex = texture( u_stencilTexture, StTc ).r; stencil = .25*(128. - StTex); FragColor.rgb = vec3(stencil, -stencil, stencil==0?.3:0); */ } void main() { fetchDNS(); FragColor.rgb = computeInteraction(); if (u_shadows && u_softShadowsQuality > 0) StencilSoftShadow(); FragColor.a = 1.0; }