mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-28 06:53:58 +00:00
99c8ba1288
Unlike uObjectColor2, this is more global state. uObjectColor2 is part of gradient calculation which may later be offloaded to a secondary buffer which already resulted in only conditionally setting it, resulting in broken special colormap application for the software renderer.
602 lines
14 KiB
GLSL
602 lines
14 KiB
GLSL
in vec4 pixelpos;
|
|
in vec3 glowdist;
|
|
in vec3 gradientdist;
|
|
|
|
in vec4 vWorldNormal;
|
|
in vec4 vEyeNormal;
|
|
in vec4 vTexCoord;
|
|
in vec4 vColor;
|
|
|
|
layout(location=0) out vec4 FragColor;
|
|
#ifdef GBUFFER_PASS
|
|
layout(location=1) out vec4 FragFog;
|
|
layout(location=2) out vec4 FragNormal;
|
|
#endif
|
|
|
|
struct Material
|
|
{
|
|
vec4 Base;
|
|
vec4 Bright;
|
|
vec3 Normal;
|
|
vec3 Specular;
|
|
float Glossiness;
|
|
float SpecularLevel;
|
|
float Metallic;
|
|
float Roughness;
|
|
float AO;
|
|
};
|
|
|
|
vec4 Process(vec4 color);
|
|
vec4 ProcessTexel();
|
|
Material ProcessMaterial();
|
|
vec4 ProcessLight(Material mat, vec4 color);
|
|
vec3 ProcessMaterialLight(Material material, vec3 color);
|
|
|
|
//===========================================================================
|
|
//
|
|
// Color to grayscale
|
|
//
|
|
//===========================================================================
|
|
|
|
float grayscale(vec4 color)
|
|
{
|
|
return dot(color.rgb, vec3(0.3, 0.56, 0.14));
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Desaturate a color
|
|
//
|
|
//===========================================================================
|
|
|
|
vec4 desaturate(vec4 texel)
|
|
{
|
|
if (uDesaturationFactor > 0.0)
|
|
{
|
|
float gray = grayscale(texel);
|
|
return mix (texel, vec4(gray,gray,gray,texel.a), uDesaturationFactor);
|
|
}
|
|
else
|
|
{
|
|
return texel;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// This function is common for all (non-special-effect) fragment shaders
|
|
//
|
|
//===========================================================================
|
|
|
|
vec4 getTexel(vec2 st)
|
|
{
|
|
vec4 texel = texture(tex, st);
|
|
|
|
//
|
|
// Apply texture modes
|
|
//
|
|
switch (uTextureMode)
|
|
{
|
|
case 1: // TM_STENCIL
|
|
texel.rgb = vec3(1.0,1.0,1.0);
|
|
break;
|
|
|
|
case 2: // TM_OPAQUE
|
|
texel.a = 1.0;
|
|
break;
|
|
|
|
case 3: // TM_INVERSE
|
|
texel = vec4(1.0-texel.r, 1.0-texel.b, 1.0-texel.g, texel.a);
|
|
break;
|
|
|
|
case 4: // TM_ALPHATEXTURE
|
|
{
|
|
float gray = grayscale(texel);
|
|
texel = vec4(1.0, 1.0, 1.0, gray*texel.a);
|
|
break;
|
|
}
|
|
|
|
case 5: // TM_CLAMPY
|
|
if (st.t < 0.0 || st.t > 1.0)
|
|
{
|
|
texel.a = 0.0;
|
|
}
|
|
break;
|
|
|
|
case 6: // TM_OPAQUEINVERSE
|
|
texel = vec4(1.0-texel.r, 1.0-texel.b, 1.0-texel.g, 1.0);
|
|
break;
|
|
|
|
case 7: //TM_FOGLAYER
|
|
return texel;
|
|
|
|
}
|
|
|
|
texel.rgb += uAddColor.rgb;
|
|
|
|
if (uObjectColor2.a == 0.0) texel *= uObjectColor;
|
|
else texel *= mix(uObjectColor, uObjectColor2, gradientdist.z);
|
|
|
|
return desaturate(texel);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Vanilla Doom wall colormap equation
|
|
//
|
|
//===========================================================================
|
|
float R_WallColormap(float lightnum, float z, vec3 normal)
|
|
{
|
|
// R_ScaleFromGlobalAngle calculation
|
|
float projection = 160.0; // projection depends on SCREENBLOCKS!! 160 is the fullscreen value
|
|
vec2 line_v1 = pixelpos.xz; // in vanilla this is the first curline vertex
|
|
vec2 line_normal = normal.xz;
|
|
float texscale = projection * clamp(dot(normalize(uCameraPos.xz - line_v1), line_normal), 0.0, 1.0) / z;
|
|
|
|
float lightz = clamp(16.0 * texscale, 0.0, 47.0);
|
|
|
|
// scalelight[lightnum][lightz] lookup
|
|
float startmap = (15.0 - lightnum) * 4.0;
|
|
return startmap - lightz * 0.5;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Vanilla Doom plane colormap equation
|
|
//
|
|
//===========================================================================
|
|
float R_PlaneColormap(float lightnum, float z)
|
|
{
|
|
float lightz = clamp(z / 16.0f, 0.0, 127.0);
|
|
|
|
// zlight[lightnum][lightz] lookup
|
|
float startmap = (15.0 - lightnum) * 4.0;
|
|
float scale = 160.0 / (lightz + 1.0);
|
|
return startmap - scale * 0.5;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// zdoom colormap equation
|
|
//
|
|
//===========================================================================
|
|
float R_ZDoomColormap(float light, float z)
|
|
{
|
|
float L = light * 255.0;
|
|
float vis = min(uGlobVis / z, 24.0 / 32.0);
|
|
float shade = 2.0 - (L + 12.0) / 128.0;
|
|
float lightscale = shade - vis;
|
|
return lightscale * 31.0;
|
|
}
|
|
|
|
float R_DoomColormap(float light, float z)
|
|
{
|
|
if ((uPalLightLevels >> 16) == 16) // gl_lightmode 16
|
|
{
|
|
float lightnum = clamp(light * 15.0, 0.0, 15.0);
|
|
|
|
if (dot(vWorldNormal.xyz, vWorldNormal.xyz) > 0.5)
|
|
{
|
|
vec3 normal = normalize(vWorldNormal.xyz);
|
|
return mix(R_WallColormap(lightnum, z, normal), R_PlaneColormap(lightnum, z), abs(normal.y));
|
|
}
|
|
else // vWorldNormal is not set on sprites
|
|
{
|
|
return R_PlaneColormap(lightnum, z);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return R_ZDoomColormap(light, z);
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Doom software lighting equation
|
|
//
|
|
//===========================================================================
|
|
float R_DoomLightingEquation(float light)
|
|
{
|
|
// z is the depth in view space, positive going into the screen
|
|
float z;
|
|
if (((uPalLightLevels >> 8) & 0xff) == 2)
|
|
{
|
|
z = distance(pixelpos.xyz, uCameraPos.xyz);
|
|
}
|
|
else
|
|
{
|
|
z = pixelpos.w;
|
|
}
|
|
|
|
float colormap = R_DoomColormap(light, z);
|
|
|
|
if ((uPalLightLevels & 0xff) != 0)
|
|
colormap = floor(colormap) + 0.5;
|
|
|
|
// Result is the normalized colormap index (0 bright .. 1 dark)
|
|
return clamp(colormap, 0.0, 31.0) / 32.0;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Check if light is in shadow according to its 1D shadow map
|
|
//
|
|
//===========================================================================
|
|
|
|
#ifdef SUPPORTS_SHADOWMAPS
|
|
|
|
float shadowDirToU(vec2 dir)
|
|
{
|
|
if (abs(dir.y) > abs(dir.x))
|
|
{
|
|
float x = dir.x / dir.y * 0.125;
|
|
if (dir.y >= 0.0)
|
|
return 0.125 + x;
|
|
else
|
|
return (0.50 + 0.125) + x;
|
|
}
|
|
else
|
|
{
|
|
float y = dir.y / dir.x * 0.125;
|
|
if (dir.x >= 0.0)
|
|
return (0.25 + 0.125) - y;
|
|
else
|
|
return (0.75 + 0.125) - y;
|
|
}
|
|
}
|
|
|
|
vec2 shadowUToDir(float u)
|
|
{
|
|
u *= 4.0;
|
|
vec2 raydir;
|
|
switch (int(u))
|
|
{
|
|
case 0: raydir = vec2(u * 2.0 - 1.0, 1.0); break;
|
|
case 1: raydir = vec2(1.0, 1.0 - (u - 1.0) * 2.0); break;
|
|
case 2: raydir = vec2(1.0 - (u - 2.0) * 2.0, -1.0); break;
|
|
case 3: raydir = vec2(-1.0, (u - 3.0) * 2.0 - 1.0); break;
|
|
}
|
|
return raydir;
|
|
}
|
|
|
|
float sampleShadowmap(vec3 planePoint, float v)
|
|
{
|
|
float bias = 1.0;
|
|
float negD = dot(vWorldNormal.xyz, planePoint);
|
|
|
|
vec3 ray = planePoint;
|
|
|
|
vec2 isize = textureSize(ShadowMap, 0);
|
|
float scale = float(isize.x) * 0.25;
|
|
|
|
// Snap to shadow map texel grid
|
|
if (abs(ray.z) > abs(ray.x))
|
|
{
|
|
ray.y = ray.y / abs(ray.z);
|
|
ray.x = ray.x / abs(ray.z);
|
|
ray.x = (floor((ray.x + 1.0) * 0.5 * scale) + 0.5) / scale * 2.0 - 1.0;
|
|
ray.z = sign(ray.z);
|
|
}
|
|
else
|
|
{
|
|
ray.y = ray.y / abs(ray.x);
|
|
ray.z = ray.z / abs(ray.x);
|
|
ray.z = (floor((ray.z + 1.0) * 0.5 * scale) + 0.5) / scale * 2.0 - 1.0;
|
|
ray.x = sign(ray.x);
|
|
}
|
|
|
|
float t = negD / dot(vWorldNormal.xyz, ray) - bias;
|
|
vec2 dir = ray.xz * t;
|
|
|
|
float u = shadowDirToU(dir);
|
|
float dist2 = dot(dir, dir);
|
|
return step(dist2, texture(ShadowMap, vec2(u, v)).x);
|
|
}
|
|
|
|
float sampleShadowmapPCF(vec3 planePoint, float v)
|
|
{
|
|
float bias = 1.0;
|
|
float negD = dot(vWorldNormal.xyz, planePoint);
|
|
|
|
vec3 ray = planePoint;
|
|
|
|
if (abs(ray.z) > abs(ray.x))
|
|
ray.y = ray.y / abs(ray.z);
|
|
else
|
|
ray.y = ray.y / abs(ray.x);
|
|
|
|
vec2 isize = textureSize(ShadowMap, 0);
|
|
float scale = float(isize.x);
|
|
float texelPos = floor(shadowDirToU(ray.xz) * scale);
|
|
|
|
float sum = 0.0;
|
|
float step_count = uShadowmapFilter;
|
|
|
|
texelPos -= step_count + 0.5;
|
|
for (float x = -step_count; x <= step_count; x++)
|
|
{
|
|
float u = fract(texelPos / scale);
|
|
vec2 dir = shadowUToDir(u);
|
|
|
|
ray.x = dir.x;
|
|
ray.z = dir.y;
|
|
float t = negD / dot(vWorldNormal.xyz, ray) - bias;
|
|
dir = ray.xz * t;
|
|
|
|
float dist2 = dot(dir, dir);
|
|
sum += step(dist2, texture(ShadowMap, vec2(u, v)).x);
|
|
texelPos++;
|
|
}
|
|
return sum / (uShadowmapFilter * 2.0 + 1.0);
|
|
}
|
|
|
|
float shadowmapAttenuation(vec4 lightpos, float shadowIndex)
|
|
{
|
|
if (shadowIndex >= 1024.0)
|
|
return 1.0; // No shadowmap available for this light
|
|
|
|
vec3 planePoint = pixelpos.xyz - lightpos.xyz;
|
|
planePoint += 0.01; // nudge light position slightly as Doom maps tend to have their lights perfectly aligned with planes
|
|
|
|
if (dot(planePoint.xz, planePoint.xz) < 1.0)
|
|
return 1.0; // Light is too close
|
|
|
|
float v = (shadowIndex + 0.5) / 1024.0;
|
|
|
|
if (uShadowmapFilter <= 0)
|
|
{
|
|
return sampleShadowmap(planePoint, v);
|
|
}
|
|
else
|
|
{
|
|
return sampleShadowmapPCF(planePoint, v);
|
|
}
|
|
}
|
|
|
|
float shadowAttenuation(vec4 lightpos, float lightcolorA)
|
|
{
|
|
float shadowIndex = abs(lightcolorA) - 1.0;
|
|
return shadowmapAttenuation(lightpos, shadowIndex);
|
|
}
|
|
|
|
#else
|
|
|
|
float shadowAttenuation(vec4 lightpos, float lightcolorA)
|
|
{
|
|
return 1.0;
|
|
}
|
|
|
|
#endif
|
|
|
|
float spotLightAttenuation(vec4 lightpos, vec3 spotdir, float lightCosInnerAngle, float lightCosOuterAngle)
|
|
{
|
|
vec3 lightDirection = normalize(lightpos.xyz - pixelpos.xyz);
|
|
float cosDir = dot(lightDirection, spotdir);
|
|
return smoothstep(lightCosOuterAngle, lightCosInnerAngle, cosDir);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Adjust normal vector according to the normal map
|
|
//
|
|
//===========================================================================
|
|
|
|
#if defined(NORMALMAP)
|
|
mat3 cotangent_frame(vec3 n, vec3 p, vec2 uv)
|
|
{
|
|
// get edge vectors of the pixel triangle
|
|
vec3 dp1 = dFdx(p);
|
|
vec3 dp2 = dFdy(p);
|
|
vec2 duv1 = dFdx(uv);
|
|
vec2 duv2 = dFdy(uv);
|
|
|
|
// solve the linear system
|
|
vec3 dp2perp = cross(n, dp2); // cross(dp2, n);
|
|
vec3 dp1perp = cross(dp1, n); // cross(n, dp1);
|
|
vec3 t = dp2perp * duv1.x + dp1perp * duv2.x;
|
|
vec3 b = dp2perp * duv1.y + dp1perp * duv2.y;
|
|
|
|
// construct a scale-invariant frame
|
|
float invmax = inversesqrt(max(dot(t,t), dot(b,b)));
|
|
return mat3(t * invmax, b * invmax, n);
|
|
}
|
|
|
|
vec3 ApplyNormalMap(vec2 texcoord)
|
|
{
|
|
#define WITH_NORMALMAP_UNSIGNED
|
|
#define WITH_NORMALMAP_GREEN_UP
|
|
//#define WITH_NORMALMAP_2CHANNEL
|
|
|
|
vec3 interpolatedNormal = normalize(vWorldNormal.xyz);
|
|
|
|
vec3 map = texture(normaltexture, texcoord).xyz;
|
|
#if defined(WITH_NORMALMAP_UNSIGNED)
|
|
map = map * 255./127. - 128./127.; // Math so "odd" because 0.5 cannot be precisely described in an unsigned format
|
|
#endif
|
|
#if defined(WITH_NORMALMAP_2CHANNEL)
|
|
map.z = sqrt(1 - dot(map.xy, map.xy));
|
|
#endif
|
|
#if defined(WITH_NORMALMAP_GREEN_UP)
|
|
map.y = -map.y;
|
|
#endif
|
|
|
|
mat3 tbn = cotangent_frame(interpolatedNormal, pixelpos.xyz, vTexCoord.st);
|
|
vec3 bumpedNormal = normalize(tbn * map);
|
|
return bumpedNormal;
|
|
}
|
|
#else
|
|
vec3 ApplyNormalMap(vec2 texcoord)
|
|
{
|
|
return normalize(vWorldNormal.xyz);
|
|
}
|
|
#endif
|
|
|
|
//===========================================================================
|
|
//
|
|
// Calculate light
|
|
//
|
|
// It is important to note that the light color is not desaturated
|
|
// due to ZDoom's implementation weirdness. Everything that's added
|
|
// on top of it, e.g. dynamic lights and glows are, though, because
|
|
// the objects emitting these lights are also.
|
|
//
|
|
// This is making this a bit more complicated than it needs to
|
|
// because we can't just desaturate the final fragment color.
|
|
//
|
|
//===========================================================================
|
|
|
|
vec4 getLightColor(Material material, float fogdist, float fogfactor)
|
|
{
|
|
vec4 color = vColor;
|
|
|
|
if (uLightLevel >= 0.0)
|
|
{
|
|
float newlightlevel = 1.0 - R_DoomLightingEquation(uLightLevel);
|
|
color.rgb *= newlightlevel;
|
|
}
|
|
else if (uFogEnabled > 0)
|
|
{
|
|
// brightening around the player for light mode 2
|
|
if (fogdist < uLightDist)
|
|
{
|
|
color.rgb *= uLightFactor - (fogdist / uLightDist) * (uLightFactor - 1.0);
|
|
}
|
|
|
|
//
|
|
// apply light diminishing through fog equation
|
|
//
|
|
color.rgb = mix(vec3(0.0, 0.0, 0.0), color.rgb, fogfactor);
|
|
}
|
|
|
|
//
|
|
// handle glowing walls
|
|
//
|
|
if (uGlowTopColor.a > 0.0 && glowdist.x < uGlowTopColor.a)
|
|
{
|
|
color.rgb += desaturate(uGlowTopColor * (1.0 - glowdist.x / uGlowTopColor.a)).rgb;
|
|
}
|
|
if (uGlowBottomColor.a > 0.0 && glowdist.y < uGlowBottomColor.a)
|
|
{
|
|
color.rgb += desaturate(uGlowBottomColor * (1.0 - glowdist.y / uGlowBottomColor.a)).rgb;
|
|
}
|
|
color = min(color, 1.0);
|
|
|
|
//
|
|
// apply brightmaps (or other light manipulation by custom shaders.
|
|
//
|
|
color = ProcessLight(material, color);
|
|
|
|
//
|
|
// apply dynamic lights
|
|
//
|
|
return vec4(ProcessMaterialLight(material, color.rgb), material.Base.a * vColor.a);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Applies colored fog
|
|
//
|
|
//===========================================================================
|
|
|
|
vec4 applyFog(vec4 frag, float fogfactor)
|
|
{
|
|
return vec4(mix(uFogColor.rgb, frag.rgb, fogfactor), frag.a);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// The color of the fragment if it is fully occluded by ambient lighting
|
|
//
|
|
//===========================================================================
|
|
|
|
vec3 AmbientOcclusionColor()
|
|
{
|
|
float fogdist;
|
|
float fogfactor;
|
|
|
|
//
|
|
// calculate fog factor
|
|
//
|
|
if (uFogEnabled == -1)
|
|
{
|
|
fogdist = max(16.0, pixelpos.w);
|
|
}
|
|
else
|
|
{
|
|
fogdist = max(16.0, distance(pixelpos.xyz, uCameraPos.xyz));
|
|
}
|
|
fogfactor = exp2 (uFogDensity * fogdist);
|
|
|
|
return mix(uFogColor.rgb, vec3(0.0), fogfactor);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Main shader routine
|
|
//
|
|
//===========================================================================
|
|
|
|
void main()
|
|
{
|
|
Material material = ProcessMaterial();
|
|
vec4 frag = material.Base;
|
|
|
|
#ifndef NO_ALPHATEST
|
|
if (frag.a <= uAlphaThreshold) discard;
|
|
#endif
|
|
|
|
if (uFogEnabled != -3) // check for special 2D 'fog' mode.
|
|
{
|
|
float fogdist = 0.0;
|
|
float fogfactor = 0.0;
|
|
|
|
//
|
|
// calculate fog factor
|
|
//
|
|
if (uFogEnabled != 0)
|
|
{
|
|
if (uFogEnabled == 1 || uFogEnabled == -1)
|
|
{
|
|
fogdist = max(16.0, pixelpos.w);
|
|
}
|
|
else
|
|
{
|
|
fogdist = max(16.0, distance(pixelpos.xyz, uCameraPos.xyz));
|
|
}
|
|
fogfactor = exp2 (uFogDensity * fogdist);
|
|
}
|
|
|
|
if (uTextureMode != 7)
|
|
{
|
|
frag = getLightColor(material, fogdist, fogfactor);
|
|
//
|
|
// colored fog
|
|
//
|
|
if (uFogEnabled < 0)
|
|
{
|
|
frag = applyFog(frag, fogfactor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
frag = vec4(uFogColor.rgb, (1.0 - fogfactor) * frag.a * 0.75 * vColor.a);
|
|
}
|
|
}
|
|
else // simple 2D (uses the fog color to add a color overlay)
|
|
{
|
|
if (uTextureMode == 7)
|
|
{
|
|
float gray = grayscale(frag);
|
|
vec4 cm = (uObjectColor + gray * (uAddColor - uObjectColor)) * 2;
|
|
frag = vec4(clamp(cm.rgb, 0.0, 1.0), frag.a);
|
|
}
|
|
frag = frag * ProcessLight(material, vColor);
|
|
frag.rgb = frag.rgb + uFogColor.rgb;
|
|
}
|
|
FragColor = frag;
|
|
#ifdef GBUFFER_PASS
|
|
FragFog = vec4(AmbientOcclusionColor(), 1.0);
|
|
FragNormal = vec4(vEyeNormal.xyz * 0.5 + 0.5, 1.0);
|
|
#endif
|
|
}
|