From a9c1fdf23abc3b074be4088fc0b67fc761267138 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Thu, 17 Jul 2025 02:52:28 +0200 Subject: [PATCH] Add specular highlights to sunlight --- .../hwrenderer/data/hw_viewpointuniforms.h | 5 ++ .../rendering/vulkan/vk_lightmapper.cpp | 2 +- .../hwrenderer/scene/hw_drawinfo.cpp | 3 + wadsrc/static/shaders/lightmap/frag_blur.glsl | 4 +- .../shaders/lightmap/frag_raytrace.glsl | 7 +- .../static/shaders/lightmap/frag_resolve.glsl | 38 +++++++--- .../shaders/lightmap/trace_levelmesh.glsl | 13 ++++ .../shaders/lightmap/trace_sunlight.glsl | 75 +++++++++++++++++++ .../shaders/scene/binding_rsbuffers.glsl | 5 ++ wadsrc/static/shaders/scene/lightmode.glsl | 7 +- .../shaders/scene/lightmodel_nolights.glsl | 11 ++- .../shaders/scene/lightmodel_normal.glsl | 10 ++- .../static/shaders/scene/lightmodel_pbr.glsl | 28 ++++++- .../shaders/scene/lightmodel_specular.glsl | 13 +++- wadsrc/static/shaders/scene/material.glsl | 2 +- 15 files changed, 193 insertions(+), 30 deletions(-) diff --git a/src/common/rendering/hwrenderer/data/hw_viewpointuniforms.h b/src/common/rendering/hwrenderer/data/hw_viewpointuniforms.h index 58668d563..4e39d7873 100644 --- a/src/common/rendering/hwrenderer/data/hw_viewpointuniforms.h +++ b/src/common/rendering/hwrenderer/data/hw_viewpointuniforms.h @@ -40,6 +40,11 @@ struct HWViewpointUniforms int mLightTilesWidth = 0; + FVector3 SunDir; + float Padding = 0.0f; + FVector3 SunColor; + float SunIntensity = 0.0f; + FVector3 mCameraNormal; void CalcDependencies() diff --git a/src/common/rendering/vulkan/vk_lightmapper.cpp b/src/common/rendering/vulkan/vk_lightmapper.cpp index f79ee354b..78ce63266 100644 --- a/src/common/rendering/vulkan/vk_lightmapper.cpp +++ b/src/common/rendering/vulkan/vk_lightmapper.cpp @@ -185,7 +185,7 @@ void VkLightmapper::Render() .RenderPass(raytrace.renderPass.get()) .RenderArea(0, 0, bakeImage.maxX, bakeImage.maxY) .Framebuffer(bakeImage.raytrace.Framebuffer.get()) - .AddClearColor(0.0f, 0.0f, 0.0f, 0.0f) + .AddClearColor(0.0f, 0.0f, 0.0f, -1.0f) .Execute(cmdbuffer); VkDeviceSize offset = 0; diff --git a/src/rendering/hwrenderer/scene/hw_drawinfo.cpp b/src/rendering/hwrenderer/scene/hw_drawinfo.cpp index 4eae82a7c..fa71ee946 100644 --- a/src/rendering/hwrenderer/scene/hw_drawinfo.cpp +++ b/src/rendering/hwrenderer/scene/hw_drawinfo.cpp @@ -138,6 +138,9 @@ void HWDrawInfo::StartScene(FRenderViewpoint &parentvp, HWViewpointUniforms *uni VPUniforms.mPalLightLevels = static_cast(gl_bandedswlight) | (static_cast(gl_fogmode) << 8) | ((int)lightmode << 16); } VPUniforms.mClipLine.X = -10000000.0f; + VPUniforms.SunDir = FVector3(level.SunDirection.X, level.SunDirection.Z, level.SunDirection.Y); + VPUniforms.SunColor = level.SunColor; + VPUniforms.SunIntensity = level.SunIntensity; } mClipper->SetViewpoint(Viewpoint); vClipper->SetViewpoint(Viewpoint); diff --git a/wadsrc/static/shaders/lightmap/frag_blur.glsl b/wadsrc/static/shaders/lightmap/frag_blur.glsl index b46290ad8..8db05df0e 100644 --- a/wadsrc/static/shaders/lightmap/frag_blur.glsl +++ b/wadsrc/static/shaders/lightmap/frag_blur.glsl @@ -8,7 +8,7 @@ vec4 centerFragColor; vec4 clampedSample(vec4 f) { - return f.a != 0.0 ? f : centerFragColor; + return f.a != -1.0 ? f : centerFragColor; } void main() @@ -18,7 +18,7 @@ void main() centerFragColor = textureOffset(tex, texCoord, ivec2(0, 0)); - if (centerFragColor.a != 0.0) + if (centerFragColor.a != -1.0) { #if defined(BLUR_HORIZONTAL) fragcolor = diff --git a/wadsrc/static/shaders/lightmap/frag_raytrace.glsl b/wadsrc/static/shaders/lightmap/frag_raytrace.glsl index 89474ce67..e7fd14f0d 100644 --- a/wadsrc/static/shaders/lightmap/frag_raytrace.glsl +++ b/wadsrc/static/shaders/lightmap/frag_raytrace.glsl @@ -24,11 +24,12 @@ void main() vec3 origin = worldpos; #if defined(USE_SUNLIGHT) - vec3 incoming = TraceSunLight(origin, normal); + float sunAttenuation = TraceSunAttenuation(origin, normal); #else - vec3 incoming = vec3(0.0); + float sunAttenuation = 0.0; #endif + vec3 incoming = vec3(0.0); for (uint j = LightStart; j < LightEnd; j++) { incoming += TraceLight(origin, normal, lights[lightIndexes[j]], 0.0, false); @@ -42,5 +43,5 @@ void main() incoming.rgb *= TraceAmbientOcclusion(origin, normal); #endif - fragcolor = vec4(incoming, 1.0); + fragcolor = vec4(incoming, sunAttenuation); } diff --git a/wadsrc/static/shaders/lightmap/frag_resolve.glsl b/wadsrc/static/shaders/lightmap/frag_resolve.glsl index 886778ed2..4224a7a7e 100644 --- a/wadsrc/static/shaders/lightmap/frag_resolve.glsl +++ b/wadsrc/static/shaders/lightmap/frag_resolve.glsl @@ -4,27 +4,36 @@ layout(set = 0, binding = 0) uniform sampler2DMS tex; layout(location = 0) in vec2 TexCoord; layout(location = 0) out vec4 fragcolor; -vec4 samplePixel(ivec2 pos, int count) +vec4 samplePixel(ivec2 pos, int samplecount) { vec4 c = vec4(0.0); - for (int i = 0; i < count; i++) + float count = 0.0; + for (int i = 0; i < samplecount; i++) { - c += texelFetch(tex, pos, i); + vec4 p = texelFetch(tex, pos, i); + if (p.a != -1.0) + { + c += p; + count++; + } } - if (c.a > 0.0) - c /= c.a; - return c; + if (count == 0.0) + return vec4(0.0, 0.0, 0.0, -1.0); + else + return c / count; } void main() { - int count = textureSamples(tex); + int samplecount = textureSamples(tex); ivec2 size = textureSize(tex); ivec2 pos = ivec2(gl_FragCoord.xy); - vec4 c = samplePixel(pos, count); - if (c.a == 0.0) + vec4 c = samplePixel(pos, samplecount); + if (c.a == -1.0) { + float count = 0.0; + c = vec4(0.0, 0.0, 0.0, -1.0); for (int y = -1; y <= 1; y++) { for (int x = -1; x <= 1; x++) @@ -34,12 +43,17 @@ void main() ivec2 pos2; pos2.x = clamp(pos.x + x, 0, size.x - 1); pos2.y = clamp(pos.y + y, 0, size.y - 1); - c += samplePixel(pos2, count); + vec4 p = samplePixel(pos2, samplecount); + if (p.a != -1.0) + { + c += p; + count++; + } } } } - if (c.a > 0.0) - c /= c.a; + if (count != 0.0) + c /= count; } fragcolor = c; diff --git a/wadsrc/static/shaders/lightmap/trace_levelmesh.glsl b/wadsrc/static/shaders/lightmap/trace_levelmesh.glsl index 5f9320d25..080abaf28 100644 --- a/wadsrc/static/shaders/lightmap/trace_levelmesh.glsl +++ b/wadsrc/static/shaders/lightmap/trace_levelmesh.glsl @@ -24,6 +24,19 @@ vec2 GetSurfaceUV(int primitiveIndex, vec3 primitiveWeights) vertices[elements[index + 0]].uv * primitiveWeights.z; } +float PassAttenuationThroughSurface(SurfaceInfo surface, vec2 uv, float attentuation) +{ + if (surface.TextureIndex == 0) + { + return attentuation; + } + else + { + vec4 color = texture(textures[surface.TextureIndex], uv); + return attentuation * (1.0 - color.a * surface.Alpha); + } +} + vec3 PassRayThroughSurface(SurfaceInfo surface, vec2 uv, vec3 rayColor) { if (surface.TextureIndex == 0) diff --git a/wadsrc/static/shaders/lightmap/trace_sunlight.glsl b/wadsrc/static/shaders/lightmap/trace_sunlight.glsl index df230328f..e0677adf3 100644 --- a/wadsrc/static/shaders/lightmap/trace_sunlight.glsl +++ b/wadsrc/static/shaders/lightmap/trace_sunlight.glsl @@ -2,6 +2,7 @@ #include vec3 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec3 rayColor); +float TraceSunRayAttenuation(vec3 origin, float tmin, vec3 dir, float tmax); vec3 TraceSunLight(vec3 origin, vec3 normal) { @@ -41,6 +42,44 @@ vec3 TraceSunLight(vec3 origin, vec3 normal) return incoming * angleAttenuation; } +float TraceSunAttenuation(vec3 origin, vec3 normal) +{ + float angleAttenuation = max(dot(normal, SunDir), 0.0); + if (angleAttenuation == 0.0) + return 0.0; + + const float minDistance = 0.01; + vec3 incoming = vec3(0.0); + const float dist = 65536.0; + + float attenuation = 0.0; + +#if defined(USE_SOFTSHADOWS) + + vec3 target = origin + SunDir * dist; + vec3 dir = SunDir; + vec3 v = (abs(dir.x) > abs(dir.y)) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 xdir = normalize(cross(dir, v)); + vec3 ydir = cross(dir, xdir); + + float lightsize = 100; + int step_count = 10; + for (int i = 0; i < step_count; i++) + { + vec2 gridoffset = getVogelDiskSample(i, step_count, gl_FragCoord.x + gl_FragCoord.y * 13.37) * lightsize; + vec3 pos = target + xdir * gridoffset.x + ydir * gridoffset.y; + attenuation += TraceSunRayAttenuation(origin, minDistance, normalize(pos - origin), dist) / float(step_count); + } + +#else + + attenuation = TraceSunRayAttenuation(origin, minDistance, SunDir, dist); + +#endif + + return attenuation; +} + vec3 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec3 rayColor) { for (int i = 0; i < 3; i++) @@ -75,3 +114,39 @@ vec3 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec3 rayColor) } return vec3(0.0); } + +float TraceSunRayAttenuation(vec3 origin, float tmin, vec3 dir, float tmax) +{ + float attenuation = 1.0; + for (int i = 0; i < 3; i++) + { + TraceResult result = TraceFirstHit(origin, tmin, dir, tmax); + + // Stop if we hit nothing. We have to hit a sky surface to hit the sky. + if (result.primitiveIndex == -1) + return 0.0; + + SurfaceInfo surface = GetSurface(result.primitiveIndex); + + // Stop if we hit the sky. + if (surface.Sky > 0.0) + return attenuation; + + // Pass through surface texture + attenuation = PassAttenuationThroughSurface(surface, GetSurfaceUV(result.primitiveIndex, result.primitiveWeights), attenuation); + + // Stop if there is no light left + if (attenuation <= 0.0) + return 0.0; + + // Move to surface hit point + origin += dir * result.t; + tmax -= result.t; + if (tmax <= tmin) + return 0.0; + + // Move through the portal, if any + TransformRay(surface.PortalIndex, origin, dir); + } + return 0.0; +} diff --git a/wadsrc/static/shaders/scene/binding_rsbuffers.glsl b/wadsrc/static/shaders/scene/binding_rsbuffers.glsl index 2f2913c2b..fddf898a2 100644 --- a/wadsrc/static/shaders/scene/binding_rsbuffers.glsl +++ b/wadsrc/static/shaders/scene/binding_rsbuffers.glsl @@ -19,6 +19,11 @@ layout(set = 1, binding = 0, std140) uniform readonly ViewpointUBO int uLightTilesWidth; // Levelmesh light tiles + vec3 SunDir; + float Padding; + vec3 SunColor; + float SunIntensity; + vec3 uCameraNormal; }; diff --git a/wadsrc/static/shaders/scene/lightmode.glsl b/wadsrc/static/shaders/scene/lightmode.glsl index a078d0a83..6b9dae2cd 100644 --- a/wadsrc/static/shaders/scene/lightmode.glsl +++ b/wadsrc/static/shaders/scene/lightmode.glsl @@ -103,15 +103,18 @@ vec4 getLightColor(Material material) // // apply lightmaps // + float sunlightAttenuation = 0.0; if (vLightmapIndex != -1) { - color.rgb += texture(textures[nonuniformEXT(vLightmapIndex)], vLightmap.xy).rgb; + vec4 lightmap = texture(textures[nonuniformEXT(vLightmapIndex)], vLightmap.xy); + color.rgb += lightmap.rgb; + sunlightAttenuation = lightmap.a; } // // apply dynamic lights // - vec4 frag = vec4(ProcessMaterialLight(material, color.rgb), material.Base.a * vColor.a); + vec4 frag = vec4(ProcessMaterialLight(material, color.rgb, sunlightAttenuation), material.Base.a * vColor.a); // // colored fog diff --git a/wadsrc/static/shaders/scene/lightmodel_nolights.glsl b/wadsrc/static/shaders/scene/lightmodel_nolights.glsl index 537b316e6..0171f92e6 100644 --- a/wadsrc/static/shaders/scene/lightmodel_nolights.glsl +++ b/wadsrc/static/shaders/scene/lightmodel_nolights.glsl @@ -1,7 +1,14 @@ -vec3 ProcessMaterialLight(Material material, vec3 color) +vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation) { - return material.Base.rgb * clamp(color + desaturate(uDynLightColor).rgb, 0.0, 1.4); + vec4 dynlight = uDynLightColor; + if (sunlightAttenuation > 0.0) + { + sunlightAttenuation *= clamp(dot(material.Normal, SunDir), 0.0, 1.0); + dynlight.rgb += SunColor.rgb * SunIntensity * sunlightAttenuation; + } + + return material.Base.rgb * clamp(color + desaturate(dynlight).rgb, 0.0, 1.4); } vec3 ProcessSWLight(Material material) diff --git a/wadsrc/static/shaders/scene/lightmodel_normal.glsl b/wadsrc/static/shaders/scene/lightmodel_normal.glsl index 3e6666a7a..ffafa5742 100644 --- a/wadsrc/static/shaders/scene/lightmodel_normal.glsl +++ b/wadsrc/static/shaders/scene/lightmodel_normal.glsl @@ -51,11 +51,17 @@ } } - vec3 ProcessMaterialLight(Material material, vec3 color) + vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation) { vec4 dynlight = uDynLightColor; vec3 normal = material.Normal; + if (sunlightAttenuation > 0.0) + { + sunlightAttenuation *= clamp(dot(normal, SunDir), 0.0, 1.0); + dynlight.rgb += SunColor.rgb * SunIntensity * sunlightAttenuation; + } + #ifndef UBERSHADER #ifdef SHADE_VERTEX @@ -160,7 +166,7 @@ } #else - vec3 ProcessMaterialLight(Material material, vec3 color) + vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation) { return material.Base.rgb; } diff --git a/wadsrc/static/shaders/scene/lightmodel_pbr.glsl b/wadsrc/static/shaders/scene/lightmodel_pbr.glsl index a1bd70f70..aeb521b4b 100644 --- a/wadsrc/static/shaders/scene/lightmodel_pbr.glsl +++ b/wadsrc/static/shaders/scene/lightmodel_pbr.glsl @@ -1,4 +1,5 @@ const float PI = 3.14159265359; +const float PBRBrightnessScale = 2.5; // For making non-PBR and PBR lights roughly the same intensity float DistributionGGX(vec3 N, vec3 H, float roughness) { @@ -49,8 +50,6 @@ vec3 ProcessLight(const DynLightInfo light, vec3 albedo, float metallic, float r vec3 L = normalize(light.pos.xyz - pixelpos.xyz); vec3 H = normalize(V + L); - const float brightnessScale = 2.5; // For making non-PBR and PBR lights roughly the same intensity - float attenuation = distanceAttenuation(distance(light.pos.xyz, pixelpos.xyz), light.radius, light.strength, light.linearity); if ((light.flags & LIGHTINFO_SPOT) != 0) { @@ -71,7 +70,7 @@ vec3 ProcessLight(const DynLightInfo light, vec3 albedo, float metallic, float r attenuation *= shadowAttenuation(light.pos.xyz, light.shadowIndex, light.softShadowRadius, light.flags); } - vec3 radiance = light.color.rgb * attenuation * brightnessScale; + vec3 radiance = light.color.rgb * attenuation * PBRBrightnessScale; // cook-torrance brdf float NDF = DistributionGGX(N, H, roughness); @@ -91,7 +90,7 @@ vec3 ProcessLight(const DynLightInfo light, vec3 albedo, float metallic, float r return vec3(0.0); } -vec3 ProcessMaterialLight(Material material, vec3 ambientLight) +vec3 ProcessMaterialLight(Material material, vec3 ambientLight, float sunlightAttenuation) { vec3 albedo = material.Base.rgb; @@ -106,6 +105,27 @@ vec3 ProcessMaterialLight(Material material, vec3 ambientLight) vec3 Lo = uDynLightColor.rgb; + if (sunlightAttenuation > 0.0) + { + vec3 L = SunDir; + vec3 H = normalize(V + L); + + sunlightAttenuation *= clamp(dot(N, L), 0.0, 1.0); + + vec3 radiance = SunColor * SunIntensity * PBRBrightnessScale * sunlightAttenuation; + + // cook-torrance brdf + float NDF = DistributionGGX(N, H, roughness); + float G = GeometrySmith(N, V, L, roughness); + vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0); + vec3 kS = F; + vec3 kD = (vec3(1.0) - kS) * (1.0 - metallic); + vec3 nominator = NDF * G * F; + float denominator = 4.0 * clamp(dot(N, V), 0.0, 1.0) * clamp(dot(N, L), 0.0, 1.0); + vec3 specular = nominator / max(denominator, 0.001); + Lo += (kD * albedo / PI + specular) * radiance; + } + #ifndef UBERSHADER if (uLightIndex >= 0) { diff --git a/wadsrc/static/shaders/scene/lightmodel_specular.glsl b/wadsrc/static/shaders/scene/lightmodel_specular.glsl index 9602d45b1..c40a48f3d 100644 --- a/wadsrc/static/shaders/scene/lightmodel_specular.glsl +++ b/wadsrc/static/shaders/scene/lightmodel_specular.glsl @@ -36,7 +36,7 @@ vec2 lightAttenuation(DynLightInfo light, vec3 normal, vec3 viewdir, float gloss return vec2(attenuation, attenuation * specularLevel * pow(specAngle, phExp)); } -vec3 ProcessMaterialLight(Material material, vec3 color) +vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation) { vec4 dynlight = uDynLightColor; vec4 specular = vec4(0.0, 0.0, 0.0, 1.0); @@ -44,6 +44,17 @@ vec3 ProcessMaterialLight(Material material, vec3 color) vec3 normal = material.Normal; vec3 viewdir = normalize(uCameraPos.xyz - pixelpos.xyz); + if (sunlightAttenuation > 0.0) + { + sunlightAttenuation *= clamp(dot(normal, SunDir), 0.0, 1.0); + vec3 color = SunColor.rgb * SunIntensity * sunlightAttenuation; + vec3 halfdir = normalize(viewdir + SunDir); + float specAngle = clamp(dot(halfdir, normal), 0.0f, 1.0f); + float phExp = material.Glossiness * 4.0f; + dynlight.rgb += color; + specular.rgb += color * material.SpecularLevel * pow(specAngle, phExp); + } + #ifndef UBERSHADER if (uLightIndex >= 0) { diff --git a/wadsrc/static/shaders/scene/material.glsl b/wadsrc/static/shaders/scene/material.glsl index 269de4e88..a9ae94519 100644 --- a/wadsrc/static/shaders/scene/material.glsl +++ b/wadsrc/static/shaders/scene/material.glsl @@ -20,7 +20,7 @@ struct Material vec4 Process(vec4 color); void SetupMaterial(inout Material mat); -vec3 ProcessMaterialLight(Material material, vec3 color); +vec3 ProcessMaterialLight(Material material, vec3 color, float sunlightAttenuation); vec3 ProcessSWLight(Material material); vec2 GetTexCoord();