diff --git a/src/r_data/models/models_ue1.cpp b/src/r_data/models/models_ue1.cpp index 838be2af7..bd4f54e3d 100644 --- a/src/r_data/models/models_ue1.cpp +++ b/src/r_data/models/models_ue1.cpp @@ -129,7 +129,7 @@ void FUE1Model::LoadGeometry() FVector3 dir[2]; dir[0] = verts[Poly.V[1]+numVerts*j].Pos-verts[Poly.V[0]+numVerts*j].Pos; dir[1] = verts[Poly.V[2]+numVerts*j].Pos-verts[Poly.V[0]+numVerts*j].Pos; - Poly.Normals.Push(dir[0]^dir[1]); + Poly.Normals.Push((dir[0]^dir[1]).Unit()); } // push polys.Push(Poly); diff --git a/src/textures/hires/hqresize.cpp b/src/textures/hires/hqresize.cpp index 6bcb2d805..01f310ed2 100644 --- a/src/textures/hires/hqresize.cpp +++ b/src/textures/hires/hqresize.cpp @@ -47,7 +47,7 @@ CUSTOM_CVAR(Int, gl_texture_hqresize, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) { - if (self < 0 || self > 22) + if (self < 0 || self > 24) { self = 0; } @@ -439,6 +439,10 @@ unsigned char *FTexture::CreateUpsampledTextureBuffer (unsigned char *inputBuffe return normalNxHelper( &normalNx, 3, inputBuffer, inWidth, inHeight, outWidth, outHeight ); case 22: return normalNxHelper( &normalNx, 4, inputBuffer, inWidth, inHeight, outWidth, outHeight ); + case 23: + return normalNxHelper( &normalNx, 5, inputBuffer, inWidth, inHeight, outWidth, outHeight ); + case 24: + return normalNxHelper( &normalNx, 6, inputBuffer, inWidth, inHeight, outWidth, outHeight ); } } return inputBuffer; diff --git a/wadsrc/static/language.enu b/wadsrc/static/language.enu index 38a1962e8..6ce6fcf23 100644 --- a/wadsrc/static/language.enu +++ b/wadsrc/static/language.enu @@ -2441,6 +2441,8 @@ OPTVAL_SOUNDSYSTEM = "Sound System"; OPTVAL_FOO_DUMB = "foo_dumb"; OPTVAL_ALIASING = "Aliasing"; OPTVAL_LINEAR = "Linear"; +OPTVAL_NEAREST = "Nearest"; +OPTVAL_PCF = "PCF"; OPTVAL_CUBIC = "Cubic"; OPTVAL_BLEP = "Band-limited step"; OPTVAL_LINEARSLOW = "Linear (Slower)"; @@ -2879,6 +2881,8 @@ OPTVAL_SCALE4X = "Scale4x"; OPTVAL_NORMAL2X = "Normal2x"; OPTVAL_NORMAL3X = "Normal3x"; OPTVAL_NORMAL4X = "Normal4x"; +OPTVAL_NORMAL5X = "Normal5x"; +OPTVAL_NORMAL6X = "Normal6x"; OPTVAL_HQ2X = "hq2x"; OPTVAL_HQ3X = "hq3x"; OPTVAL_HQ4X = "hq4x"; diff --git a/wadsrc/static/language.fr b/wadsrc/static/language.fr index 181c9e0db..c43a3c222 100644 --- a/wadsrc/static/language.fr +++ b/wadsrc/static/language.fr @@ -2418,6 +2418,8 @@ OPTVAL_OLD = "Ancien"; OPTVAL_DEFAULT = "Défaut"; OPTVAL_SOUNDSYSTEM = "Système Sonore"; OPTVAL_LINEAR = "Linéaire"; +OPTVAL_NEAREST = "Nearest"; +OPTVAL_PCF = "PCF"; OPTVAL_CUBIC = "Cubique"; OPTVAL_BLEP = "Step limité sur bande"; OPTVAL_LINEARSLOW = "Linéaire (Lent)"; diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 0bd45a7d7..f210af477 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -2188,6 +2188,8 @@ OptionValue "HqResizeModes" 20, "$OPTVAL_NORMAL2X" 21, "$OPTVAL_NORMAL3X" 22, "$OPTVAL_NORMAL4X" + 23, "$OPTVAL_NORMAL5X" + 24, "$OPTVAL_NORMAL6X" } OptionValue "HqResizeModesNoMMX" @@ -2258,8 +2260,8 @@ OptionValue ShadowMapQuality OptionValue ShadowMapFilter { - 0, "$OPTVAL_LINEAR" - 1, "PCF" + 0, "$OPTVAL_NEAREST" + 1, "$OPTVAL_PCF" } diff --git a/wadsrc/static/shaders/glsl/main.fp b/wadsrc/static/shaders/glsl/main.fp index c78bb0e02..c8ad439cb 100644 --- a/wadsrc/static/shaders/glsl/main.fp +++ b/wadsrc/static/shaders/glsl/main.fp @@ -160,19 +160,21 @@ float R_DoomLightingEquation(float light) float shadowDirToU(vec2 dir) { - if (abs(dir.x) > abs(dir.y)) + if (abs(dir.y) > abs(dir.x)) { - if (dir.x >= 0.0) - return dir.y / dir.x * 0.125 + (0.25 + 0.125); + float x = dir.x / dir.y * 0.125; + if (dir.y >= 0.0) + return 0.125 + x; else - return dir.y / dir.x * 0.125 + (0.75 + 0.125); + return (0.50 + 0.125) + x; } else { - if (dir.y >= 0.0) - return dir.x / dir.y * 0.125 + 0.125; + float y = dir.y / dir.x * 0.125; + if (dir.x >= 0.0) + return (0.25 + 0.125) - y; else - return dir.x / dir.y * 0.125 + (0.50 + 0.125); + return (0.75 + 0.125) - y; } } @@ -180,7 +182,7 @@ 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; + return step(dist2, texture(ShadowMap, vec2(u, v)).x); } float sampleShadowmapLinear(vec2 dir, float v) @@ -209,6 +211,40 @@ float sampleShadowmapLinear(vec2 dir, float v) return mix(step(dist2, depth0), step(dist2, depth1), t); } +vec2 shadowmapAdjustedRay(vec4 lightpos) +{ + vec3 planePoint = pixelpos.xyz - lightpos.xyz; + + if (dot(planePoint.xz, planePoint.xz) < 1.0) + return planePoint.xz * 0.5; + + vec3 ray = normalize(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 bias = 1.0; + float negD = dot(vWorldNormal.xyz, planePoint); + float t = negD / dot(vWorldNormal.xyz, ray) - bias; + return ray.xz * t; +} + //=========================================================================== // // Check if light is in shadow using Percentage Closer Filtering (PCF) @@ -222,22 +258,20 @@ float shadowmapAttenuation(vec4 lightpos, float shadowIndex) 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; + vec2 ray = shadowmapAdjustedRay(lightpos); if (uShadowmapFilter <= 0) { - ray -= dir * 2.0; // Shadow acne margin - return sampleShadowmapLinear(ray, v); + return sampleShadowmap(ray, v); + //return sampleShadowmapLinear(ray, v); } else { - ray -= dir * 2.0; // Shadow acne margin - dir = dir * min(length / 50.0, 1.0); // avoid sampling behind light + float length = length(ray); + if (length < 3.0) + return 1.0; + + vec2 dir = ray / length * min(length / 50.0, 1.0); // avoid sampling behind light vec2 normal = vec2(-dir.y, dir.x); vec2 bias = dir * 10.0; @@ -251,10 +285,6 @@ float shadowmapAttenuation(vec4 lightpos, float shadowIndex) } return sum / uShadowmapFilter; } -#if 0 // nearest shadow filter (not used) - ray -= dir * 6.0; // Shadow acne margin - return sampleShadowmap(ray, v); -#endif } float shadowAttenuation(vec4 lightpos, float lightcolorA) diff --git a/wadsrc/static/shaders/glsl/shadowmap.fp b/wadsrc/static/shaders/glsl/shadowmap.fp index fb2257b92..4bc507c3d 100644 --- a/wadsrc/static/shaders/glsl/shadowmap.fp +++ b/wadsrc/static/shaders/glsl/shadowmap.fp @@ -2,20 +2,22 @@ in vec2 TexCoord; layout(location=0) out vec4 FragColor; +// A node in an AABB binary tree with lines stored in the leaf nodes struct GPUNode { - vec2 aabb_min; - vec2 aabb_max; - int left; - int right; - int line_index; - int padding; + vec2 aabb_min; // Min xy values for the axis-aligned box containing the node and its subtree + vec2 aabb_max; // Max xy values + int left; // Left subnode index + int right; // Right subnode index + int line_index; // Line index if it is a leaf node, otherwise -1 + int padding; // Unused - maintains 16 byte alignment }; +// 2D line segment, referenced by leaf nodes struct GPULine { - vec2 pos; - vec2 delta; + vec2 pos; // Line start position + vec2 delta; // Line end position - line start position }; layout(std430, binding = 2) buffer LightNodes @@ -33,6 +35,7 @@ layout(std430, binding = 4) buffer LightList vec4 lights[]; }; +// Overlap test between line segment and axis-aligned bounding box. Returns true if they overlap. bool overlapRayAABB(vec2 ray_start2d, vec2 ray_end2d, vec2 aabb_min2d, vec2 aabb_max2d) { // To do: simplify test to use a 2D test @@ -60,6 +63,8 @@ bool overlapRayAABB(vec2 ray_start2d, vec2 ray_end2d, vec2 aabb_min2d, vec2 aabb return true; // overlap; } +// Intersection test between two line segments. +// Returns the intersection point as a value between 0-1 on the ray line segment. 1.0 if there was no hit. float intersectRayLine(vec2 ray_start, vec2 ray_end, int line_index, vec2 raydelta, float rayd, float raydist2) { const float epsilon = 0.0000001; @@ -82,11 +87,14 @@ float intersectRayLine(vec2 ray_start, vec2 ray_end, int line_index, vec2 raydel return 1.0; } +// Returns true if an AABB tree node is a leaf node. Leaf nodes contains a line. bool isLeaf(int node_index) { return nodes[node_index].line_index != -1; } +// Perform ray intersection test between the ray line segment and all the lines in the AABB binary tree. +// Returns the intersection point as a value between 0-1 on the ray line segment. 1.0 if there was no hit. float rayTest(vec2 ray_start, vec2 ray_end) { vec2 raydelta = ray_end - ray_start; @@ -98,6 +106,9 @@ float rayTest(vec2 ray_start, vec2 ray_end) float t = 1.0; + // Walk the AABB binary tree searching for nodes touching the ray line segment's AABB box. + // When it reaches a leaf node, use a line segment intersection test to see if we got a hit. + int stack[16]; int stack_pos = 1; stack[0] = nodes.length() - 1; @@ -131,6 +142,8 @@ float rayTest(vec2 ray_start, vec2 ray_end) void main() { + // Find the light that belongs to this texel in the shadowmap texture we output to: + int lightIndex = int(gl_FragCoord.y); vec4 light = lights[lightIndex]; @@ -139,17 +152,32 @@ void main() if (radius > 0.0) { - vec2 pixelpos; - switch (int(gl_FragCoord.x) / int(ShadowmapQuality/4.0)) + // We found an active light. Calculate the ray direction for the texel. + // + // The texels are laid out so that there are four projections: + // + // * top-left to top-right + // * top-right to bottom-right + // * bottom-right to bottom-left + // * bottom-left to top-left + // + vec2 raydir; + float u = gl_FragCoord.x / ShadowmapQuality * 4.0; + switch (int(u)) { - case 0: pixelpos = vec2((gl_FragCoord.x - float(ShadowmapQuality/8.0)) / float(ShadowmapQuality/8.0), 1.0); break; - case 1: pixelpos = vec2(1.0, (gl_FragCoord.x - float(ShadowmapQuality/4.0 + ShadowmapQuality/8.0)) / float(ShadowmapQuality/8.0)); break; - case 2: pixelpos = vec2(-(gl_FragCoord.x - float(ShadowmapQuality/2.0 + ShadowmapQuality/8.0)) / float(ShadowmapQuality/8.0), -1.0); break; - case 3: pixelpos = vec2(-1.0, -(gl_FragCoord.x - float(ShadowmapQuality*3.0/4.0 + ShadowmapQuality/8.0)) / float(ShadowmapQuality/8.0)); break; + 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; } - pixelpos = lightpos + pixelpos * radius; + // Find the position for the ray starting at the light position and travelling until light contribution is zero: + vec2 pixelpos = lightpos + raydir * radius; + + // Check if we hit any line between the light and the end position: float t = rayTest(lightpos, pixelpos); + + // Calculate the square distance for the hit, if any: vec2 delta = (pixelpos - lightpos) * t; float dist2 = dot(delta, delta); diff --git a/wadsrc/static/zscript/menu/optionmenu.txt b/wadsrc/static/zscript/menu/optionmenu.txt index ede7c0217..3e1853735 100644 --- a/wadsrc/static/zscript/menu/optionmenu.txt +++ b/wadsrc/static/zscript/menu/optionmenu.txt @@ -595,10 +595,12 @@ class GLTextureGLOptions : OptionMenu break; case 16: case 18: + case 23: multiplier = 25; break; case 17: case 19: + case 24: multiplier = 36; break; }