gzdoom-gles/wadsrc/static/shaders/glsl/main.fp
Christoph Oelckers bb8db9422f - scaled down the texture colorization feature for easier usability.
It makes little sense exposing every minute detail of this through UDMF.
Setting it up that way is far too complicated. Using virtual textures that map to a real texture plus a colorization record should be far easier to use by mappers.
This also doesn't piggyback on the Doom64 color feature anymore and is completely separate, despite some redundancies.
This is still missing the texture definition part, though.
2019-12-20 22:25:10 +01:00

698 lines
18 KiB
GLSL

layout(location = 0) in vec4 vTexCoord;
layout(location = 1) in vec4 vColor;
layout(location = 2) in vec4 pixelpos;
layout(location = 3) in vec3 glowdist;
layout(location = 4) in vec3 gradientdist;
layout(location = 5) in vec4 vWorldNormal;
layout(location = 6) in vec4 vEyeNormal;
#ifdef NO_CLIPDISTANCE_SUPPORT
layout(location = 7) in vec4 ClipDistanceA;
layout(location = 8) in vec4 ClipDistanceB;
#endif
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 dodesaturate(vec4 texel, float factor)
{
if (factor != 0.0)
{
float gray = grayscale(texel);
return mix (texel, vec4(gray,gray,gray,texel.a), factor);
}
else
{
return texel;
}
}
//===========================================================================
//
// Desaturate a color
//
//===========================================================================
vec4 desaturate(vec4 texel)
{
return dodesaturate(texel, uDesaturationFactor);
}
//===========================================================================
//
// Texture tinting code originally from JFDuke but with a few more options
//
//===========================================================================
const int Tex_Blend_Alpha = 1;
const int Tex_Blend_Screen = 2;
const int Tex_Blend_Overlay = 3;
const int Tex_Blend_Hardlight = 4;
vec4 ApplyTextureManipulation(vec4 texel, int blendflags)
{
// Step 1: desaturate according to the material's desaturation factor.
texel = dodesaturate(texel, uTextureModulateColor.a);
// Step 2: Invert if requested
if ((blendflags & 8) != 0)
{
texel.rgb = vec3(1.0 - texel.r, 1.0 - texel.g, 1.0 - texel.b);
}
// Step 3: Apply additive color
texel.rgb += uTextureAddColor.rgb;
// Step 4: Colorization, including gradient if set.
texel.rgb *= uTextureModulateColor.rgb;
// Before applying the blend the value needs to be clamped to [0..1] range.
texel.rgb = clamp(texel.rgb, 0.0, 1.0);
// Step 5: Apply a blend. This may just be a translucent overlay or one of the blend modes present in current Build engines.
if (uObjectBlendMode != 0)
{
vec3 tcol = texel.rgb * 255.0; // * 255.0 to make it easier to reuse the integer math.
vec4 tint = uTextureBlendColor * 255.0;
switch (blendflags & 7)
{
default:
tcol.b = tcol.b * (1.0 - uTextureBlendColor.a) + tint.b * uTextureBlendColor.a;
tcol.g = tcol.g * (1.0 - uTextureBlendColor.a) + tint.g * uTextureBlendColor.a;
tcol.r = tcol.r * (1.0 - uTextureBlendColor.a) + tint.r * uTextureBlendColor.a;
break;
// The following 3 are taken 1:1 from the Build engine
case Tex_Blend_Screen:
tcol.b = 255.0 - (((255.0 - tcol.b) * (255.0 - tint.r)) / 256.0);
tcol.g = 255.0 - (((255.0 - tcol.g) * (255.0 - tint.g)) / 256.0);
tcol.r = 255.0 - (((255.0 - tcol.r) * (255.0 - tint.b)) / 256.0);
break;
case Tex_Blend_Overlay:
tcol.b = tcol.b < 128.0? (tcol.b * tint.b) / 128.0 : 255.0 - (((255.0 - tcol.b) * (255.0 - tint.b)) / 128.0);
tcol.g = tcol.g < 128.0? (tcol.g * tint.g) / 128.0 : 255.0 - (((255.0 - tcol.g) * (255.0 - tint.g)) / 128.0);
tcol.r = tcol.r < 128.0? (tcol.r * tint.r) / 128.0 : 255.0 - (((255.0 - tcol.r) * (255.0 - tint.r)) / 128.0);
break;
case Tex_Blend_Hardlight:
tcol.b = tint.b < 128.0 ? (tcol.b * tint.b) / 128.0 : 255.0 - (((255.0 - tcol.b) * (255.0 - tint.b)) / 128.0);
tcol.g = tint.g < 128.0 ? (tcol.g * tint.g) / 128.0 : 255.0 - (((255.0 - tcol.g) * (255.0 - tint.g)) / 128.0);
tcol.r = tint.r < 128.0 ? (tcol.r * tint.r) / 128.0 : 255.0 - (((255.0 - tcol.r) * (255.0 - tint.r)) / 128.0);
break;
}
texel.rgb = tcol / 255.0;
}
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;
}
// Apply the texture modification colors.
int blendflags = int(uTextureAddColor.a); // this alpha is unused otherwise
if (blendflags != 0)
{
// only apply the texture manipulation if it contains something.
texel = ApplyTextureManipulation(texel, blendflags);
}
// Apply the Doom64 style material colors on top of everything from the texture modification settings.
// This may be a bit redundant in terms of features but the data comes from different sources so this is unavoidable.
texel.rgb += uAddColor.rgb;
if (uObjectColor2.a == 0.0) texel *= uObjectColor;
else texel *= mix(uObjectColor, uObjectColor2, gradientdist.z);
// Last but not least apply the desaturation from the sector's light.
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()
{
#ifdef NO_CLIPDISTANCE_SUPPORT
if (ClipDistanceA.x < 0 || ClipDistanceA.y < 0 || ClipDistanceA.z < 0 || ClipDistanceA.w < 0 || ClipDistanceB.x < 0) discard;
#endif
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
}