mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-24 21:11:39 +00:00
832df6d43a
A lot of work that's only needed to be able to use the hardware renderer's 2D code with the software renderer.
519 lines
12 KiB
GLSL
519 lines
12 KiB
GLSL
in vec4 pixelpos;
|
|
in vec3 glowdist;
|
|
|
|
in vec4 vWorldNormal;
|
|
in vec4 vEyeNormal;
|
|
in vec4 vTexCoord;
|
|
in vec4 vColor;
|
|
|
|
out vec4 FragColor;
|
|
#ifdef GBUFFER_PASS
|
|
out vec4 FragFog;
|
|
out vec4 FragNormal;
|
|
#endif
|
|
|
|
vec4 Process(vec4 color);
|
|
vec4 ProcessTexel();
|
|
vec4 ProcessLight(vec4 color);
|
|
vec3 ProcessMaterial(vec3 material, vec3 color);
|
|
|
|
//===========================================================================
|
|
//
|
|
// Color to grayscale
|
|
//
|
|
//===========================================================================
|
|
|
|
float grayscale(vec4 color)
|
|
{
|
|
return dot(color.rgb, vec3(0.4, 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_MASK
|
|
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_REDTOALPHA
|
|
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;
|
|
}
|
|
if (uObjectColor2.a == 0.0) texel *= uObjectColor;
|
|
else texel *= mix(uObjectColor, uObjectColor2, glowdist.z);
|
|
|
|
return desaturate(texel);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Doom lighting equation exactly as calculated by zdoom.
|
|
//
|
|
//===========================================================================
|
|
float R_DoomLightingEquation(float light)
|
|
{
|
|
// L is the integer light level used in the game
|
|
float L = light * 255.0;
|
|
|
|
// z is the depth in view/eye space, positive going into the screen
|
|
float z;
|
|
if ((uPalLightLevels >> 8) == 2)
|
|
{
|
|
z = distance(pixelpos.xyz, uCameraPos.xyz);
|
|
}
|
|
else
|
|
{
|
|
z = pixelpos.w;
|
|
}
|
|
|
|
// The zdoom light equation
|
|
float vis = min(uGlobVis / z, 24.0 / 32.0);
|
|
float shade = 2.0 - (L + 12.0) / 128.0;
|
|
float lightscale;
|
|
if ((uPalLightLevels & 0xff) != 0)
|
|
lightscale = float(-floor(-(shade - vis) * 31.0) - 0.5) / 31.0;
|
|
else
|
|
lightscale = shade - vis;
|
|
|
|
// Result is the normalized colormap index (0 bright .. 1 dark)
|
|
return clamp(lightscale, 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.x) > abs(dir.y))
|
|
{
|
|
if (dir.x >= 0.0)
|
|
return dir.y / dir.x * 0.125 + (0.25 + 0.125);
|
|
else
|
|
return dir.y / dir.x * 0.125 + (0.75 + 0.125);
|
|
}
|
|
else
|
|
{
|
|
if (dir.y >= 0.0)
|
|
return dir.x / dir.y * 0.125 + 0.125;
|
|
else
|
|
return dir.x / dir.y * 0.125 + (0.50 + 0.125);
|
|
}
|
|
}
|
|
|
|
float sampleShadowmap(vec2 dir, float v)
|
|
{
|
|
float u = shadowDirToU(dir);
|
|
float dist2 = dot(dir, dir);
|
|
return texture(ShadowMap, vec2(u, v)).x > dist2 ? 1.0 : 0.0;
|
|
}
|
|
|
|
float sampleShadowmapLinear(vec2 dir, float v)
|
|
{
|
|
float u = shadowDirToU(dir);
|
|
float dist2 = dot(dir, dir);
|
|
|
|
vec2 isize = textureSize(ShadowMap, 0);
|
|
vec2 size = vec2(isize);
|
|
|
|
vec2 fetchPos = vec2(u, v) * size - vec2(0.5, 0.0);
|
|
if (fetchPos.x < 0.0)
|
|
fetchPos.x += size.x;
|
|
|
|
ivec2 ifetchPos = ivec2(fetchPos);
|
|
int y = ifetchPos.y;
|
|
|
|
float t = fract(fetchPos.x);
|
|
int x0 = ifetchPos.x;
|
|
int x1 = ifetchPos.x + 1;
|
|
if (x1 == isize.x)
|
|
x1 = 0;
|
|
|
|
float depth0 = texelFetch(ShadowMap, ivec2(x0, y), 0).x;
|
|
float depth1 = texelFetch(ShadowMap, ivec2(x1, y), 0).x;
|
|
return mix(step(dist2, depth0), step(dist2, depth1), t);
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// Check if light is in shadow using Percentage Closer Filtering (PCF)
|
|
//
|
|
//===========================================================================
|
|
|
|
#define PCF_FILTER_STEP_COUNT 3
|
|
#define PCF_COUNT (PCF_FILTER_STEP_COUNT * 2 + 1)
|
|
|
|
// #define USE_LINEAR_SHADOW_FILTER
|
|
#define USE_PCF_SHADOW_FILTER 1
|
|
|
|
float shadowmapAttenuation(vec4 lightpos, float shadowIndex)
|
|
{
|
|
if (shadowIndex >= 1024.0)
|
|
return 1.0; // No shadowmap available for this light
|
|
|
|
float v = (shadowIndex + 0.5) / 1024.0;
|
|
|
|
vec2 ray = pixelpos.xz - lightpos.xz;
|
|
float length = length(ray);
|
|
if (length < 3.0)
|
|
return 1.0;
|
|
|
|
vec2 dir = ray / length;
|
|
|
|
#if defined(USE_LINEAR_SHADOW_FILTER)
|
|
ray -= dir * 6.0; // Shadow acne margin
|
|
return sampleShadowmapLinear(ray, v);
|
|
#elif defined(USE_PCF_SHADOW_FILTER)
|
|
ray -= dir * 2.0; // Shadow acne margin
|
|
dir = dir * min(length / 50.0, 1.0); // avoid sampling behind light
|
|
|
|
vec2 normal = vec2(-dir.y, dir.x);
|
|
vec2 bias = dir * 10.0;
|
|
|
|
float sum = 0.0;
|
|
for (float x = -PCF_FILTER_STEP_COUNT; x <= PCF_FILTER_STEP_COUNT; x++)
|
|
{
|
|
sum += sampleShadowmap(ray + normal * x - bias * abs(x), v);
|
|
}
|
|
return sum / PCF_COUNT;
|
|
#else // nearest shadow filter
|
|
ray -= dir * 6.0; // Shadow acne margin
|
|
return sampleShadowmap(ray, v);
|
|
#endif
|
|
}
|
|
|
|
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()
|
|
{
|
|
#define WITH_NORMALMAP_UNSIGNED
|
|
#define WITH_NORMALMAP_GREEN_UP
|
|
//#define WITH_NORMALMAP_2CHANNEL
|
|
|
|
vec3 interpolatedNormal = normalize(vWorldNormal.xyz);
|
|
|
|
vec3 map = texture(normaltexture, vTexCoord.st).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()
|
|
{
|
|
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(vec4 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(color);
|
|
|
|
//
|
|
// apply dynamic lights
|
|
//
|
|
return vec4(ProcessMaterial(material.rgb, color.rgb), material.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 = 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()
|
|
{
|
|
vec4 frag = ProcessTexel();
|
|
|
|
#ifndef NO_ALPHATEST
|
|
if (frag.a <= uAlphaThreshold) discard;
|
|
#endif
|
|
|
|
switch (uFixedColormap)
|
|
{
|
|
case 0: // in-game rendering.
|
|
{
|
|
float fogdist = 0.0;
|
|
float fogfactor = 0.0;
|
|
|
|
|
|
|
|
//
|
|
// calculate fog factor
|
|
//
|
|
if (uFogEnabled != 0)
|
|
{
|
|
if (uFogEnabled == 1 || uFogEnabled == -1)
|
|
{
|
|
fogdist = pixelpos.w;
|
|
}
|
|
else
|
|
{
|
|
fogdist = max(16.0, distance(pixelpos.xyz, uCameraPos.xyz));
|
|
}
|
|
fogfactor = exp2 (uFogDensity * fogdist);
|
|
}
|
|
|
|
frag = getLightColor(frag, fogdist, fogfactor);
|
|
|
|
//
|
|
// colored fog
|
|
//
|
|
if (uFogEnabled < 0)
|
|
{
|
|
frag = applyFog(frag, fogfactor);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 1: // special colormap
|
|
{
|
|
float gray = grayscale(frag);
|
|
vec4 cm = uFixedColormapStart + gray * uFixedColormapRange;
|
|
frag = vec4(clamp(cm.rgb, 0.0, 1.0), frag.a*vColor.a);
|
|
break;
|
|
}
|
|
|
|
case 2: // fullscreen tint.
|
|
{
|
|
frag = vColor * frag * uFixedColormapStart;
|
|
break;
|
|
}
|
|
|
|
case 3: // fog layer
|
|
{
|
|
float fogdist;
|
|
float fogfactor;
|
|
|
|
//
|
|
// calculate fog factor
|
|
//
|
|
if (uFogEnabled == -1)
|
|
{
|
|
fogdist = pixelpos.w;
|
|
}
|
|
else
|
|
{
|
|
fogdist = max(16.0, distance(pixelpos.xyz, uCameraPos.xyz));
|
|
}
|
|
fogfactor = exp2 (uFogDensity * fogdist);
|
|
|
|
frag = vec4(uFogColor.rgb, (1.0 - fogfactor) * frag.a * 0.75 * vColor.a);
|
|
break;
|
|
}
|
|
|
|
case 4: // simple 2D (reuses a uniform for the special colormap for the color overlay.)
|
|
{
|
|
frag = frag * vColor;
|
|
frag.rgb = frag.rgb + uFixedColormapStart.rgb;
|
|
break;
|
|
}
|
|
|
|
}
|
|
FragColor = frag;
|
|
#ifdef GBUFFER_PASS
|
|
FragFog = vec4(AmbientOcclusionColor(), 1.0);
|
|
FragNormal = vec4(vEyeNormal.xyz * 0.5 + 0.5, 1.0);
|
|
#endif
|
|
}
|
|
|