#version 400 // vertex shader output in vec4 var_Position; in vec4 var_PositionWorld; in vec2 var_TexDiffuse; in vec2 var_TexNormal; in vec2 var_TexSpecular; in mat3 var_TangentBitangentNormalMatrix; in vec4 var_Color; out vec4 FragColor; //uniform mat4 u_modelMatrix; uniform float u_minLevel, u_gamma; // lights const int MAX_LIGHTS = 16; #define ShadowExperiment true uniform int u_lightCount; uniform float u_softShadowsRadius[MAX_LIGHTS]; uniform vec3 u_lightOrigin[MAX_LIGHTS]; uniform vec3 u_lightColor[MAX_LIGHTS]; uniform mat4 u_lightProjectionFalloff[MAX_LIGHTS]; uniform vec4 u_shadowRect[MAX_LIGHTS]; // textures uniform sampler2D u_diffuseTexture; uniform sampler2D u_normalTexture; uniform sampler2D u_shadowMap; // compute normal from normal map, move from [0, 1] to [-1, 1] range, normalize vec4 diffuse = texture( u_diffuseTexture, var_TexDiffuse ); vec4 bumpTexel = texture ( u_normalTexture, var_TexNormal ) * 2. - 1.; vec3 RawN = normalize( bumpTexel.wyz ); vec3 N = var_TangentBitangentNormalMatrix * RawN; //float NdotH = clamp( dot( N, H ), 0.0, 1.0 ); vec3 CubeMapDirectionToUv(vec3 v, out int faceIdx) { vec3 v1 = abs(v); float maxV = max(v1.x, max(v1.y, v1.z)); faceIdx = 0; if(maxV == v.x) { v1 = -v.zyx; } else if(maxV == -v.x) { v1 = v.zyx * vec3(1, -1, 1); faceIdx = 1; } else if(maxV == v.y) { v1 = v.xzy * vec3(1, 1, -1); faceIdx = 2; } else if(maxV == -v.y) { v1 = v.xzy * vec3(1, -1, 1); faceIdx = 3; } else if(maxV == v.z) { v1 = v.xyz * vec3(1, -1, -1); faceIdx = 4; } else { //if(maxV == -v.z) { v1 = v.xyz * vec3(-1, -1, 1); faceIdx = 5; } v1.xy /= -v1.z; return v1; } vec2 ShadowTexSize = textureSize(u_shadowMap, 0); vec2 ShadowTexelStep = 1/ShadowTexSize; vec4 ShadowAtlasForVector4(vec3 v, int i, out vec4 sampleWeights) { int faceIdx; vec3 v1 = CubeMapDirectionToUv(v, faceIdx); vec2 shadow2d = (v1.xy * .5 + .5 ) * u_shadowRect[i].ww + u_shadowRect[i].xy; shadow2d.x += (u_shadowRect[i].w) * faceIdx; // u_shadowRect[i].w is passed as width minus one - need to add one to get the original width value vec4 d = textureGather(u_shadowMap, shadow2d); vec2 wgt = fract(shadow2d * ShadowTexSize - 0.5); vec2 mwgt = vec2(1) - wgt; sampleWeights = vec4(mwgt.x, wgt.x, wgt.x, mwgt.x) * vec4(wgt.y, wgt.y, mwgt.y, mwgt.y); vec4 blocker4 = u_softShadowsRadius[i] / (1 - d); return blocker4; } vec3 Barycentric(vec2 p, int tri) { // https://gamedev.stackexchange.com/questions/23743/whats-the-most-efficient-way-to-find-barycentric-coordinates vec2 t[4] = vec2[4]( vec2(0,0), vec2(0,1), vec2(1,0), vec2(1,1) ); vec2 a = t[tri+0]; vec2 b = t[tri+1]; vec2 c = t[tri+2]; vec2 v0 = b - a, v1 = c - a, v2 = p - a; float den = v0.x * v1.y - v1.x * v0.y; float v = (v2.x * v1.y - v1.x * v2.y) / den; float w = (v0.x * v2.y - v2.x * v0.y) / den; float u = 1.0f - v - w; return vec3(u,v,w); } vec3 debugColor; float OutsideShadowVolume(vec3 v, int i) { int faceIdx; vec3 v1 = CubeMapDirectionToUv(v, faceIdx); // v1.xy += sin(gl_FragCoord.xy)*1e-3; vec2 shadow2d = (v1.xy * .5 + .5 ) * u_shadowRect[i].ww + u_shadowRect[i].xy; shadow2d.x += (u_shadowRect[i].w + ShadowTexelStep.x) * faceIdx; // u_shadowRect[i].w is passed as width minus one - need to add one to get the original width value vec4 d = textureGather(u_shadowMap, shadow2d); vec4 blockerZ4 = u_softShadowsRadius[i] / (1 - d); float testZ = -v1.z; vec2 texels = u_shadowRect[i].w * ShadowTexSize; vec2 x0y0 = v1.xy * .5 + .5; vec2 wgt = fract(x0y0 * texels - 0.0); vec2 mwgt = vec2(1) - wgt; vec4 sampleWeights = vec4(mwgt.x, wgt.x, wgt.x, mwgt.x) * vec4(wgt.y, wgt.y, mwgt.y, mwgt.y); #ifdef ALGO_TOGGLE float localZ = dot(blockerZ4, sampleWeights); if(isinf(localZ)) localZ = min(min(blockerZ4[0], blockerZ4[1]),min(blockerZ4[2],blockerZ4[3])); #else int tri = (wgt.y + wgt.x < 1) ? 0 : 1; vec3 bariCoord = Barycentric(wgt, tri); vec3 blockerZ3 = tri == 0 ? blockerZ4.wxz : blockerZ4.xzy; float localZ = dot(blockerZ4, sampleWeights); localZ = dot(blockerZ3, bariCoord); if(isinf(localZ)) localZ = min(min(blockerZ3[0], blockerZ3[1]),blockerZ3[2]); #endif debugColor = sampleWeights.xyz; // return clamp((localZ - testZ)*1e-1, 0, 1); return float(localZ > testZ + 0e-0); } float Unshadowed(vec3 lightDir, int i) { // 0 - fully occluded, 1 - fully lit //lightDir = (u_modelMatrix*vec4(lightDir, 0)).xyz; vec3 L = normalize( -lightDir ); L = ( -lightDir ); #ifdef ShadowExperiment return OutsideShadowVolume(L, i); #endif vec3 absL = abs(lightDir); float maxAbsL = max(absL.x, max(absL.y, absL.z)); float shadowMapResolution = (textureSize(u_shadowMap, 0).x * u_shadowRect[i].w); vec3 normal = /*mat3(u_modelMatrix) */ var_TangentBitangentNormalMatrix[2]; float lightFallAngle = -dot(normal, L); float errorMargin = 2.0 * maxAbsL / ( shadowMapResolution * max(lightFallAngle, 0.1) ); //process central shadow sample float centerFragZ = maxAbsL; vec4 sampleWeights; vec4 blockerZ = ShadowAtlasForVector4(L, i, sampleWeights); vec4 lit4 = step(centerFragZ - errorMargin, blockerZ); float lit = dot(sampleWeights, lit4); return lit; } float ShadowStage(int i) { vec3 lightDir = u_lightOrigin[i] - var_Position.xyz; return Unshadowed(lightDir, i); } vec3 PointLight(int i) { vec3 lightDir = u_lightOrigin[i] - var_Position.xyz; vec3 L = normalize( lightDir ); float NdotL = dot( N, L ); float thisLit = NdotL; vec4 lightTC = var_Position * u_lightProjectionFalloff[i]; { // quadratic falloff/projection texture replacement vec3 pt = lightTC.xyw - vec3(0.5); float dist2 = dot(pt, pt); thisLit *= max(0.25 - dist2, 0) * 4; } if(thisLit <= 0) return vec3(0); float sm = u_shadowRect[i].z; if(sm >= 0) thisLit *= ShadowStage(i); return thisLit * u_lightColor[i]; } vec3 AmbientLight(int i) { vec3 thisLit = u_lightColor[i]; // light boundary cut off vec4 lightTC = var_Position * u_lightProjectionFalloff[i]; vec4 cutOff = 1 - step(0.5, abs(0.5 - lightTC)); //thisLit *= cutOff.x * cutOff.y * cutOff.z; thisLit *= cutOff.x * cutOff.y; return thisLit; } void main() { vec3 lit = vec3(0); for(int i=0; i < u_lightCount; i++) { float sm = u_shadowRect[i].z; if(sm == -2) lit += AmbientLight(i); else lit += PointLight(i); //FragColor.rgb = lit; return; } // local gamma replacement if (u_minLevel != 0 ) // home-brewed "pretty" linear lit = lit * (1.0 - u_minLevel) + vec3(u_minLevel); FragColor.rgb = lit * diffuse.rgb * var_Color.rgb; if(u_gamma != 1 ) // old-school exponential FragColor.rgb = pow(FragColor.rgb, vec3(1.0 / u_gamma)); //FragColor.rgb = mix(FragColor.rgb, debugColor, .7); FragColor.a = diffuse.a; }